这里写目录标题
什么是RPC
当一个系统并的庞大的时候就会对服务进行拆分。假如一个电商系统就会给拆分为用户服务,订单服务,商品服务等。而在给用户提供服务的时候这些服务之间经常会需要协作,需要传递一些数据。有很多种解决方案,而RPC是一种使用非常广的方案。
一句话概括,RPC就是一种解决服务之间协作,用来传递消息的一种技术方案。
RPC通信流程
RPC分为服务提供方和服务调用方。 服务提供方和服务消息方之间传递信息需要通过网络传递。网络中传递信息都是二进制的,因此我们需要在服务提供方将java对象转换成二进制文件,在通过网络传递到服务提供方,服务提供方将二进制文件转换成java对象来进行数据处理。这个将java对象转成二进制叫做序列化,将二进制转成java对象的过程就叫做反序列化。
RPC中的协议
在数据序列化的过程中,是需要使用协议的。因为服务之间序列化的技术有很多种,如JSON,thrift等等, 服务需要知道使用是哪种技术来将二进制数据转换成java对象。
我们可能还会在协议中放协议长度,协议标示,消息ID,消息类型等等参数。因此我们可以将协议设计成以面的这种方式。
图中说的魔术位是指这是什么协议,如DUBBO,HTTP,REDIS等等协议。
下图是传递过程
RPC中的网络通信技术
在服务调用方和服务提供方之间会建立一个长连接,而在java中处理长连接的较好方式就是使用NIO多路复用,java原生的NIO的API并不是很友好,因此大多数的RPC都是采用的netty来实现的。 netty就是对nio进行了封装。
服务之间传输的二进制文件有可能不是一次性传输完成的,因此就需要多次接收然后在拼接。 这个过程是在应用的内存空间进行的,这有可能会造成数据的多次移动,复制等等操作,浪费了服务器资源。 因此netty使用了零拷贝技术。大体过程如下:
RPC中的动态代理
在我们使用RPC的时候会发现他和普通的接口使用方式是一样的,由其是在配合spring的时候。都是注入一个接口bean然后直接调用方法。 这背后的原理就是动态代理。 RPC会自动给接口生成一个代理类,项目运行过程中实际绑定的是这个接口生成的代理类。这样在接口方法被调用的时候, 它实际上就会被拦截到。这样就可以在生成的代理类中添加远程调用逻辑了。
大家可以看下图,图分两部分, 上部分为开发人员的视角,下部分为rpc的视角。
动态代理的实现技术有很多,JDK原生就有动态代理。当然也可以使用别的技术如:Byte Buddy。
RPC的架构设计
RPC的架构图
主要分为可以扩展和不可扩展的。可扩展的是将所以功能插件化。dubbo也是插件化的,对java自带的插件机制进行了扩展,让其可以自动注入依赖类。
RPC使用的服务发现
注册中心的作用
服务的调用方和服务提供方有可能不是在一台机子上,也有可能一个服务调用方会调用多个服务提供方,那服务调用方怎么获取服务提高方的地址来发起调用呢? 这个时候就需要有注册中心了。
服务注册:在服务提供方启动的时候,将对外暴露的接口注册到注册中心之中,注册中心将这个服务节点的IP和接口保存下来。
服务订阅:在服务调用方启动的时候,去注册中心查找并订阅提供方的IP,然后缓存到本地,并用于后续的远程调用。
注册中心选择
使用zookeeper做为注册中心一种选择比较广泛的应用,zk+dubbo很多公司都会这么使用。
zk搭建的注册中心集群是强一制性的,就是CP,如果master节点出现问题,集群会重新选举出一个master节点,在这其间集群是没有办法使用的。不能接受请求。
zookeeper除了做为注册中心使用还是可以做为配置中心使用的。
zookeeper还可以实现分布式锁。
zookeeper还是hadoop生态圈中的重要一员。
使用eureka也是一个种选择,他是最终一至性的集群(AP)。 是用java语言写的, 分为服务端和客户端, 有自我保护功能, 如果在短时间内,发现很多节点都无法心跳检查,他会怀疑是网络问题,就不会在注册中心把注册的服务删除掉。 服务调用方可以正常调用。
现在更流程第二种方式,使用最终一致性来做为注册中心。
RPC的健康检查
这里的健康检查是指心跳机制,RPC的服务调用方和服务提供方会建议长连接,调用方会订时给提供方发送请求,查看调用方的状态。
调用方会计算出一个可用率,这个可用率低于一定的值时就会将这个服务提供方的地址在自己的内存中标记为不可用。 调用方会隔一段时间在次检查这个地址, 如果可用率达到标准就会调整为正常状态。 否则进入死亡状态。
可用率的计算是某一个时间窗口内接口调用成功次数的百分比(成功次数/总调用次数)
RPC中的路由选择
RPC内部可以配置规则, 调用方发起请求的时候,可以根据请求参数的规则来判断选择哪一个提供方。
可以通过注册中心将规则下发到调用方。
RPC路由功能的一个典型应用场景就是灰度发布,我们可以通过路由功能完成定点调用,黑名单白名单等等功能。
RPC中的负载均衡
RPC中的负载均衡是放在调用方的,每一次发起RPC调用,服务调用者都会通过配置的负载均衡插件,自主选择一个服务节点,发起RPC调用。
RPC调用方会有一个打分机制, 通过心跳机制将调用方的系统的基础信息获取到。如负载指标 ,CPU核心数,内存大小,请求处理的耗时,节点的健康状态等等。根据这些信息就可以判断选择一个调用方。还可以一个百分比的值, 得分高的就分配流量多一些,得分低的就分配流量低一些,还可以单纯的随机,轮循。
RPC中的异常重试
RPC的异常重试功能仍然是在调用方实现的,用户可以自行设置是否开启重试以及重试次数。
RPC会捕捉特定的异常,如:网络超时异常,网络连接异常等等。一定捕捉了这个异常就会重新调用请求。 当然,服务提供方的接口一定要做好幂等性,哪果没有做好幂等性就会造成脏数据。
RPC还有一个请求超时的概念,如果服务提供方的接口性能特别慢,虽然可以影响但是超过时间了RPC的调用方也不会等待而是直接返回一个请求超时的异常。
如果我们把请求超时时间设置为5s,请求重试次数是3次,每一次都耕时两秒,是不是会导致请求超时,虽然第三次可以正常返回结果,但是服务调用方仍然会返回一个超时异常。
为了解决这个问题, 我们可每一次重试的时候都会把这次请求的时间重置为0。 也就是说请求超时时间计算的请求成功的一次时间,而不是重试的所有请求的时间。
RPC关闭服务
RPC在关闭服务的时候,是不可以直接强制关闭的,因为如果有还没有处理完的请求还需要处理完,同时不能在接受新的请求了。 Runtime.addShutdownHook方法。
下面这个图就是关闭服务的流程,不只是RPC的关闭流程是这样的, 一些中间件如消息队列和缓存的关闭流程大体都是这样的。
一般实现这种功能的方式都是使用java自带的关闭服务的勾子。
RPC启动流程
有一个叫做启动预热的概念:简单来说,就是让刚启动的服务提供方的流程减少,被调用的次数随着时间增加慢慢增长到正常值。
RPC调用方可以通过注册中心拿到服务提供方的注册时间,然后根据这个启动时间来计算一个权重,刚刚启动的时候流量低。这个实现可以添加到负载均衡当中。也可以独立出来。
如果RPC和spring一起使用的时候,也许会发现启动的速度会有一些慢, 如果这个时间先启动了rpc,就会信息注册到注册中心了,就会接到请求。实际上这个时候spring还没有完成启动, 这个时候就需要延时暴露,只有当服务完成启动的时候才会将信息注册到注册中心。
我们可以在服务提供方应用启动后,接口注册到注册中心前,预留一个Hook过程 ,让用户可实现可扩展的Hook逻辑。用户可以在Hook里面模拟调用逻辑,从而使用jvm指令能够预热起来。并且启用可以在Hook里面事先预加载一些资源。只有等资源都加载完成后,最后才把接口注册到注册中心。
引用
以上内容为极客时间《RPC实战与核心原理》的学习所得。
交个朋友好吗?
以上内容均为读书所得, 更多有趣有料的科技资讯请关注公众号。(交个朋友)