一、RPC框架设计的核心模块
1、服务发现
2、健康检查
3、路由策略
4、负载均衡
5、异常重试
6、优雅关闭
7、优雅启动
8、熔断限流
9、服务分组
二、服务发现
1、整体架构
2、技术选型
(1)DNS(不可用)
- 如果服务端IP 端口下线了,服务调用者不能及时摘除下线节点。
- 如果服务端ip,端口 扩容,新上线的服务提供方,调用方不能及时发现。
- DNS存在缓存时间长的问题
(2)负载均衡设备 (不可用)
- 搭建负载均衡设备或 TCP/IP 四层代理,需求额外成本;
- 请求流量都经过负载均衡设备,多经过一次网络传输,会额外浪费些性能;
- 负载均衡添加节点和摘除节点,一般都要手动添加,当大批量扩容和下线时,会有大量的人工操作和生效延迟;
- 我们在服务治理的时候,需要更灵活的负载均衡策略,目前的负载均衡设备的算法还满足不了灵活的需求。
(3)zookeeper(可用,但存在性能瓶颈)
ZooKeeper 集群性能-当服务集群大时,容易出现性能问题。
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
在极端情况下保证AP ,实现最终一致性。
(4)消息总线的形式实现服务发现机制
- 当有服务上线,注册中心节点收到注册请求,服务列表数据发生变化,会生成一个消息,推送给消息总线,每个消息都有整体递增的版本。
- 消息总线会主动推送消息到各个注册中心,同时注册中心也会定时拉取消息。
- 对于获取到消息的在消息回放模块里面回放,只接受大于本地版本号的消息,小于本地版本号的消息直接丢弃,从而实现最终一致性。
- 消费者订阅可以从注册中心内存拿到指定接口的全部服务实例,并缓存到消费者的内存里面。
- 采用推拉模式,消费者可以及时地拿到服务实例增量变化情况,并和内存中的缓存数据进行合并。
- 为了性能,这里采用了两级缓存,注册中心和消费者的内存缓存,通过异步推拉模式来确保最终一致性。
三、健康检查
RPC 框架里面的一个核心的功能——健康检测,它能帮助我们从连接列表里面过滤掉一些存在问题的节点,避免在发请求的时候选择出有问题的节点而影响业务。
但是在设计健康检测方案的时候,我们不能简单地从 TCP 连接是否健康、心跳是否正常等简单维度考虑,因为健康检测的目的就是要保证“业务无损”,所以在设计方案的时候,我们可以加入业务请求可用率因素,这样能最大化地提升 RPC 接口可用率。
- 健康状态:建立连接成功,并且心跳探活也一直成功;
- 亚健康状态:建立连接成功,但是心跳请求连续失败;
- 死亡状态:建立连接失败。
也可以通过接口的可用率这个突破口,判定服务节点的健康状况。
可用率的计算方式是某一个时间窗口内接口调用成功次数的百分比(成功次数 / 总调用次数)。当可用率低于某个比例就认为这个节点存在问题,把它挪到亚健康列表,这样既考虑了高低频的调用接口,也兼顾了接口响应时间不同的问题。
四、路由策略
ip路由策略==>实现ip选择
参数路由策略==>基于请求入参,服务治理框架的配置端配置路由策略。实现定时化流量分配。
五、负载均衡
当我们的一个服务节点无法支撑现有的访问量时,我们会部署多个节点,组成一个集群,然后通过负载均衡,将请求分发给这个集群下的每个服务节点,从而达到多个服务节点共同分担请求压力的目的。
负载均衡主要分为软负载和硬负载,软负载就是在一台或多台服务器上安装负载均衡的软件,如 LVS、Nginx 等,硬负载就是通过硬件设备来实现的负载均衡,如 F5 服务器等。负载均衡的算法主要有随机法、轮询法、最小连接法等。
负载均衡算法包括
- 随机权重
- Hash
- 轮询
- 自适应负载均衡算法
自适应负载均衡算法
- 添加服务指标收集器,并将其作为插件,默认有运行时状态指标收集器、请求耗时指标收集器。
- 运行时状态指标收集器收集服务节点 CPU 核数、CPU 负载以及内存等指标,在服务调用者与服务提供者的心跳数据中获取。
- 请求耗时指标收集器收集请求耗时数据,如平均耗时、TP99、TP999 等。
- 可以配置开启哪些指标收集器,并设置这些参考指标的指标权重,再根据指标数据和指标权重来综合打分。
- 通过服务节点的综合打分与节点的权重,最终计算出节点的最终权重,之后服务调用者会根据随机权重的策略,来选择服务节点。
六、异常重试
rpc框架支持重试机制,但业务使用时,需要注意业务的接口是否是幂等接口
- 业务逻辑必须是幂等的
- 每次超时时间需要重置(保证重试的成功率)
- 以及去掉有问题的服务节点后,在剩余节点选择可重试节点
- 匹配可重试的异常策略
- 具有重试最大次数限制
七、优雅的服务关闭
目的:在服务停机时,最大程度减少业务影响。
服务启动时,给jvm运行环境,注册一个钩子函数,在jvm进程关闭时,启动钩子函数执行
- 开启挡板(新流量不接收处理,返回特定异常。服务端基于特定异常,选择其他节点重试请求)
- 通知调用方服务下线(调用方不进行下线服务节点流量分配,服务端也要保存客户端的列表)
- 等待处理中的请求结束(服务提供方,通过技术器表现当前服务处理的请求数,要有超时机制,等待时间不能太长(10s))
八、优雅的服务启动
那什么叫启动预热呢?简单来说,就是让刚启动的服务提供方应用不承担全部的流量,而是让它被调用的次数随着时间的移动慢慢增加,最终让流量缓和地增加到跟已经运行一段时间后的水平一样。
【1】服务发布时,向注册中心暴露自己服务启动的时间,调用方感知到服务端存在时,动态调整权重,达到服务预热的效果
- 基于权重的负载均衡,但是这个权重是由服务提供方设置的,属于一个固定状态。
- 现在我们要让这个权重变成动态的,并且是随着时间的推移慢慢增加到服务提供方设定的固定值,整个过程如上图所示:预热过程图通过这个小逻辑的改动,我们就可以保证当服务提供方运行时长小于预热时间时,对服务提供方进行降权,减少被负载均衡选择的概率,避免让应用在启动之初就处于高负载状态,从而实现服务提供方在启动后有一个预热的过程。
- 大批量服务重启时,这种策略会失效。但可以采取动态自适应负载机制决策。
【2】服务发布时,延迟暴露自己给注册中心
- 接口注册到注册中心前,预留一个 Hook 过程,让用户可以实现可扩展的 Hook 逻辑。用户可以在 Hook 里面模拟调用逻辑,从而使 JVM 指令能够预热起来,并且用户也可以在 Hook 里面事先预加载一些资源,只有等所有的资源都加载完成后,最后才把接口注册到注册中心。
九、熔断限流
服务端的自我保护(限流机制)
- 最简单的计数器,还有可以做到平滑限流的滑动窗口
- 漏斗算法
- 令牌桶算法
调用端的自我保护(熔断机制)
- 动态代理是 RPC 调用的第一个关口。在发出请求时先经过熔断器,如果状态是闭合则正常发出请求,如果状态是打开则执行熔断器的失败策略。
- 熔断器的状态:打开,关闭,探测 三个状态
- 熔断器基于线程中断机制实现(超过指定时间未得到响应则进行熔断)
十、服务分组
对服务提供方进行分组,达标,实现流量隔离,隔离业务流量。在服务异常时,降低业务影响。
也是路由的一种特殊策略提现