protobuf 介绍,Python 和 Go 编写 rpc 服务(gRPC) (4)

显然 rpc 中一个非常重要的一步就是数据的序列化和反序列化,如果你能实现一个更好的数据序列化和反序列化协议,那么你能实现一个更好的 rpc 框架。

zerorpc 实现 rpc 调用

zerorpc 是利用 zeroMQ 消息队列 + msg 消息序列化(二进制)来实现类似于 gRPC(一个 rpc 框架,我们后面重点介绍)的功能,并且还能够跨语言调用(Nodejs 和 Python)。主要使用到 zeroMQ 的通信模式 ROUTER-DEALER,并模拟 grpc 的请求响应式和应答流式 rpc,而且还支持 PUB-SUB 通信模式的远程调用。

但是注意:zerorpc 并不需要我们安装一个 zeroMQ 消息队列,它是一个 Python 第三方库,只是使用了里面的通信模式而已。zerorpc 依赖 msgpack-python pyzmq future greenlet gevent,直接 pip install zerorpc 即可,会自动解决依赖。另外我们看到它依赖 gevent,说明 zerorpc 是支持并发的。

一元调用

一元调用类似于 xmlrpc,创建一个 rpc 服务,将一个类注册到里面,然后监听端口即可。

import zerorpc class Vtuber: vtubers = ["神乐七奈", "夏色祭", "凑-阿库娅"] def show_vtubers(self): return self.vtubers def add_vtuber(self, vtuber): if vtuber not in self.vtubers: self.vtubers.append(vtuber) def remove_vtuber(self, vtuber): if vtuber in self.vtubers: self.vtubers.remove(vtuber) vtuber = Vtuber() server = zerorpc.Server(vtuber) server.bind("tcp://0.0.0.0:7777") server.run()

以上是服务端的代码,可以看到不同的框架虽然调用方式不同,但整体都是大同小异的。然后我们编写客户端的代码:

import zerorpc client = zerorpc.Client() client.connect("tcp://127.0.0.1:7777") print(client.show_vtubers()) # [\'神乐七奈\', \'夏色祭\', \'凑-阿库娅\']

在功能上,没有太大的区别,但是很明显速度要比 xmlrpc 快很多。因此如果我们需要在业务上实现一个简单的 rpc,那么 zerorpc 是我们的首选。

流式调用

什么是流式调用呢?举个简单的例子,我们服务里面的某个函数执行了数据查询,那么可以查询完毕之后一次性返回,也可以查询一部分就返回一部分,像水流一样源源不断。

import zerorpc class StreamingRPC: # 必须使用该装饰器进行装饰,否则会有异常 @zerorpc.stream def streaming_range(self, start, end, step=1): return range(start, end, step) server = zerorpc.Server(StreamingRPC()) server.bind("tcp://0.0.0.0:7777") server.run()

然后是客户端:

import zerorpc client = zerorpc.Client() client.connect("tcp://127.0.0.1:7777") for item in client.streaming_range(1, 10, 2): print(item) """ 1 3 5 7 9 """

我们目前的客户端服务端通信方式都是通过 tcp 直连的方式,使用 msgpack 将消息序列化之后直接通过 tcp 将数据传送过去,而 zerorpc 是不需要依赖 zeroMQ 消息队列的。但是它也可以选择使用 zeroMQ 消息队列,客户端将消息发送到队列中,服务端从队列中取出消息,业务逻辑执行完毕之后再将结果序列化并放到队列中,客户端再去取。

因此这种方式就实现了解耦,而且也实现了异步,客户端发送完毕之后就没必要等待了。可以先去做别的事情,没事往队列里面看一看,如果有数据就取出来,没有的话就继续干其它的。此外还可以实现限流,只要给消息队列设置一个容量即可,当然也可以实现复杂均衡等等,这个不是我们的重点,我们的重点的是 gRPC。

所以 zerorpc 相对还是比较完善的,支持 Nodejs 和 Python,但为什么我们不选择它呢?首先是生态,zerorpc 的 生态不如 gRPC;以及语言的支持,gRPC 支持绝大部分的语言,而且它的功能要比 zerorpc 更强大。

使用 Go 开发 rpc

下面我们来看看如何在 Go 中进行 rpc 开发,当然有了 Python 开发 rpc 的经验,在使用 Go 的时候就简单多了。

这里我使用的是 go1.14,采用的是 go module,强烈建议你也使用这种包管理方式。因为早期的 Go 在包管理方面实际上做的不是太完善,特别是需要将整个工程目录放在 GOPATH 的 src 下真的是诟病无数。但是自从引入了 go module 就方便许多了,因此后续在贴代码的时候文件所在的层级目录就不说了。

我们以一个简单的 hello world 为例,看看怎么使用 Go 开进行 rpc 的开发。首先编写服务端:

package main import ( "fmt" "net" "net/rpc" ) // 定义一个简单的结构体,里面不包含任何的成员,第一个例子越简单越好 // 我们可以把这个结构体想象成 Python 中的类 type HelloService struct { } // 然后绑定方法 // 接收两个参数: string *string,返回一个 error,参数和返回值类型比较固定,我们没有选择的权利 func (server *HelloService) Hello (request string, reply *string) error { // 函数的返回值是一个 error,但显然该服务返回给客户端的数据不可能是一个 error // 没错,返回值是用过 reply 指定的,它是一个指针,我们可以修改它 *reply = fmt.Sprintf("hello %s", request) return nil } func main() { // 接下来要做什么了?显然是实例化一个 Server,然后将 HelloService 注册到 rpc 服务当中,然后客户端调用它绑定的方法 // 这里叫 listener listener, _ := net.Listen("tcp", ":9999") // 注册:第一个参数是名字(很重要,介绍客户端的时候会说);第二个参数结构体实例的指针,相当于 Python 中的实例对象 _ = rpc.RegisterName("Hello Service", &HelloService{}) // 启动服务,等待连接的到来 // 而一旦新的连接进来的时候,会返回一个连接,然后 rpc.ServeConn,后续的一系列操作都由该 rpc 来接管了 conn, _ := listener.Accept() rpc.ServeConn(conn) }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwfsgj.html