日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線(xiàn)溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
memcached源碼閱讀筆記

閱讀 memcached ***有 libevent 基礎(chǔ),memcached 是基于libevent 構(gòu)建起來(lái)的。通由 libevent 提供的事件驅(qū)動(dòng)機(jī)制觸發(fā) memcached 中的 IO 事件。

個(gè)人認(rèn)為,閱讀源碼的起初最忌鉆牛角尖,如頭文件里天花亂墜的結(jié)構(gòu)體到底有什么用。源文件里稀里嘩啦的函數(shù)是做什么的。剛開(kāi)始并沒(méi)必要事無(wú)巨細(xì)弄清楚頭文件每個(gè)類(lèi)型定義的具體用途;很可能那些是不緊要的工具函數(shù),知道他的功能和用法就沒(méi)他事了。

來(lái)看 memcached 內(nèi)部做了什么事情。memcached 是用 c 語(yǔ)言實(shí)現(xiàn),必須有一個(gè)入口函數(shù)main(),memcached 的生命從這里開(kāi)始。

初始化過(guò)程

建立并初始化 main_base,即主線(xiàn)程的事件中心,這是 libevent 里面的概念,可以把它理解為事件分發(fā)中心。

建立并初始化 memcached 內(nèi)部容器數(shù)據(jù)結(jié)構(gòu)。

建立并初始化空閑連接結(jié)構(gòu)體數(shù)組。

建立并初始化線(xiàn)程結(jié)構(gòu)數(shù)組,指定每個(gè)線(xiàn)程的入口函數(shù)是worker_libevent(),并創(chuàng)建工作線(xiàn)程。從worder_libevent()的實(shí)現(xiàn)來(lái)看,工作線(xiàn)程都會(huì)調(diào)用event_base_loop()進(jìn)入自己的事件循環(huán)。

根據(jù) memcached 配置,開(kāi)啟以下兩種服務(wù)模式中的一種:

以 UNIX 域套接字的方式接受客戶(hù)的請(qǐng)求
以 TCP/UDP 套接字的方式接受客戶(hù)的請(qǐng)求
memcached 有可配置的兩種模式: UNIX 域套接字和 TCP/UDP,允許客戶(hù)端以?xún)煞N方式向 memcached 發(fā)起請(qǐng)求??蛻?hù)端和服務(wù)器在同一個(gè)主機(jī)上的情況下可以用 UNIX 域套接字,否則可以采用 TCP/UDP 的模式。兩種模式是不兼容的。特別的,如果是 UNIX 域套接字或者 TCP 模式,需要建立監(jiān)聽(tīng)套接字,并在事件中心注冊(cè)了讀事件,回調(diào)函數(shù)是event_handler(),我們會(huì)看到所有的連接都會(huì)被注冊(cè)回調(diào)函數(shù)是 event_handler()。

調(diào)用event_base_loop()開(kāi)啟 libevent 的事件循環(huán)。到此,memcached 服務(wù)器的工作正式進(jìn)入了工作。如果遇到致命錯(cuò)誤或者客戶(hù)明令結(jié)束 memcached,那么才會(huì)進(jìn)入接下來(lái)的清理工作。

UNIX 域套接字和 UDP/TCP 工作模式

在初始化過(guò)程中介紹了這兩種模式,memcached 這么做為的是讓其能更加可配置。TCP/UDP 自不用說(shuō),UNIX 域套接字有獨(dú)特的優(yōu)勢(shì):

在同一臺(tái)主機(jī)上進(jìn)行通信時(shí),是不同主機(jī)間通信的兩倍
UNIX 域套接口可以在同一臺(tái)主機(jī)上,不同進(jìn)程之間傳遞套接字描述符
UNIX 域套接字可以向服務(wù)器提供客戶(hù)的憑證(用戶(hù)id或者用戶(hù)組id)
其他關(guān)于 UNIX 域套接字優(yōu)缺點(diǎn)的請(qǐng)參看: https://pangea.stanford.edu/computing/UNIX/overview/advantages.php

工作線(xiàn)程管理和線(xiàn)程調(diào)配方式

在thread_init(),setup_thread()函數(shù)的實(shí)現(xiàn)中,memcached 的意圖是很清楚的。每個(gè)線(xiàn)程都有自己獨(dú)有的連接隊(duì)列,即 CQ,注意這個(gè)連接隊(duì)列中的對(duì)象并不是一個(gè)或者多個(gè) memcached 命令,它對(duì)應(yīng)一個(gè)客戶(hù)! 一旦一個(gè)客戶(hù)交給了一個(gè)線(xiàn)程,它的余生就屬于這個(gè)線(xiàn)程了! 線(xiàn)程只要被喚醒就立即進(jìn)入工作狀態(tài),將自己 CQ 隊(duì)列的任務(wù)所有完完成。當(dāng)然,每一個(gè)工作線(xiàn)程都有自己的 libevent 事件中心。

很關(guān)鍵的線(xiàn)索是thread_init()的實(shí)現(xiàn)中,每個(gè)工作線(xiàn)程都創(chuàng)建了讀寫(xiě)管道,所能給我們的提示是: 只要利用 libevent 在工作線(xiàn)程的事件中心注冊(cè)讀管道的讀事件,就可以按需喚醒線(xiàn)程,完成工作,很有意思,而setup_thread()的工作正是讀管道的讀事件被注冊(cè)到線(xiàn) 程的事件中心,回調(diào)函數(shù)是thread_libevent_process().thread_libevent_process()的工作就是從工作線(xiàn) 程自己的 CQ 隊(duì)列中取出任務(wù)執(zhí)行,而往工作線(xiàn)程工作隊(duì)列中添加任務(wù)的是dispatch_conn_new(),此函數(shù)一般由主線(xiàn)程調(diào)用。下面是主線(xiàn)程和工作線(xiàn)程的工 作流程:

前幾天在微博上,看到 @高端小混混 的微博,轉(zhuǎn)發(fā)了:

“多任務(wù)并行處理的兩種方式,一種是將所有的任務(wù)用隊(duì)列存儲(chǔ)起來(lái),每個(gè)工作者依次去 拿一個(gè)來(lái)處理,直到做完所有的>任務(wù)為止。另一種是將任務(wù)平均分給工作者,先做完任務(wù)的工作者就去別的工作者那里拿一些任務(wù)來(lái)做,同樣直到所有任務(wù) 做完為止。兩種方式的結(jié)果如何?根據(jù)自己的場(chǎng)景寫(xiě)碼驗(yàn)證?!?/p>

memcached 所采用的模式就是這里所說(shuō)的第二種! memcached 的線(xiàn)程分配模式是:一個(gè)主線(xiàn)程和多個(gè)工作線(xiàn)程。主線(xiàn)程負(fù)責(zé)初始化和將接收的請(qǐng)求分派給工作線(xiàn)程,工作線(xiàn)程負(fù)責(zé)接收客戶(hù)的命令請(qǐng)求和回復(fù)客戶(hù)。

#p#

存儲(chǔ)容器

memcached 是做緩存用的,內(nèi)部肯定有一個(gè)容器?;氐絤ain()中,調(diào)用assoc_init()初始化了容器–hashtable,采用頭插法插入新數(shù)據(jù),因?yàn)轭^ 插法是最快的。memcached 只做了一級(jí)的索引,即 hash; 接下來(lái)的就靠 memcmp() 在鏈表中找數(shù)據(jù)所在的位置。memcached 容器管理的接口主要在 item.h .c 中.

 連接管理

每個(gè)連接都會(huì)建立一個(gè)連接結(jié)構(gòu)體與之對(duì)應(yīng)。main()中會(huì)調(diào)用conn_init()建立連接結(jié)構(gòu)體數(shù)組。連接結(jié)構(gòu)體 struct conn 記錄了連接套接字,讀取的數(shù)據(jù),將要寫(xiě)入的數(shù)據(jù),libevent event 結(jié)構(gòu)體以及所屬的線(xiàn)程信息。

當(dāng)有新的連接時(shí),主線(xiàn)程會(huì)被喚醒,主線(xiàn)程選定一個(gè)工作線(xiàn)程 thread0,在 thread0 的寫(xiě)管道中寫(xiě)入數(shù)據(jù),特別的如果是接受新的連接而不是接受新的數(shù)據(jù),寫(xiě)入管道的數(shù)據(jù)是字符 ‘c’。工作線(xiàn)程因管道中有數(shù)據(jù)可讀被喚醒,thread_libevent_process()被調(diào)用,新連接套接字被注冊(cè)了 event_handler()回調(diào)函數(shù),這些工作在conn_new()中完成。因此,客戶(hù)端有命令請(qǐng)求的時(shí)候(譬如發(fā)起 get key 命令),工作線(xiàn)程都會(huì)被觸發(fā)調(diào)用event_handler()。

當(dāng)出現(xiàn)致命錯(cuò)誤或者客戶(hù)命令結(jié)束服務(wù)(quit 命令),關(guān)于此連接的結(jié)構(gòu)體內(nèi)部的數(shù)據(jù)會(huì)被釋放(譬如曾經(jīng)讀取的數(shù)據(jù)),但結(jié)構(gòu)體本身不釋放,等待下一次使用。如果有需要,連接結(jié)構(gòu)體數(shù)組會(huì)指數(shù)自增。

一個(gè)請(qǐng)求的工作流程

memcached 服務(wù)一個(gè)客戶(hù)的時(shí)候,是怎么一個(gè)過(guò)程,試著去調(diào)試模擬一下。當(dāng)一個(gè)客戶(hù)向 memcached 發(fā)起請(qǐng)求時(shí),主線(xiàn)程會(huì)被喚醒,接受請(qǐng)求。接下來(lái)的工作在連接管理中有說(shuō)到。

客戶(hù)已經(jīng)與 memcached 服務(wù)器建立了連接,客戶(hù)在終端(黑框框)敲擊 get key + 回車(chē)鍵,一個(gè)請(qǐng)求包就發(fā)出去了。從連接管理中已經(jīng)了解到所有連接套接字都會(huì)被注冊(cè)回調(diào)函數(shù)為event_handler(),因此 event_handler()會(huì)被觸發(fā)調(diào)用。

 
 
 
 
  1. void event_handler(const int fd,const short which,void *arg) { 
  2.     conn *c; 
  3.   
  4.     c = (conn *)arg; 
  5.     assert(c != NULL); 
  6.   
  7.     c->whichwhich = which; 
  8.   
  9.     /* sanity */ 
  10.     if (fd != c->sfd) { 
  11.         if (settings.verbose > 0) 
  12.             fprintf(stderr,"Catastrophic: event fd doesn't match conn fd!\n"); 
  13.         conn_close(c); 
  14.         return; 
  15.     } 
  16.   
  17.     drive_machine(c); 
  18.   
  19.     /* wait for next event */ 
  20.     return; 

event_handler()調(diào)用了drive_machine().drive_machine()是請(qǐng)求處理的開(kāi)端,特別的當(dāng)有新的連接 時(shí),listen socket 也是有請(qǐng)求的,所以建立新的連接也會(huì)調(diào)用drive_machine(),這在連接管理有提到過(guò)。下面是drive_machine()函數(shù)的骨架:

 
 
 
 
  1. // 請(qǐng)求的開(kāi)端。當(dāng)有新的連接的時(shí)候 event_handler() 會(huì)調(diào)用此函數(shù)。 
  2. static void drive_machine(conn *c) { 
  3.     bool stop = false; 
  4.     int sfd,flags = 1; 
  5.     socklen_t addrlen; 
  6.     struct sockaddr_storage addr; 
  7.     int nreqs = settings.reqs_per_event; 
  8.     int res; 
  9.     const char *str; 
  10.   
  11.     assert(c != NULL); 
  12.   
  13.     while (!stop) { 
  14.         // while 能保證一個(gè)命令被執(zhí)行完成或者異常中斷(譬如 IO 操作次數(shù)超出了一定的限制) 
  15.   
  16.         switch(c->state) { 
  17.         // 正在連接,還沒(méi)有 accept 
  18.         case conn_listening: 
  19.   
  20.         // 等待新的命令請(qǐng)求 
  21.         case conn_waiting: 
  22.   
  23.         // 讀取數(shù)據(jù) 
  24.         case conn_read: 
  25.   
  26.         // 嘗試解析命令 
  27.         case conn_parse_cmd : 
  28.   
  29.         // 新的命令請(qǐng)求,只是負(fù)責(zé)轉(zhuǎn)變 conn 的狀態(tài) 
  30.         case conn_new_cmd: 
  31.   
  32.         // 真正執(zhí)行命令的地方 
  33.         case conn_nread: 
  34.   
  35.         // 讀取所有的數(shù)據(jù),拋棄!!! 一般出錯(cuò)的情況下會(huì)轉(zhuǎn)換到此狀態(tài) 
  36.         case conn_swallow: 
  37.   
  38.         // 數(shù)據(jù)回復(fù) 
  39.         case conn_write: 
  40.   
  41.         case conn_mwrite: 
  42.   
  43.         // 連接結(jié)束。一般出錯(cuò)或者客戶(hù)顯示結(jié)束服務(wù)的情況下回轉(zhuǎn)換到此狀態(tài) 
  44.         case conn_closing: 
  45.         } 
  46.     } 
  47.     return; 

通過(guò)修改連接結(jié)構(gòu)體狀態(tài) struct conn.state 執(zhí)行相應(yīng)的操作,從而完成一個(gè)請(qǐng)求,完成后 stop 會(huì)被設(shè)置為 true,一個(gè)命令只有執(zhí)行結(jié)束(無(wú)論結(jié)果如何)才會(huì)跳出這個(gè)循環(huán)。我們看到 struct conn 有好多種狀態(tài),一個(gè)正常執(zhí)行的命令狀態(tài)的轉(zhuǎn)換是:

 
 
 
 
  1. conn_new_cmd->conn_waiting->conn_read->conn_parse_cmd->conn_nread->conn_mwrite->conn_close 

這個(gè)過(guò)程任何一個(gè)環(huán)節(jié)出了問(wèn)題都會(huì)導(dǎo)致?tīng)顟B(tài)轉(zhuǎn)變?yōu)?conn_close。帶著剛開(kāi)始的問(wèn)題把從客戶(hù)連接到一個(gè)命令執(zhí)行結(jié)束的過(guò)程是怎么樣的:

客戶(hù)connect()后,memcached 服務(wù)器主線(xiàn)程被喚醒,接下來(lái)的調(diào)用鏈?zhǔn)莈vent_handler()->drive_machine()被調(diào)用,此時(shí)主線(xiàn)程對(duì)應(yīng) conn 狀態(tài)為 conn_listining,接受請(qǐng)求

 
 
 
 
  1. dispatch_conn_new(sfd,conn_new_cmd,EV_READ | EV_PERSIST,DATA_BUFFER_SIZE,tcp_transport); 

dispatch_conn_new()的工作是往工作線(xiàn)程工作隊(duì)列中添加任務(wù)(前面已經(jīng)提到過(guò)),所以其中一個(gè)沉睡的工作線(xiàn)程會(huì)被喚醒,thread_libevent_process()會(huì)被工作線(xiàn)程調(diào)用,注意這些機(jī)制都是由 libevent 提供的。

thread_libevent_process()調(diào)用conn_new()新建 struct conn 結(jié)構(gòu)體,且狀態(tài)為 conn_new_cmd,其對(duì)應(yīng)的就是剛才accept()的連接套接字.conn_new()最關(guān)鍵的任務(wù)是將剛才接受的套接字在 libevent 中注冊(cè)一個(gè)事件,回調(diào)函數(shù)是event_handler()。循環(huán)繼續(xù),狀態(tài) conn_new_cmd 下的操作只是只是將 conn 的狀態(tài)轉(zhuǎn)換為 conn_waiting;

循環(huán)繼續(xù),conn_waiting 狀態(tài)下的操作只是將 conn 狀態(tài)轉(zhuǎn)換為 conn_read,循環(huán)退出。

此后,如果客戶(hù)端不請(qǐng)求服務(wù),那么主線(xiàn)程和工作線(xiàn)程都會(huì)沉睡,注意這些機(jī)制都是由 libevent 提供的。

客戶(hù)敲擊命令「get key」后,工作線(xiàn)程會(huì)被喚醒,event_handler()被調(diào)用了??? 又被調(diào)用了.event_handler()->drive_machine(),此時(shí) conn 的狀態(tài)為 conn_read。conn_read 下的操作就是讀數(shù)據(jù)了,如果讀取成功,conn 狀態(tài)被轉(zhuǎn)換為 conn_parse_cmd。

循環(huán)繼續(xù),conn_parse_cmd 狀態(tài)下的操作就是嘗試解析命令: 可能是較為簡(jiǎn)單的命令,就直接回復(fù),狀態(tài)轉(zhuǎn)換為 conn_close,循環(huán)接下去就結(jié)束了; 涉及存取操作的請(qǐng)求會(huì)導(dǎo)致 conn_parse_cmd 狀態(tài)轉(zhuǎn)換為 conn_nread。

循環(huán)繼續(xù),conn_nread 狀態(tài)下的操作是真正執(zhí)行存取命令的地方。里面的操作無(wú)非是在內(nèi)存尋找數(shù)據(jù)項(xiàng),返回?cái)?shù)據(jù)。所以接下來(lái)的狀態(tài) conn_mwrite,它的操作是為客戶(hù)端回復(fù)數(shù)據(jù)。

狀態(tài)又回到了 conn_new_cmd 迎接新的請(qǐng)求,直到客戶(hù)命令結(jié)束服務(wù)或者發(fā)生致命錯(cuò)誤。大概就是這么個(gè)過(guò)程。

#p#

memcached 的分布式

memcached 的服務(wù)器沒(méi)有向其他 memcached 服務(wù)器收發(fā)數(shù)據(jù)的功能,意即就算部署多個(gè) memcached 服務(wù)器,他們之間也沒(méi)有任何的通信。memcached 所謂的分布式部署也是并非平時(shí)所說(shuō)的分布式。所說(shuō)的「分布式」是通過(guò)創(chuàng)建多個(gè) memcached 服務(wù)器節(jié)點(diǎn),在客戶(hù)端添加緩存請(qǐng)求分發(fā)器來(lái)實(shí)現(xiàn)的。memcached 的更多的時(shí)候限制是來(lái)自網(wǎng)絡(luò) I/O,所以應(yīng)該盡量減少網(wǎng)絡(luò) I/O。

我在 github 上分享了 memcached 的源碼剖析注釋: 這里


本文題目:memcached源碼閱讀筆記
本文來(lái)源:http://www.5511xx.com/article/cddjhoo.html