我们先回顾一下 rpc 的历史,再介绍我们常用的两个Go RPC框架。一个是 rpc 本身的官方库,还有就是大家常用的 Grpc 框架,我并不想在这个分享会推广我的框架,而是想跟大家分享一下我在做这个框架的过程中考虑的一些问题。大家如果没有用过 rpc 的框架的话可以了解一下这方面的知识,正在用的同学也可以熟悉一下使用rpc框架的时候需要考虑的方面, 如果碰巧你正在做rpc框架,可以互相交流一下,看看其他人是怎么做rpc框架的,了解一下别人做的工作。
在介绍之前,可以分享一个 rpc 的改造故事。
一个RPC改造的故事
这是今年年初春节的时候,一个网友的故事,在QQ群里面分享的一个真实的励志故事。这是一位游戏开发界知名的网友,现在在一家香港公司主要做海外游戏的项目,他们面临一个问题:他们部署了很多游戏服务器,但是当大家登录的时候会有一些恶意的攻击,他要拦截相关所有的操作把日志发到一个集群里面,存到数据库做分析。一开始用 Java 去做,然后出现诸多的问题,性能也非常低下,在去年的时候,他们用了 rpcx2.0 框架去做了改造,这是改造完运行半年的数据一张数据截图,应该是不到一天的统计量,有32亿的请求量。年终的时候他们老板算了一下,通过这次改造减少了一半的服务器的数量,费用从一个月100万港元减少到50万港元租赁服务器带宽等等费用,这给公司的运行成本带来不少好处,也给开发和运维人员带来了非常大的便利。我觉得它是非常好、典型的例子,它在半年的运营过程中,没有发现一个问题,非常稳定。只有一个问题是其中一个路由器坏了闪断了一下,但是和我们的软件没有任何关系。
RPC的历史
我们看看rpc的历史演变过程,最开始起源应该从互联网创建的时候就有这种萌芽,那时候不叫 rpc ,发一个request消息到另外一台机器上,返回相应的结果response。
1970年-1995年
1980年的时候SUN提出一个 onerpc 的概念,他在做NFS的时候用到了rpc的概念,接下来的几年,相继提出了好几个RFC规范,但是rpc的概念真正提出的是是1981年施乐公司的Bruce Jay Nelson,也可以称之为rpc之父,施乐公司大家都清楚那是一家传奇性的公司,这个人提出RPC概念以后,在九几年获得ACM的大奖,但是他不到50岁的时候因为一场病去世了,后来1991年的时候, 比较老一点的程序员都了解CORBA的服务调用,但是非常的复杂,以前我们给美国做视频项目的时候,就使用CORBA,使用起来特别的难,我觉得基本上就算被历史淘汰了。
1997年-2010年
1997年各家公司都在炒分布式服务调用的概念,windows 的DCOM+等等,Java提供了 RMI 远程调用的概念,1998年IBM、微软、谷歌等公司提出了webservice的概念。多年前的SOA,各家公司都拼命的推自己的标准,这里面形成了一批以 WS 开头的标准,虽然号称当时还是比较成功的,但是从现在看起来,过于复杂,而且各个公司之间互相的利益的关系, 给SOA的应用带来了制约。同时也有一些开发者提供了一些简化性的 XMRPC 的开发,但是我觉得大家记得比较清楚的是2000年 RESTFUL 这个概念,对资源来说,我们常做CRUD 的开发,创建、查询、修改、删除等操作,然后映射到 Post、Get、Put、Delete、Patch 等HTTP方法上,它把操作对象看成一种资源,这种风格一直影响到现在,现在有很多rpc的框架都是以这种方式提供的。Facebook 提出一个 rpc 的框架 THRIFT。 json -rpc 推出2.0。Go 标准库里面有json-rpc 1.0的支持,json-rpc 2.0有一些第三方库。
2011年-2018年
DUBBO在2011年开源,当时淘宝内部不使用了,但是墙内开花墙外香,外面很多公司,尤其是电商方面的公司,反而相应fork 进行维护,印象中当当网和京东最初都使用了 dubbo rpc 的框架,dubbo 在去年的时候,这个项目重新做维护,也专门有了相应的维护开发人员,一些依赖包的版本也做了升级,现在又活跃起来了。当时的时候,基本上处于一种不再维护的状态。Spring cloud这种框架很多在使用。2015年Grpc出现,2017年腾讯和百度也推出了自己的 Grpc 的框架,但是属于C++的框架,流行度还不广,基本上都在自家使用,现在像淘宝的蚂蚁金服和淘宝本身相当于两个公司,蚂蚁金服也在推自己的rpc的框架。rpc 的框架太多了,这里也就不一一列举了。
Client&Server
rpc本身调用流程是非常简单的,在 Client端利用stub,相当于把方法的调用传递给服务器,再返回到Client。
内部的处理流程,一般会使用Stub,将调用过程封装起来,接口调用会通过一个框架去实现。
官方RPC库
首先来介绍 golang 标准库的rpc, 也是由当初的大牛开发的,功能强大,但是代码非常的简便非常的好。利用标准库开发rpc非常简单,我们首先定义传入参数和传出参数,再定义一个对象,实现它的服务方法就可以了。
client首先连接你的服务器,直接把你的服务方法的名字和参数传进来,传出的内容作为最后一个参数,官方的库是非常好的,但是没有人维护,所有的功能都已经冻结了,除非有特殊的 BUG。如果我们自己要写简单rpc程序 建议你用官方的 rpc 库去开发。
如果要自己定义库或者是方法,可以实现这两个接口,但是要想做进一步的扩展比较困难,所以后来我自己写内核传输的协议,不再包装它,之前我看到有人想这个包装一下实现rpc框架,我建议你不要去做,因为被限制住了,可以参考它的之后写自己的rpc框架。
GRPC-Go
GRPC-GO
Grpc 是跨语言rpc框架,提供很多流行编程语言的支持,dubbo和Motan则是偏重服务治理的框架。Grpc是通过IDL文件,定义我们的服务,内部的通讯是通过HTTP/2进行通讯的,效率是非常高的,去年的时候又专门针对性能做了进一步的优化,花了很长时间提升了性能,我测试了一下,大约有50%的性能的提升,所以说性能提升非常的高。本身的数据编码方式通过ProTobuf 编码协议实现的,我们一般使用 gogo-protobuf 第三方库做开发。它的性能比官方的性能更好。这是Grpc库的服务端的代码,几行就可以实现服务。
GRPC-GO streaming
CLIENT 服务调用也非常简单,提供了流的控制,可以是单向的也可以是双向的,grpc是支持双向的。
HTTP
HTTP 本身有性能的瓶颈, 即使设置长连接, 客户端也需要等待前一个消息返回才能处理下一个(可能有些框架,比如fasthttp支持pipelining)。而GRPC, 是通过HTTP/2的传输方式,可以并行的发送请求,极大的提高了服务的并行调用。 它将一些元数据放在head frame中,payload放在data frame中。用 wireshark 抓包分析,可以看到grpc的具体的协议的实现。
GRPC-GO protocol
wireshark已经支持了GRPC协议,你可以方便的看它的协议以及protobuf的数据。
服务治理的RPC框架的实现
下面我讲一下开发rpcx这个框架过程中一些考量和技巧,希望对大家做rpc的框架开发有帮助。
RPC框架
当我们谈论RPC的时候,比如官方的rpc或者GRPC, 一般谈论的是rpc的一个范围很小的概念。但是当我们从大的方面去考虑的时候,比如说微服务, 我们需要考虑很多方面。在这一点上,rpc和grpc很欠缺,虽然grpc开始增加一些load balancer的实验性的功能,但是从服务治理的方面来讲,还是很欠缺的,我觉得这一欠缺导致了grpc不会大规模的在企业中应用,除非再做进一步的开发。
Protocol
协议可以采用http restful api的方式,或者HTTP/2,但是从性能上考虑,最好采用TCP。当然从便利性上考虑,http restful api还是很不错的。通过Magic number可以对每一帧进行校验,貌似dubbo的magic number是DUBB。MagicNumber的多少是Tradeoff的决策。rpcx只是用它来辅助校验,所以只使用了一个字节。
最好接着就是version字段,这样不同的版本后面可能是不同的数据格式, 如果放在最后就限制死了。
一些参数尽量压缩以节省空间。
Transport
我们看传输方式,一个是TCP方式传输,另外是 UDP 协议,UDP 是不可靠的传输方式,中间有丢包的概念,有很多库试下了可信赖的UDP,相当于它做了很多的检查,常用的有谷歌的 quic,腾讯有的部门也在使用,还有KCP,rudp等。
codec
可用的编码方式有很多,但是首先要支持跨语言的处理,msgpack 是常用的编码方式,protobuf也是常用的编码方式,json是通用的实现方式,thrift也是跨语言的,但是追求极致性能的话可以用go特有的编码库,有些比protobuf性能高出一倍,右侧列了很多项目,大家可以关注一下,这里面有很多性能优异的编码库,但不是跨语言的,只能在go里面使用。
注册中心
另一个非常重要的是注册中心,做服务治理,注册中心是非常重要的一个组件,我们需要把服务登记到注册中心里面,之后客户端才能感知到相应的服务节点,比较常用的是 etcd 、consul、zookeeper,有人问是ETCD 好还是 consul 好,这个很难说,本身都是很优秀的框架,但是微博里面都没有使用,因为这些框架追求的是要数据一致性,而我们追求的是高可用性,我们自己做配置开发平台,跨数据中心的。
路由/负载均衡
随机
当有多个服务器时,甚至分布在多个数据中心,我们需要一定的算法从中挑选出来给客户端调用,在这列举几个常用的算法,最简单的是随机算法,随机选择一个,分配到谁就到谁。
轮询
还有一个是轮询,保证每个服务器平均得到访问调用的机会。
一致性哈希
一致性哈希,可以根据用户的请求,计算哈希值,可以映射到对应的服务器上面,可以保证相同的请求能路由到相同的节点,根据哈希选择的不同,可以有不同的算法;或者根据clientIP地址计算相同的IP就路由到同一台机器上。
基于权重的轮询
基于权重的轮询,这个比较好的算法是nginx的平滑的基于权重的轮训算法,即使在很短的时间内,也不会出现强求都落在一台机器上,避免雪崩现象。
基于地理位置
基于地理位置选择,就是北京的机器client尽量访问北京的机房,如果北京和广州之间要访问有几十毫秒的延迟,美国东海岸和西海岸的机房,有上百的数据延迟。所以要尽量选择同一地区的机房的数据。
基于网络质量
但是有时基于地理位置,同一个机房,同是在北京,分布在不同的地方,不同的网络服务商,可能访问也是不太一样的,所以有时候可能会基于网络质量去访问,可以调用服务,看看反馈花费时间是多少,进行相应权重的分配,这也是一种常见的算法。
定制路由算法
定制路由算法:有些用户提出来某一个服务,要分配到其中一个机房的其中一台测试机器上,可以提供一个接口让用户定制他的算法,要把相应的参数要传递过来,基于这些参数做一个选择。
失败处理
失败的处理完全是参考 dubbo 的实现,但是额外加了一些功能。比如说 failbackup。如果服务节点意外挂了或者服务失败了,设置failfast的话就会马上反馈给 client ;如果是failtry在网络闪断情况下有重试的功能;failover,访问第一个节点失败可以继续访问第二个节点;还有failbackup,就是利用这种资源换延迟,来一个请求之后,如果等十毫秒还没有结果的话,立马把这个请求发送给第二个节点,这两个节点谁先返回过来,我就把结果返回回去,提高延迟,但是有时候会占用双倍资源。
超时、容错
微服务调用有连接超时的时候,网络的原因还有恶意攻击问题,比如很多客户端连接你的服务器,也不断掉,会占用很多资源,处理这些请求,相当于占用你大量的服务器的资源,如果设置超时,可以在一定的时间内把一些关闭。还有 Context 超时是常用的方式。断路器的模式,如果一个服务器暂时不可用的话,最好把它标记为在一定时间内不可用,这样我以后的服务不会再选择它做路由,这样可以避免大量的请求发送到无用或者暂时不可用的服务器上,这个熔断器可能在一定的时间内要进行重试,在可用的时候要恢复过来。熔断器也有好几个框架实现。
跨语言
跨语言,是一个很严重的问题,在初创公司里面,没有太大的问题,都用go开发,没有语言的问题,但是在大的公司里面有很多不同语言,这种情况下要考虑跨语言的问题。现在小米的张志勇负责做rpcx-java的开发,支持纯java方式实现rpcx,做的很不错,还有gateway,http 接口等方式提供http接口,以便支持跨语言调用。
双向调用
本身rpc不设置双向调用,但是有一些需求,要求服务器发一些通知给客户端,所以需要双向的通讯,这一功能grpc也支持。
管理界面
提供UI管理服务,禁用一些服务,进行一些管理的东西,都是通过注册中心拉取过来数据。
另外微服务很重要的一点,是要对服务有trace的概念,我们一般记录节点的一些信息,trace会中间请求一些信息发送过去。Java可以往很多服务器发送数据,我们也可以加相应的数据展示出来进行监控。
安全
其他还有一些安全的东西,比如 Auth,需要权限验证;另外还有限流。还有消息大小的限制,假设提供一个服务里面有一个字段,本来是希望用255个字节数据,假设有个人传个2G的葫芦娃的数据,会对你本身的服务器的资源极大的占用,要对服务器接收的数据大小进行控制,另外提供白名单和黑名单进行控制。
压缩
zip 是普遍的压缩方式,snappy 追求流的压缩方式。
benchmark
去年做过一些对于rpc框架 benchmark 的测试。因为我们不可能做一个覆盖各种各样的测试场景,这里只是提供一个特定的场景。假设服务是有一个10毫秒的消耗的话或者30毫秒的消耗(sleep),标准库性能最好,其次是rpcx, grpc和thrift稍差一些,但也比http restful api高很多。
我分享的原因就是大家以后在使用rpc框架开发的时候,或者是自己做rpc框架的时候,可以从这几个角度进行考虑。
Q&A
提问:之前我们也做过 go rpc,用的是你刚才提的 gateway 的方式,对client提供http接口,使用http和grpc两种方式跟后面的微服务通讯。我们刚开始在一个服务上用了HTTP跟gsrpc这两种方式,但是测试结果显示,用grpc跟http相比,性能提升不是很大。还有 Gateway 链接服务的时候是一条链接还是多条链接?自己去做一个链接还是一个链接池?
晁岳攀:首先回答你第二个问题:一个链接就足够了。很多同学说,为什么用一个链接?从数据传输来说,一个链接是足够的,因为是异步地,性能不会阻塞在网络数据的传输上。现在回答第一个问题:可能你有两个原因,一是你的服务占用时间较长,第二个原因可能你的client通讯方式是瓶颈, 你gateway提供给client是http方式。
提问:rpcx 和 grpc 出来的定位是什么? rpcx支持protobuf3吗?
晁岳攀:grpc 不会在企业大规模使用,或者说不会直接在企业中规模很大的服务中的推广。因为本身它的服务治理很弱。rpcx支持protobuf3, 而且默认提供了protobuf3的编码器实现。
提问:比如说春节的时候做降级的时候,具体动作是什么,我们这边没有降级,我刚才想了一下,去年天猫双十一的时候,看过阿里那边的双十一数据,前半小时,比如说你打开界面,是不会给你展示有几个红包给你发送,客户端做了处理,比如流量比较大的时候,有一些不是特别重要的服务直接关掉,客户端做一些处理,会把模块隐藏掉,做限流,几个容器在做处理,其他的容器在客户端,容器数量比较少的,是小范围提供,大范围的用户在用服务的时候怎么用,客户端要做怎样的处理?