1. 故事起源:为什么我们需要分布式架构
2008年,我在一家电商公司负责单体架构的订单系统。黑色星期五当天,系统在流量激增500%后彻底崩溃——数据库连接池耗尽、缓存雪崩、服务无响应。那次事故让我深刻认识到:当业务发展到一定规模,单体架构就像用纸牌搭的房子,轻轻一碰就会坍塌。
分布式架构的核心价值在于解耦与弹性。通过将系统拆分为多个独立服务,我们获得了:
- 横向扩展能力(加机器就能提升性能)
- 故障隔离(一个服务挂了不影响其他功能)
- 技术异构性(不同服务可以用最适合的技术栈)
以支付系统为例:在单体架构中,支付模块崩溃会导致整个系统不可用;而分布式架构下,即使支付服务宕机,用户仍能浏览商品、加入购物车,只是暂时无法结算——这种"优雅降级"正是现代互联网服务的标配。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. .NET 生态中的分布式技术栈
2.1 基础通信框架
.NET 提供了多种分布式通信方案,各有适用场景:
| 技术 | 协议 | 适用场景 | 典型延迟 |
|---|---|---|---|
| WCF | SOAP | 企业级系统集成 | 50-100ms |
| gRPC | HTTP/2 | 服务间高性能通信 | 5-15ms |
| SignalR | WebSocket | 实时消息推送 | <10ms |
| Azure Service Bus | AMQP | 跨云消息队列 | 20-50ms |
实战建议:新项目首选 gRPC,.NET 6+ 内置的
Grpc.AspNetCore包提供了开箱即用的高性能通信能力。我曾用它在8核机器上实现过每秒12万次的服务调用。
2.2 服务发现与负载均衡
没有服务发现的分布式系统就像没有GPS的出租车队。.NET 生态的常见方案:
- Consul:通过
Consul.NET客户端库注册服务
csharp复制var client = new ConsulClient();
var registration = new AgentServiceRegistration {
ID = "order-service-1",
Name = "order-service",
Address = "10.0.0.5",
Port = 5000,
Check = new AgentServiceCheck {
HTTP = "http://10.0.0.5:5000/health",
Interval = TimeSpan.FromSeconds(10)
}
};
await client.Agent.ServiceRegister(registration);
- Kubernetes Service:在K8s环境中自动实现DNS轮询
yaml复制# service.yaml
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- protocol: TCP
port: 80
targetPort: 5000
2.3 数据一致性挑战
分布式事务是架构师们的"噩梦"。我在金融项目中踩过的坑:
- 跨服务转账:账户A扣款成功,但账户B加款失败
- 库存超卖:多个节点同时判断有库存导致超卖
解决方案对比:
| 方案 | 一致性 | 性能 | 适用场景 |
|---|---|---|---|
| 2PC | 强一致 | 差 | 银行核心系统 |
| TCC | 最终 | 中 | 电商交易 |
| Saga | 最终 | 好 | 长业务流程 |
| 本地消息表 | 最终 | 好 | 支付与通知分离 |
我的经验法则:能用最终一致就不用强一致。某次用TCC实现跨境支付,将吞吐量从200TPS提升到1500TPS。
3. 实战:构建电商订单系统
3.1 服务拆分设计
现代电商典型服务划分:
code复制- 用户服务 (Identity)
- 商品服务 (Catalog)
- 库存服务 (Inventory)
- 订单服务 (Order)
- 支付服务 (Payment)
- 推荐服务 (Recommendation)
每个服务独立部署,通过API Gateway聚合。关键技巧:
- 服务边界按业务能力划分,而非技术层级
- 共享数据库是分布式系统的"癌症",必须杜绝
- 领域事件(Domain Events)是服务间通信的最佳实践
3.2 订单创建流程实现
典型分布式事务场景:创建订单 → 扣库存 → 支付
Saga模式实现方案:
csharp复制// OrderSaga.cs
public class OrderSaga {
private readonly IInventoryClient _inventory;
private readonly IPaymentClient _payment;
public async Task CreateOrder(Order order) {
try {
// 步骤1:冻结库存
await _inventory.ReserveStock(order.Items);
// 步骤2:创建支付预授权
var paymentId = await _payment.CreateAuthorization(order.Total);
// 步骤3:确认订单
await _orderRepository.Create(order);
} catch (Exception ex) {
// 补偿逻辑
await _inventory.CancelReservation(order.Items);
await _payment.CancelAuthorization(paymentId);
throw;
}
}
}
性能优化技巧:
- 库存预扣减采用Redis原子操作:
csharp复制var stock = await _redis.StringDecrementAsync($"stock:{sku}", quantity);
if (stock < 0) {
await _redis.StringIncrementAsync($"stock:{sku}", quantity);
throw new OutOfStockException();
}
- 支付服务采用熔断机制:
csharp复制services.AddHttpClient<IPaymentClient, PaymentClient>()
.AddPolicyHandler(Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
4. 生产环境中的血泪教训
4.1 分布式追踪的必要性
没有完善的追踪系统,调试分布式服务就像在迷宫里摸黑找路。我的团队曾花费3天定位一个跨5个服务的超时问题,最终发现是商品服务的SQL缺少索引。
推荐方案:
csharp复制// Startup.cs
services.AddOpenTelemetry(builder => {
builder.AddAspNetCoreInstrumentation();
builder.AddHttpClientInstrumentation();
builder.AddSqlClientInstrumentation();
builder.AddOtlpExporter(options => {
options.Endpoint = new Uri("http://jaeger:4317");
});
});
关键指标监控项:
- 服务调用链路的95线延迟
- 跨服务错误传播路径
- 数据库查询的N+1问题
4.2 缓存一致性陷阱
某次大促,我们因为缓存更新策略不当导致商品价格显示错误,直接损失80万订单。教训是:
- 永远采用"先更新数据库,再删除缓存"策略
- 对关键数据使用双删策略:
csharp复制public async Task UpdateProductPrice(string id, decimal price) {
// 第一删
_cache.Remove($"product:{id}");
await _db.Products.UpdateOneAsync(
p => p.Id == id,
Builders<Product>.Update.Set(p => p.Price, price));
// 延迟第二删
_ = Task.Delay(1000).ContinueWith(_ => {
_cache.Remove($"product:{id}");
});
}
4.3 混沌工程实践
故障注入测试是我们每个迭代的必修课。使用Polly模拟各种异常:
csharp复制// 模拟30%的随机失败
var chaosPolicy = Policy<HttpResponseMessage>
.HandleResult(r => new Random().Next(1, 100) <= 30)
.RetryAsync(3);
典型测试场景:
- 随机杀死30%的Pod
- 在数据库写入时随机注入500ms延迟
- 模拟网络分区,阻断特定服务间通信
经过半年混沌工程训练,我们的系统可用性从99.5%提升到99.95%。
