在ASP.NET Core项目里做微服务拆分后,服务之间不再是一个进程内的方法调用,而是跨网络的交互。C#实现微服务通信如果只写死用HttpClient去Post一个地址,短时间高并发下会耗尽 socket 或触发端口耗尽。真正落地时要考虑连接复用、超时、重试、服务寻址和序列化效率,这些都决定了系统的稳定性。

一、使用HttpClientFactory避免套接字耗尽
很多初学者会每次new一个HttpClient来发请求,这种做法在微服务高频互调时非常危险。HttpClient实现了IDisposable,但把它using掉并不会立刻释放底层TCP连接,操作系统会保持TIME_WAIT状态,几分钟后才能回收,并发一高就报无法连接。ASP.NET Core提供的HttpClientFactory能维护一个可复用的Handler池。
在Program里注册类型化客户端后,框架自动做生命周期管理,还能挂委托配置默认头与超时。下面代码演示如何注册并注入使用:
// Program.cs 中注册
builder.Services.AddHttpClient("orderClient", client =>
{
client.BaseAddress = new Uri("http://order-service");
client.Timeout = TimeSpan.FromSeconds(3);
});
// 使用处注入
public class ProductService
{
private readonly IHttpClientFactory _factory;
public ProductService(IHttpClientFactory factory)
{
_factory = factory;
}
public async Task<string> GetOrderAsync(int id)
{
var client = _factory.CreateClient("orderClient");
var resp = await client.GetAsync($"/api/order/{id}");
return await resp.Content.ReadAsStringAsync();
}
}
这种方式的优势是清晰且不易出错,缺点是所有策略都写在字符串命名的客户端里,复杂策略建议配合Polly扩展。另外注意BaseAddress结尾的斜杠,拼接路径时容易因双斜杠引发路由不匹配。
二、结合Polly实现重试与断路器
微服务网络不可靠,下游偶发延迟或重启会导致调用失败。如果无脑重试会加剧雪崩,所以要用断路器在错误率过高时快速失败。Polly是C#里成熟的韧性库,能和HttpClientFactory无缝集成。
下面展示在ASP.NET Core里给命名客户端加重试与熔断策略。当连续3次失败就打开熔断10秒,期间直接抛异常不走网络:
using Polly;
using Polly.Extensions.Http;
var retry = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(2, i => TimeSpan.FromMilliseconds(200 * i));
var circuit = HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(10));
builder.Services.AddHttpClient("payClient")
.AddPolicyHandler(retry)
.AddPolicyHandler(circuit);
优点是把容错逻辑从业务代码剥离,统一治理。要注意熔断状态是单例级别,多实例部署时各自统计,不能替代网关层全局限流。对于非幂等写操作,重试要谨慎,最好用Outbox模式保证最终一致。
三、基于gRPC的强类型高性能调用
当服务间需要频繁、低延迟通信,JSON序列化与HTTP文本协议就成为瓶颈。gRPC用HTTP/2加Protobuf二进制编码,在C#里通过dotnet-grpc工具生成客户端和服务端骨架,调用像本地方法一样。
定义proto后,ASP.NET Core端添加包并映射服务,客户端用Channel连接。以下为客户端调用示例:
// 生成后的客户端调用
using var channel = GrpcChannel.ForAddress("http://inventory-service");
var client = new Inventory.InventoryClient(channel);
var reply = await client.DeductAsync(new DeductRequest { Sku = "A1", Qty = 2 });
Console.WriteLine(reply.Success);
gRPC性能明显优于REST,但调试不如JSON直观,且浏览器原生不支持,适合内部服务。字段变更要遵循向后兼容,否则旧客户端会解析失败。若需跨语言,Protobuf契约要先在独立仓库管理。
四、通过Consul做服务发现
硬编码服务地址在容器伸缩时不可行。Consul可让服务启动后注册自身,调用方查询健康节点。C#用Consul客户端或集成第三方库实现。
下面代码演示从Consul获取订单服务地址再发起请求,实际生产中可封装成DelegatingHandler自动解析:
var consul = new ConsulClient();
var health = await consul.Health.Service("order-service", null, true);
var node = health.Response.First();
var baseUrl = $"http://{node.Service.Address}:{node.Service.Port}";
服务发现解耦了部署拓扑,但要注意缓存结果避免每次请求都查Consul,可结合本地心跳刷新。网络分区时可能出现陈旧列表,要配合熔断使用。
五、消息驱动异步通信
不是所有调用都要同步等待。对于削峰和解耦,可用RabbitMQ或Kafka发事件。C#用MassTransit能定义消息契约并自动序列化。
示例发送订单创建事件:
public record OrderCreated(int OrderId);
public class Publisher
{
private readonly IPublishEndpoint _bus;
public Publisher(IPublishEndpoint bus) { _bus = bus; }
public Task SendAsync(int id) => _bus.Publish(new OrderCreated(id));
}
异步方式提升可用性与吞吐,但带来最终一致和幂等消费难题。建议核心链路用同步加容错,边缘业务用消息。综合上述高级方法,按场景组合才能构建稳健的微服务通信体系。
C#ASP.NET_Core微服务通信修改时间:2026-08-07 06:48:28