1. 程式人生 > >memcached的學習(10)

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採用的這種技術很值得學習。