memcache的執行緒模型

myownstars發表於2015-02-01
MC採用一master多worker的工作模型,由master負責accept客戶端請求,然後以RR分發給worker;
-t 執行緒數,用於處理請求,預設為4 
-b backlog佇列長度,預設1024 

執行緒結構體
  1. typedef struct {  
  2.     pthread_t thread_id;        /* unique ID of this thread */  
  3.     struct event_base *base;    /* libevent handle this thread uses */  
  4.     struct event notify_event;  /* listen event for notify pipe */  
  5.     int notify_receive_fd;      /* receiving end of notify pipe */  
  6.     int notify_send_fd;         /* sending end of notify pipe */  
  7.     CQ  new_conn_queue;         /* queue of new connections to handle */  
  8. } LIBEVENT_THREAD;  
每個執行緒都包含一個CQ佇列,一條通知管道pipe 和一個libevent的例項event_base ;
當master接受新連線後,通過pipe通知worker;
無論是主執行緒還是workers執行緒全部通過libevent管理網路事件,實際上每個執行緒都是一個單獨的libevent例項 ;

啟動流程




程式碼實現
master呼叫accept等待客戶端連線(accept為非阻塞呼叫),返回的sfd也同樣被設定為非阻塞,之後分發給worker;
下面看看dispatch_conn_new內部是如何進行連結分發的。
  1. void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags,  
  2.                        int read_buffer_size, enum network_transport transport) {  
  3.     CQ_ITEM *item = cqi_new();//建立一個conn_item 
  4.     char buf[1];  
  5.     int tid = (last_thread + 1) % settings.num_threads;//通過round-robin演算法選擇一個執行緒 

  6.     LIBEVENT_THREAD *thread = threads + tid;//thread陣列儲存了所有的工作執行緒  

  7.     cq_push(thread->new_conn_queue, item);//投遞item資訊到Worker執行緒的工作佇列中  

  8.     buf[0] = 'c';  
  9.     //在Worker執行緒的notify_send_fd寫入字元c,表示有連線     
  10.     if (write(thread->notify_send_fd, buf, 1) != 1) {  
  11.         perror("Writing to thread notify pipe");  
  12.     } 

worker thread在管道描述符notify_send_fd的回撥函式為thread_libevent_process,故一旦有資料到達立刻執行
//子執行緒會在PIPE管道讀上面建立libevent事件,事件回撥函式是thread_libevent_process 
  1. static void thread_libevent_process(int fd, short which, void *arg) {  
  2.     LIBEVENT_THREAD *me = arg;  
  3.     CQ_ITEM *item;  
  4.     char buf[1];  
  5.   
  6.     if (read(fd, buf, 1) != 1)//PIPE管道讀取一個位元組的資料  
  7.         if (settings.verbose > 0)  
  8.             fprintf(stderr, "Can't read from libevent pipe\n");  
  9.   
  10.     switch (buf[0]) {  
  11.     case 'c'://如果是c,則處理網路連線  
  12.     item = cq_pop(me->new_conn_queue);//從連線佇列讀出Master執行緒投遞的訊息 \

  1.     if (NULL != item) {  
  2.         conn *c = conn_new(item->sfd, item->init_state, item->event_flags,  
  3.                            item->read_buffer_size, item->transport, me->base);//建立連線 

conn_new裡面會建立sfd的網路監聽libevent事件,事件回撥函式為event_handler,而event_handler的執行流程最終會進入到業務處理的狀態機中。
  1. conn *conn_new(const int sfd, enum conn_states init_state, const int event_flags,  
  2.         const int read_buffer_size, enum network_transport transport,  
  3.         struct event_base *base)  
  4. {  
  5.     conn *c = conn_from_freelist();//獲取一個空閒連線,conn是Memcached內部對網路連線的一個封裝  
  6.   
  7.     if (NULL == c)//如果沒有空閒的連線  
  8.     {  
  9.         if (!(c = (conn *) calloc(1, sizeof(conn))))//申請空間  
  10.         {  
  11.             fprintf(stderr, "calloc()\n");  
  12.             return NULL;  
  13.         }MEMCACHED_CONN_CREATE(c);

  1. //每個conn都自帶讀入和輸出緩衝區,在進行網路收發資料時,特別方便  
  2.         c->rbuf = (char *) malloc((size_t) c->rsize);  
  3.         c->wbuf = (char *) malloc((size_t) c->wsize);  
  4.         c->ilist = (item **) malloc(sizeof(item *) * c->isize);  

  1.  //建立sfd描述符上面的event事件,事件回撥函式為event_handler  
  2.     event_set(&c->event, sfd, event_flags, event_handler, (void *) c);  
  3.     event_base_set(base, &c->event);  
  4.     c->ev_flags = event_flags;  
  5.     if (event_add(&c->event, 0) == -1)  
  6.     {         
  7.        //如果建立libevent事件失敗,將建立的conn新增到空閒列表中  
  8.        if (conn_add_to_freelist(c))  
  9.         {  
  10.             conn_free(c);  
  11.         }  
  12.         perror("event_add");  
  13.         return NULL;  
  14.     }  

  1. //libevent事件回撥函式的處理,回撥函式被呼叫時,表明Memcached監聽的埠號有網路事件到了  
  2. void event_handler(const int fd, const short which, void *arg)  
  3. {  
  4. conn *c;  
  1. //進入業務處理狀態機  
  2. drive_machine(c);  
  3.   


執行緒通訊
master和worker通過連線佇列進行單向通訊,即Master接受到新的客戶端連線時,將sfd封裝到conn_queue_item並投送給woker,Worker從其連線佇列中讀取conn_queue_item同客戶端連線進行互動,詳情參見dispatch_conn_new;
  1. struct conn_queue_item {  
  2.     int               sfd;//accept之後的描述符  
  3.     enum conn_states  init_state;//連線的初始狀態  
  4.     int               event_flags;//libevent標誌  
  5.     int               read_buffer_size;//讀取資料緩衝區大小  
  6.     enum network_transport     transport;//內部通訊所用的協議  
  7.     CQ_ITEM          *next;//用於實現連結串列的指標  
  8. };  

  9. struct conn_queue {  
  10.     CQ_ITEM *head;//頭指標,注意這裡是單連結串列,不是雙向連結串列  
  11.     CQ_ITEM *tail;//尾部指標,  
  12.     pthread_mutex_t lock;//鎖  
  13.     pthread_cond_t  cond;//條件變數  
  14. };  

  15. //獲取一個連線  
  16. static CQ_ITEM *cq_pop(CQ *cq) {  
  17.     CQ_ITEM *item;  
  18.   
  19.     pthread_mutex_lock(&cq->lock);//執行加鎖操作  
  20.     item = cq->head;//獲得頭部指標指向的資料  
  21.     if (NULL != item) {  
  22.         cq->head = item->next;//更新頭指標資訊  
  23.         if (NULL == cq->head)//這裡為空的話,則尾指標也為空,連結串列此時為空  
  24.             cq->tail = NULL;  
  25.     }
  26.     pthread_mutex_unlock(&cq->lock);//釋放鎖操作  
  27.   
  28.     return item;  
  29. }  

參考連結 http://blog.csdn.net/column/details/lc-memcached.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/15480802/viewspace-1422565/,如需轉載,請註明出處,否則將追究法律責任。

相關文章