背景
在 Microservice-Core-Template 项目中,我实现了一个轻量级的 .NET 6 微服务架构模板。本文在原有基础上,进一步总结熔断降级、分布式追踪、配置中心等生产环境不可缺少的核心实践。
微服务核心组件全景
┌─────────────────────────────────────────────────────────────┐
│ API Gateway (Ocelot/YARP) │
│ 限流 · 鉴权 · 负载均衡 · 路由 │
└──────────────────────┬──────────────────────────────────────┘
│
┌─────────────┴─────────────┐
│ Service Discovery │
│ (Consul / Nacos) │
└─────────────┬─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ Order │ │ User │ │Product │
│ Service │ │ Service │ │ Service │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└──────────────┼──────────────┘
│
┌─────────────┴─────────────┐
│ Message Bus │
│ (RabbitMQ / Kafka) │
└───────────────────────────┘
1. 服务注册与发现(Consul)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // 服务注册
builder.Services.AddConsul(options =>
{
options.Address = new Uri("http://consul:8500");
});
app.UseConsul(new AgentServiceRegistration
{
ID = $"order-service-{Guid.NewGuid()}",
Name = "order-service",
Address = Environment.GetEnvironmentVariable("SERVICE_HOST") ?? "localhost",
Port = 5001,
Tags = new[] { "api", "v1" },
Check = new AgentServiceCheck
{
HTTP = "http://localhost:5001/health",
Interval = TimeSpan.FromSeconds(10),
Timeout = TimeSpan.FromSeconds(5),
DeregisterCriticalServiceAfter = TimeSpan.FromMinutes(1)
}
});
|
健康检查端点:
1
2
3
4
5
6
7
8
9
10
| // 添加详细健康检查(数据库、Redis、外部依赖)
builder.Services.AddHealthChecks()
.AddMySql(connectionString, name: "mysql")
.AddRedis(redisConnection, name: "redis")
.AddUrlGroup(new Uri("http://user-service/health"), name: "user-service");
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
|
2. API 网关(Ocelot + Consul)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| // ocelot.json
{
"Routes": [
{
"DownstreamPathTemplate": "/api/{everything}",
"DownstreamScheme": "http",
"ServiceName": "order-service",
"LoadBalancerOptions": { "Type": "RoundRobin" },
"UpstreamPathTemplate": "/order/{everything}",
"UpstreamHttpMethod": ["GET", "POST", "PUT", "DELETE"],
"RateLimitOptions": {
"EnableRateLimiting": true,
"Period": "1m",
"Limit": 100
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10000,
"TimeoutValue": 5000
}
}
],
"GlobalConfiguration": {
"ServiceDiscoveryProvider": {
"Scheme": "http",
"Host": "consul",
"Port": 8500,
"Type": "Consul"
},
"RateLimitOptions": {
"DisableRateLimitHeaders": false,
"QuotaExceededMessage": "请求过于频繁,请稍后重试"
}
}
}
|
3. 统一 JWT 鉴权
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // 在网关层统一验证 Token,下游服务无需重复鉴权
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = configuration["JWT:Issuer"],
ValidateAudience = true,
ValidAudience = configuration["JWT:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(configuration["JWT:SecretKey"]!)),
ClockSkew = TimeSpan.FromSeconds(30)
};
});
|
4. 熔断降级(Polly)
生产环境必须为所有外部调用加上熔断、重试、超时策略:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| // 使用 Polly v8(.NET 8 推荐)
builder.Services.AddHttpClient<IOrderServiceClient, OrderServiceClient>()
.AddResilienceHandler("order-pipeline", builder =>
{
// 重试:最多 3 次,指数退避
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.HandleResult(r => r.StatusCode >= HttpStatusCode.InternalServerError)
});
// 熔断:连续 5 次失败则熔断 30 秒
builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
MinimumThroughput = 10,
SamplingDuration = TimeSpan.FromSeconds(30),
BreakDuration = TimeSpan.FromSeconds(30)
});
// 超时:单次调用最长 5 秒
builder.AddTimeout(TimeSpan.FromSeconds(5));
});
|
降级回调(Fallback):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class OrderServiceClient(HttpClient http, ILogger<OrderServiceClient> logger)
: IOrderServiceClient
{
public async Task<List<OrderDto>> GetOrdersAsync(int userId)
{
try
{
return await http.GetFromJsonAsync<List<OrderDto>>($"/api/orders/{userId}")
?? [];
}
catch (BrokenCircuitException)
{
logger.LogWarning("订单服务熔断,返回降级数据");
return []; // 降级:返回空列表或缓存数据
}
}
}
|
5. 服务间通信
同步通信(HTTP + gRPC)
1
2
3
4
5
6
7
8
9
10
11
12
| // HTTP + HttpClientFactory + 服务发现
builder.Services.AddHttpClient("user-service", client =>
{
client.BaseAddress = new Uri("http://user-service");
client.Timeout = TimeSpan.FromSeconds(5);
});
// gRPC(高性能场景推荐)
builder.Services.AddGrpcClient<UserService.UserServiceClient>(options =>
{
options.Address = new Uri("http://user-service:5002");
});
|
异步通信(RabbitMQ + MassTransit)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| // 事件定义
public record OrderCreatedEvent(int OrderId, int UserId, decimal TotalAmount);
// 发布事件(订单服务)
builder.Services.AddMassTransit(x =>
{
x.UsingRabbitMq((ctx, cfg) =>
{
cfg.Host("rabbitmq://rabbitmq", h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.ConfigureEndpoints(ctx);
});
});
// 消费事件(通知服务)
public class OrderCreatedConsumer(IEmailSender email)
: IConsumer<OrderCreatedEvent>
{
public async Task Consume(ConsumeContext<OrderCreatedEvent> context)
{
var evt = context.Message;
await email.SendAsync(
subject: $"订单 #{evt.OrderId} 已创建",
body: $"金额:{evt.TotalAmount:C}");
}
}
|
6. 分布式事务(Saga 模式)
微服务不能共用数据库,跨服务事务用 Saga 编排:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| // 使用 MassTransit Saga StateMachine
public class OrderStateMachine : MassTransitStateMachine<OrderSaga>
{
public State Submitted { get; private set; } = null!;
public State Paid { get; private set; } = null!;
public State Shipped { get; private set; } = null!;
public State Cancelled { get; private set; } = null!;
public Event<OrderSubmittedEvent> OrderSubmitted { get; private set; } = null!;
public Event<PaymentCompletedEvent> PaymentCompleted { get; private set; } = null!;
public Event<PaymentFailedEvent> PaymentFailed { get; private set; } = null!;
public OrderStateMachine()
{
InstanceState(x => x.CurrentState);
Event(() => OrderSubmitted, x => x.CorrelateById(m => m.Message.OrderId));
Event(() => PaymentCompleted, x => x.CorrelateById(m => m.Message.OrderId));
Event(() => PaymentFailed, x => x.CorrelateById(m => m.Message.OrderId));
Initially(
When(OrderSubmitted)
.Then(ctx => ctx.Saga.UserId = ctx.Message.UserId)
.PublishAsync(ctx => ctx.Init<ProcessPaymentCommand>(new
{
ctx.Message.OrderId, ctx.Message.Amount
}))
.TransitionTo(Submitted));
During(Submitted,
When(PaymentCompleted).TransitionTo(Paid),
When(PaymentFailed)
.PublishAsync(ctx => ctx.Init<CancelOrderCommand>(new
{
ctx.Message.OrderId, Reason = "支付失败"
}))
.TransitionTo(Cancelled));
}
}
|
7. 链路追踪(OpenTelemetry)
分布式系统必须有追踪才能排查跨服务问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddSource("OrderService")
.AddOtlpExporter(opt =>
{
opt.Endpoint = new Uri("http://jaeger:4317");
});
})
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddRuntimeInstrumentation()
.AddPrometheusExporter(); // Prometheus 抓取
});
// 自定义 Span
private static readonly ActivitySource _activitySource = new("OrderService");
public async Task<Order> CreateAsync(CreateOrderDto dto)
{
using var activity = _activitySource.StartActivity("CreateOrder");
activity?.SetTag("order.userId", dto.UserId);
activity?.SetTag("order.amount", dto.Amount);
var order = await _repo.AddAsync(new Order { ... });
activity?.SetTag("order.id", order.Id);
return order;
}
|
8. Docker Compose 编排
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| version: '3.8'
services:
consul:
image: consul:1.15
ports: ["8500:8500"]
command: consul agent -dev -client=0.0.0.0
api-gateway:
build: ./Gateway
ports: ["5000:80"]
depends_on: [consul]
environment:
- ASPNETCORE_ENVIRONMENT=Production
order-service:
build: ./Services/OrderService
environment:
- CONSUL_HOST=consul
- ConnectionStrings__Default=Server=mysql;Database=orders;...
depends_on: [consul, mysql]
deploy:
replicas: 2 # 水平扩展
user-service:
build: ./Services/UserService
depends_on: [consul, mysql]
rabbitmq:
image: rabbitmq:3-management
ports: ["5672:5672", "15672:15672"]
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:7-alpine
ports: ["6379:6379"]
jaeger:
image: jaegertracing/all-in-one:latest
ports: ["16686:16686", "4317:4317"]
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
volumes:
mysql_data:
|
最佳实践总结
| 关注点 | 实践建议 |
|---|
| 单一职责 | 每个微服务只负责一个业务领域 |
| 数据隔离 | 每个服务拥有独立数据库,禁止跨库 Join |
| 异步优先 | 服务间尽量用消息队列解耦,降低时间耦合 |
| 幂等设计 | 消息消费、API 调用必须是幂等的 |
| 熔断降级 | 所有外部调用必须加 Polly 策略 |
| 健康检查 | 每个服务必须暴露 /health 端点 |
| 链路追踪 | 集成 OpenTelemetry + Jaeger 追踪跨服务请求 |
| 配置外置 | 配置放 Consul KV 或 Nacos,不要写死在代码里 |
| 独立部署 | 每个服务有独立的 CI/CD 流水线 |
⚠️ 微服务不是银弹:只有当团队规模超过 10 人、业务模块明确、需要独立扩展时才值得引入。过早拆分只会增加运维负担。
源码地址:github.com/witeem/Microservice-Core-Template