C#微服务服务发现实战指南
1. 技术选型与架构设计
1.1 技术栈
- Consul:服务注册中心,支持健康检查与多数据中心。
- gRPC:高性能跨服务通信协议。
- ASP.NET Core:微服务框架。
- Polly:实现熔断、重试等容错策略。
1.2 架构图
服务提供者 → Consul注册 → 服务消费者
↑ ↓
└── 心跳续约 → 服务发现 → gRPC调用
2. 核心代码实现
2.1 服务提供者:注册与心跳
// 服务注册类(ServiceRegistration.cs)
public class ServiceRegistration
{
private readonly IConsulClient _consulClient;
private readonly string _serviceName;
private readonly string _serviceId;
private readonly string _address;
private readonly int _port;
private readonly TimeSpan _heartbeatInterval = TimeSpan.FromSeconds(10);
public ServiceRegistration(
IConsulClient consulClient,
string serviceName,
string serviceId,
string address,
int port)
{
_consulClient = consulClient;
_serviceName = serviceName;
_serviceId = serviceId;
_address = address;
_port = port;
}
public async Task RegisterAsync()
{
// 注册服务到Consul
await _consulClient.Agent.ServiceRegister(new AgentServiceRegistration
{
ID = _serviceId,
Name = _serviceName,
Address = _address,
Port = _port,
Check = new AgentServiceCheck
{
HTTP = $"http://{_address}:{_port}/health",
Interval = TimeSpan.FromSeconds(15), // 健康检查间隔
Timeout = TimeSpan.FromSeconds(5)
}
});
// 启动心跳续约任务
await StartHeartbeatAsync();
}
private async Task StartHeartbeatAsync()
{
while (true)
{
try
{
// 发送心跳续约
await _consulClient.Agent.CheckPassAsync(_serviceId);
await Task.Delay(_heartbeatInterval);
}
catch (Exception ex)
{
Console.WriteLine($"心跳续约失败: {ex.Message}");
}
}
}
}
注释解析:
AgentServiceCheck.HTTP
:指向服务的健康检查端点(如/health
)。StartHeartbeatAsync
:每10秒向Consul发送心跳,避免因网络波动被标记为失效。Check.Timeout
:健康检查超时时间,防止慢响应导致误判。
2.2 服务消费者:发现与负载均衡
// 服务发现类(ServiceDiscovery.cs)
public class ServiceDiscovery
{
private readonly IConsulClient _consulClient;
private readonly string _serviceName;
private List<ServiceEntry> _serviceInstances = new List<ServiceEntry>();
private readonly object _lock = new object();
public ServiceDiscovery(IConsulClient consulClient, string serviceName)
{
_consulClient = consulClient;
_serviceName = serviceName;
}
public async Task<List<ServiceEntry>> GetHealthyInstancesAsync()
{
// 从Consul获取健康实例
var queryResult = await _consulClient.Health.Service(_serviceName, statusPass: true);
return queryResult.Response
.Where(r => r.Service.ID != null && r.Service.Port != 0)
.Select(r => new ServiceEntry
{
ServiceId = r.Service.ID,
Address = r.Service.Address,
Port = r.Service.Port,
Healthy = r.Checks.All(c => c.Status == HealthStatus.Passing)
})
.ToList();
}
public async Task<string> GetRandomInstanceAsync()
{
// 负载均衡:随机选择一个健康实例
var instances = await GetHealthyInstancesAsync();
if (instances.Count == 0)
throw new Exception("无可用服务实例");
lock (_lock)
{
var random = new Random();
var index = random.Next(instances.Count);
return $"{instances[index].Address}:{instances[index].Port}";
}
}
}
public class ServiceEntry
{
public string ServiceId { get; set; }
public string Address { get; set; }
public int Port { get; set; }
public bool Healthy { get; set; }
}
注释解析:
statusPass: true
:仅返回健康状态为Passing
的实例。lock (_lock)
:保证多线程调用时的线程安全。- 可扩展负载均衡策略(如轮询、加权)替换
Random
逻辑。
2.3 gRPC服务调用示例
// gRPC客户端(GrpcClient.cs)
public class GrpcClient
{
private readonly ServiceDiscovery _serviceDiscovery;
private readonly Channel _channel;
private readonly MyService.MyServiceClient _client;
public GrpcClient(ServiceDiscovery serviceDiscovery)
{
_serviceDiscovery = serviceDiscovery;
var address = await _serviceDiscovery.GetRandomInstanceAsync();
_channel = new Channel(address, ChannelCredentials.Insecure);
_client = new MyService.MyServiceClient(_channel);
}
public async Task<GetResponse> CallServiceAsync()
{
try
{
var request = new GetRequest { /* 参数 */ };
return await _client.GetMethodAsync(request);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable)
{
// 服务不可用,重新获取实例
var newAddress = await _serviceDiscovery.GetRandomInstanceAsync();
_channel.ShutdownAsync();
_channel = new Channel(newAddress, ChannelCredentials.Insecure);
_client = new MyService.MyServiceClient(_channel);
return await CallServiceAsync(); // 重试
}
}
}
注释解析:
- 自动重试机制:当服务不可用时,重新获取实例并重试。
- 使用
ChannelCredentials.Insecure
仅适用于测试环境,生产环境应启用TLS。
2.4 自定义健康检查端点
// 健康检查控制器(HealthController.cs)
[ApiController]
[Route("/health")]
public class HealthController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
// 实现自定义健康逻辑(如数据库连接、缓存状态)
if (IsHealthy())
return Ok("Healthy");
else
return StatusCode(503, "Service Unavailable");
}
private bool IsHealthy()
{
// 示例:检查数据库连接
using (var connection = new SqlConnection("YourConnectionString"))
{
try
{
connection.Open();
return true;
}
catch
{
return false;
}
}
}
}
3. 高级特性与优化
3.1 服务雪崩防护(Polly策略)
// 熔断与重试配置(CircuitBreakerPolicy.cs)
public class CircuitBreakerPolicy
{
public static AsyncPolicyWrap<HttpResponseMessage> GetPolicy()
{
var retryPolicy = Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
);
var circuitBreakerPolicy = Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30)
);
return retryPolicy.Wrap(circuitBreakerPolicy);
}
}
3.2 多数据中心支持
// 跨数据中心服务发现(MultiDatacenterDiscovery.cs)
public class MultiDatacenterDiscovery : ServiceDiscovery
{
private readonly List<string> _datacenters = new List<string> { "dc1", "dc2" };
public override async Task<List<ServiceEntry>> GetHealthyInstancesAsync()
{
var allInstances = new List<ServiceEntry>();
foreach (var dc in _datacenters)
{
_consulClient.Configuration.Datacenter = dc;
allInstances.AddRange(await base.GetHealthyInstancesAsync());
}
return allInstances;
}
}
4. 部署与运行
4.1 服务提供者配置
// Program.cs(服务提供者)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddGrpc();
// Consul配置
builder.Configuration.AddConsul(
configuration =>
{
configuration.Address = new Uri("http://consul:8500"); // Consul地址
});
var app = builder.Build();
// 服务注册
var serviceRegistration = new ServiceRegistration(
consulClient: app.Services.GetRequiredService<IConsulClient>(),
serviceName: "MyService",
serviceId: Guid.NewGuid().ToString(),
address: "localhost",
port: 5000
);
await serviceRegistration.RegisterAsync();
app.MapHealthChecks("/health");
app.Run();
4.2 服务消费者配置
// Program.cs(服务消费者)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient();
builder.Services.AddSingleton<IConsulClient>(new ConsulClient(
config =>
{
config.Address = new Uri("http://consul:8500");
}));
builder.Services.AddTransient<ServiceDiscovery>();
builder.Services.AddTransient<GrpcClient>();
var app = builder.Build();
app.Run();
5. 性能优化与避坑指南
5.1 缓存服务实例列表
// 缓存优化(CachedServiceDiscovery.cs)
public class CachedServiceDiscovery : ServiceDiscovery
{
private readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
private const string CacheKey = "ServiceInstances";
public override async Task<List<ServiceEntry>> GetHealthyInstancesAsync()
{
if (_cache.TryGetValue(CacheKey, out List<ServiceEntry> instances))
return instances;
instances = await base.GetHealthyInstancesAsync();
_cache.Set(CacheKey, instances, TimeSpan.FromSeconds(30));
return instances;
}
}
5.2 避免单点故障
// Consul集群配置(docker-compose.yml)
version: '3'
services:
consul:
image: consul
ports:
- "8500:8500"
environment:
- CONSUL_BIND_INTERFACE=eth0
- CONSUL_CLIENT=0.0.0.0
command: agent -server -bootstrap-expect=1 -ui -bind=0.0.0.0 -client=0.0.0.0
6. 完整代码与扩展
6.1 项目结构
src/
├── MyService.Provider/
│ ├── ServiceRegistration.cs
│ ├── HealthController.cs
│ └── Program.cs
├── MyService.Consumer/
│ ├── ServiceDiscovery.cs
│ ├── GrpcClient.cs
│ └── Program.cs
└── Shared/
└── MyService.proto # gRPC定义文件
6.2 完整部署流程
# 1. 启动Consul集群
docker run -d --name consul -p 8500:8500 consul
# 2. 构建并运行服务提供者
dotnet run --project MyService.Provider
# 3. 构建并运行服务消费者
dotnet run --project MyService.Consumer
7. 你的微服务够“健壮”吗?
通过本文,你已掌握:
- Consul服务注册与健康检查:动态管理服务实例。
- gRPC高性能调用:实现跨服务通信。
- 自定义心跳与熔断策略:防止雪崩与脑裂。