本文首发于思否,作者 [你的名字]。微服务早已不是大厂的专属,随着 .NET 生态的成熟和容器化技术的普及,中小团队也能构建高可用、可扩展的微服务体系。本文将带你从零搭建一套基于 .NET 8 的微服务应用,涵盖服务拆分、API 网关(YARP)、服务发现(Consul)、容错(Polly)、分布式追踪(OpenTelemetry)、分布式事务(Saga)以及 Kubernetes 部署,全部代码可运行,适合生产实践参考。
微服务的核心价值在于 独立部署、独立扩展、独立故障隔离,而非简单的“拆成多个项目”。在 .NET 技术栈中,我们拥有 ASP.NET Core 的高性能、丰富的中间件生态以及强大的云原生支持,可以轻松构建轻量级微服务。
但微服务也引入了一系列挑战:
本文的目标:构建一个“订单服务 + 库存服务 + 支付服务”的典型电商场景,解决上述所有问题。
我们采用以下组件:
组件 | 选型 | 说明 |
|---|---|---|
服务框架 | ASP.NET Core 8 WebApi | 跨平台、高性能 |
API 网关 | YARP(Yet Another Reverse Proxy) | 微软开源,高性能反向代理 |
服务注册/发现 | Consul | 轻量级,支持健康检查 |
负载均衡 | YARP 内置 + 客户端负载均衡 | 结合 Consul |
容错/熔断 | Polly | 超时、重试、熔断策略 |
分布式追踪 | OpenTelemetry + Jaeger | 标准协议,可视化链路 |
日志聚合 | Serilog + Elasticsearch + Kibana(可选) | 结构化日志 |
分布式事务 | Saga(MassTransit + RabbitMQ) | 异步补偿模式 |
容器编排 | Kubernetes(Minikube 或 K3s) | 生产级部署 |
配置管理 | Kubernetes ConfigMap + Secret | 或使用 Azure App Configuration |
服务拆分:
# 安装 .NET 8 SDK
# 安装 Docker Desktop
# 启用 Kubernetes(或使用 Minikube)
# 安装 Consul(开发用 docker 运行)
docker run -d --name consul -p 8500:8500 -e CONSUL_BIND_INTERFACE=eth0 consul:latest
# 安装 RabbitMQ
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
# 安装 Jaeger(用于追踪)
docker run -d --name jaeger -p 16686:16686 -p 4317:4317 jaegertracing/all-in-one:latest创建 Shared 类库,包含公共 DTO、异常、常量。
// Shared/DTOs/OrderDto.cs
public class OrderDto
{
public int Id { get; set; }
public string CustomerId { get; set; } = string.Empty;
public List<OrderItemDto> Items { get; set; } = new();
public decimal TotalAmount { get; set; }
public string Status { get; set; } = "Pending"; // Pending, Paid, Shipped, Cancelled
public DateTime CreatedAt { get; set; }
}
public class CreateOrderRequest
{
public string CustomerId { get; set; } = string.Empty;
public List<OrderItemDto> Items { get; set; } = new();
}创建事件消息契约(用于 MassTransit 消息传递):
// Shared/Events/OrderSubmitted.cs
public record OrderSubmitted
{
public Guid CorrelationId { get; init; }
public int OrderId { get; init; }
public string CustomerId { get; init; } = default!;
public List<InventoryItem> Items { get; init; } = new();
}
public record InventoryItem
{
public int ProductId { get; init; }
public int Quantity { get; init; }
}dotnet new webapi -n Services.Order
dotnet add Services.Order package Consul
dotnet add Services.Order package Polly
dotnet add Services.Order package MassTransit.RabbitMQ
dotnet add Services.Order package OpenTelemetry.Exporter.Jaeger
dotnet add Services.Order package OpenTelemetry.Extensions.Hosting
dotnet add Services.Order package Serilog.AspNetCoreusing Consul;
using MassTransit;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Polly;
using Polly.Extensions.Http;
using Serilog;
var builder = WebApplication.CreateBuilder(args);
// Serilog
builder.Host.UseSerilog((ctx, cfg) =>
cfg.ReadFrom.Configuration(ctx.Configuration));
// 服务注册 Consul
builder.Services.AddSingleton<IConsulClient, ConsulClient>(p =>
new ConsulClient(cfg => cfg.Address = new Uri("http://localhost:8500")));
builder.Services.AddHostedService<ConsulRegistrationService>(); // 自定义健康检查注册
// HttpClient 容错(调用 Inventory 和 Payment)
builder.Services.AddHttpClient("InventoryClient", client =>
{
client.BaseAddress = new Uri("http://localhost:5002/");
})
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));
// MassTransit + RabbitMQ
builder.Services.AddMassTransit(x =>
{
x.UsingRabbitMq((ctx, cfg) =>
{
cfg.Host("localhost", "/", h => { });
cfg.ConfigureEndpoints(ctx);
});
});
// OpenTelemetry 追踪
builder.Services.AddOpenTelemetry()
.WithTracing(tracer => tracer
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddJaegerExporter());
// 数据库(使用 EF Core + PostgreSQL)
builder.Services.AddDbContext<OrderDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("OrderDb")));
// 业务服务
builder.Services.AddScoped<IOrderService, OrderService>();
// 控制器
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// 中间件
app.UseSerilogRequestLogging();
app.UseSwagger();
app.UseSwaggerUI();
app.UseAuthorization();
app.MapControllers();
// 自动迁移数据库
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();
db.Database.Migrate();
}
app.Run();public class ConsulRegistrationService : IHostedService
{
private readonly IConsulClient _consul;
private readonly IHostApplicationLifetime _lifetime;
public ConsulRegistrationService(IConsulClient consul, IHostApplicationLifetime lifetime)
{
_consul = consul;
_lifetime = lifetime;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var registration = new AgentServiceRegistration()
{
ID = $"order-service-{Guid.NewGuid()}",
Name = "order-service",
Address = "localhost",
Port = 5001,
Check = new AgentHttpHealthCheck()
{
Interval = TimeSpan.FromSeconds(10),
HTTP = "http://localhost:5001/health"
}
};
await _consul.Agent.ServiceRegister(registration, cancellationToken);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
// 注销逻辑(可选)
}
}public class OrderService : IOrderService
{
private readonly OrderDbContext _db;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IPublishEndpoint _publishEndpoint;
public OrderService(OrderDbContext db, IHttpClientFactory httpClientFactory, IPublishEndpoint publishEndpoint)
{
_db = db;
_httpClientFactory = httpClientFactory;
_publishEndpoint = publishEndpoint;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
// 1. 计算总价(调用 Product Service 获取价格)
var total = await CalculateTotalAsync(request.Items);
// 2. 创建订单(状态为 Submitted)
var order = new Order
{
CustomerId = request.CustomerId,
TotalAmount = total,
Status = "Submitted",
CreatedAt = DateTime.UtcNow,
Items = request.Items.Select(i => new OrderItem
{
ProductId = i.ProductId,
Quantity = i.Quantity,
UnitPrice = i.UnitPrice // 从 Product Service 获取
}).ToList()
};
_db.Orders.Add(order);
await _db.SaveChangesAsync();
// 3. 发布 OrderSubmitted 事件(触发 Saga)
await _publishEndpoint.Publish(new OrderSubmitted
{
CorrelationId = Guid.NewGuid(),
OrderId = order.Id,
CustomerId = order.CustomerId,
Items = order.Items.Select(i => new InventoryItem
{
ProductId = i.ProductId,
Quantity = i.Quantity
}).ToList()
});
return order;
}
private async Task<decimal> CalculateTotalAsync(List<OrderItemDto> items)
{
// 调用 Product Service 获取价格(带 Polly 重试)
var client = _httpClientFactory.CreateClient("ProductClient");
decimal total = 0;
foreach (var item in items)
{
var response = await client.GetAsync($"/api/products/{item.ProductId}");
if (response.IsSuccessStatusCode)
{
var product = await response.Content.ReadFromJsonAsync<Product>();
total += product.Price * item.Quantity;
}
}
return total;
}
}我们采用 异步 Saga 模式,通过消息驱动协调多个服务。
创建 OrderSagaState 和 OrderSaga 类(可使用 MassTransit 的 MassTransitStateMachine):
public class OrderSaga : MassTransitStateMachine<OrderSagaState>
{
public State Submitted { get; private set; }
public State InventoryReserved { get; private set; }
public State PaymentProcessed { get; private set; }
public State Completed { get; private set; }
public State Faulted { get; private set; }
public Event<OrderSubmitted> OrderSubmitted { get; private set; }
public Event<InventoryReserved> InventoryReservedEvent { get; private set; }
public Event<PaymentCompleted> PaymentCompletedEvent { get; private set; }
public Event<Fault<InventoryReserveFailed>> InventoryFailed { get; private set; }
public OrderSaga()
{
InstanceState(x => x.CurrentState);
// 定义事件关联
Event(() => OrderSubmitted, x => x.CorrelateById(m => m.Message.CorrelationId));
Event(() => InventoryReservedEvent, x => x.CorrelateById(m => m.Message.CorrelationId));
Event(() => PaymentCompletedEvent, x => x.CorrelateById(m => m.Message.CorrelationId));
Event(() => InventoryFailed, x => x.CorrelateById(m => m.Message.CorrelationId));
Initially(
When(OrderSubmitted)
.Then(context =>
{
context.Instance.OrderId = context.Message.OrderId;
context.Instance.CustomerId = context.Message.CustomerId;
})
.PublishAsync(context => context.Init<ReserveInventory>(new
{
CorrelationId = context.Instance.CorrelationId,
OrderId = context.Instance.OrderId,
Items = context.Message.Items
}))
.TransitionTo(Submitted)
);
During(Submitted,
When(InventoryReservedEvent)
.Then(context => context.Instance.ReservationId = context.Message.ReservationId)
.PublishAsync(context => context.Init<ProcessPayment>(new
{
CorrelationId = context.Instance.CorrelationId,
OrderId = context.Instance.OrderId,
Amount = context.Message.Amount
}))
.TransitionTo(InventoryReserved),
When(InventoryFailed)
.PublishAsync(context => context.Init<OrderFailed>(new
{
CorrelationId = context.Instance.CorrelationId,
OrderId = context.Instance.OrderId,
Reason = "库存不足"
}))
.TransitionTo(Faulted)
);
During(InventoryReserved,
When(PaymentCompletedEvent)
.Then(context => context.Instance.PaymentId = context.Message.PaymentId)
.PublishAsync(context => context.Init<OrderCompleted>(new
{
CorrelationId = context.Instance.CorrelationId,
OrderId = context.Instance.OrderId
}))
.Finalize()
);
SetCompletedWhenFinalized();
}
}每个服务需要实现对应的消费者:
ReserveInventory,尝试扣减库存,回复 InventoryReserved 或 InventoryReserveFailed。ProcessPayment,尝试支付,回复 PaymentCompleted 或 PaymentFailed。public class ReserveInventoryConsumer : IConsumer<ReserveInventory>
{
private readonly InventoryDbContext _db;
private readonly IPublishEndpoint _publish;
public ReserveInventoryConsumer(InventoryDbContext db, IPublishEndpoint publish)
{
_db = db;
_publish = publish;
}
public async Task Consume(ConsumeContext<ReserveInventory> context)
{
var message = context.Message;
try
{
// 检查并扣减库存(带并发控制)
foreach (var item in message.Items)
{
var stock = await _db.Stocks.FindAsync(item.ProductId);
if (stock == null || stock.Quantity < item.Quantity)
throw new InsufficientStockException($"产品 {item.ProductId} 库存不足");
stock.Quantity -= item.Quantity;
}
await _db.SaveChangesAsync();
// 成功回复
await _publish.Publish(new InventoryReserved
{
CorrelationId = message.CorrelationId,
OrderId = message.OrderId,
ReservationId = Guid.NewGuid().ToString(),
Amount = message.TotalAmount // 需传递总金额
});
}
catch (Exception ex)
{
// 失败回复
await _publish.Publish(new InventoryReserveFailed
{
CorrelationId = message.CorrelationId,
OrderId = message.OrderId,
Reason = ex.Message
});
}
}
}YARP 是微软开源的代理库,可以基于路由配置将请求转发到不同服务。
dotnet new webapi -n ApiGateway
dotnet add ApiGateway package Yarp.ReverseProxyappsettings.json{
"ReverseProxy": {
"Routes": {
"orders": {
"ClusterId": "order-cluster",
"Match": {
"Path": "/api/orders/{**catch-all}"
},
"Transforms": [
{ "PathPattern": "/api/orders/{**catch-all}" }
]
},
"inventory": {
"ClusterId": "inventory-cluster",
"Match": {
"Path": "/api/inventory/{**catch-all}"
}
},
"products": {
"ClusterId": "product-cluster",
"Match": {
"Path": "/api/products/{**catch-all}"
}
}
},
"Clusters": {
"order-cluster": {
"Destinations": {
"order1": {
"Address": "http://order-service:5001/"
}
},
"LoadBalancingPolicy": "RoundRobin"
},
// 其他集群类似
}
}
}在 Program.cs 中启用 YARP:
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
app.MapReverseProxy();YARP 原生支持 Consul,但需安装 Yarp.ReverseProxy.ServiceDiscovery.Consul 并配置:
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddConsulServiceDiscoveryProvider();并在配置中更改 Cluster 的 Destination 为动态源:
"Clusters": {
"order-cluster": {
"DiscoveryMode": "Consul",
"Consul": {
"ServiceName": "order-service"
}
}
}FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish Services.Order/Services.Order.csproj -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "Services.Order.dll"]docker build -t myapp/order-service:latest -f Services.Order/Dockerfile .
docker tag myapp/order-service:latest myregistry/order-service:1.0
docker push myregistry/order-service:1.0order-service-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
namespace: prod
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: myregistry/order-service:1.0
ports:
- containerPort: 80
env:
- name: ConnectionStrings__OrderDb
valueFrom:
secretKeyRef:
name: db-secret
key: connection-string
- name: Consul__Address
value: "http://consul-service:8500"
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 15
---
apiVersion: v1
kind: Service
metadata:
name: order-service
namespace: prod
spec:
selector:
app: order-service
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP同理为其他服务和网关创建资源。
kubectl apply -f deploy/
kubectl expose deployment api-gateway --type=NodePort --port=80 --target-port=80 -n prod所有服务添加上述追踪配置后,会自动上报 Trace 到 Jaeger。访问 Jaeger UI http://localhost:16686 可查看请求链路。
建议在每个服务中配置 Serilog 输出到 Elasticsearch,使用 Kibana 进行集中日志查询。
Log.Logger = new LoggerConfiguration()
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://elasticsearch:9200"))
{
IndexFormat = "logs-{0:yyyy.MM.dd}"
})
.CreateLogger();/health 端点,用于 K8s 探针和 Consul 检查。CircuitBreaker,防止级联故障。CorrelationId 去重)。本文完整展示了基于 .NET 8 的微服务解决方案,涵盖了通信、注册发现、网关、容错、追踪、事务和容器化。这套架构可以支撑每天百万级请求的中型系统。
进阶方向:
微服务不是银弹,但它为我们提供了应对复杂业务场景的武器。合理拆分、稳步推进,才能充分发挥其优势。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。