Memcached--set 过程跟踪解析
来源:互联网 发布:王力宏 李靓蕾 知乎 编辑:程序博客网 时间:2024/06/05 10:55
看完memcahed好几天了,急需进行总结一番.
//set key 0 0 5 hello
下面从主函数开始
thread_init(settings.num_threads, main_base);//初始化事件线程,并进行循环的运行
Setup_thread(&threads[i]);//创建线程事件
thread_libevent_process//当管道有数据来时进行唤醒调用
{
....
if (read(fd, buf, 1) != 1) 此处为阻塞的地方
.....
}
server_socket_unix(settings.socketpath,settings.access))//设置SOCKET进行监听
sfd = new_socket_unix()//创建一个sfd
Bind();
Listen();
conn_new(sfd,conn_listening,....,main_base)//创建对该sfd的监听事件
{
conn *c = conn_from_freelist(); //从空的连接队列中拿出一个,如果没有则重新申请一个conn空间
event_set(&c->event, sfd, event_flags, event_handler, (void *)c);//加入事件进行监听
event_handler(const int fd, const short which, void *arg)//主要调用drive_machine()
drive_machine(c);//初始化时c->state为conn_listening
{
switch(c->state)
{
case conn_listening:
sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen)//这里一直等待客户端的连接
}
}
}
此时客户端进行连接Memcached telnet localhost 11211
drive_machine(c)中的sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen)被唤醒。
开始分发该sfd到线程事件中dispatch_conn_new(sfd,conn_new_cmd,.....)
//接收到一个连接时init state为conn_new_cmd,该sfd为客户端连接fd
{
CQ_ITEM *item = cqi_new(); //从cqi_freelist中取出一个cqi,如果没有则分配64个CQ_ITEM在从其中拿.
int tid = (last_thread + 1) % settings.num_threads;
//轮询选择线程事件
cq_push(thread->new_conn_queue, item); //将该item加入到线程连接队列中
write(thread->notify_send_fd, "", 1)//向该线程事件所属管道中发送一字节表示有连接进来,唤醒该线程事件thread_libevent_process函数进行处理.
}
thread_libevent_process//当管道有数据来时进行唤醒调用
{
....
if (read(fd, buf, 1) != 1) 停止阻塞,读取一个字节数据
item = cq_pop(me->new_conn_queue);
//从连接队列中取出一个item
conn *c = conn_new(item->sfd,item->init_state,item->event_flags,.... me->base)
//创建连接,并将该sfd 加入到该线程事件的base中进行监听处理.此时的item->init_state为conn_new_cmd
{
conn *c = conn_from_freelist(); //从空的连接队列中拿出一个,如果没有则重新申请一个conn空间
fprintf(stderr, "<%d new ascii client connection.\n", sfd);//在服务器打印客户端连接信息
event_set(...,sfd...,event_handler,(void *)c)
event_handler(const int fd, const short which, void *arg)
//主要调用drive_machine()
drive_machine(c)
//此时c->state为conn_new_cmd
case conn_new_cmd:
reset_cmd_handler(c);
{
conn_shrink(c);//缩短该连接所申请的一些不必要的空
if (c->rbytes > 0) {
conn_set_state(c, conn_parse_cmd);
} else {
conn_set_state(c, conn_waiting);
//现在是在此处,将c->state设置为conn_waiting
}
}
}
.....
}
接下来仍然是drive_machine(c){//此时c->state为conn_waiting
case conn_waiting:
update_event(c, EV_READ | EV_PERSIST)//由于状态进行了改变需要在事件中进行跟新
event_set(&c->event, c->sfd, new_flags, event_handler, (void *)c);//依然调用event_handler进行处理
conn_set_state(c, conn_read);//然后将c->state设置为conn_read
接下来仍然是回调drive_machine(c){//此时c->state为conn_read
case conn_read:
res = IS_UDP(c->transport) ? try_read_udp(c) : try_read_network(c);//此处为try_read_network(c)
{
if (c->rcurr != c->rbuf) {//此处应该是将还未处理完的数据拷贝至rbuf处
if (c->rbytes != 0) /* otherwise there's nothing to copy */
memmove(c->rbuf, c->rcurr, c->rbytes);//将rcurr中的rbytes个数据拷贝到rbuf中
c->rcurr = c->rbuf;
int avail = c->rsize - c->rbytes;//还剩下可以利用的bites数来存储
res = read(c->sfd, c->rbuf + c->rbytes, avail);
//此处用来等待客户发送命令过来,如果客户还没发送则阻塞在此处。最终是把客户命令存储在c->rbuf中
}
switch (res) //try_read_network(c)读取完毕后的返回值
case READ_DATA_RECEIVED:
conn_set_state(c, conn_parse_cmd);//开始解析命令
}
}
此时连接的客户端开始发送命令:
set key 0 0 5
Hello
在try_read_network(c)中的res = read(c->sfd, c->rbuf + c->rbytes, avail);被唤醒读取完成后返回READ_DATA_RECEIVED.开始进行解析命令。
drive_machine(c){//此时c->state为conn_parse_cmd
conn_parse_cmd:
if (try_read_command(c) == 0) {
/* wee need more data! */
conn_set_state(c, conn_waiting);
}
try_read_command(conn *c):
c->protocol = ascii_prot;//此处为ascii
el = memchr(c->rcurr, '\n', c->rbytes);//找到第一个'\n'的位置
//c->rcurr="set key 0 0 5\r\nhello\r\n"即el="\nhello\r\n"
cont = el + 1;//去掉/n即cont="hello\r\n" ,cont为该value
if ((el - c->rcurr) > 1 && *(el - 1) == '\r') {
el--;
}
*el = '\0';//将c->rcurr="set key 0 0 5\r\nhello\r\n"后面的\r\nhello\r\n去掉,即c->rcurr=set key 0 0 5.其中c->rbuf中后面的\r\nhello\r\n也去掉了
process_command(c, c->rcurr);//将rcurr中的数据分段存入tokens中
{
token_t tokens[MAX_TOKENS];
size_t ntokens;
add_msghdr(c)//确保还有空间用来存储回复给客户端的信息
ntokens = tokenize_command(command, tokens, MAX_TOKENS);
//解析命令,将命令分段存在tokens中,解析完成后//toknes[0]=”set”,toknes[1]=”key”,toknes[2]=”0”,toknes[3]=”0”
//toknes[4]=”5”,ntokens=6
(strcmp(tokens[COMMAND_TOKEN].value, "set") == 0 && (comm = NREAD_SET))
process_update_command(c, tokens, ntokens, comm, false);//调用该函数
{
key = tokens[KEY_TOKEN].value; //即key
nkey = tokens[KEY_TOKEN].length;
it = item_alloc(key, nkey, flags, realtime(exptime), vlen);//分配一个item空间
ITEM_set_cas(it, req_cas_id);//设置cas
c->item = it; //
c->ritem = ITEM_data(it); //item的value缓存,存储了item的value值 //什么时候将value存进去的?这里是获取该value的位置?
c->rlbytes = it->nbytes;
c->cmd = comm;
conn_set_state(c, conn_nread);//将状态设置为 conn_nread
}
}
c->rbytes -= (cont - c->rcurr);//此处为7也就是“hello\r\n”的大小
c->rcurr = cont;//c->rcurr=cont="hello\r\n"
}
然后再调用drive_machine(conn *c)//c->state=conn_nread
{
case conn_nread:
if (c->rlbytes == 0) {//此时c->rlbytes=7.c->rbytes=7
complete_nread(c);
break;
}//如果不等于0则表示还要接收客户数据或者value值还未读取存入,本次为读取存入value值到c->ritem也即ITEM_data(it)中
if (c->rbytes > 0) {
int tocopy = c->rbytes > c->rlbytes ? c->rlbytes : c->rbytes;
if (c->ritem != c->rcurr) {
memmove(c->ritem, c->rcurr, tocopy);//将value拷贝至c->ritem
}
c->ritem += tocopy;
c->rlbytes -= tocopy;
c->rcurr += tocopy;
c->rbytes -= tocopy;
if (c->rlbytes == 0) {
break;//终端后经过回调继续来到这里,进入complete_nread(c);
}
res = read(c->sfd, c->ritem, c->rlbytes);//此处为读取后面未接受完的数据,本次不运行至这里.
}
}
complete_nread(c):
{
complete_nread_ascii(c);
{
item *it = c->item; //存储数据的item的位置即key
int comm = c->cmd; //即set
ret = store_item(it, comm, c);
//即调用do_store_item(item, comm, c);本次操作中调用do_item_link(it);其中将该item进行存储,加入到hashtable中
switch (ret) {
case STORED:
out_string(c, "STORED");////如果不需要回复给客户端则将c->state=conn_new_cmd.进行返回监听新的命令
{
if (c->noreply)
conn_set_state(c, conn_new_cmd);
return;
}
add_msghdr(c);//msg增加头部
memcpy(c->wbuf, str, len);
//c->wbuf是输出到客户端的数据,例如set ...则c->wbuf="STORE\r\n"
memcpy(c->wbuf + len, "\r\n", 2);
c->wbytes = len + 2;
c->wcurr = c->wbuf;
conn_set_state(c, conn_write);
//然后将状态转换成conn_write
c->write_and_go = conn_new_cmd;
//结束conn_write后则进入 conn_new_cmd继续监听命令
return;
}
break;
}
}
static void drive_machine(conn *c){//c->state=conn_write
case conn_write:
add_iov(c, c->wcurr, c->wbytes)//将数据写入msg中
{
.....
m = &c->msglist[c->msgused - 1]; m->msg_iov[m->msg_iovlen].iov_base = (void *)buf;
/ /将STORE/r/n存储准备发送
m->msg_iov[m->msg_iovlen].iov_len = len;
c->msgbytes += len;
c->iovused++;
m->msg_iovlen++;
......
}//返回成功购没有进行break,接着进入conn_mwrite
case conn_mwrite:
switch (transmit(c))
/进行传输 res = sendmsg(c->sfd, m, 0);//发送信息
//此时客户端会接受到信息STORE/r/n
else if (c->state == conn_write) {
....
conn_set_state(c, c->write_and_go);
//接着转换该连接状态。本次为conn_new_cmd,就行监听客户端的下一个命令
}
以上就是从客户端开始连接到发送set key 0 0 5\r\nhello\r\n 命令的全过程
下面来对上面的c->state进行总结:
首先服务器监听自身 fd,主线程的c->state一直为conn_listening,
当客户端进行连接后,分发客户端sfd到一个线程事件中,该c->state=conn_new_cmd.
然后将c->state=conn_waiting进行等待客户端发送命令。
接受到命令后.c->state=conn_read进行读取命令到rbuf中。
然后c->state=conn_parse_cmd开始进行解析命令,将key存放到c->item中,c->ritem 指向value的存放地址,c->cmd存放命令set.
接着c->state=conn_nread,将item和value进行存储.
将发送给客户端的数据存储到msg中,c->state=conn_write.c->write_and_go = conn_new_cmd,
接着将数据STORE\r\n发送给客户.最后c->state=c->write_and_go = conn_new_cmd.
进入下一个循环。
- Memcached--set 过程跟踪解析
- Zend Framework1.9 启动过程跟踪解析
- 解析memcached
- 跟踪文件set events
- memcached set过期时间
- memcached--存储命令--set
- memcached全面解析--memcached基础
- Oracle跟踪事件 -- set events
- Oracle跟踪事件 -- set events
- Oracle 跟踪事件 set event
- Oracle 跟踪事件 set event
- Oracle 跟踪事件 set event
- Oracle 跟踪事件 set event
- Oracle 跟踪事件 set event
- Oracle 跟踪事件 set event .
- Oracle 跟踪事件 set event
- Oracle 跟踪事件 set event
- oracle 跟踪事件 set event
- java struts2文件的上传和下载 可以通用
- 扒光TP
- Hbase FilterBase源码研究
- 如何在Openstack的控制节点使用命令手动创建一个虚拟机
- android library projects cannot be launched
- Memcached--set 过程跟踪解析
- Android应用开发之内存优化
- Spring配置文件的拆分
- gcc
- innerText
- ScrollView中嵌套ListView汇总
- Hbase RowFilter
- 输出满足条件n=a!+b!+c!的所有三位数
- 用链表解决约瑟夫(Josephus)问题