因爲微服務架構慢慢被更多人使用後,迎面而來的問題是如何作好微服務間通訊的方案。咱們先分析下目前最經常使用的兩種服務間通訊方案。git
場景:A服務主動發起請求到B服務,同步方式
範圍:只在微服務間通訊應用github
技術:NotNetCore.Cap + Rabbitmq + Database
場景:A服務要在B服務作某件事情後響應,異步方式
實現:B服務在完成某件事情後發佈消息,A服務訂閱此消息
範圍:只在微服務間通訊應用web
經過對比,兩種方式徹底不同。rpc是相似於http請求的及時響應機制,可是比http更輕量、快捷,它更像之前的微軟的WCF,能夠自動生成客戶端代碼,充分體現了面向實體對象的遠程調用的思想;Eventbus是異步的消息機制,基於cap的思想,不關心下游訂閱方服務是否消費成功,保障了主服務業務的流暢性,同時也是一款分佈式事務的實現方案,能夠保障分佈式架構中的數據的最終一致性。docker
咱們今天主要介紹gRPC在微服務中的實踐案例。api
以.net core webapi 項目爲例,詳細說明如何集成gRPC。安全
建立web api項目,此步驟說明省略服務器
引入gRPC 服務端須要的 nuget包,Grpc.AspNetCore 2.30.0和Grpc.Core 2.30.0架構
考慮到項目發佈後,有webapi自己的http的接口和gRPC的接口都要給外部訪問,就須要暴露http1和http2兩個端口。app
方式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 的攔截器來統一處理一些事情。
新建項搜索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,繼承自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,有定義好的一套狀態碼和異常封裝。
在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服務端搭建完成。
以.net core webapi 項目爲例,詳細說明如何集成gRPC客戶端
建立web api項目,此步驟說明省略
引入gRPC 客戶端須要的 nuget包,Google.Protobuf 3.12.四、Grpc.Tools 2.30.0和Grpc.Net.ClientFactory 2.30.0
將服務端的 order.proto 拷貝到客戶端的web api項目中,並在csproj文件中添加ItemGroup節點。GrpcServices="Client"表示當前是客戶端。改好後從新生成下項目。
<ItemGroup> <Protobuf Include="Protos/OutpAggregation.proto" GrpcServices="Client" /> </ItemGroup>
在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客戶端,而後就可使用裏面的同步或者異步方法啦!