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);
});
[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客户端,然后就可以使用里面的同步或者异步方法啦!