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.csHelloGrpc.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/06/15 16:40:29
Copyright ©2018 程序员网址导航 粤ICP备14091834号