ssdb get的设计问题

来源:互联网 发布:在线制作淘宝商品图片 编辑:程序博客网 时间:2024/06/08 01:09

背景

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

get-ssdb-bench没结束之前,切入set-ssdb-bench对get命令测试,并发连接数是200./get-ssdb-bench 127.0.0.1 8888 4000000 200qps: 41877对set命令测试,并发连接数是200./set-ssdb-bench 127.0.0.1 8888 4000000 200qps: 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 200qps: 41877./set-ssdb-bench 127.0.0.1 8888 4000000 200qps: 23454

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

Q&A

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

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

0 0
原创粉丝点击