-
Notifications
You must be signed in to change notification settings - Fork 58
replica-server: support table level write throttling #204
Conversation
有没有具体的效果可以截图展示一下,贴在这里? |
在测试集群上进行功能验证。 集群:c4tst-benchmark 客户端:pegasus-YCSB 在启动测试之前(16:20左右),设置测试表的throttling环境变量:【1】
测试一段时间后(16:42左右),改变测试表的throttling环境变量:【2】
测试一段时间后(16:55左右),改变测试表的throttling环境变量:【3】
测试一段时间后(17:00左右),改变测试表的throttling环境变量:【4】
测试一段时间后(17:07左右),改变测试表的throttling环境变量:【5】
测试一段时间后(17:17左右),改变测试表的throttling环境变量:【6】
测试一段时间后(17:22左右),改变测试表的throttling环境变量:【7】
通过falcon记录以下4条曲线:
|
changed = false; | ||
return true; | ||
} | ||
reset(changed, old_str); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
从一个值调为另一个值时,中间是不是会被短暂关闭一下?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
因为都是串行的,所以中间关闭也没有问题。不过这里的逻辑有点tricky,我重构一下。
完成度高的话quota确实是比qps来讲更好的选择,不过我也能理解第一版只做 qps 比较简单 😄 |
我是说限流"qps"还是限流"mb/s"这种比较好,这两个的实现难度感觉差不多 |
_counter_recent_write_throttling_delay_count->increment(); | ||
} else { // type == throttling_controller::REJECT | ||
request->add_ref(); | ||
tasking::enqueue(LPC_WRITE_THROTTLING_DELAY, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
另外reject是不是应该立即返回?你delay了一段时间再reject,很有可能客户端已经超时了,从而引发重试。尽早把reject的消息返回去,让client sdk有一个把error message抛给上层的机会。这样对于错误处理和我们排错都更方便一些。
不然客户端那边只知道一个超时,server端看log才知道什么原因。
更进一步看,一条message究竟是该reject还是该delay,应该是根据客户端的超时时长来决定的,而不是直接拍脑袋配的?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里我仔细考虑过,思量再三,最后觉得还是reject的时候在server端统一delay下比较好。
要考虑一般用户的使用方式:
- 很多用户在catch异常后都是立即重试(可能有些有经验的用户会sleep一下,但是按照我们的经验这样的用户比较少),一方面太频繁的请求会增大server压力,另一方面立即重试再次失败的概率也很大。所以从根本上我觉得“尽早把reject的消息返回去”并不是一个最佳策略,我希望通过一种机制来人为制造一种delay。而在客户端制造delay的话,一是需要用户都升级到最新版本客户端,二是每种语言都要增加这种机制。所以比较简单的办法就是在server端制造delay。
- 一般用户都是直接catch PException异常,要么直接重试要么放弃,通常并不会区分异常的具体类型再做处理。在delay一段时间后reject,客户端会收到PERR_BUSY或者PERR_TIMEOUT错误,通常写操作的超时都设得比较大,所以一般都是PERR_BUSY错误。
我也考虑过获取客户端的超时时长来确定delay长度,但是当时是考虑到有的语言没有传Timeout过来。不过我这里可以再优化一下:如果客户端有把timeout传过来,则可以使用这个信息(譬如 delay = client_timeout / 2);如果客户端没传,则使用配置的delay时间。这样尽量让客户端能收到ERR_BUSY的异常。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
timeout貌似是一个message的字段,是所有客户端都必须得传的?不传其实意思就是按默认值来。
server端delay这种设计感觉比较诡异,因为你把错误抛给客户端之后,客户端的有机会知道为什么timeout。而你不扔回去,oncall的压力就还在server端这边。其实就是这么想这个问题:业务跑过来和你说,又出问题了怎么回事?是stacktrace上有个err_busy好,还是stack_trace上有个err_timeout好……再说了,升级一下客户端版本其实也没啥吧。
而且在server端delay,这些都是要排到asio的队列里面的,它都要和别的timer抢资源的。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里我现在允许设置reject的时候delay为0,这样两种方式都可以用,毕竟server端delay我觉得还是有必要提供的。
你说的server端delay的问题,我觉得无非是多用点内存,asio队列这点压力不大,你想想这里的delay一般都是1秒以内,能积压多少呢?我们读QPS还是经常几十万呢,和使用的timeout timer来比,这点压力完全不用担心。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
我确认了一下,C++客户端是设置的client_timeout的,但是java客户端没有。
所以我刚刚提交了一个改进: XiaoMi/pegasus-java-client#24
然后我再改进一下吧,尽量用上client端传过来的client_timeout,避免因为delay造成超时。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
qps 和 mb/s 都可以有,我之前看 hbase 好像就是有 qps/size/quota(capacity unit) 三种限流方式,现在只有一种问题也不大,业务对 qps 的预算和实际qps我感觉还是比较一致的。 |
关于是基于qps还是size做限流,我其实都考虑过,并且周三在给大家分享throttling方案的时候,这两种限流方式我都说过。在实现的时候,基于size的限流我还写过部分代码,但是后来觉得还不成熟,所以又去掉了。主要原因是:
目前开发feature的原则是:来自业务需求;方案简单有效;做最小需求集合,避免过早优化;易扩展。 |
size还是qps问题不大,我也就提一下。 |
419ef2e
if (_reject_qps > 0 && _cur_request_count > _reject_qps) { | ||
_cur_request_count--; | ||
int64_t client_timeout = request->header->client.timeout_ms; | ||
if (client_timeout > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
假如我 client_timeout = 20s, env.delay_ms = 1s,按这算法是不是这个请求应该要延迟 10s 才执行嘛,这样不对。应该仍然是只 delay 1s,避免系统过低负载。
有个麻烦但是比较鲁棒的写法:
if(client_timeout > 0 && client_timeout/2 < _delay_ms) {
delay_ms = client_timeout/2;
} else {
delay_ms = _delay_ms;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
仔细看看,std::min()
} | ||
if (_delay_qps > 0 && _cur_request_count > _delay_qps) { | ||
int64_t client_timeout = request->header->client.timeout_ms; | ||
if (client_timeout > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delay 和 reject 都会用到这个 delay_ms 的计算的代码,这里代码重复了,麻烦抽成一个函数把:
int64_t calculate_request_delay_ms(int64_t client_timeout_ms, int64_t delay_ms) {
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
没几行代码,没必要
if (client_timeout > 0) { | ||
delay_ms = std::min(_delay_ms, client_timeout / 2); | ||
} else { | ||
delay_ms = _delay_ms; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_delay_ms
建议改名 _env_delay_ms
表示是 app_env 中设置的延时时间。_delay_ms 与 delay_ms 名字相近,不易辨认。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
我觉得还好吧,IDE显示的颜色都不一样。如果这里不易辨认的话,那么很多构造函数中(参数和私有成员也只有下划线的区别)都存在这个问题。。。
// 'parse_error' is set when return false. | ||
// 'changed' is set when return true. | ||
// 'old_env_value' is set when 'changed' is set to true. | ||
bool parse_from_env(const std::string &env_value, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
注释写一下正确的 env_value 格式应该是怎么样的:
Here is the correct format for `env_value`:
[qps(int32)]*[delay_ms(int32)]*["reject"/"delay"]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
没什么大问题就先这样吧,一修改又要重新approve。这个拖太久了。
User can enable write throttling through setting app's env:
for example:
which means:
User can provide only one of 'delay' or 'reject' strategy, like:
or