RPC 实战与核心原理-高级篇笔记

本文探讨了异步RPC在单机吞吐量优化中的关键,介绍了时钟轮在超时处理中的应用,以及如何通过流量回放确保技术升级的灵活性。同时涵盖了分布式环境下的问题定位、动态分组的秒级扩缩容和泛化调用的实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

17 | 异步RPC:压榨单机吞吐量

阻塞-非阻塞-异步-同步-的理解
一次 RPC 调用的本质就是调用端向服务端发送一条请求消息,服务端收到消息后进行处理,处理之后响应给调用端一条响应消息,调用端收到响应消息之后再进行处理,最后将最终的返回值返回给动态代理。

这里我们可以看到,对于调用端来说,向服务端发送请求消息与接收服务端发送过来的响应消息,这两个处理过程是两个完全独立的过程,这两个过程甚至在大多数情况下都不在一个线程中进行。那么是不是说 RPC 框架的调用端,对于 RPC 调用的处理逻辑,内部实现就是异步的呢?

不错,对于 RPC 框架,无论是同步调用还是异步调用,调用端的内部实现都是异步的。

调用端发送的每条消息都一个唯一的消息标识

  • 实际上调用端向服务端发送请求消息之前会先创建一个 Future,
  • 并会存储这个消息标识与这个 Future 的映射
  • 动态代理所获得的返回值最终就是从这个 Future 中获取的
  • 当收到服务端响应的消息时,调用端会根据响应消息的唯一标识,通过之前存储的映射找到对应的 Future
  • 将结果注入给那个 Future
  • 再进行一系列的处理逻辑,
  • 最后动态代理从 Future 中获得到正确的返回值。

所谓的同步调用,不过是 RPC 框架在调用端的处理逻辑中主动执行了这个 Future 的 get 方法,让动态代理等待返回值;

而异步调用则是 RPC 框架没有主动执行这个 Future 的 get 方法,用户可以从请求上下文中得到这个 Future,自己决定什么时候执行这个 Future 的 get 方法。

在这里插入图片描述
这是客户端的异步,那么服务端怎么做异步呐?能做吗?

能做,其实就是服务端自身业务逻辑处理的太慢了,需要优化。

RPC 框架提供一种回调方式,让业务逻辑可以异步处理,处理完之后调用RPC 框架的回调接口,将最终的结果通过回调的方式响应给调用端。

可以让 RPC 框架支持 CompletableFuture,实现 RPC 调用在调用端与服务端之间完全异步。
在这里插入图片描述

18 | 安全体系:如何建立可靠的安全体系?(暂略)

19 | 分布式环境下如何快速定位问题?

  1. 为了在分布式环境下能够快速地定位问题,RPC 框架应该对框架自身的异常进行详细地封装,每类异常都要有明确的异常标识码,并将其整理成一份简明的文档,异常信息中要尽量包含服务接口名、服务分组、调用端与服务端的 IP,以及产生异常的原因等信息,这样对于使用方来说就非常便捷了。
  2. 分布式链路跟踪:见:天机阁——全链路跟踪系统设计与实现

20 | 详解时钟轮在RPC中的应用

在这里插入图片描述
无论是同步调用还是异步调用,调用端内部实行的都是异步,而调用端在向服务端发送消息之前会创建一个 Future,并存储这个消息标识与这个 Future 的映射,当服务端收到消息并且处理完毕后向调用端发送响应消息,调用端在接收到消息后会根据消息的唯一标识找到这个 Future,并将结果注入给这个 Future。

那在这个过程中,如果服务端没有及时响应消息给调用端呢?调用端该如何处理超时的请求?

没错,就是可以利用定时任务每次创建一个 Future,我们都记录这个 Future 的创建时间与这个 Future 的超时时间,并且有一个定时任务进行检测,当这个 Future 到达超时时间并且没有被处理时,我们就对这个 Future 执行超时逻辑。

那么由谁来进行超时检测?(等价于 Redis 中的过期)一种方式就是每创建一个 Future 我们都启动一个线程,之后 sleep,到达超时时间就触发请求超时的处理逻辑。另一种就是:用一个线程来处理所有的定时任务,还以刚才那个 Future 超时处理的例子为例。假设我们要启动一个线程,这个线程每隔 100 毫秒会扫描一遍所有的处理 Future 超时的任务,当发现一个 Future 超时了,我们就执行这个任务,对这个 Future 执行超时逻辑。

但是:同样是高并发的请求,那么扫描任务的线程每隔 100 毫秒要扫描多少个定时任务呢?如果调用端刚好在 1 秒内发送了 1 万次请求,这 1 万次请求要在 5 秒后才会超时,那么那个扫描的线程在这个 5 秒内就会不停地对这 1 万个任务进行扫描遍历,要额外扫描 40 多次(每 100 毫秒扫描一次,5 秒内要扫描近 50 次),很浪费 CPU

什么是时钟轮?

网上查阅资料即可~

时钟轮在 RPC 中的应用

  • 调用端请求超时处理,这里我们就可以应用到时钟轮,我们每发一次请求,都创建一个处理请求超时的定时任务放到时钟轮里,在高并发、高访问量的情况下,时钟轮每次只轮询一个时间槽位中的任务,这样会节省大量的 CPU。
  • 调用端与服务端启动超时也可以应用到时钟轮,以调用端为例,假设我们想要让应用可以快速地部署,例如 1 分钟内启动,如果超过 1 分钟则启动失败。我们可以在调用端启动时创建一个处理启动超时的定时任务,放到时钟轮里。
  • 心跳检测 是要定时重复执行的,在定时任务的执行逻辑的最后,我们可以重设这个任务的执行时间,把它重新丢回到时钟轮里即可。

注意点

  • 时间槽位的单位时间越短,时间轮触发任务的时间就越精确。例如时间槽位的单位时间是 10 毫秒,那么执行定时任务的时间误差就在 10 毫秒内,如果是 100 毫秒,那么误差就在 100 毫秒内。

21 | 流量回放:保障业务技术升级的神器

RPC 怎么支持流量回放?

  • 所有的请求都会经过 RPC,那么我们在 RPC 里面是不是就可以很方便地拿到每次请求的出入参数?拿到这些出入参数后,我们只要把这些出入参数旁录下来,并把这些旁录结果用异步的方式发送到一个固定的地方保存起来,这样就完成了流量回放里面的录制功能。
  • 有了真实的请求入参之后,剩下的就是怎么把这些请求参数转发到我们要回归测试的应用里面。在 RPC 中,我们把能够接收请求的应用叫做服务提供方,那就是说我们只需要模拟一个应用调用方,把刚才收到的请求参数重新发送一遍到要回归测试的应用里面,然后比对录制拿到的请求结果和新请求的结果,就可以完成请求回放的效果。整个过程如下图所示:
    在这里插入图片描述

22 | 动态分组:超高效实现秒级扩缩容

23 | 如何在没有接口的情况下进行RPC调用?(泛化调用)

  1. 场景一:
    在这里插入图片描述
  2. 场景二:
    在这里插入图片描述

如何解决?

在这里插入图片描述
我们可以定义一个统一的接口(GenericService),调用端在创建 GenericService 代理时指定真正需要调用的接口的接口名以及分组名,而 GenericService 接口的 $invoke 方法的入参就是方法名以及参数信息。

这样我们传递给服务端所需要的所有信息,包括接口名、业务分组名、方法名以及参数信息等都可以通过调用 GenericService 代理的 $invoke 方法来传递。具体的接口定义如下:

class GenericService {

  Object $invoke(String methodName, 
  			String[] paramTypes, Object[] params);
}

这个通过统一的 GenericService 接口类生成的动态代理,来实现在没有接口的情况下进行 RPC 调用的功能,我们就称之为泛化调用

24 | 如何在线上环境里兼容多种RPC协议?

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值