摘要
- 一,远程调用技术简史
- 二,gRPC简介
- 三,gRPC示例代码
自从产业界发明机器联网的那一天就已经开始探索最优的远程通信机制。操作系统如UNIX,Windows和Linux等都有实现远程通信的内部协议,挑战在于如何向开发人员开放一个通信框架。
一,远程调用技术简史
在20世纪90年代,当TCP / IP协议日臻成熟变成网络通信的黄金标准时,焦点转移到跨平台通信 - 一台计算机可以通过某种类型网络在另一台计算机上发起一个动作。 CORBA,DCOM,Java RMI技术,在核心网络基础设施之上创造了一个对开发者友好的抽象层。这些技术还试图发展出一套与开发语言无关的通信框架,这一点对于客户机/服务器体系结构至关重要。
随着本世纪初Web技术的演进,HTTP逐渐演变为事实上的通信标准.HTTP结合XML提供了一种自我描述,不依赖语言,与平台无关的远程通信框架。这种结合的成果是SOAP和WSDL标准,它们保证了在各种运行环境和平台之间实现互操作的标准化。
下一个冲击互联网的浪潮是Web编程。许多开发人员发现定义SOAP标准的HTTP和XML的组合过度严格。这个JavaScript和JSON开始流行了.Web 2.0现象(API发挥了关键作用),JSON替代XML成为首选的协议.HTTP和JSON这对致命的组合,催生了一个新的非官方标准REST .SOAP要求严格遵守标准和结构定义,仅局限于大型企业应用程序,而REST在当代开发人员中很受欢迎。
1.1 HTTP,REST和微服务
REST在Web开发中的应用程序开始基于REST实现,即使是内部序列化和通信模式的领域。但HTTP是最有效的消息交换协议吗?即使在同一上下文,同一网络,或者是同一台机器上运行的服务之间?HTTP的便捷性与高性能之间需要作出权衡,这促使我们回到问题的起点,寻找微服务架构中最优的通信框架。
进入gRPC时代 - 来自谷歌,现代的轻量级通信协议。这是一个高性能的,开源的通用远程过程调用(RPC)框架,它可以在多种开发语言,任何操作系统上运行。
gRPC在推出的第一年内就被CoreOS,Netflix,Square和Cockroach Labs等机构采用。CoreOS团队的Etcd,是一种分布式键/值存储服务,采用gRPC实现端通信。电信公司如Cisco,Juniper和Arista都使用gRPC实现数据流遥测和网络设备配置。
1.2什么是gRPC?
当我第一次遇到gRPC时,它使我想到CORBA。两个框架都基于语言无关的接口定义语言(IDL)声明服务,通过特定的语言绑定实现。
客户机在桩(Stub)上调用一个方法(method),调用过程由底层协议透明地处理。
gRPC的秘诀在于处理序列化的方式.gRPC基于Protocol Buffer,一个开源的用于结构化数据序列化机制,它是语言和平台无关的.Protocol Buffer的描述非常详细,与XML类似。但是它们比其他协议格式更小,更快,效率更高。任何需要序列化的自定义数据类型在gRPC被定义为一个协议缓冲区。
Protocol Buffer的最新版本是Proto3,支持多种开发语言的代码生成,Java,C ++,Python,Ruby,Java Lite,JavaScript,Objective-C和C#。访问器(setter和getter)为每个字段提供定义。
相比于REST + JSON组合,gRPC提供更好的性能和安全性。它极大的促进了在客户端和服务器之间使用SSL / TLS进行身份验证和数据交换加密。
为什么微服务开发者需要使用gRPC?gRPC采用HTTP / 2以支持高性能的,可扩展的API。报文使用二进制而不是文本通信可以保持载荷紧凑,高效.HTTP / 2请求在一个TCP连接上可支持多路复用,允许多个消息并发传送而不影响网络资源利用率。gRPC使用报头压缩(header compression)来减少请求和响应的大小。
二,gRPC简介
2.1创建gRPC服务的流程
- 在Protocol Buffer(.proto)文件中描述服务和载荷结构
- 从.proto文件生成gRPC代码
- 用一种开发语言实现服务端
- 创建一个客户端调用服务
- 运行服务端和客户端
- 注意:Node.js客户端不需要生成存根(Stub),只要Protocol Buffer文件是可访问的,它就可以直接与服务端对话。
三,gRPC示例代码
为了进一步熟悉gRPC,我们将用Python语言创建一个简单的计算服务。它将同时被一个Python客户端和一个Node.js客户端调用。以下测试示例运行在Mac OS X中。
- 环境准备
// 配置 Python gRPC python -m pip install virtualenv virtualenv venvsource venv/bin/activate python -m pip install --upgrade pip //安装 gRPC 和 gRPC Tools python -m pip install grpcio python -m pip install grpcio-tools // 配置 Node.js gRPC npm install grpc --global //创建目录 mkdir Proto mkdir Server mkdir -p Client/Python mkdir -p Client/Node
- 创建协议缓冲区文件
//Proto/Calc.protosyntax = "proto3";package calc; service Calculator { rpc Add (AddRequest) returns (AddReply) {} rpc Substract (SubstractRequest) returns (SubstractReply) {} rpc Multiply (MultiplyRequest) returns (MultiplyReply) {} rpc Divide (DivideRequest) returns (DivideReply) {} } message AddRequest{ int32 n1=1; int32 n2=2; } message AddReply{ int32 n1=1; } message SubstractRequest{ int32 n1=1; int32 n2=2; } message SubstractReply{ int32 n1=1; } message MultiplyRequest{ int32 n1=1; int32 n2=2; } message MultiplyReply{ int32 n1=1; } message DivideRequest{ int32 n1=1; int32 n2=2; } message DivideReply{ float f1=1; }
- 生成Python服务端和客户端代码
$ python -m grpc.tools.protoc --python_out=. --grpc_python_out=. --proto_path=. Calc.proto $ cp Calc_pb2.py ../Server $ cp Calc_pb2.py ../Client/Python $ cp Calc.proto ../Client/Node
- 创建服务端
# Server/Calc_Server.pyfrom concurrent import futuresimport timeimport grpcimport Calc_pb2import Calc_pb2_grpc _ONE_DAY_IN_SECONDS = 60 * 60 * 24class Calculator(Calc_pb2.CalculatorServicer): def Add(self, request, context): return Calc_pb2.AddReply(n1=request.n1+request.n2) def Substract(self, request, context): return Calc_pb2.SubstractReply(n1=request.n1-request.n2) def Multiply(self, request, context): return Calc_pb2.MultiplyReply(n1=request.n1*request.n2) def Divide(self, request, context): return Calc_pb2.DivideReply(f1=request.n1/request.n2)def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) Calc_pb2_grpc.add_CalculatorServicer_to_server(Calculator(), server) server.add_insecure_port('[::]:50050') server.start() try: while True: time.sleep(_ONE_DAY_IN_SECONDS) except KeyboardInterrupt: server.stop(0)if __name__ == '__main__': serve()
- 启动服务端
python Calc_Server.py
- 创建Python客户端
# Client/Python/Calc_Client.pyfrom __future__ import print_functionimport grpcimport Calc_pb2import Calc_pb2_grpcdef run(): channel = grpc.insecure_channel('localhost:50050') stub = Calc_pb2_grpc.CalculatorStub(channel) response = stub.Add(Calc_pb2.AddRequest(n1=20,n2=10)) print(response.n1) response = stub.Substract(Calc_pb2.SubstractRequest(n1=20,n2=10)) print(response.n1) response = stub.Multiply(Calc_pb2.MultiplyRequest(n1=20,n2=10)) print(response.n1) response = stub.Divide(Calc_pb2.DivideRequest(n1=20,n2=10)) print(response.f1)if __name__ == '__main__': run()
- 创建Node.js客户端
//Client/Node/Calc_Client.jsvar PROTO_PATH = 'Calc.proto';var grpc = require('grpc');var calc_proto = grpc.load(PROTO_PATH).calc;var params={n1:20, n2:10};function main() { var client = new calc_proto.Calculator('localhost:50050', grpc.credentials.createInsecure()); client.divide(params, function(err, response) { console.log(response.f1); }); client.multiply(params, function(err, response) { console.log(response.n1); }); client.substract(params, function(err, response) { console.log(response.n1); }); client.add(params, function(err, response) { console.log(response.n1); }); } main();
- 启动客户端Node.js / Python
$ python Calc_Client.py 30 10 200 2.0 $ node Calc_Client.js 30 10 200 2.0
登录 | 立即注册