📡 微服務通訊:同步 vs 非同步
📌 微服務通訊方式概覽
微服務通訊模式
├── 同步通訊(請求-回應)
│ ├── HTTP / REST
│ └── gRPC
└── 非同步通訊(事件驅動)
├── Message Queue(RabbitMQ、Azure Service Bus)
└── Event Streaming(Kafka)
| 方式 | 耦合度 | 即時性 | 可靠性 | 適用場景 |
|---|---|---|---|---|
| HTTP REST | 中 | 高 | 依賴對方在線 | 查詢、CRUD |
| gRPC | 中 | 高 | 依賴對方在線 | 服務間高效能通訊 |
| Message Queue | 低 | 低 | 高(訊息持久化) | 事件通知、長流程 |
📌 同步通訊:HttpClient + HttpClientFactory
為什麼要用 HttpClientFactory?
// ❌ 直接 new HttpClient — 會導致 Socket 耗盡!
var client = new HttpClient(); // 每次都建立新連線
var response = await client.GetAsync("http://product-service/api/products");
// 連線不會被正確釋放 → Socket exhaustion
// ✅ 使用 HttpClientFactory — 連線池管理
// Program.cs 註冊
builder.Services.AddHttpClient("ProductService", client =>
{
client.BaseAddress = new Uri("http://product-service");
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.Timeout = TimeSpan.FromSeconds(10);
});
// 使用
public class OrderService
{
private readonly IHttpClientFactory _httpClientFactory;
public OrderService(IHttpClientFactory httpClientFactory)
=> _httpClientFactory = httpClientFactory;
public async Task<ProductDto?> GetProductAsync(int productId)
{
var client = _httpClientFactory.CreateClient("ProductService");
var response = await client.GetAsync($"/api/products/{productId}");
if (!response.IsSuccessStatusCode) return null;
return await response.Content
.ReadFromJsonAsync<ProductDto>();
}
}
具型別 HttpClient (Typed Client)
// 更推薦的方式:具型別 HttpClient
public class ProductServiceClient
{
private readonly HttpClient _client;
public ProductServiceClient(HttpClient client)
{
client.BaseAddress = new Uri("http://product-service");
_client = client;
}
public async Task<ProductDto?> GetProductAsync(int id)
=> await _client.GetFromJsonAsync<ProductDto>($"/api/products/{id}");
public async Task<List<ProductDto>> GetAllProductsAsync()
=> await _client.GetFromJsonAsync<List<ProductDto>>("/api/products") ?? new();
public async Task<bool> CheckStockAsync(int productId, int quantity)
{
var response = await _client.GetAsync(
$"/api/products/{productId}/stock?required={quantity}");
return response.IsSuccessStatusCode;
}
}
// 註冊
builder.Services.AddHttpClient<ProductServiceClient>();
📌 gRPC 在 .NET 中的實作
gRPC 使用 Protocol Buffers 進行序列化,效能比 JSON 高出很多。
// Protos/product.proto
syntax = "proto3";
option csharp_namespace = "ProductService.Grpc";
package product;
service ProductGrpc {
rpc GetProduct (GetProductRequest) returns (ProductReply);
rpc GetProducts (GetProductsRequest) returns (stream ProductReply);
}
message GetProductRequest {
int32 id = 1;
}
message GetProductsRequest {
string category = 1;
int32 page_size = 2;
}
message ProductReply {
int32 id = 1;
string name = 2;
string description = 3;
double price = 4;
int32 stock_quantity = 5;
}
gRPC 服務端
public class ProductGrpcService : ProductGrpc.ProductGrpcBase
{
private readonly ProductDbContext _db;
public ProductGrpcService(ProductDbContext db) => _db = db;
public override async Task<ProductReply> GetProduct(
GetProductRequest request, ServerCallContext context)
{
var product = await _db.Products.FindAsync(request.Id)
?? throw new RpcException(
new Status(StatusCode.NotFound, "產品不存在"));
return new ProductReply
{
Id = product.Id,
Name = product.Name,
Description = product.Description,
Price = (double)product.Price,
StockQuantity = product.StockQuantity
};
}
}
gRPC 客戶端
// 在 Order Service 中呼叫 Product Service 的 gRPC
builder.Services.AddGrpcClient<ProductGrpc.ProductGrpcClient>(options =>
{
options.Address = new Uri("http://product-service:5001");
});
public class OrderService
{
private readonly ProductGrpc.ProductGrpcClient _productClient;
public OrderService(ProductGrpc.ProductGrpcClient productClient)
=> _productClient = productClient;
public async Task<ProductReply> GetProductInfoAsync(int productId)
{
return await _productClient.GetProductAsync(
new GetProductRequest { Id = productId });
}
}
📌 非同步通訊:RabbitMQ
安裝 RabbitMQ
# 用 Docker 啟動 RabbitMQ
docker run -d --name rabbitmq \
-p 5672:5672 -p 15672:15672 \
rabbitmq:3-management
使用 MassTransit 整合 RabbitMQ
// 定義事件(共用的契約)
public record OrderCreatedEvent(
Guid OrderId,
Guid CustomerId,
List<OrderItemInfo> Items,
decimal TotalAmount,
DateTime CreatedAt);
public record OrderItemInfo(int ProductId, int Quantity, decimal UnitPrice);
// ── 發布者(Order Service) ──
public class OrderService
{
private readonly IPublishEndpoint _publishEndpoint;
public OrderService(IPublishEndpoint publishEndpoint)
=> _publishEndpoint = publishEndpoint;
public async Task CreateOrderAsync(CreateOrderCommand command)
{
// 1. 儲存訂單到資料庫
var order = new Order { /* ... */ };
await _orderRepo.AddAsync(order);
// 2. 發布事件 — 其他服務會自動收到
await _publishEndpoint.Publish(new OrderCreatedEvent(
order.Id, order.CustomerId, order.Items.Select(i =>
new OrderItemInfo(i.ProductId, i.Quantity, i.UnitPrice)).ToList(),
order.TotalAmount, DateTime.UtcNow));
}
}
// ── 消費者(Inventory Service) ──
public class OrderCreatedConsumer : IConsumer<OrderCreatedEvent>
{
private readonly IInventoryService _inventory;
public OrderCreatedConsumer(IInventoryService inventory)
=> _inventory = inventory;
public async Task Consume(ConsumeContext<OrderCreatedEvent> context)
{
var message = context.Message;
foreach (var item in message.Items)
{
await _inventory.ReserveStockAsync(
item.ProductId, item.Quantity);
}
}
}
註冊 MassTransit
// Program.cs — Inventory Service
builder.Services.AddMassTransit(x =>
{
x.AddConsumer<OrderCreatedConsumer>();
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host("rabbitmq", h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.ConfigureEndpoints(context);
});
});
📌 範例比較:訂單建立的同步 vs 非同步
// ── 同步方式:Order Service 直接呼叫 Inventory Service ──
public async Task<OrderResult> CreateOrderSync(CreateOrderCommand cmd)
{
// 1. 呼叫庫存服務檢查 → 等待回應
var stockOk = await _inventoryClient.CheckStockAsync(cmd.ProductId, cmd.Quantity);
if (!stockOk) return OrderResult.Fail("庫存不足");
// 2. 呼叫庫存服務扣減 → 等待回應
await _inventoryClient.ReserveStockAsync(cmd.ProductId, cmd.Quantity);
// 3. 儲存訂單
var order = CreateOrder(cmd);
await _orderRepo.AddAsync(order);
return OrderResult.Success(order.Id);
// 缺點:庫存服務掛了 → 整個下單流程失敗
}
// ── 非同步方式:透過事件通知 ──
public async Task<OrderResult> CreateOrderAsync(CreateOrderCommand cmd)
{
// 1. 直接儲存訂單(狀態:Pending)
var order = CreateOrder(cmd, OrderStatus.Pending);
await _orderRepo.AddAsync(order);
// 2. 發布事件 → 不等待回應
await _publishEndpoint.Publish(new OrderCreatedEvent(order.Id, /* ... */));
return OrderResult.Success(order.Id);
// 優點:庫存服務暫時掛了也沒關係,訊息會排隊等待處理
}
下一章: 我們將學習 API Gateway,統一管理所有微服務的入口。