联系我们

公司地址: 上海市沪宜公路1188号4号楼
     一层
联系电话:021-31080981
电子邮箱:soline@soline.com.cn
邮政编码:201802

RPC中间件解决方案

RPC中的服务治理

RPC的基础是网络通信+序列化,只是基本的实现,其上还有更多其他治理问题,也是分布式中的普遍问题。


序列化

序列化框架的性能、内存、数据大小以及兼容性


服务发现

zookeeper

  1. 管理端创建服务根目录

  2. 服务提供方在provider目录下创建临时节点,储存生产者的信息

  3. 服务消费方在consumer目录下创建临时节点,储存消费方的信息

  4. 消费方watch生产者目录,当生产者节点发生变化时得到通知


最终一致性

zk的cp特性会带来性能问题,在大规模的服务发现中,AP才是王道。

如使用注册中心收到注册消息后,将消息投入到消息总线中来同步到其他注册中心,或如erureka中的通过定时任务来进行同步。

这种方式要注意消息的去重,同步消息要附带单调递增的版本号,其他注册中心只接受版本号更高的更新消息。


推拉模式结合,推主要是callback,拉是客户端轮训


健康监测

服务发现拿到的实例有可能是不可用的。

需要加入健康监测机制如心跳机制,来评估节点的健康状态,如健康、亚健康(连续几次心跳失败)、不可用状态(连接失败)。服务调用时优先使用健康节点。

同时为了防止成功失败间断出现且连续次数不达到阈值,从而出现误判情况,需要使用其他维度的方式进行计算健康度,如eureka中的30秒内失败次数这种在一段时间窗口范围内的失败次数作为可用率来衡量。

同时健康监测可能也要监控服务自己的其他依赖是否可用,如服务虽然可用,但其redis可能挂了。目前sc中是使用actuator的health端点来监控总体的健康水平


路由策略

调用者在挑选服务提供方时,加入筛选逻辑。如流量切换和灰度发布的场景,使用路由策略将旧应用的流量缓慢平滑的切入到新应用,完成后下线旧应用。

将选择做成配置进行动态下发。


负载均衡

配置权重来控制不同机器的流入流量,如果在发现可用性降低后再去调,已经影响到了业务,最好提前智能的控制权重。

如果使用负载均衡设备,会有以下问题:

  1. 负载均衡设备的单点问题,如果是集群则大规模的上下线和扩容带来运维复杂

  2. 额外的负载均衡设备成本

  3. 额外的网络代理访问

  4. 负载均衡策略统一,无法根据不同场景灵活配置不同策略

所以RPC需要自己来实现负载均衡

常用的算法如轮训、随机、哈希、权重等。

自适应:设置指标收集器实时收集提供方的负载信息(可以通过心跳收集)以及服务调用的各种如延时等指标,综合打分,来智能负载均衡。


路由和均衡的区别一般是根据路由设定的规则筛选出一批提供者,然后使用均衡算法进行均衡调用。


异常重试

RPC走网络,所以必定需要异常重试,但时间和次数必须把握好。太长会hang住业务,太短会误判导致数据不一致。同时注意重试一般要剔除上次异常的节点,且重试时要重置超时时间。


但是要注意由于网络抖动,rpc接口一般要求幂等,此时异常重试才是有意义的,否则反而可能造成重大问题。同时并不是所有的异常都可以重试,有些异常是业务抛出来的具有特定意义的,不能重试。可以配置白名单,特定的异常才允许重试。



优雅启停

提供方要下线时,调用方不知道,可能造成影响。

提供方下线时像注册中心进行下线操作,然后注册中心下发到各消费方,从而剔除下线节点,但由于最终一致性的问题,消费方可能无法及时的拿到提供方的下线消息。

一种方法时提供方主动通知所有调用方,因为服务费持有调用方的长链接,遍历调用告知下线即可,但可能存在问题,如调用和关闭通知在很小的时间差同时发生,由于网络抖动,会发生提供方通知关闭完成,进行关闭过程中,调用的请求打过来了,由于在关闭过程中,部分对象被销毁,继续处理业务可能产生错误,因此应该继续调用。

此时可以设置一个关闭标志位,当调用过来时发现正在关闭,不进行处理,直接失败让调用方去重试其他节点。同时为了不影响正在进行的任务,设置任务进行计数器,打开关闭标志位后,等待计数器归零或延迟一定时间后再进行关闭操作。



在应用启动时,主线程进行顺序加载时,向注册中心注册后还有其他bean需要加载,即应用来没有完全启动,如果此时有流量流入,可能出错。需要延迟接入。此时可以延迟注册的动作,在应用启动完成后再进行注册,同时提供注册前置动作,模拟请求,进行应用预热,完成后再进行注册。


或者刚启动的应用没有缓存、编译的提升,速度较慢,一旦大规模流量涌入可能造成大面积超时,因此有时需要让应用先接入小部分流量进行预热后才接入大规模流量。这种可以在负载均衡算法中通过服务提供方的启动时间(或注册时间)作为权重来进行流量分配。


对于大批节点需要重启的,由于延迟接入和预热可能造成可用性降低,最好的解决方案是分批次重启。


熔断限流

当某个提供方负载太高,需要使用限流来限制访问,降低压力。

限流可用分单机限流和限流服务,前者是调用者根据配置的限流信息自己决定自己的限流程度,如10台机器每台机器都是相同的限流幅度,后者是统一接入限流服务来判断是否要启动限流。两者各有优劣。


对于依赖于下层服务的上层服务,由于下层服务导致的上层服务不可用,可能会导致整个调用链的崩溃,此时要启动熔断策略,保护调用链上的上层调用方。


流量隔离

服务提供方以一个整体的方式,为所有不同的接口调用提供服务,如果某个接口的流量激增,可能打爆所有服务提供方,造成其他业务接口也无法使用,因此需要进行流量隔离,对服务提供方进行分组,不同消费者拿到的服务方实例不同,进行调用。

可以在服务发现的时候进行改造,服务发现要附带业务分组属性来进行发现。

由于分组,对于某个特定接口提供方可用节点变少了,为了保证高可用,可以设置主次分组,当主分组节点全部不可用时,可以暂时借用次分组的节点进行服务,同时为了不过分影响次分组的节点,可以只分配一小部分节点作为次分组节点。


隔离也可以用于环境隔离,对开发测试灰度等不同环境互不影响。


异步

RPC的性能大多浪费在业务的同步耗时,使用异步提升吞吐量能够显著提升性能。

对于RPC的请求和响应,属于两个独立操作,一般是请求时会附带消息id,并创建future返回,同时维持id和future的映射,拿到响应时根据映射找到future并将结果进行set。同步的RPC会主动get来阻塞等待结果。

服务端的异步在收到消息时消息的序列化拆包等动作是在io线程,但是对于业务逻辑一般会使用单独的线程池跑。业务线程池把任务跑完后又要交给IO去响应,可以使用completefuture来更进一步的异步,提升吞吐量。调用方同样也可以使用回调的方式来进一步的异步。


安全

为了防止任何人都能调用接口,需要提供安全身份认证,调用方获取密钥来进行身份认证。

或者其他方法,限定接口调用权限


时钟轮

在批量定时任务执行的过程中(如定时扫描超时任务),如果大量任务比较延后,会造成扫描线程重复扫描大量不会被执行的任务,浪费CPU。

设置时钟轮,将不同延迟时间执行的任务放在不同的时间槽中,扫描线程固定频率按照时钟轮扫描,仅扫描对应时钟轮槽中的任务,大大减少扫描任务数量。其时间复杂度相比优先队列更低,netty中提供了TimeWheel。


流量回放

类似tcpcopy和nginx的流量回放,进行回归测试、压测等

只要记录所有请求,在重新发一遍即可


动态分组

对于流量隔离,在分组的时候需要支持动态分组,以灵活应对流量变化。

可以通过修改注册中心的形式实现,或者动态配置


无接口调用

没有服务方提供的接口,或者调用者只需要很少的接口,没必要去依赖全部的提供方的接口,提供方可以提供一个泛华的接口,调用方发送消息中提供方法名等必要的RPC调用的相关信息,由服务方进行解析调用并返回结果即可。