滑动窗口限速 周期抖动现象

异步压测 vs 同步压测

场景介绍

利用baidu rpc_press工具的进行单client 与单server的压测。

  1. 利用同步测试模式

    最大能力压测,qps 23000,cpu利用率85%, cpu是瓶颈

  2. 利用异步压测方式,设定单线程滑动窗口2000,进行压测

    初始时平均时延1ms,没有长尾。cpu利用率45%左右,随着时间进行,平均时延逐渐增长,开始有长尾出现。从服务器端查看,超时请求都在请求队列中(在处理前的等待时间很长)。cpu任何一个核心,IO,都不是瓶颈。只能是这一时刻请求很多,并且持续时间非常短。并且重启压测工具后,时延变为1ms,开始上一个过程。判断,长时间运行导致某些状态累积。重启压测工具不重启server,状态重置,说明压测工具长时间运行会累积运行状态中某些因素。导致短时间内发送大量请求,(监控是s级别),周期一定小于s。

    将压测工具发送每个请求时间打印出来,发现压力分布如图

图一
一个线程(一个窗口)测试。水平单位20ms, 纵坐标单位应该为200,(qps 1W) 为啥大多数点都在400左右?因为那中间有很多为0的点,没有显示,给人错觉平均超过200

图二
两个线程(两个窗口叠加)水平单位10ms, 纵坐标单位应该为200,(qps 2W)

原因分析:

猜测:滑动窗口因为有周期性,cpu的时间分片会导致限速不均匀,而周期性的内部反馈影响叠加等,最终形成波峰波谷的周期性震荡
震荡的振幅和波长应该和CPU时间片长短,周期,窗口长度等有关(控制专业相关的小朋友是不是可以据此写个论文)

问题反思:

  1. 同步压测的性能要高于异步压测,但是线上状况是异步的。同步测试只能反映server的极限性能,不能作为线上性能。
    线上状况应该是异步的,并且波峰波谷等是不可预期的,只能通过预留性能buffer来解决。
  2. 系统中假如真的出现压力波峰,立即返回失败可能会比等待超时要更好。案例一:系统A,队列等待队列无限长,上游会超时重试。高峰时,部分请求超时,积压请求堆积在A请求队列中,A上游重发(一次),导致上游流量翻倍,直接导致整个系统雪崩,直到内存耗尽,整个集群重启。
  3. server端为异步,可以通过限制请求队列长度和请求并发数(对于异步处理来说就是接受到请求,到返回之前都算正在并发的请求)防止单机短时间热点问题。可以利用baidu rpc client重试机制

拓展阅读

  1. 限流算法 (令牌桶,漏桶,计数器,滑动窗口)
    // 滑动窗口算法 实现 伪代码
    std::deque<int64_t> timeq;
    size_t MAX_QUEUE_SIZE = (size_t)req_rate;
    if (MAX_QUEUE_SIZE < 100) {
        MAX_QUEUE_SIZE = 100;
    } else if (MAX_QUEUE_SIZE > 2000) { 
        MAX_QUEUE_SIZE = 2000; 
    }
    timeq.push_back(base::gettimeofday_us());
    while (!_stop) {
        baidu::rpc::Controller* cntl = new baidu::rpc::Controller;
        msg_index = (msg_index + _options.test_thread_num) % _msgs.size();
        Message* request = _msgs[msg_index];
        Message* response = _pbrpc_client->get_output_message();
        const int64_t start_time = base::gettimeofday_us();
        google::protobuf::Closure* done = baidu::rpc::NewCallback<
            RpcPress, 
            RpcPress*, 
            baidu::rpc::Controller*, 
            Message*, 
            Message*, int64_t>
            (this, &RpcPress::handle_response, cntl, request, response, start_time);
        const baidu::rpc::CallId cid1 = cntl->call_id();
        _pbrpc_client->call_method(cntl, request, response, done);
        LOG(WARNING) << start_time;
        _sent_count << 1;

        if (_options.test_req_rate <= 0) { 
            baidu::rpc::Join(cid1);
        } else {
            int64_t end_time = base::gettimeofday_us();
            int64_t expected_elp = 0;
            int64_t actual_elp = 0;
            timeq.push_back(end_time);
                       if (timeq.size() > MAX_QUEUE_SIZE) {
                actual_elp = end_time - timeq.front();
                timeq.pop_front();
                expected_elp = (int64_t)(1000000 * timeq.size() / req_rate);
            } else {
                actual_elp = end_time - timeq.front();
                expected_elp = (int64_t)(1000000 * (timeq.size() - 1) / req_rate);
            }
            if (actual_elp < expected_elp) {
                usleep(expected_elp - actual_elp);
            }
        }
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值