微服務間通訊常見的兩種方式
由於微服務架構慢慢被更多人使用後,迎面而來的問題是如何做好微服務間通訊的方案。我們先分析下目前最常用的兩種服務間通訊方案。
gRPC(rpc遠端呼叫)
場景:A服務主動發起請求到B服務,同步方式
範圍:只在微服務間通訊應用
EventBus(基於訊息佇列的整合事件)
技術:NotNetCore.Cap + Rabbitmq + Database
場景:A服務要在B服務做某件事情後響應,非同步方式
實現:B服務在完成某件事情後釋出訊息,A服務訂閱此訊息
範圍:只在微服務間通訊應用
通過對比,兩種方式完全不一樣。rpc是類似於http請求的及時響應機制,但是比http更輕量、快捷,它更像以前的微軟的WCF,可以自動生成客戶端程式碼,充分體現了面向實體物件的遠端呼叫的思想;Eventbus是非同步的訊息機制,基於cap的思想,不關心下游訂閱方服務是否消費成功,保障了主服務業務的流暢性,同時也是一款分散式事務的實現方案,可以保障分散式架構中的資料的最終一致性。
我們今天主要介紹gRPC在微服務中的實踐案例。
gRPC-Server(服務端)
框架介紹
- .Net Core sdk 3.1
- Grpc.AspNetCore 2.30.0
- Grpc.Core 2.30.0
搭建步驟
以.net core webapi 專案為例,詳細說明如何整合gRPC。
建立專案
建立web api專案,此步驟說明省略
引入nuget包
引入gRPC 服務端需要的 nuget包,Grpc.AspNetCore 2.30.0和Grpc.Core 2.30.0
外部訪問
考慮到專案釋出後,有webapi本身的http的介面和gRPC的介面都要給外部訪問,就需要暴露http1和http2兩個埠。
方式1:本地除錯時,可以直接暴露http和https,如果你的伺服器支援https,也可以在生產環境使用https來訪問gRPC服務。
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseNLog()
.UseUrls("http://*:5000;https://*:5001");
方式2:如果在容器化部署場景下,一般會在dockerfile中指定ASPNETCORE_PORT環境變數,然後程式監聽http1和http2兩個埠。
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
var aspnetcorePort = Environment.GetEnvironmentVariable("ASPNETCORE_PORT") ?? 5000;
int.TryParse(aspnetcorePort, out int port);
webBuilder.ConfigureKestrel(options =>
{
options.ListenAnyIP(port, options => options.Protocols = HttpProtocols.Http1);
options.ListenAnyIP(port + 1, options => options.Protocols = HttpProtocols.Http2);
})
.UseStartup<Startup>();
webBuilder.UseNLog();
});
異常處理
由於gRPC服務端只能throw 基於 Grpc.Core.RpcException 的異常型別,所以我們可以自定義中介軟體來統一處理下異常
using Grpc.Core;
using Grpc.Core.Interceptors;
using System;
using System.Threading.Tasks;
public class ExceptionInterceptor : Interceptor
{
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation
)
{
try
{
return await continuation(request, context);
}
catch (RpcException ex)
{
throw ex;
}
catch (Exception ex)
{
throw new RpcException(new Status(StatusCode.Internal, ex.Message + "\r\n" + ex.StackTrace));
}
}
}
程式碼中被繼承的 Interceptor 是 Grpc.Core.Interceptors.Interceptor。主要處理的目的是把在gRPC介面中丟擲的非 RpcException 的異常,轉換為 RpcException。此中介軟體也是根據具體的業務需求來做的,主要是告訴大家可以重寫 Grpc.Core.Interceptors.Interceptor 的攔截器來統一處理一些事情。
定義協議緩衝區(protocol3)
新建項搜尋rpc可以出現協議緩衝區檔案
定義示例介面,建立訂單方法,以及建立訂單入參和出參。關於proto3協議具體說明,請參考往期文章。
syntax = "proto3";
option csharp_namespace = "GrpcTest.Protos";
service Order {
rpc CreateOrder (CreateOrderRequest) returns (CreateOrderReply);
}
message CreateOrderRequest {
string ItemCode = 1;
string ItemName = 2;
string Spec = 3;
double Price = 4;
double Quantity = 5;
string Unit = 6;
double Cost = 7;
}
message CreateOrderReply {
bool success = 1;
}
在專案的csproj檔案中,需要有proto包含進去,GrpcServices="Server"表示當前是服務端。改好後重新生成下專案。
<ItemGroup>
<Protobuf Include="Protos/GrpcTest.Protos" GrpcServices="Server" />
</ItemGroup>
建立OrderService
手動建立OrderService,繼承自Order.OrderBase(proto自動生成的程式碼)
public class OrderService : Order.OrderBase
{
public async override Task<CreateOrderReply> CreateOrder(CreateOrderRequest request, ServerCallContext context)
{
//todo something
//throw RpcException異常
throw new RpcException(new Status(StatusCode.NotFound, "資源不存在"));
//返回
return new CreateOrderReply
{
Success = true
};
}
}
重寫CreateOrder方法,此處就可以寫你的實際的業務程式碼,相當於Controller介面入口。如果業務中需要主動丟擲異常,可以使用RpcException,有定義好的一套狀態碼和異常封裝。
修改Startup
在ConfigureServices方法中加入AddGrpc,以及上面提到的異常處理中介軟體,程式碼如下
services.AddGrpc(option => option.Interceptors.Add<ExceptionInterceptor>());
在Configure方法中將OrderService啟用,程式碼如下
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<OrderService>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("this is a gRPC server");
});
});
至此 gRPC服務端搭建完成。
gRPC-Client(客戶端)
框架介紹
- .Net Core sdk 3.1
- Google.Protobuf 3.12.4
- Grpc.Tools 2.30.0
- Grpc.Net.ClientFactory 2.30.0
搭建步驟
以.net core webapi 專案為例,詳細說明如何整合gRPC客戶端
建立專案
建立web api專案,此步驟說明省略
引入nuget包
引入gRPC 客戶端需要的 nuget包,Google.Protobuf 3.12.4、Grpc.Tools 2.30.0和Grpc.Net.ClientFactory 2.30.0
引入proto檔案
將服務端的 order.proto 拷貝到客戶端的web api專案中,並在csproj檔案中新增ItemGroup節點。GrpcServices="Client"表示當前是客戶端。改好後重新生成下專案。
<ItemGroup>
<Protobuf Include="Protos/OutpAggregation.proto" GrpcServices="Client" />
</ItemGroup>
修改Startup
在ConfigureServices方法中加入AddGrpcClient,程式碼如下
services.AddHttpContextAccessor();
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var baseUrl = "http://localhost:5001/";
services.AddGrpcClient<Order.OrderClient>(
options =>
{
options.Address = new Uri(baseUrl);
});
注意:要使用.NET Core客戶端呼叫不安全的gRPC服務,需要進行其他配置。 gRPC客戶端必須將System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport開關設定為true,並在伺服器地址中使用http。可以在以下連結檢視具體說明。
[Troubleshoot gRPC on .NET Core]
另外說明下services.AddGrpcClient方法,來自於nuget包Grpc.Net.ClientFactory 2.30.0,將gRPC客戶端的注入封裝,具體程式碼實現可以檢視以下連結。
客戶端呼叫
以在Controller中呼叫為例,示例程式碼如下
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly Order.OrderClient _orderClient;
public WeatherForecastController(Order.OrderClient orderClient)
{
_orderClient = orderClient;
}
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
var result = await _orderClient.CreateOrderAsync(new CreateOrderRequest
{
ItemCode = "123",
ItemName = "名稱1"
});
}
}
通過建構函式注入gRPC客戶端,然後就可以使用裡面的同步或者非同步方法啦!