memcached的学习(10)

2018.6.14

讲解Memcached如何从客户端读取命令,并且解析命令,然后处理命令并且向客户端回应消息。其中,Memcached是通过sendmsg函数向客户端发送数据的,这里我们具体分析Memcached回应消息的技术细节。
在这里插入图片描述
右边对应了响应客户端需要准备好发送的数据结构,值得注意的是每次发送的是一个msghrd的结构体指向的缓存空间。这是一种多缓冲技术,也就是和之前我们说的发送缓冲区是不一样的概念。

传统的发送缓冲区,就是一个缓冲区,一次性将缓冲区的数据都发送出去,一般需要预设置一段大的buff,这样的效率很低,有可能缓冲区也会满,导致数据的丢失。所以unix提出了一种新的iovec多缓冲区的技术,具体如下:

I/O vector,与readv和wirtev操作相关的结构体。readv和writev函数用于在一次函数调用中读、写多个非连续缓冲区。有时也将这两个函数称为散布读(scatter read)和聚集写(gather write)。

顾名思义,就是可以指定多个缓冲区进行一次性发送,这样的好处是,解放了缓冲区预设的限制,同时减少了系统的调用。

和以前方法比的优缺点:

通常的情况下,程序可能会在多个地方产生不同的buffer,如 nginx,第一个phase里都可能会产生buffer,放进一个chain里,

如果对每个buffer调用一次send,系统调用的个数将直接等于buffer的个数,对于多buffer的情况会很糟。

可能大家会想到重新分配一个大的buffer, 再把数据全部填充进去,这样其实只用了一次系统调用了。又或者在一开始就预先分配一块足够大的内存。

这两种情况是能满足要求,不过都不足取,前一种会浪费内存,后一种方法对phase的独立性有影响。
那么在memcached中是如何利用这种多缓冲技术的呢?

Memcached消息回应源码分析

数据结构

我们继续看一下conn这个结构。conn结构我们上一期说过,主要是存储单个客户端的连接详情信息。每一个客户端连接到Memcached都会有这么一个数据结构。

  1. typedef struct conn conn;  
  2. struct conn {  
  3.     //....  
  4.     /* data for the mwrite state */  
  5.     //iov主要存储iov的数据结构  
  6.     //iov数据结构会在conn_new中初始化,初始化的时候,系统会分配400个iovec的结构,最高水位600个  
  7.     struct iovec *iov;  
  8.     //iov的长度  
  9.     int    iovsize;   /* number of elements allocated in iov[] */  
  10.     //iovused 这个主要记录iov使用了多少  
  11.     int    iovused;   /* number of elements used in iov[] */  
  12.   
  13.     //msglist主要存储msghdr的列表数据结构  
  14.     //msglist数据结构在conn_new中初始化的时候,系统会分配10个结构  
  15.     struct msghdr *msglist;  
  16.     //msglist的长度,初始化为10个,最高水位100,不够用的时候会realloc,每次扩容都会扩容一倍  
  17.     int    msgsize;   /* number of elements allocated in msglist[] */  
  18.     //msglist已经使用的长度  
  19.     int    msgused;   /* number of elements used in msglist[] */  
  20.     //这个参数主要帮助记录那些msglist已经发送过了,哪些没有发送过。  
  21.     int    msgcurr;   /* element in msglist[] being transmitted now */  
  22.     int    msgbytes;  /* number of bytes in current msg */  
  23. }  

我们可以看一下conn_new这个方法,这个方法应该在第一章节的时候讲到过。这边主要看一下iov和msglist两个参数初始化的过程。

  1. conn *conn_new(const int sfd, enum conn_states init_state,  
  2.         const int event_flags, const int read_buffer_size,  
  3.         enum network_transport transport, struct event_base *base) {  
  4. //...  
  5.         c->rbuf = c->wbuf = 0;  
  6.         c->ilist = 0;  
  7.         c->suffixlist = 0;  
  8.         c->iov = 0;  
  9.         c->msglist = 0;  
  10.         c->hdrbuf = 0;  
  11.   
  12.         c->rsize = read_buffer_size;  
  13.         c->wsize = DATA_BUFFER_SIZE;  
  14.         c->isize = ITEM_LIST_INITIAL;  
  15.         c->suffixsize = SUFFIX_LIST_INITIAL;  
  16.         c->iovsize = IOV_LIST_INITIAL; //初始化400  
  17.         c->msgsize = MSG_LIST_INITIAL; //初始化10  
  18.         c->hdrsize = 0;  
  19.   
  20.         c->rbuf = (char *) malloc((size_t) c->rsize);  
  21.         c->wbuf = (char *) malloc((size_t) c->wsize);  
  22.         c->ilist = (item **) malloc(sizeof(item *) * c->isize);  
  23.         c->suffixlist = (char **) malloc(sizeof(char *) * c->suffixsize);  
  24.         c->iov = (struct iovec *) malloc(sizeof(struct iovec) * c->iovsize); //初始化iov  
  25.         c->msglist = (struct msghdr *) malloc(  
  26.                 sizeof(struct msghdr) * c->msgsize); //初始化msglist  
  27. //...  
  28. }  

数据结构关系图(iov和msglist之间的关系):
在这里插入图片描述
从图上看,memcached每次发送给客户端都是一个msglist中的msghdr元素,这个元素中包含了不同缓冲区的数据,例如c->iov中的数据1 2 3 4,注意这里1 2 3 4不是连续的数据,指向各个数据所在位置的指针。由于一个命令可以得到多个返回数据,所以一个命令可能携带有多个iov,所有一个命令的全部返回结果可能包含多个msghdr。

memcached发送客户端的消息响应的流程如下:
在这里插入图片描述

思考与分析:

这里我们可以学习到的是,memcached利用的多缓冲技术,其实发送缓冲区的数据很简单,但是对于这种多数据的存取,高并发的存取,使用传统的send不停的调用,或者是内存的拷贝显然是会影响效率,memcached采用的这种技术很值得学习。

猜你喜欢

转载自blog.csdn.net/u012414189/article/details/84317322