首页 > 编程知识 正文

rpc框架是什么,微服务rpc调用

时间:2023-05-05 13:54:11 阅读:170600 作者:2942

苏宁的系统间交互最初使用的是中心化ESB架构,随着系统拆分工作的展开和业务量的快速上升,系统间调用规模越来越大,ESB中心化架构带来的中心资源隔离、中心容量动态评估、问题排查另外,随着自主研发系统逐渐取代商用系统,协议转换等工作逐渐弱化,苏宁需要尽快制定更加轻量化、中心化的系统间服务调用方案。

苏宁远程服务框架(RSF )致力于解决系统之间的服务调用问题,提供透明、高性能的RPC服务调用。 目前已应用于苏宁1000系统,日服务呼叫量200亿次左右,是苏宁使用最广泛的技术组件。

开源世界中成熟的RPC很多,像spring remoting这样简单的东西很多,被广泛使用,只需要几行代码和组成就可以实现系统间方法调用,但都还停留在callout服务上。 对于成千上万个系统协同工作的复杂电子商务平台,仅仅实现系统之间的协同还不够,还存在许多需要考虑的问题,如动态注册和发现服务节点、生产问题的快速介入、服务管理等。 在不同的环境、背景下,有各自的需求和挑战。 这是我们选择建立自己的RPC框架的中心原因。

本文重点介绍了RSF的主要特点,以及我们面临的挑战和相应的解决方案。

重点特性1 .同步、异步Future、异步Callback三种调用模型

同步调用:以最常见的格式使用同步调用时,当前调用线程将阻止等待服务调用的结果或抛出异常。

异步future :调用将立即返回,当前线程不会被阻塞,也不会等待服务提供者完成执行。 稍后可以通过Future get获得服务提供者的返回结果。 这种调用格式在特定场景中特别有用,可实现并行调用服务。 Future get将阻止当前线程,直到服务器端返回结果或抛出异常。

异步Callback :调用立即返回,当前线程不会被阻塞,也不会等待服务提供商完成执行。 当服务器返回结果或抛出异常时,异步执行callback的相应方法。

使用Future和Callback异步调用可能会阻止调用线程阻塞服务调用的结果。 可以结合其他异步技术来异步化整个调用链。

2 .服务端异步返回调用结果

服务器端异步返回调用结果的机制类似于Async Servlet。 当前正在处理调用请求的线程不返回最终结果,而是在其他线程上异步将结果返回给消费者。 例如,服务实现方在实现服务a时,需要调用依赖的其他外部服务b。 如果外部服务b通过Http协议开放服务,则可以在支持异步的Http客户端上调用外部服务b,并通过异步回调返回对a的上次调用结果。 如果b也通过RSF开放服务器,则可以通过异步Callback调用b,并通过Callback返回a的最终调用结果。

处理请求a的服务提供商线程本身只需启动一次异步Http或RSF的调用就可以终止,而不阻止等待b的调用结果。

通过这些异步手段,可以实现整个调用链的异步化,不会造成线程堵塞或浪费等待服务调用的结果,大大提高整体资源利用率。 在线程技术还支配着java的今天,不阻塞线程、减少阻塞是非常重要的。

3 .所有服务呼叫相关配置统一管理,修改后实时生效

例如,与服务呼叫相关的所有配置,例如服务呼叫的超时时间、重试次数、许可、负载均衡方法、流控制、熔断、成员资格、服务路由策略等,都与rsf APP的应用程序端的代码和配置文件这对于在线问题的介入非常重要,在出现服务质量等问题的同时,该能力也能与监控系统有机结合,实现自动控制。

4 .重试和称重防范

如果调用进程在进行服务调用时出现可重试异常(例如,网络异常、调用方资源不足等),并且配置为允许重试,则重试将开始。

与大多数重试设计相比,RSF重试稍微复杂一些。 大多数重试设计都旨在避免多个请求(包括重试)交叉。 例如,假设第一个请求超时,发生第二个重试请求,并且第一个请求的结果在第二个请求期间返回。 大多数重试设计都认为第一个请求的生命周期已结束,因此将忽略第一个请求的结果。 RSF认为,第一个请求返回的结果是有效的,该设计的目的是尽可能地成功调用,但也出现了复杂的并发相关问题,未详细展开。

如果服务调用是幂等的,则无论调用多少次都不影响系统状态,重试是安全的。 但是,如果服务调用不是幂等,则重试必须考虑防止重量的问题。 RSF包括允许用户定义自己的防重量逻辑的扩展点,以及基于redis的默认防重量实现。

5 .自动注册和发现服务节点

Service discovery是服务器框架中最核心的部件,该部件目标明确,服务器节点的上下线(包括扩展容量、APP分发、节点停机等场景)会引起服务器端节点列表的改变如何达成有各种各样的设计,有像Netflix/eu那样的基于中心化的

reka,或者基于 ZooKeeper、etcd 的简单一点的方案,也有去中心化的方案,这个部件对数据一致性要求并不高,并不追求数据强一致性,但是如何做到可靠非常关键,试想如果这个部件出问题,导致消费方错误的认为服务方节点全部或者大部分都下线了,会引起什么样的后果,如果是中心化的设计,则会引发全局性的灾难。

RSF 的服务节点发现采用的是中心化的设计,但是我们认为去中心化的思路更优,因为不存在中心化架构下的中心瓶颈,出问题也不会是全局性的灾难,我们也曾基于 gossip 完整设计了一个方案,但是评估后认为实现较为复杂,重点要规避的风险是任何情况下都不会引发 gossip 风暴。

RSF 的服务发现会在本文后半部分稍微深入的展开探讨。

6. 负载均衡

当消费方发起一次服务调用时,RSF 会基于随机策略优先选择当前负载低(Least Pending Requests)的服务提供方节点,选择过程同时也会加入提供方节点权重因子。这种负载均衡方式能基于服务方节点的实时处理能力进行动态调整,能较好的规避短板效应。并且,负载均衡还会优先选择当前和提供方的连接已就绪的(关于 RSF 的连接管理,本文后半部分会稍微深入展开探讨),并且没有被熔断的服务方节点,这些策略目的都是为了为每次请求选择最优的服务方节点。

7. 熔断

RSF 的熔断有两种,一种是服务方法级的熔断,当调用某服务方法出现较高异常比例时,会禁止访问该服务方法一段时间,这段时间过去后,允许少量的请求,如果依然出现较高异常比例时,则继续禁止访问一段时间,否则放开访问限制。合适的设置消费方服务方法熔断,既可以保护服务提供方,避免其已经处于不健康状态下时继续给压。也可以避免消费方应用大量线程因等待服务方结果返回被阻塞(在同步调用下),有效的保护服务消费方自身。从而避免事故级联蔓延。但同时,一旦触发服务方法熔断,后果是严重的,会引起服务方法在一段时间内完全被禁止访问,所以应根据服务自身情况合理的设置触发条件阀值,不应该因为瞬间的服务质量毛刺导致轻易被触发。

RSF 另外一种熔断是针对服务方节点的,当某个服务方节点出现超时、资源繁忙等等异常时,会快速被熔断,负载均衡在选择提供方节点时,会优先选择没有被熔断的服务方节点,以提高调用成功率。

8. 并发流控

RSF 的并发流控有两种,一种是服务提供方侧的流控,一种是服务消费方侧的流控。

服务提供方侧的流控,是为了避免服务请求的并发量超出其设计的承受能力,从而引起各种蔓延以至整个服务方最终被冲垮,应该合理的设置服务方法的并发阀值,超过并发阀值的请求会被快速拒绝,从而有效的保护服务方。 在服务提供方,RSF 维护一个线程池,该线程池负责服务调用的业务代码执行。线程池有一个任务排队队列,一旦排队队列满了,请求将被拒绝。线程池的线程数和排队队列长度都可以在服务配置平台中设置。

为服务设置并发阀值,是将有限宝贵的线程池线程及排队数资源分配给服务。

并发阀值可以分组来设置,也可以为某一个服务方法单独设置。如果使用分组的方式进行设置,那么分组下的所有接口方法将共享一个计数器和阀值。

服务消费方侧的流控,RSF 可以针对某一个服务方法设置某一个服务消费方应用的并发流控阀值。当调用开始时并发计数 +1,当调用结束(调用返回或抛出异常都认为是结束)时并发计数 -1。当计数累计超过指定阀值,则抛出超出并发阀值相关的异常。

合适的设置消费方流控,既可以保护服务提供方,也可以避免消费方大量线程因等待服务方结果返回被阻塞(在同步调用下),有效的保护服务消费方自身。

9. 服务路由

RSF 服务路由是根据调用请求参数列表,调用方机房信息,服务方节点机房部署拓扑等信息,将请求路由到正确的目标服务方节点的过程,比如会员系统可能在多个机房部署相同的服务,并基于会员编号特定的规则将会员数据分布到不同的机房,那么在调用获取会员信息服务时,RSF 需要根据调用参数中的会员编号以及会员服务的机房部署拓扑信息,将请求路由到正确的机房中的服务方节点。

RSF 的服务路由在苏宁多机房多活项目中发挥至关重要的作用,当前支持优先同机房、会员编号分片、主机房、自定义脚本等多种路由策略。

10. 服务治理及监控

RSF 提供全量服务调用统计信息,以帮助服务提供方进行服务质量的持续优化,服务调用的统计信息包括调用次数、失败率、响应时间(平均 /TP90/TP99/TP999)等核心指标,服务相关方可以针对这些核心指标设置安全阀值进行告警。

RSF 还提供了端到端的完整 trace 能力,可以清晰的看到某一次调用的各个时间点明细,如调用方什么时候发起的请求,请求什么时候写到 socket,请求什么时间点到达服务方节点,在服务方线程池中排队等待了多长时间,什么时候开始执行业务代码,业务代码执行了多长时间等等。 这些能力对迅速感知及定位线上问题至关重要。

11. 与非 JAVA 系统的交互

苏宁大部分的系统基于 JAVA,但也存在一些老的系统如 SAP 或一些异构系统如使用 PHP/NODEJS 等,这些系统目前和 RSF 的交互使用 RSF 提供的 SAP Adapter 和 HTTP Adapter 来达到。

一些挑战

下面稍微深入的展开探讨下我们经历过的一些挑战和解决方案。

1. 服务节点注册和发现中心的扩展能力及稳定性

RSF 早期版本的服务节点注册和发现模块基于 ZooKeeper,思路也很简单,就是服务方节点上线的时候向 ZK 写入一个临时节点,当服务方节点主动下线时删除这个临时节点,当服务方节点宕机或其他异常状况时,依赖 ZK 的 session timeout 机制由 ZK server 自动剔除这个临时节点,当发生 session expire 时恢复这个临时节点。当服务方节点列表发生变更时,通过 ZK 的 watch 机制,将最新的服务节点列表下发给订阅的服务消费方节点。这种方案实现简单,但是当 ZK 集群出现故障时,大量服务方节点发生 session timeout,引起大量服务方临时节点下线。如果消费方完全信任 ZK 下发的服务方节点列表就会引发服务不可用的灾难。

另外,ZK 集群因为数据一致性设计的考量,所有的写操作都要经过 leader,包括 session create,session expire,临时节点写入等等都要经过 leader,因此 ZK 集群的写能力是存在单机瓶颈的,就是不管 ZK 集群怎么扩容,写能力就那么多,并且随着加入的 follower 节点越多,写能力越差(其实加入 observer 越多,对写能力也会有影响,毕竟 leader 也要把数据同步给 observer)。

在服务节点注册和发现这个场景下,服务数量 * 每个服务的节点数,这是一个非常巨大的数字,试想当 ZK 集群在最坏情况下要集中处理这么多临时节点数据的写入,还有大量的 session 恢复涉及的数据写入,会造成 leader 处理严重延迟,由此导致的更坏的情况是一个 session 刚恢复了,又因为后续写操作或心跳处理超时导致又 expire,然后又去恢复这种局面,恢复的时间难以估计。

RSF 目前采用的方案如图 1,服务方节点注册和续约是通过 Redis 来达到的,当服务方节点启动时向 Redis 写入该节点提供的服务列表信息,然后定时发 expire 指令来续约这份信息,当服务方节点主动下线时,从 Redis 删除该数据。当服务方节点宕机或其他异常状况时,依赖 Redis 的 expire 机制来自动删除这份数据。

pump 订阅所有 redis 的 key space,当 redis 的 key space 发生变化时都会通知到 pump,pump 聚合所有的 Redis 数据,将提供方节点 - 服务列表信息的数据转化为服务 - 提供方节点列表的数据结构,写进 ZK。消费方获取及更新服务的服务方节点列表还是通过 ZK 来达到。

在这种设计下,以 Redis 的处理能力,少量的几台 Redis 就可以处理几十万的服务节点注册和续约。ZK 方面,只有 pump 节点向 ZK 写入数据,写入的频率是 pump 侧控制的(不需要每次 redis 数据变动都会写一次 ZK,可以做秒级延迟合并处理),并且数据经过压缩,因此,这部分数据的写入对 ZK 基本没有写压力。

实际测试下来,这种设计经过横向扩展后,可以轻松的处理几万的服务 * 几十万的服务节点的规模,能满足我们未来一段时间的需求。

另外,还有一个值得一提的问题就是如何解决大量消费方的 session 相关操作对 ZK 的压力(虽然没有了临时节点,但是 session create,expire 等还是会都经过 leader),在 ZK 3.5.X 版本中有新的 local session 的概念,session 的生命周期都在 follower 或 subscriber 各自节点本地处理,不会再跟 leader 进行交互。具体细节这里不再展开。

另外,因为存在中心化的设计,所以还是要考虑灾难应对的问题,在 RSF 组件侧我们也提供了灾难应对的能力,即使注册中心出现问题,也能快速在组件侧进行自动修正。

2. 连接数控制的问题

大部分基于 TCP 的 RPC 框架,都是消费方和所有的服务方节点建立长连接,并且通过心跳包机制来保活或检查连接健康情况,针对某些会消费很多服务的应用,或者某些有很多消费方的服务来说,连接数会是一个问题,即使当期没问题,至少存在扩展性方面的问题。而且,这些同时建立的长连接,大部分时间里都是空闲的,存在资源的浪费。

RSF 采用的是和部分服务jaddx的方案,就是消费方选择和提供方列表中一部分节点建立连接,在这个思路下,还需要进一步考虑的问题是:

采用部分建连的策略,需要考虑连接不均衡的问题。

当服务方节点列表发生少量变化时(比如服务方节点宕机),不应导致大面积的连接转移。

需要考虑服务之间的连接尽量重用等问题。

在有业务路由的场景下,无法预先部分建连,也就是请求的时候,才知道目标提供方,所以没办法预建连。

RSF 最终使用的策略是:

和部分服务方节点按需建连,并且当连接超过设定的业务空闲期,就关闭连接。

动态调整连接,以尽量做到连接均衡。

算法和逻辑保证当消费方或服务方节点部分增减时不引起大面积连接转移。

当不同服务的服务方列表有重合时,保证连接重用。

结语

服务调用框架作为最广泛使用的基础技术组件,除了一些基本的共通的能力,每个企业在不同发展阶段,都会或多或少的有自己的特定问题,你遇到的问题换个上下文背景可能问题根本不存在,对应的解决方案也是如此,只有在特定的上下文背景下最合适的方案。

转载于:https://my.oschina.net/u/3990822/blog/2239884

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。