RPC 系列(四十):Backup Request 策略

有时为了保证可用性,需要同时访问两路服务,哪个先返回就取哪个。在 brpc 中,这有多种做法。

在 Channel 中开启 backup request

Channel 开启 backup request 后,会先向其中一个 server 发送请求,如果在 ChannelOptions.backup_request_ms 后还没回来,再向另一个 server 发送。之后哪个先回来就取哪个。在设置了合理的 backup_request_ms 后,大部分时候只会发一个请求,对后端服务只有一倍压力。

以官方示例 backup_request_c++ 为例:client 设定了在 2ms 后发送 backup request,server 在碰到偶数位的请求后会故意睡眠 20ms 以触发 backup request。

运行后,client 端和 server 端的日志分别如下,“index” 是请求的编号。可以看到 server 端在收到第一个请求后会故意 sleep 20ms,client 端之后发送另一个同样 index 的请求,最终的延时并没有受到故意 sleep 的影响。

client 端日志:各请求的最终延时不受 server 故意 sleep 20ms 的影响

server 端日志:对偶数编号的请求 sleep 20ms 以触发 backup request

/rpcz 也显示 client 在 2ms 后触发了 backup 超时并发出了第二个请求。

/rpcz 显示 2ms 后触发 backup 超时并发出了第二个请求

借助 latency_cdf 选择 backup_request_ms

可以观察 brpc 默认提供的 latency_cdf 图,或自行添加。cdf 图的 y 轴是延时(默认微秒),x 轴是小于 y 轴延时的请求的比例。在下图中,选择 backup_request_ms=2ms 可以大约覆盖 95.5% 的请求,选择 backup_request_ms=10ms 则可以覆盖 99.99% 的请求。

client_latency_cdf 曲线:约 95% 的请求延时在 2ms 以内

自行添加的方法:

#include <bvar/bvar.h>
#include <butil/time.h>
...
bvar::LatencyRecorder my_func_latency("my_func");
...
butil::Timer tm;
tm.start();
my_func();
tm.stop();
my_func_latency << tm.u_elapsed();  // u代表微秒,还有s_elapsed(), m_elapsed(), n_elapsed()分别对应秒,毫秒,纳秒。

// 好了,在/vars中会显示my_func_qps, my_func_latency, my_func_latency_cdf等很多计数器。

SelectiveChannel 方式【推荐】

建立一个开启 backup request 的 SelectiveChannel,其中包含两个 sub channel。访问这个 SelectiveChannel 和上面的情况类似,会先访问一个 sub channel,如果在 ChannelOptions.backup_request_ms 后没返回,再访问另一个 sub channel。如果一个 sub channel 对应一个集群,这个方法就是在两个集群间做互备。SelectiveChannel 的例子可参考官方示例 selective_echo_c++,具体做法请参考上面的过程。

发起两个异步 RPC 后 Join【不推荐】

发起两个异步 RPC 后 Join 它们,它们的 done 内是相互取消的逻辑,官方示例为 cancel_c++。这种方法的问题是总会发两个请求,对后端服务有两倍压力,这个方法怎么算都是不经济的,你应该尽量避免用这个方法。

系列导航


   转载规则


《RPC 系列(四十):Backup Request 策略》 由 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
RPC 系列(四十一):自适应并发限流 RPC 系列(四十一):自适应并发限流
服务的处理能力是有客观上限的。当请求速度超过服务的处理速度时,服务就会过载。如果服务持续过载,会导致越来越多的请求积压,最终所有的请求都必须等待较长时间才能被处理,从而使整个服务处于瘫痪状态。 与之相对的,如果直接拒绝掉一部分请求,反而能够
2024-06-11
下一篇 
RPC 系列(三十九):brpc 的一致性哈希 RPC 系列(三十九):brpc 的一致性哈希
一些场景希望同样的请求尽量落到一台机器上,比如访问缓存集群时,我们往往希望同一种请求能落到同一个后端上,以充分利用其上已有的缓存,不同的机器承载不同的稳定 working set;而不是随机地散落到所有机器上,那样的话会迫使所有机器缓存所有
2024-05-28
  目录