gRPC-微服務間通信實踐


微服務間通信常見的兩種方式

由於微服務架構慢慢被更多人使用后,迎面而來的問題是如何做好微服務間通信的方案。我們先分析下目前最常用的兩種服務間通信方案。

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客戶端的注入封裝,具體代碼實現可以查看以下鏈接。

Grpc.Net.ClientFactory

客戶端調用

以在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客戶端,然后就可以使用里面的同步或者異步方法啦!

ReadMe Card


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM