ssdb get的设计问题

背景

对ssdb进行性能测试,当跑以下测试用例时,惊奇发现set的qps跌到了百位数!

get-ssdb-bench没结束之前,切入set-ssdb-bench
对get命令测试,并发连接数是200
./get-ssdb-bench 127.0.0.1 8888 4000000 200
qps: 41877
对set命令测试,并发连接数是200
./set-ssdb-bench 127.0.0.1 8888 4000000 200
qps: 220.02

单独对set命令测试,set命令请求能达到40000+的qps。

分析

ssdb的代码优美,结构清晰。容易找到ssdb处理请求的核心逻辑(src/net/server.cpp)。

//只保留核心代码
void NetworkServer::serve(){
    //ssdb 根据命令的读写属性,将其放到对应的处理队列。
    //命令与队列的对应关系可查serv.cpp的reg_procs函数
    //REG_PROC(get, "r");get命令是在此线程处理
    //REG_PROC(set, "wt"); set命令异步处理处理
    writer = new ProcWorkerPool("writer");
    writer->start(num_writers);
    reader = new ProcWorkerPool("reader");
    reader->start(num_readers);

    ready_list_t ready_list;
    ready_list_t ready_list_2;
    ready_list_t::iterator it;
    const Fdevents::events_t *events;
    ...
    while(!quit){
        ...
        ready_list.swap(ready_list_2);
        ready_list_2.clear();

        if(!ready_list.empty()){
            // ready_list not empty, so we should return immediately
            events = fdes->wait(0);
        }else{
            events = fdes->wait(50);
        }
        if(events == NULL){
            log_fatal("events.wait error: %s", strerror(errno));
            break;
        }

        for(int i=0; i<(int)events->size(); i++){
            const Fdevent *fde = events->at(i);
            ...
            }else if(fde->data.ptr == this->reader || fde->data.ptr == this->writer){
                //此逻辑很关键。
                //当发送队列有消息待发送时,就会触发这个逻辑
                ProcWorkerPool *worker = (ProcWorkerPool *)fde->data.ptr;
                ProcJob job;
                //注意!
                //每个epoll loop从发送队列取出一条消息,发送响应。
                if(worker->pop(&job) == 0){
                    log_fatal("reading result from workers error!");
                    exit(0);
                }
                if(proc_result(&job, &ready_list) == PROC_ERROR){
                    //
                }
            }else{
                proc_client_event(fde, &ready_list);
            }
        }

        for(it = ready_list.begin(); it != ready_list.end(); it ++){
            ...
            ProcJob job;
            job.link = link;
            this->proc(&job);
            if(job.result == PROC_THREAD){
                fdes->del(link->fd());
                continue;
            }
            if(job.result == PROC_BACKEND){
                fdes->del(link->fd());
                this->link_count --;
                continue;
            }

            if(proc_result(&job, &ready_list_2) == PROC_ERROR){
                //
            }
        } // end foreach ready link
    }
}
//请求处理函数
void NetworkServer::proc(ProcJob *job){
    ...
    if(cmd->flags & Command::FLAG_THREAD){ //如果命令需要放到线程中执行
        if(cmd->flags & Command::FLAG_WRITE){ //如果命令是写命令
            job->result = PROC_THREAD;
            writer->push(*job); //set请求会放到这个处理队列
        }else{ //如果命令是读命令。
            job->result = PROC_THREAD;
            reader->push(*job);//get命令的处理逻辑不走这里
        }
        return; 
    }

    proc_t p = cmd->proc;
    job->time_wait = 1000 * (millitime() - job->stime);
    job->result = (*p)(this, job->link, *req, &resp);//get请求会就地处理
    job->time_proc = 1000 * (millitime() - job->stime) - job->time_wait;
    ...
}

从上述代码可以获得几个重要信息:

  • get请求在epoll loop就地处理,set请求异步处理。
  • 每次epoll loop 从set处理队列取出一条响应消息,发送。
    那么当有400个get并发连接时,会出现什么情况?
    一个set请求过来,放到异步队列,接下来整个线程要处理400个get请求。就算set请求处理非常快,也要等下一个epoll loop才有机会把消息发送出去。
    因此,上面bench出现的低qps是因为,处理完set请求-> 发送消息,这之间有较大的延迟。

解决方法

最简单有效的方法就是让get不要在epoll线程处理。
方法很简单,在(src/serv.cpp) 把 REG_PROC(get, “r”) 改为 REG_PROC(get, “rt”)。
再跑bench

./get-ssdb-bench 127.0.0.1 8888 4000000 200
qps: 41877
./set-ssdb-bench 127.0.0.1 8888 4000000 200
qps: 23454

PS,给ssdb的作者提issue,希望在项目代码中做这个修改。(Edit in 2015-05-19, 作者已采纳修改)

Q&A

  • 增加set请求的并发连接数能改善吗?
    不能。因为每次epoll loop 都只会从set处理队列取一条响应消息,并且ssdb默认的处理写请求的线程只有一条。因此无补于事,甚至会造成更大的延迟。

此问题在一般的bench里面暴露不出来,希望这篇文章能够帮助大家。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值