文章目录
仿
gRPC
功能实现像调用本地方法一样调用其他服务器方法
gRPC
功能实现像调用本地方法一样调用其他服务器方法
简介
在介绍gRPC
简介之前我们先了解一写概念:
单体架构
单体架构简单理解就是所有的业务代码都在一台服务器上,一旦某个服务宕机,会引起整个应用不可用,隔离性差。只能整体应用进行伸缩,例如整体打包部署一台或多台服务器,浪费资源,可伸缩性差。代码耦合在一起,可维护性差。
微服务架构
解决了单体架构的弊端。可按需拆成多个服务,例如针对用户的请求非常多,针对支付的请求少,可以将用户业务功能拆为多个服务器,支付业务功能拆为单个服务器。
也有一些弊端,例如代码冗余,同样的代码在多个服务器上都要写,例如接口等。服务与服务之间存在调用关系,服务拆分之后,就是服务和服务之间发生是进程与进程之间的调用,服务器与服务器之间的调用。
这时候就需要发起网络调用, 网络调用一般使用HTTP
,但是在微服务架构中,HTTP
虽然方便,但性能较低,这时候就需要引入RPC
(远程过程调用),通过自定义协议发起TCP
调用,来加快传输效率。
RPC
RPC
的全称是Remote Procedure Call
,远程过程调用。这是一种协议,是用来屏蔽分布式计算中的各种调用细节,使得你可以像是本地调用一样直接调用一个远程的函数。
gPRC
gRPC
是一个高性能、开源和通用的RPC
框架,面向移动和 HTTP/2
设计。目前提供 C
、Java
和 Go
语言版本,分别是:grpc
, grpc-java
, grpc-go.
其中 C
版本支持 C
, C++
, Node.js
, Python
, Ruby
, Objective-C
, PHP
和 C#
支持。
中文文档:http://doc.oschina.net/grpc
在gRPC
中,我们称调用方为client
,被调用方为server
。跟其他的RPC
框架一样,gRPC
也是基于服务定义的思想。简单的来讲,就是我们通过某种方式来描述一个服务,这种描述方式是语言无关的。在这个服务定义的过程中,我们描述了我们提供的服务服务名是什么,有哪些方法可以被调用,这些方法有什么样的入参,有什么样的回参。
也就是说,在定义好了这些服务、这些方法之后,gRPC
会屏蔽底层的细节,client
只需要直接调用定义好的方法,就能拿到预期的返回结果。对于server
端来说,还需要实现我们定义的方法。同样的,gRPC
也会帮我们屏蔽底层的细节,我们只需要实现所定义的方法的具体逻辑即可。
可以发现,在上面的描述过程中,所谓的服务定义,就跟定义接口的语义是很接近的。我更愿意理解为这是一种"约定”,双方约定好接口,然后server
实现这个接口,client
调用这个接口的代理对象。到于其他的细节,交给gRPC
。
gRPC
交互逻辑
服务端逻辑
- 创建
gRPC Server
对象,可以理解为它是Server
端的抽象对象。 - 将
server
(其包含需要被调用的服务端接口)注册到gRPC Server
的内部注册中心。- 这样可以在接受到请求时,通过内部的服务发现。发现该服务端接口并转接进行逻辑处理。
- 创建
Listen
,监听TCP
端口。 gRPC Server
开始lis.Accept
,直到Stop
。
客户端逻辑
- 创建与给定目标服务端的连接交互。
- 创建
server
的客户端对象。 - 发送
RPC
请求,等待同步响应,得到回调后返回响应结果。 - 输出响应结里。
示例图
如下图:业务服务器调用登录业务服务器,支付服务器,库存服务器。
原生实现仿gRPC
框架
因为gRPC
框架目前不支持IRIS/Caché
,所以这里我们了解gRPC
原理后,仿造gRPC
框架实现类似的功能。通过正常编写代码无感知的情况下调用其他服务器上的代码方法。
注:为了显示这里使用Caché
作为客户端,IRIS
作为服务端。
编写客户端方法
- 首先在客户端也就是调用端创建客户端类
Util.RPC.Client
,代码如下:%DispatchClassMethod
- 动态派发方法是实现无感知的关键。SERVERIP
- 目标服务端的IP
地址。PORT
- 目标服务端的端口号。
Class Util.RPC.Client Extends %RegisteredObject
{
Parameter SERVERIP = "127.0.0.1";
Parameter PORT = 7788;
ClassMethod %DispatchClassMethod(class As %String, method As %String, args...) [ ServerOnly = 1 ]
{
#; 客户端通信、客户端需要设置服务器IP与端口号
#dim clientSocket As %IO.Socket = ##class(%IO.Socket).%New()
s host = ..#SERVERIP
s port = ..#PORT
s clientSocket.TranslationTable = "UTF8"
d clientSocket.Open(host, port, .sc)
s obj = {} //注释1
s obj.class = class
s obj.method = method
s params = []
s i = ""
for {
s i = $o(args(i))
q:(i = "")
d params.%Push(args(i))
}
s obj.params = params
d clientSocket.WriteLine(obj.%ToJSON()) //注释2
while (1) {
s data = clientSocket.ReadLine()
if (data '= "" ){ //注释3
ret data
}
}
q $$$OK
}
}
- 创建空类
M.Login
,M.Pay
,M.Stock
分别继承Util.RPC.Client
。- 目的是模拟交互接口,因为要调用其他服务器方法,首先要确定要调用的服务器接口名称。
-
按照常规调用方法的模式,来编写方法。
-
这里实际上是没有
Login
,Pay
,Stock
方法的,因为上一步创建的类为空类。 -
如果按照常规直接调用方法肯定会提示方法不存在的错误,这里实际上我们调用的是服务端的方法。
-
可以观察到,这里是按照正常调用类方法的方式编写的代码,并没有其他额外的操作。
-
Class M.RPC Extends %RegisteredObject
{
/// d ##class(M.RPC).Biz()
ClassMethod Biz()
{
w ##class(M.Login).Login("yx","123456"),!
w ##class(M.Pay).Pay(100),!
w ##class(M.Stock).Stock(3),!
}
}
编写服务端方法
- 创建服务端监听
Util.RPC.Server
类,这里模拟的是gRPC
创建Server
对象,创建Listen
,监听TCP
端口。代码如下:- 参数
PORT
为服务端监听和开启的接口。
- 参数
Class Util.RPC.Server Extends %RegisteredObject
{
Parameter PORT = 7788;
/// d ##class(Util.RPC.Server).ServerRPC()
ClassMethod ServerRPC()
{
#; 服务端通信、服务端需要打开端口,等待客户端通信
#dim severSocket As %IO.ServerSocket = ##class(%IO.ServerSocket).%New()
s port = ..#PORT
s severSocket.TranslationTable="UTF8"
s severSocket.ConnectionQueueSize = 2
d severSocket.Open(port, 10, .sc)
q:($$$ISERR(sc)) "Open:" _ $System.Status.GetOneErrorText(sc)
d severSocket.Listen(10, .sc)
q:($$$ISERR(sc)) "Listen:" _ $System.Status.GetOneErrorText(sc)
while (1) {
s data = severSocket.ReadLine()
if (data '= "") {
s obj = {}.%FromJSON(data) //注释1
s arg = obj.params.%Size()
for i = 1 : 1 : arg{
s arg(i) = obj.params.%Get(i - 1)
}
s ret = $classmethod(obj.class, obj.method, arg...) //注释2
d severSocket.WriteLine(ret) //注释3
}
}
q $$$OK
}
}
-
编写服务端
M.Login
的Login
方法,M.Pay
的Pay
方法,M.Stock
的Stock
方法。-
这里使用
IRIS
当服务端,实现的方法都在服务端,客户端是没有该方法的。 -
客户端调用的方法实际上是服务端的上的方法。
-
综合演示
-
IRIS
服务端开启监听方法。 -
客户端
Caché
调用无感知业务逻辑业务员Biz()
方法。-
可观察到客户端直接调用到了服务端的方法,并且编码方式跟正常编写代码并无差别。
-
客户端不需要了解底层的细节,
client
只需要直接调用定义好的方法。
-
通过这种方式我们就实现了类似gRPC
的功能,像正常编写代码一样调用服务端的程序。
创造价值,分享学习,一起成长,相伴前行,欢迎大家提出意见,共同交流。