Skip to content

Latest commit

 

History

History
120 lines (84 loc) · 6.3 KB

File metadata and controls

120 lines (84 loc) · 6.3 KB

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

当后端server可以挂在一个命名服务内时

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

示例代码见example/backup_request_c++。这个例子中,client设定了在2ms后发送backup request,server在碰到偶数位的请求后会故意睡眠20ms以触发backup request。

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

img

img

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

img

选择合理的backup_request_ms

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

img

自行添加的方法:

#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等很多计数器。

Backup Request 限流

如需限制 backup request 的发送比例,可使用内置工厂函数创建限流策略,也可自行实现 BackupRequestPolicy 接口。

优先级顺序:backup_request_policy > backup_request_ms

使用内置限流策略

调用 CreateRateLimitedBackupPolicy 创建限流策略,并将其设置到 ChannelOptions.backup_request_policy

#include "brpc/backup_request_policy.h"
#include <memory>

brpc::RateLimitedBackupPolicyOptions opts;
opts.backup_request_ms = 10;       // 超过10ms未返回时发送backup请求
opts.max_backup_ratio = 0.3;       // backup请求比例上限30%
opts.window_size_seconds = 10;     // 滑动窗口宽度(秒)
opts.update_interval_seconds = 5;  // 缓存比例的刷新间隔(秒)

// CreateRateLimitedBackupPolicy返回的指针由调用方负责释放。
// policy的生命周期必须长于channel——先销毁channel,再销毁policy。
std::unique_ptr<brpc::BackupRequestPolicy> policy(
    brpc::CreateRateLimitedBackupPolicy(opts));

brpc::ChannelOptions options;
options.backup_request_policy = policy.get(); // Channel不拥有该对象
channel.Init(..., &options);
// channel必须在policy析构之前销毁。

参数说明(RateLimitedBackupPolicyOptions):

字段 默认值 说明
backup_request_ms -1 超时阈值(毫秒)。-1 表示继承 ChannelOptions.backup_request_ms(仅在通过 ChannelOptions.backup_request_policy 设置策略时有效;通过 Controller 注入时没有 channel 级的回退值,应显式指定 >= 0 的值)。必须 >= -1。
max_backup_ratio 0.1 backup比例上限,取值范围 (0, 1]
window_size_seconds 10 滑动窗口宽度(秒),取值范围 [1, 3600]
update_interval_seconds 5 缓存刷新间隔(秒),必须 >= 1

参数不合法时 CreateRateLimitedBackupPolicy 返回 NULL

使用自定义 BackupRequestPolicy

如需完全控制,可实现 BackupRequestPolicy 接口并设置到 ChannelOptions.backup_request_policy

#include "brpc/backup_request_policy.h"

class MyBackupPolicy : public brpc::BackupRequestPolicy {
public:
    int32_t GetBackupRequestMs(const brpc::Controller*) const override {
        return 10; // 10ms后发送backup
    }
    bool DoBackup(const brpc::Controller*) const override {
        return should_allow_backup(); // 自定义逻辑
    }
    void OnRPCEnd(const brpc::Controller*) override {
        // 每次RPC结束时调用,可在此更新统计
    }
};

MyBackupPolicy my_policy;
brpc::ChannelOptions options;
options.backup_request_policy = &my_policy; // Channel不拥有该对象,需保证其生命周期长于Channel
channel.Init(..., &options);

实现说明

  • 比例通过bvar计数器在滑动时间窗口内统计。缓存值通过无锁CAS选举最多每 update_interval_seconds 刷新一次,因此每次RPC的开销极低(公共路径仅有两次原子读)。
  • Backup决策在做出时立即计数(RPC完成前),以便在延迟抖动期间更快地反馈。总RPC数在完成时统计。这意味着比例在抖动期间可能短暂滞后,这是设计有意为之——限流器的目标是近似的尽力而为的节流,而非精确执行。
  • 每个使用限流的Channel会维护两个 bvar::Window 采样任务,在Channel数量极多的部署中请留意此开销。

当后端server不能挂在一个命名服务内时

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

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