第八章 微服务通信实现

第八章 微服务通信实现

微服务之间的通信是分布式系统中最具挑战性的部分。我见过太多团队因为通信设计不当而导致系统性能低下、故障频发。这一章,我想分享一些实战经验,帮助你设计高效可靠的微服务通信方案。

8.1 同步通信:REST vs gRPC

同步通信就像打电话,你必须等待对方接听并回应。虽然直观,但在分布式系统中需要谨慎使用。

8.1.1 HTTP/REST:最通用的选择

REST是微服务通信的"通用语言",几乎所有技术栈都支持。但用好REST并不容易。

RESTful API设计实战

// 服务端实现 - ASP.NET Core Web API
[ApiController]
[Route("api/v1/[controller]")]
[Produces("application/json")]
public class ProductsController : ControllerBase
{
    private readonly IMediator _mediator;
    private readonly ILogger<ProductsController> _logger;
    
    public ProductsController(IMediator mediator, ILogger<ProductsController> logger)
    {
        _mediator = mediator;
        _logger = logger;
    }
    
    [HttpGet("{id:guid}")]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
    public async Task<ActionResult<ProductDto>> GetProduct(Guid id)
    {
        _logger.LogInformation("Getting product {ProductId}", id);
        
        try
        {
            var result = await _mediator.Send(new GetProductByIdQuery(id));
            
            return result.Match<ActionResult<ProductDto>>(
                product => Ok(product),
                notFound => NotFound());
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting product {ProductId}", id);
            return Problem(
                title: "An error occurred while retrieving the product",
                detail: ex.Message,
                statusCode: StatusCodes.Status500InternalServerError);
        }
    }
    
    [HttpPost]
    [Authorize(Roles = "Admin,ProductManager")]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<ProductDto>> CreateProduct([FromBody] CreateProductCommand command)
    {
        var result = await _mediator.Send(command);
        
        return result.Match<ActionResult<ProductDto>>(
            product => CreatedAtAction(
                nameof(GetProduct), 
                new { id = product.Id }, 
                product),
            validationError => BadRequest(validationError));
    }
    
    [HttpPut("{id:guid}")]
    [Authorize(Roles = "Admin,ProductManager")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult> UpdateProduct(Guid id, [FromBody] UpdateProductCommand command)
    {
        if (id != command.ProductId)
        {
            return BadRequest("Product ID mismatch");
        }
        
        var result = await _mediator.Send(command);
        
        return result.Match<ActionResult>(
            _ => NoContent(),
            error => BadRequest(new { Error = error }));
    }
    
    [HttpDelete("{id:guid}")]
    [Authorize(Roles = "Admin")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult> DeleteProduct(Guid id)
    {
        var result = await _mediator.Send(new DeleteProductCommand(id));
        
        return result.Match<ActionResult>(
            _ => NoContent(),
            error => NotFound(new { Error = error }));
    }
    
    // 批量操作 - 避免N+1查询问题
    [HttpGet]
    [ProducesResponseType(typeof(PagedResult<ProductDto>), StatusCodes.Status200OK)]
    public async Task<ActionResult<PagedResult<ProductDto>>> GetProducts(
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 20,
        [FromQuery] string category = null,
        [FromQuery] decimal? minPrice = null,
        [FromQuery] decimal? maxPrice = null)
    {
        var query = new GetProductsQuery(page, pageSize, category, minPrice, maxPrice);
        var result = await _mediator.Send(query);
        
        return Ok(result);
    }
}

HTTP客户端实现

// 使用HttpClientFactory实现健壮的HTTP客户端
public class ProductServiceClient : IProductServiceClient
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<ProductServiceClient> _logger;
    private readonly JsonSerializerOptions _jsonOptions;
    
    public ProductServiceClient(HttpClient httpClient, ILogger<ProductServiceClient> logger)
    {
        _httpClient = httpClient;
        _logger = logger;
        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };
    }
    
    public async Task<ProductDto> GetProductAsync(Guid productId)
    {
        try
        {
            var response = await _httpClient.GetAsync($"/api/v1/products/{productId}");
            
            if (response.StatusCode == HttpStatusCode.NotFound)
            {
                return null;
            }
            
            response.EnsureSuccessStatusCode();
            
            var content = await response.Content.ReadAsStringAsync();
            return JsonSerializer.Deserialize<ProductDto>(content, _jsonOptions);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "HTTP error getting product {ProductId}", productId);
            throw new ServiceCommunicationException($"Failed to get product {productId}", ex);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unexpected error getting product {ProductId}", productId);
            throw;
        }
    }
    
    public async Task<PagedResult<ProductDto>> SearchProductsAsync(
        string category = null, 
        decimal? minPrice = null, 
        int page = 1, 
        int pageSize = 20)
    {
        var queryParameters = new Dictionary<string, string>
        {
            ["page"] = page.ToString(),
            ["pageSize"] = pageSize.ToString()
        };
        
        if (!string.IsNullOrEmpty(category))
            queryParameters["category"] = category;
        if (minPrice.HasValue)
            queryParameters["minPrice"] = minPrice.Value.ToString();
            
        var queryString = QueryHelpers.AddQueryString("/api/v1/products", queryParameters);
        
        var response = await _httpClient.GetAsync(queryString);
        response.EnsureSuccessStatusCode();
        
        var content = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<PagedResult<ProductDto>>(content, _jsonOptions);
    }
    
    public async Task<ProductDto> CreateProductAsync(CreateProductRequest request)
    {
        var jsonContent = new StringContent(
            JsonSerializer.Serialize(request, _jsonOptions),
            Encoding.UTF8,
            "application/json");
            
        var response = await _httpClient.PostAsync("/api/v1/products", jsonContent);
        
        if (!response.IsSuccessStatusCode)
        {
            var errorContent = await response.Content.ReadAsStringAsync();
            _logger.LogError("Failed to create product. Status: {StatusCode}, Error: {Error}", 
                response.StatusCode, errorContent);
            throw new ServiceCommunicationException($"Failed to create product: {response.StatusCode}");
        }
        
        var content = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<ProductDto>(content, _jsonOptions);
    }
}

// 服务注册
builder.Services.AddHttpClient<IProductServiceClient, ProductServiceClient>(client =>
{
    client.BaseAddress = new Uri(builder.Configuration["Services:ProductService"]);
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.Timeout = TimeSpan.FromSeconds(30);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .WaitAndRetryAsync(
            retryCount: 3,
            sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
            onRetry: (outcome, timespan, retryCount, context) =>
            {
                var logger = context.Values["logger"] as ILogger;
                logger?.LogWarning("Retry {RetryCount} after {Timespan}s", retryCount, timespan.TotalSeconds);
            });
}

static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(
            exceptionsAllowedBeforeBreaking: 3,
            durationOfBreak: TimeSpan.FromSeconds(30),
            onBreak: (exception, duration) =>
            {
                // 记录熔断器打开
            },
            onReset: () =>
            {
                // 记录熔断器关闭
            });
}

8.1.2 gRPC:高性能的内部通信

gRPC特别适合服务间通信,它的高效性和强类型特性让开发更加可靠。

gRPC服务定义

// product.proto
syntax = "proto3";

option csharp_namespace = "ProductService.Grpc";

package product;

service ProductService {
  rpc GetProduct (GetProductRequest) returns (GetProductResponse);
  rpc GetProducts (GetProductsRequest) returns (GetProductsResponse);
  rpc CreateProduct (CreateProductRequest) returns (CreateProductResponse);
  rpc UpdateProduct (UpdateProductRequest) returns (UpdateProductResponse);
  rpc DeleteProduct (DeleteProductRequest) returns (DeleteProductResponse);
  
  // 服务器流式调用 - 实时库存更新
  rpc SubscribeStockUpdates (StockSubscriptionRequest) returns (stream StockUpdate);
  
  // 双向流式调用 - 批量产品操作
  rpc BatchProductOperations (stream BatchProductRequest) returns (stream BatchProductResponse);
}

message GetProductRequest {
  string product_id = 1;
}

message GetProductResponse {
  Product product = 1;
}

message Product {
  string id = 1;
  string sku = 2;
  string name = 3;
  string description = 4;
  Money price = 5;
  string category_id = 6;
  repeated ProductImage images = 7;
  int32 stock_quantity = 8;
}

message Money {
  decimal amount = 1;
  string currency = 2;
}

message ProductImage {
  string url = 1;
  string alt_text = 2;
}

message GetProductsRequest {
  int32 page = 1;
  int32 page_size = 2;
  string category_id = 3;
  Money min_price = 4;
  Money max_price = 5;
}

message GetProductsResponse {
  repeated Product products = 1;
  int32 total_count = 2;
  int32 page = 3;
  int32 page_size = 4;
}

message StockUpdate {
  string product_id = 1;
  int32 new_quantity = 2;
  int32 reserved_quantity = 3;
  google.protobuf.Timestamp update_time = 4;
}

message StockSubscriptionRequest {
  repeated string product_ids = 1;
}

gRPC服务实现

// gRPC服务实现
public class ProductGrpcService : global::ProductService.Grpc.ProductService.ProductServiceBase
{
    private readonly IProductRepository _repository;
    private readonly IStockSubscriptionService _subscriptionService;
    private readonly ILogger<ProductGrpcService> _logger;
    
    public ProductGrpcService(
        IProductRepository repository,
        IStockSubscriptionService subscriptionService,
        ILogger<ProductGrpcService> logger)
    {
        _repository = repository;
        _subscriptionService = subscriptionService;
        _logger = logger;
    }
    
    public override async Task<GetProductResponse> GetProduct(
        GetProductRequest request, 
        ServerCallContext context)
    {
        try
        {
            var productId = Guid.Parse(request.ProductId);
            var product = await _repository.GetByIdAsync(new ProductId(productId));
            
            if (product == null)
            {
                throw new RpcException(new Status(StatusCode.NotFound, $"Product {request.ProductId} not found"));
            }
            
            return new GetProductResponse
            {
                Product = MapToGrpcProduct(product)
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting product {ProductId}", request.ProductId);
            throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
        }
    }
    
    public override async Task SubscribeStockUpdates(
        StockSubscriptionRequest request,
        IServerStreamWriter<StockUpdate> responseStream,
        ServerCallContext context)
    {
        var productIds = request.ProductIds.Select(Guid.Parse).ToList();
        var cancellationToken = context.CancellationToken;
        
        await _subscriptionService.SubscribeToStockUpdatesAsync(
            productIds,
            async update =>
            {
                if (!cancellationToken.IsCancellationRequested)
                {
                    await responseStream.WriteAsync(new StockUpdate
                    {
                        ProductId = update.ProductId.ToString(),
                        NewQuantity = update.NewQuantity,
                        ReservedQuantity = update.ReservedQuantity,
                        UpdateTime = Timestamp.FromDateTime(update.UpdateTime.ToUniversalTime())
                    });
                }
            },
            cancellationToken);
    }
    
    public override async Task BatchProductOperations(
        IAsyncStreamReader<BatchProductRequest> requestStream,
        IServerStreamWriter<BatchProductResponse> responseStream,
        ServerCallContext context)
    {
        await foreach (var request in requestStream.ReadAllAsync())
        {
            try
            {
                var response = await ProcessBatchOperation(request);
                await responseStream.WriteAsync(response);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing batch operation");
                await responseStream.WriteAsync(new BatchProductResponse
                {
                    Success = false,
                    ErrorMessage = ex.Message
                });
            }
        }
    }
    
    private Product MapToGrpcProduct(Domain.Product product)
    {
        return new Product
        {
            Id = product.Id.Value.ToString(),
            Sku = product.SKU,
            Name = product.Name,
            Description = product.Description,
            Price = new Money
            {
                Amount = decimal.ToDouble(product.Price.Amount),
                Currency = product.Price.Currency
            },
            CategoryId = product.CategoryId.Value.ToString(),
            StockQuantity = product.StockQuantity
        };
    }
}

gRPC客户端使用

// gRPC客户端实现
public class ProductGrpcClient : IProductServiceClient
{
    private readonly ProductService.ProductServiceClient _client;
    private readonly ILogger<ProductGrpcClient> _logger;
    
    public ProductGrpcClient(ProductService.ProductServiceClient client, ILogger<ProductGrpcClient> logger)
    {
        _client = client;
        _logger = logger;
    }
    
    public async Task<ProductDto> GetProductAsync(Guid productId)
    {
        try
        {
            var request = new GetProductRequest { ProductId = productId.ToString() };
            var response = await _client.GetProductAsync(request);
            
            return MapToDto(response.Product);
        }
        catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound)
        {
            return null;
        }
        catch (RpcException ex)
        {
            _logger.LogError(ex, "gRPC error getting product {ProductId}", productId);
            throw new ServiceCommunicationException($"Failed to get product {productId}", ex);
        }
    }
    
    public async Task SubscribeToStockUpdatesAsync(
        List<Guid> productIds, 
        Func<StockUpdateDto, Task> onUpdate,
        CancellationToken cancellationToken)
    {
        var request = new StockSubscriptionRequest();
        request.ProductIds.AddRange(productIds.Select(id => id.ToString()));
        
        using var call = _client.SubscribeStockUpdates(request, cancellationToken: cancellationToken);
        
        await foreach (var update in call.ResponseStream.ReadAllAsync(cancellationToken))
        {
            var updateDto = new StockUpdateDto
            {
                ProductId = Guid.Parse(update.ProductId),
                NewQuantity = update.NewQuantity,
                ReservedQuantity = update.ReservedQuantity,
                UpdateTime = update.UpdateTime.ToDateTime()
            };
            
            await onUpdate(updateDto);
        }
    }
    
    private ProductDto MapToDto(Product product)
    {
        return new ProductDto
        {
            Id = Guid.Parse(product.Id),
            SKU = product.Sku,
            Name = product.Name,
            Description = product.Description,
            Price = new Money(product.Price.Amount, product.Price.Currency),
            CategoryId = Guid.Parse(product.CategoryId),
            StockQuantity = product.StockQuantity
        };
    }
}

// gRPC客户端注册
builder.Services.AddGrpcClient<ProductService.ProductServiceClient>(o =>
{
    o.Address = new Uri(builder.Configuration["Services:ProductService:Grpc"]);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
    return new SocketsHttpHandler
    {
        PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
        KeepAlivePingDelay = TimeSpan.FromSeconds(60),
        KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
        EnableMultipleHttp2Connections = true
    };
});

8.1.3 同步通信的挑战与解决方案

服务发现

// 使用Consul进行服务发现
public class ConsulServiceDiscovery : IServiceDiscovery
{
    private readonly ConsulClient _consulClient;
    private readonly ILogger<ConsulServiceDiscovery> _logger;
    
    public async Task<ServiceEndpoint> DiscoverServiceAsync(string serviceName)
    {
        var services = await _consulClient.Health.Service(serviceName, tag: null, passingOnly: true);
        
        if (services.Response.Length == 0)
        {
            throw new ServiceNotFoundException($"No healthy instances found for service {serviceName}");
        }
        
        // 简单的轮询负载均衡
        var serviceEntry = services.Response[_random.Next(services.Response.Length)];
        
        return new ServiceEndpoint
        {
            Address = serviceEntry.Service.Address,
            Port = serviceEntry.Service.Port,
            Tags = serviceEntry.Service.Tags
        };
    }
    
    public async Task RegisterServiceAsync(string serviceName, string serviceId, string address, int port)
    {
        var registration = new AgentServiceRegistration
        {
            ID = serviceId,
            Name = serviceName,
            Address = address,
            Port = port,
            Check = new AgentServiceCheck
            {
                HTTP = $"http://{address}:{port}/health",
                Interval = TimeSpan.FromSeconds(10),
                Timeout = TimeSpan.FromSeconds(5),
                DeregisterCriticalServiceAfter = TimeSpan.FromMinutes(1)
            }
        };
        
        await _consulClient.Agent.ServiceRegister(registration);
    }
}

负载均衡

// 自定义负载均衡策略
public interface ILoadBalancer
{
    Task<ServiceEndpoint> GetNextEndpointAsync(string serviceName);
}

public class RoundRobinLoadBalancer : ILoadBalancer
{
    private readonly IServiceDiscovery _serviceDiscovery;
    private readonly ConcurrentDictionary<string, int> _counters = new();
    
    public async Task<ServiceEndpoint> GetNextEndpointAsync(string serviceName)
    {
        var endpoints = await _serviceDiscovery.GetServiceEndpointsAsync(serviceName);
        
        if (endpoints.Count == 0)
            throw new ServiceNotFoundException($"No endpoints found for service {serviceName}");
            
        var counter = _counters.AddOrUpdate(serviceName, 0, (key, value) => (value + 1) % endpoints.Count);
        return endpoints[counter];
    }
}

public class WeightedLoadBalancer : ILoadBalancer
{
    private readonly IServiceDiscovery _serviceDiscovery;
    private readonly Random _random = new();
    
    public async Task<ServiceEndpoint> GetNextEndpointAsync(string serviceName)
    {
        var endpoints = await _serviceDiscovery.GetServiceEndpointsAsync(serviceName);
        
        // 根据权重选择服务端点
        var totalWeight = endpoints.Sum(e => e.Weight);
        var randomValue = _random.NextDouble() * totalWeight;
        
        var currentWeight = 0.0;
        foreach (var endpoint in endpoints)
        {
            currentWeight += endpoint.Weight;
            if (randomValue <= currentWeight)
                return endpoint;
        }
        
        return endpoints.Last();
    }
}

容错机制

// 使用Polly实现重试和熔断
public class ResilientServiceClient
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IAsyncPolicy<HttpResponseMessage> _resilientPolicy;
    
    public ResilientServiceClient(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
        
        // 组合策略:重试 + 熔断 + 超时
        _resilientPolicy = Policy
            .WrapAsync(
                Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)),
                Policy
                    .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
                    .WaitAndRetryAsync(
                        retryCount: 3,
                        sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                        onRetry: (outcome, timespan, retryCount, context) =>
                        {
                            var logger = context.Values["logger"] as ILogger;
                            logger?.LogWarning("Retry {RetryCount} after {Timespan}s", retryCount, timespan.TotalSeconds);
                        }),
                Policy
                    .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
                    .CircuitBreakerAsync(
                        exceptionsAllowedBeforeBreaking: 5,
                        durationOfBreak: TimeSpan.FromSeconds(30),
                        onBreak: (exception, duration) =>
                        {
                            // 熔断器打开,可以在这里发送告警
                        },
                        onReset: () =>
                        {
                            // 熔断器重置
                        })
            );
    }
    
    public async Task<T> GetAsync<T>(string serviceName, string endpoint)
    {
        var httpClient = _httpClientFactory.CreateClient(serviceName);
        
        var response = await _resilientPolicy.ExecuteAsync(async () =>
        {
            return await httpClient.GetAsync(endpoint);
        });
        
        response.EnsureSuccessStatusCode();
        
        var content = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<T>(content);
    }
}

8.2 异步通信:解耦的艺术

异步通信就像发邮件,你不需要等待对方立即回复。这种模式在微服务架构中特别重要,可以有效解耦服务。

8.2.1 消息队列的选择

// 使用RabbitMQ实现异步通信
public class RabbitMqEventBus : IEventBus
{
    private readonly IConnection _connection;
    private readonly IModel _channel;
    private readonly ILogger<RabbitMqEventBus> _logger;
    private readonly string _exchangeName = "events";
    
    public RabbitMqEventBus(IConnectionFactory connectionFactory, ILogger<RabbitMqEventBus> logger)
    {
        _logger = logger;
        _connection = connectionFactory.CreateConnection();
        _channel = _connection.CreateModel();
        
        // 声明交换器
        _channel.ExchangeDeclare(
            exchange: _exchangeName,
            type: ExchangeType.Topic,
            durable: true,
            autoDelete: false);
    }
    
    public async Task PublishAsync<T>(T integrationEvent) where T : IntegrationEvent
    {
        var eventName = typeof(T).Name;
        var message = JsonSerializer.Serialize(integrationEvent);
        var body = Encoding.UTF8.GetBytes(message);
        
        var properties = _channel.CreateBasicProperties();
        properties.Persistent = true; // 消息持久化
        properties.MessageId = integrationEvent.Id.ToString();
        properties.Timestamp = new AmqpTimestamp(DateTimeOffset.UtcNow.ToUnixTimeSeconds());
        
        _channel.BasicPublish(
            exchange: _exchangeName,
            routingKey: eventName,
            mandatory: true,
            basicProperties: properties,
            body: body);
            
        _logger.LogInformation("Published event {EventName}: {EventId}", eventName, integrationEvent.Id);
    }
    
    public void Subscribe<T, THandler>() 
        where T : IntegrationEvent 
        where THandler : IEventHandler<T>
    {
        var eventName = typeof(T).Name;
        var queueName = $"{typeof(THandler).Name}.{eventName}";
        
        // 声明队列
        _channel.QueueDeclare(
            queue: queueName,
            durable: true,
            exclusive: false,
            autoDelete: false);
            
        // 绑定队列到交换器
        _channel.QueueBind(
            queue: queueName,
            exchange: _exchangeName,
            routingKey: eventName);
            
        var consumer = new AsyncEventingBasicConsumer(_channel);
        consumer.Received += async (model, ea) =>
        {
            try
            {
                var body = ea.Body.ToArray();
                var message = Encoding.UTF8.GetString(body);
                
                var @event = JsonSerializer.Deserialize<T>(message);
                var handler = ActivatorUtilities.CreateInstance<THandler>(serviceProvider);
                
                await handler.HandleAsync(@event);
                
                _channel.BasicAck(ea.DeliveryTag, multiple: false);
                _logger.LogInformation("Processed event {EventName}: {EventId}", eventName, @event.Id);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing event {EventName}", eventName);
                _channel.BasicNack(ea.DeliveryTag, multiple: false, requeue: false);
            }
        };
        
        _channel.BasicConsume(
            queue: queueName,
            autoAck: false,
            consumer: consumer);
    }
}

8.2.2 事件驱动架构

// 定义领域事件
public abstract record IntegrationEvent
{
    public Guid Id { get; }
    public DateTime OccurredOn { get; }
    
    protected IntegrationEvent()
    {
        Id = Guid.NewGuid();
        OccurredOn = DateTime.UtcNow;
    }
}

public record OrderCreatedEvent : IntegrationEvent
{
    public Guid OrderId { get; }
    public Guid CustomerId { get; }
    public List<OrderItemDto> Items { get; }
    public decimal TotalAmount { get; }
    public DateTime CreatedAt { get; }
    
    public OrderCreatedEvent(Guid orderId, Guid customerId, List<OrderItemDto> items, decimal totalAmount)
    {
        OrderId = orderId;
        CustomerId = customerId;
        Items = items;
        TotalAmount = totalAmount;
        CreatedAt = DateTime.UtcNow;
    }
}

public record StockReservedEvent : IntegrationEvent
{
    public Guid OrderId { get; }
    public Dictionary<Guid, int> ReservedItems { get; }
    public DateTime ReservedAt { get; }
    
    public StockReservedEvent(Guid orderId, Dictionary<Guid, int> reservedItems)
    {
        OrderId = orderId;
        ReservedItems = reservedItems;
        ReservedAt = DateTime.UtcNow;
    }
}

// 事件处理器
public class OrderCreatedEventHandler : IEventHandler<OrderCreatedEvent>
{
    private readonly IInventoryService _inventoryService;
    private readonly IEventBus _eventBus;
    private readonly ILogger<OrderCreatedEventHandler> _logger;
    
    public async Task HandleAsync(OrderCreatedEvent @event)
    {
        _logger.LogInformation("Processing OrderCreatedEvent: {OrderId}", @event.OrderId);
        
        try
        {
            var reservedItems = new Dictionary<Guid, int>();
            
            // 为每个订单项预留库存
            foreach (var item in @event.Items)
            {
                var reserved = await _inventoryService.ReserveStockAsync(
                    item.ProductId, 
                    item.Quantity);
                    
                if (!reserved)
                {
                    // 库存不足,发布库存不足事件
                    await _eventBus.PublishAsync(new StockInsufficientEvent(
                        @event.OrderId,
                        item.ProductId,
                        item.Quantity
                    ));
                    
                    _logger.LogWarning("Insufficient stock for product {ProductId}", item.ProductId);
                    return;
                }
                
                reservedItems[item.ProductId] = item.Quantity;
            }
            
            // 库存预留成功,发布事件
            await _eventBus.PublishAsync(new StockReservedEvent(
                @event.OrderId,
                reservedItems
            ));
            
            _logger.LogInformation("Stock reserved for order: {OrderId}", @event.OrderId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing OrderCreatedEvent: {OrderId}", @event.OrderId);
            throw;
        }
    }
}

// 事件总线抽象
public interface IEventBus
{
    Task PublishAsync<T>(T integrationEvent) where T : IntegrationEvent;
    void Subscribe<T, THandler>() where T : IntegrationEvent where THandler : IEventHandler<T>;
}

public interface IEventHandler<in T> where T : IntegrationEvent
{
    Task HandleAsync(T @event);
}

8.2.3 Saga模式:管理分布式事务

// Saga实现 - 订单处理流程
public class OrderProcessingSaga : Saga<OrderProcessingSagaData>,
    IAmStartedByMessages<OrderCreatedEvent>,
    IHandleMessages<StockReservedEvent>,
    IHandleMessages<PaymentCompletedEvent>,
    IHandleMessages<StockInsufficientEvent>,
    IHandleMessages<PaymentFailedEvent>
{
    private readonly IOrderRepository _orderRepository;
    private readonly IEventBus _eventBus;
    
    protected override void ConfigureHowToFindSaga(SagaPropertyMapper<OrderProcessingSagaData> mapper)
    {
        mapper.ConfigureMapping<OrderCreatedEvent>(m => m.OrderId).ToSaga(s => s.OrderId);
        mapper.ConfigureMapping<StockReservedEvent>(m => m.OrderId).ToSaga(s => s.OrderId);
        mapper.ConfigureMapping<PaymentCompletedEvent>(m => m.OrderId).ToSaga(s => s.OrderId);
        mapper.ConfigureMapping<StockInsufficientEvent>(m => m.OrderId).ToSaga(s => s.OrderId);
        mapper.ConfigureMapping<PaymentFailedEvent>(m => m.OrderId).ToSaga(s => s.OrderId);
    }
    
    public async Task Handle(OrderCreatedEvent message, IMessageHandlerContext context)
    {
        Data.OrderId = message.OrderId;
        Data.CustomerId = message.CustomerId;
        Data.TotalAmount = message.TotalAmount;
        Data.Status = OrderStatus.Pending;
        
        // Saga开始,等待库存预留结果
        _logger.LogInformation("OrderProcessingSaga started for order: {OrderId}", message.OrderId);
    }
    
    public async Task Handle(StockReservedEvent message, IMessageHandlerContext context)
    {
        Data.StockReserved = true;
        Data.ReservedItems = message.ReservedItems;
        
        // 库存预留成功,开始支付流程
        await _eventBus.PublishAsync(new ProcessPaymentCommand(
            Data.OrderId,
            Data.CustomerId,
            Data.TotalAmount
        ));
        
        _logger.LogInformation("Stock reserved for order: {OrderId}, proceeding to payment", message.OrderId);
    }
    
    public async Task Handle(PaymentCompletedEvent message, IMessageHandlerContext context)
    {
        Data.PaymentCompleted = true;
        Data.PaymentTransactionId = message.TransactionId;
        
        // 支付成功,确认订单
        var order = await _orderRepository.GetByIdAsync(new OrderId(Data.OrderId));
        order.Confirm();
        await _orderRepository.UpdateAsync(order);
        
        // 发布订单确认事件
        await _eventBus.PublishAsync(new OrderConfirmedEvent(
            Data.OrderId,
            Data.PaymentTransactionId
        ));
        
        MarkAsComplete(); // Saga完成
        _logger.LogInformation("OrderProcessingSaga completed for order: {OrderId}", message.OrderId);
    }
    
    public async Task Handle(StockInsufficientEvent message, IMessageHandlerContext context)
    {
        // 库存不足,取消订单
        var order = await _orderRepository.GetByIdAsync(new OrderId(Data.OrderId));
        order.Cancel("Insufficient stock");
        await _orderRepository.UpdateAsync(order);
        
        await _eventBus.PublishAsync(new OrderCancelledEvent(
            Data.OrderId,
            "Insufficient stock"
        ));
        
        MarkAsComplete();
        _logger.LogWarning("OrderProcessingSaga cancelled for order {OrderId} due to insufficient stock", message.OrderId);
    }
    
    public async Task Handle(PaymentFailedEvent message, IMessageHandlerContext context)
    {
        // 支付失败,释放库存并取消订单
        await _eventBus.PublishAsync(new ReleaseStockCommand(
            Data.OrderId,
            Data.ReservedItems
        ));
        
        var order = await _orderRepository.GetByIdAsync(new OrderId(Data.OrderId));
        order.Cancel("Payment failed");
        await _orderRepository.UpdateAsync(order);
        
        await _eventBus.PublishAsync(new OrderCancelledEvent(
            Data.OrderId,
            "Payment failed"
        ));
        
        MarkAsComplete();
        _logger.Warning("OrderProcessingSaga cancelled for order {OrderId} due to payment failure", message.OrderId);
    }
}

public class OrderProcessingSagaData : ContainSagaData
{
    public Guid OrderId { get; set; }
    public Guid CustomerId { get; set; }
    public decimal TotalAmount { get; set; }
    public bool StockReserved { get; set; }
    public bool PaymentCompleted { get; set; }
    public Dictionary<Guid, int> ReservedItems { get; set; }
    public string PaymentTransactionId { get; set; }
    public OrderStatus Status { get; set; }
}

8.3 API网关:统一的入口

API网关是微服务架构中的重要组件,它提供了统一的入口点,处理横切关注点。

8.3.1 Ocelot网关配置

// ocelot.json
{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/v1/products/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "product-service",
          "Port": 80
        }
      ],
      "UpstreamPathTemplate": "/products/{everything}",
      "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer",
        "AllowedScopes": [ "product_api" ]
      },
      "RateLimitOptions": {
        "ClientWhitelist": [ "admin-client-id" ],
        "EnableRateLimiting": true,
        "Period": "1s",
        "PeriodTimespan": 1,
        "Limit": 10
      },
      "FileCacheOptions": {
        "TtlSeconds": 300,
        "Region": "products"
      }
    },
    {
      "DownstreamPathTemplate": "/api/v1/orders/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "order-service",
          "Port": 80
        }
      ],
      "UpstreamPathTemplate": "/orders/{everything}",
      "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer",
        "AllowedScopes": [ "order_api" ]
      },
      "AddHeadersToRequest": {
        "X-User-Id": "Claims[sub] > value",
        "X-User-Email": "Claims[email] > value"
      }
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "https://api.yourdomain.com",
    "RequestIdKey": "X-Request-ID",
    "AdministrationPath": "/admin"
  }
}

8.3.2 自定义中间件

// 自定义网关中间件
public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;
    
    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();
        
        // 记录请求
        _logger.LogInformation("Incoming request: {Method} {Path} from {IP}",
            context.Request.Method,
            context.Request.Path,
            context.Connection.RemoteIpAddress);
            
        await _next(context);
        
        stopwatch.Stop();
        
        // 记录响应
        _logger.LogInformation("Request completed: {Method} {Path} - {StatusCode} in {ElapsedMs}ms",
            context.Request.Method,
            context.Request.Path,
            context.Response.StatusCode,
            stopwatch.ElapsedMilliseconds);
    }
}

public class AuthenticationMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ITokenValidationService _tokenValidationService;
    
    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.Headers.TryGetValue("Authorization", out var authHeader))
        {
            if (authHeader.ToString().StartsWith("Bearer "))
            {
                var token = authHeader.ToString().Substring(7);
                var validationResult = await _tokenValidationService.ValidateTokenAsync(token);
                
                if (validationResult.IsValid)
                {
                    var claimsIdentity = new ClaimsIdentity(validationResult.Claims, "Bearer");
                    context.User = new ClaimsPrincipal(claimsIdentity);
                }
                else
                {
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    return;
                }
            }
        }
        
        await _next(context);
    }
}

8.4 小结

微服务通信是分布式系统的核心挑战。记住几个关键原则:

  1. 选择合适的通信方式:内部通信用gRPC,外部API用REST,异步用消息队列
  2. 设计容错机制:重试、熔断、超时、降级一个都不能少
  3. 避免同步调用链:尽量使用异步通信解耦服务
  4. 监控和追踪:分布式追踪是定位问题的利器
  5. 版本兼容性:接口演进要考虑向后兼容

最重要的是,通信方式的选择要基于业务需求。不要为了技术而技术,选择最适合当前场景的解决方案。

在下一章中,我们将探讨数据一致性和分布式事务,这是微服务架构中的另一个核心挑战。

posted @ 2026-01-22 21:41  高宏顺  阅读(2)  评论(0)    收藏  举报