More than code

More Than Code
The efficiency of your iteration of reading, practicing and thinking decides your understanding of the world.
  1. 首页
  2. 未分类
  3. 正文

有关rpc的一些疑惑

2024年7月6日 350点热度 0人点赞 0条评论

最近在读一个存储系统的代码的时候,产生了一些疑惑。背景是,这个存储系统对于一些重试的请求处理的比较敏感,(应该是写数据的系统都会敏感一些,因为要避免数据写错了),所以就涉及到一些RPC重试的语意的问题。

一个最基本的问题是,当我们发出一个RPC的时候,预期对端作出的行为是什么:
* 如果RPC超时了,对端可能执行了rpc,也可能没有执行rpc
* 如果我们不做重试的话,对端是否只会执行一次rpc呢
* 如果我们做了重试的话,对端的行为预期是什么样的,会过滤掉重试的请求吗
* 如果重试的response和之前的response同时返回了,我们希望处理那个response呢

首先考虑架在TCP/IP之上的RPC服务,有关用户态网络协议的,比如RDMA什么的暂时不考虑。

那么这里首先要考虑的是TCP层的重试语意,TCP层保证了这个链接下,每个byte对端的接收都是exactly once的——维护一个窗口来给上游一个连续的,不重不漏的字节流。

但是在真实的情况中,我们大概率不会只使用一个链接来发送请求。一般都会有链接复用的技术,所以其实依赖TCP层的去重基本上意义不大。

并且在一些存储系统中,如果发生宕机,还是需要考虑重试请求,那么这时候原本维护的链接的上下文已经丢失了,再次重试的链接已经是新的链接了,所以也无法保证exactly once

其实到这里,基本的结论就是,应用层不要依赖任何的假设(比如依赖RPC层不会重试等),对于要写入的数据,要保证能够处理重复请求的case,不受到RPC层的影响。

从client发送request是这个道理,其实client接收response也是一样的道理。不过这里按照直观的语意是,如果上次的请求失败了,进行了重试。
* 如果是rpc层做的重试,rpc层应该保证只有一个response被处理了。(比如第一个回来的人处理了)
* 如果是应用层的重试,rpc层应该会过滤掉上一次rpc返回的请求,只关注这一次的。

其实brpc处理上述逻辑也比较简单:
* 接收请求是bthread_id_lock -> process -> bthread_id_unlock_and_destroy
* 如果出现了重复的返回值,那么第二个请求的返回值会在bthread_id_lock的时候失败,因为上一个人已经destroy他了

因为我们不知道对端什么时候会返回请求,所以才会有了上面的设计。那么对于应用层来说:
* 如果是读请求的重复response,一般无所谓,返回任意一个就好了
* 对于写请求的response,因为上面说了tcp层保证了exactly once,所以要么是重复的request返回的,要么是对端的server重复的发送了response
* 我有点怀疑server端的回包会不会重试,不过就算会,其实处理也相当于执行了一次请求。
* 如果是重复的request导致的,那么可能第二次写入会有一些其他的报错,这时候就需要应用层去做处理了,比如发现是重复就认为已经成功,或者换一个位置继续写入,直到返回确定性的错误为止。

总结下来,作为rpc的使用者,任何时候应用层不要假设exactly once的语意,如果需要,那么自己保证。需要怀疑任何位置都有可能出现未知的重试。
* 不过对于掌控力比较强的框架,可以有一定的假设,比如在用brpc的时候,可以假设大概率,如果我不设置rpc的重试,那么执行一定是at-most-once的语意,不会出现client侧发一次,rpc层处理两次的情况。不过这种情况也需要兜底报错,只是说这种情况出现的概率极低(出bug什么的)
* 对于最少假设的原则来讲,只需要记住如下的规则:
* 如果收到了response,那么请求至少被执行了一次
* 如果没有收到response,那么请求可能被执行了(一次或多次),也可能没有被执行
* 更加强一点的则是考虑at-least-once和at-most-once:
* at-lease-once,如果收到了response,那么请求至少被执行了一次
* 一些请求有幂等语意的系统比较适合这种。比如brpc打开backup request就是这种case,第一个请求还没有返回的时候,可以用其他的链接再去发送一次请求。
* at-most-once,如果收到了response,那么请求至多被执行了一次
* 比如brpc/grpc的实现就是这种,重试的逻辑由用户负责,一次RPC只会通过一个链接发送一次,下层的重试会被TCP层过滤掉。所以只要client不发送第二次请求,那么server一定不会执行第二次。

标签: 暂无
最后更新:2024年7月6日

sheep

think again

点赞
< 上一篇
下一篇 >

文章评论

取消回复

COPYRIGHT © 2021 heavensheep.xyz. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS