gRPC 微服务之 .Net Core 2.2 如何实现
gRPC 微服务在Go语言社区,可谓是应用非常广泛,它同时也支持其他市面上几乎所有的流行语言,比如Java,PHP,NodeJS,C#,Ruby,Python,c/c++等等。微软的Visual Studio 2019将提供项目模版直接支持gRPC,用户可以直接创建gRPC项目。但是我还是更加中意原滋原味的使用C#语言来创建gRPC微服务,今天我就基于.Net Core 2.2来开发一个功能比较齐全的gRPC微服务,基本可以满足小规模的企业用户使用(没有包含服务注册发现,这个将在以后架构的文章中介绍)。下面的源码都放在Github上,见文章最下方的地址。

1. 创建项目

项目 | 说明 |
IService | 业务层接口,微服务将调用这些接口,而不直接调用业务实现 |
Service | 业务实现,实现了全部的业务接口 |
IRepository | 数据接口,业务实现调用这个接口 |
Repository | 数据实现,实现了全部的数据接口 |
Implement | gRPC服务的实现全部在这个项目 |
Interface | gRPC的定义,包括消息的定义,方法的定义等等 |
Models | 实体对象,我们的业务不会直接使用gRPC消息,而是使用转换后的实体对象 |
MicroClient | 客户端调用 |
MicroServer | 服务端实现 |
2. 安装gRPC核心包
Google.Protobuf
Grpc
还有工具包,它不会直接在项目中使用,但是它提供了protoc.exe与grpc_csharp_plugin.exe执行文件,在生成c#类时要用到
Grpc.Tools
将这两个包安装到gRPC/Interface项目,其他地方都是间接引用
3. 服务定义
syntax = "proto3";
option csharp_namespace = "ASY.Hrefs.gRPC.Interface";
package hello;
service HelloSrv {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
默认gRPC使用protocol buffers作为接口定义语言,它定义了所有的承载信息,还有服务方法。message定义的是消息或者叫做实体,rpc定义了方法,service定义了服务。
4. 生成c#类
setlocal
@rem enter this directory
cd /d %~dp0
set TOOLS_PATH=C:\Users\hemin\.nuget\packages\grpc.tools\1.21.0\tools\windows_x64
%TOOLS_PATH%\protoc.exe -I gRPC/Interface --csharp_out gRPC/Interface gRPC/Interface/hello.proto --grpc_out gRPC/Interface --plugin=protoc-gen-grpc=%TOOLS_PATH%\grpc_csharp_plugin.exe
endlocal
我在解决方案目录定义了一个generate_protos.bat文件,内容如上面的,你只需要执行这个bat文件即可,它的作用就是通过hello.proto定义文件生成两个C#文件,Hello.cs和HelloGrpc.cs,不管是服务实现,还是客户端调用,都会用到它们。
5. 服务实现
namespace ASY.Hrefs.gRPC.Implement
{
public class HelloServiceImpl : HelloSrv.HelloSrvBase
{
IHelloService HelloService;
public HelloServiceImpl(IHelloService helloService)
{
this.HelloService = helloService;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
HelloReply response = new HelloReply();
response.Message = this.HelloService.SayHello(request.Name);
return Task.FromResult(response);
}
}
}
实现类实现自HelloGrpc.cs生成的抽象基类,我们可以看见,它实现了一个方法SayHello,对应我们在hello.proto里面定义的方法。IHelloService是业务接口,通过依赖注入方式注入,这里使用了.Net Core自带的功能。
6. 服务宿主
namespace ASY.Hrefs.MicroServer
{
public class HostServer : IHostedService
{
private readonly ILogger logger;
private readonly HelloSrv.HelloSrvBase server;
Server gRPC = null;
public HostServer(ILogger<HostServer> logger, HelloSrv.HelloSrvBase server)
{
this.logger = logger;
this.server = server;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
logger.LogInformation("服务启动...");
gRPC = new Server
{
Services = { HelloSrv.BindService(server) },
Ports = { new ServerPort("0.0.0.0", 50088, ServerCredentials.Insecure) }
};
gRPC.Start();
Console.WriteLine("Greeter server listening on port " + 50088);
Console.WriteLine("Press any key to stop the server...");
await Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation("服务停止...");
await gRPC.ShutdownAsync();
}
}
}
宿主是使用了微软的扩张组建Microsoft.Extensions.Hosting.IHostedService,它提供了启动以及停止的方法,方便我们启动停止gRPC服务,对部署特别友好。
7. 客服端调用
namespace ASY.Hrefs.MicroClient
{
class Program
{
static void Main(string[] args)
{
Stopwatch watch = new Stopwatch();
watch.Start();
Parallel.For(1, 2000, Run);
watch.Stop();
long ms = watch.ElapsedMilliseconds;
Console.WriteLine($"用时 {ms}ms");
Console.Read();
}
static void Run(int i)
{
HelloRequest request = new HelloRequest();
request.Name = "Jimmy, " + i.ToString();
HelloReply reply = new HelloReply();
reply = GrpcClient.SayHello(request);
Console.WriteLine(reply.Message);
}
private static Lazy<HelloSrv.HelloSrvClient> Connection = new Lazy<HelloSrv.HelloSrvClient>(() =>
{
var channel = new Channel(string.Format("{0}:{1}", "127.0.0.1", 50088), ChannelCredentials.Insecure);
HelloSrv.HelloSrvClient client = new HelloSrv.HelloSrvClient(channel);
return client;
});
private static HelloSrv.HelloSrvClient GrpcClient
{
get
{
return Connection.Value;
}
}
}
}
这个调用有点复杂,我使用延迟类实现了一个gRPC客户端的单例,这样不用每次创建通道,可以提高每次访问的速度,在生产环境中,你也需要这么做,不然会在高并发情况下出问题。我这里是为测试2000个并行时的速度,在单例与非单例的性能差别,你也可稍微改下代码测试下,当然你也可以只关注核心的调用代码即可。
Github源码:https://github.com/iissy/gRPCNetCore
Posted by 何敏 on 2019/6/15 16:40:29