☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
microservices 中級

📡 微服務通訊:同步 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,統一管理所有微服務的入口。

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊