res = requests.get("URL")
但是,两个问题:http 协议较为复杂,效率低,相对笨重调用方式不像本地调用简单方便,无法做到让调用者感知不到远程调用的逻辑RPC 的实现原理实际情况下,RPC 很少用 http 协议来进行数据传输毕竟只是想传输一下数据,何必动用到一个文本传输的应用层协议呢一般我们会选择直接传输二进制数据不管你用何种协议进行数据传输,一个完整的 RPC 过程,都可以用下面这张图来描述:以左边的 Client 端为例,Application 就是 RPC 的调用方,Client Stub 就是我们上面说到的代理对象,也就是那个看起来像是 Calculator 的实现类其实内部是通过 RPC 方式来进行远程调用的代理对象至于 Client Run-time Library,则是实现远程调用的工具包,比如 Python 的 socket 模块最后通过底层网络实现实现数据的传输这个过程中最重要的就是序列化和反序列化,因为传输的数据包必须是二进制的直接丢一个 Python 对象过去,人家也不认识我们必须把 Python 对象序列化为二进制格式,传给 Server 端Server 端接收到之后,再反序列化为 Python 对象Python 实现 RPCPython 实现 RPC 需要使用 rpyc 模块首先当然是安装模块:pip3 install rpyc -i https://pypi.douban.com/simple
安装好之后,我们就可以使用 rpyc,很容易地搭建起 Python 版本的 RPC 客户端和服务端了客户端 client.py 的代码为:import rpyc# 参数主要是 host, portconn = rpyc.connect('localhost', 9999)# cal 是服务端的那个以“exposed_”开头的方法print('start')while 1: try: num = int(input('请输入一个数字[任意非数字退出]:')) cResult = conn.root.cal(num) # 这一句是客户端的精华,调用服务端的函数 print(cResult) except Exception: breakprint('end')conn.close()
服务端 server.py 的代码为:from rpyc import Servicefrom rpyc.utils.server import ThreadedServerclass TestService(Service): # 对于服务端来说, 只有以"exposed_"打头的方法才能被客户端调用,所以要提供给客户端的方法都得加"exposed_" def exposed_cal(self, num): return num 2sr = ThreadedServer(TestService, port=9999, auto_register=False)sr.start()
gRPC 框架目前流行的开源 RPC 框架还是比较多的,比如阿里巴巴的 Dubbo、Facebook 的 Thrift、Google 的 gRPC、Twitter 的 Finagle 等gRPC 是 Google 公布的开源软件,基于最新的 HTTP 2.0 协议,并支持常见的众多编程语言RPC 框架是基于 HTTP 协议实现的,底层使用到了 Netty 框架的支持Thrift 是 Facebook 的开源 RPC 框架,主要是一个跨语言的服务开发框架用户只要在其之上进行二次开发就行,应用对于底层的 RPC 通讯等都是透明的不过这个对于用户来说需要学习特定领域语言这个特性,还是有一定成本的Dubbo 是阿里集团开源的一个极为出名的 RPC 框架,在很多互联网公司和企业应用中广泛使用协议和序列化框架都可以插拔是极其鲜明的特色接下来我们以使用较为广泛的 gRPC 为例学习下 RPC 框架的使用gRPC 是 Google 开放的一款 RPC (Remote Procedure Call) 框架,建立在 HTTP2 之上,使用 Protocol Buffers通过一个 .proto 文件,你可以定义你的数据的结构,并生成基于各种语言的代码目前支持的语言很多,有 Python、golang、js、java 等等有了 protocol buffers 之后,Google 进一步推出了 gRPC通过 gRPC,我们可以在 .proto 文件中也一并定义好 service,让远端使用的 client 可以如同调用本地的 library 一样使用基于这个原理,我们甚至可以实现跨语言的方法调用比如上面的图片中,gRPC Server 是由 C++ 写的,Client 则分別是 Java 以及 Ruby,Server 跟 Client 端则是通过 protocol buffers 来信息传递接下来,我们按照下面的流程,搭建一个 gRPC 模型安装 grpc 模块:pip3 install grpcio grpcio-tools -i https://pypi.douban.com/simple
定义功能函数 calculate.py,示例中的是用来计算给定数字的平方根:import math# 求平方def square(x): return math.sqrt(x)
创建 calculate.proto 文件,在这里描述我们要使用的 message 以及 service:syntax = "proto3";message Number { float value = 1;}service Calculate { rpc Square(Number) returns (Number) {}}
生成 gRPC 类这部分可能是整个过程中最 “黑盒子” 的部分,我们将使用特殊工具自动生成类在当前目录下执行下面的命令:python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. calculate.proto
你会看到生成了两个文件:calculate_pb2.py 包含 message 信息(calculate_pb2.Number)calculate_pb2_grpc.py 包含 server(calculate_pb2_grpc.CalculatorServicer)和 client(calculate_pb2_grpc.CalculatorStub)创建 gRPC 服务端:import grpcimport calculate_pb2import calculate_pb2_grpcimport calculatefrom concurrent import futuresimport time# 创建一个 CalculateServicer 继承自 calculate_pb2_grpc.CalculateServicerclass CalculateServicer(calculate_pb2_grpc.CalculateServicer): def Square(self, request, context): response = calculate_pb2.Number() response.value = calculate.square(request.value) # 在这里进行计算 return response# 创建一个 gRPC serverserver = grpc.server(futures.ThreadPoolExecutor(max_workers=10))# 利用 add_CalculateServicer_to_server 这个方法把上面定义的 CalculateServicer 加到 server 中calculate_pb2_grpc.add_CalculateServicer_to_server(CalculateServicer(), server)# 让 server 跑在 port 50051 中print('Starting server. Listening on port 50051.')server.add_insecure_port('[::]:50051')server.start()# 因为 server.start() 不会阻塞,添加睡眠循环以持续服务try: while True: time.sleep(24 60 60)except KeyboardInterrupt: server.stop(0)
启动 gRPC server 服务端:python server.py
创建 gRPC 客户端 client.py:import grpcimport calculate_pb2import calculate_pb2_grpc# 打开 gRPC channel,连接到 localhost:50051channel = grpc.insecure_channel('localhost:50051')# 创建一个 stub (gRPC client)stub = calculate_pb2_grpc.CalculateStub(channel)# 创建一个有效的请求消息 Numbernumber = calculate_pb2.Number(value=int(input('请输入一个数字:')))# 带着 Number 去调用 Squareresponse = stub.Square(number)print(response.value)
运行 gRPC 服务端:python client.py
总结RPC 主要用于公司内部的服务调用,性能消耗低,传输效率高,实现复杂HTTP 主要用于对外的异构环境,浏览器接口调用,App 接口调用,第三方接口调用等RPC 适用场景(大型的网站,内部子系统较多、接口非常多的情况下适合使用 RPC):长链接不必每次通信都要像 HTTP 一样去 3 次握手,减少了网络开销注册发布机制RPC 框架一般都有注册中心,有丰富的监控管理发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作安全性,没有暴露资源操作微服务支持就是最近流行的服务化架构、服务化治理,RPC 框架是一个强力的支撑RPC 没那么简单要实现一个 RPC 不算难,难的是实现一个高性能高可靠的 RPC 框架比如,既然是分布式了,那么一个服务可能有多个实例,你在调用时,要如何获取这些实例的地址呢?这时候就需要一个服务注册中心,比如在 Dubbo 里头,就可以使用 Zookeeper 作为注册中心在调用时,从 Zookeeper 获取服务的实例列表,再从中选择一个进行调用那么选哪个调用好呢?这时候就需要负载均衡了,于是你又得考虑如何实现复杂均衡,比如 Dubbo 就提供了好几种负载均衡策略这还没完,总不能每次调用时都去注册中心查询实例列表吧,这样效率多低呀,于是又有了缓存,有了缓存,就要考虑缓存的更新问题,blablabla……你以为就这样结束了,没呢,还有这些:客户端总不能每次调用完都干等着服务端返回数据吧,于是就要支持异步调用;服务端的接口修改了,老的接口还有人在用,怎么办?总不能让他们都改了吧?这就需要版本控制了;服务端总不能每次接到请求都马上启动一个线程去处理吧?于是就需要线程池;服务端关闭时,还没处理完的请求怎么办?是直接结束呢,还是等全部请求处理完再关闭呢?……如此种种,都是一个优秀的 RPC 框架需要考虑的问题(图片来源网络,侵删)
0 评论