自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

memcached源碼閱讀筆記

開發(fā) 項目管理
閱讀memcached最好有l(wèi)ibevent 基礎(chǔ),memcached是基于libevent 構(gòu)建起來的。通由libevent 提供的事件驅(qū)動機制觸發(fā) memcached中的IO事件。

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

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

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

初始化過程

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

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

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

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

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

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

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

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

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

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

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

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

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

120131223103045

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

“多任務(wù)并行處理的兩種方式,一種是將所有的任務(wù)用隊列存儲起來,每個工作者依次去 拿一個來處理,直到做完所有的>任務(wù)為止。另一種是將任務(wù)平均分給工作者,先做完任務(wù)的工作者就去別的工作者那里拿一些任務(wù)來做,同樣直到所有任務(wù) 做完為止。兩種方式的結(jié)果如何?根據(jù)自己的場景寫碼驗證。”

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

#p#

存儲容器

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

220131223103240

 連接管理

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

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

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

一個請求的工作流程

memcached 服務(wù)一個客戶的時候,是怎么一個過程,試著去調(diào)試模擬一下。當一個客戶向 memcached 發(fā)起請求時,主線程會被喚醒,接受請求。接下來的工作在連接管理中有說到。

客戶已經(jīng)與 memcached 服務(wù)器建立了連接,客戶在終端(黑框框)敲擊 get key + 回車鍵,一個請求包就發(fā)出去了。從連接管理中已經(jīng)了解到所有連接套接字都會被注冊回調(diào)函數(shù)為event_handler(),因此 event_handler()會被觸發(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()是請求處理的開端,特別的當有新的連接 時,listen socket 也是有請求的,所以建立新的連接也會調(diào)用drive_machine(),這在連接管理有提到過。下面是drive_machine()函數(shù)的骨架:

  1. // 請求的開端。當有新的連接的時候 event_handler() 會調(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 能保證一個命令被執(zhí)行完成或者異常中斷(譬如 IO 操作次數(shù)超出了一定的限制) 
  15.   
  16.         switch(c->state) { 
  17.         // 正在連接,還沒有 accept 
  18.         case conn_listening: 
  19.   
  20.         // 等待新的命令請求 
  21.         case conn_waiting: 
  22.   
  23.         // 讀取數(shù)據(jù) 
  24.         case conn_read: 
  25.   
  26.         // 嘗試解析命令 
  27.         case conn_parse_cmd : 
  28.   
  29.         // 新的命令請求,只是負責轉(zhuǎn)變 conn 的狀態(tài) 
  30.         case conn_new_cmd: 
  31.   
  32.         // 真正執(zhí)行命令的地方 
  33.         case conn_nread: 
  34.   
  35.         // 讀取所有的數(shù)據(jù),拋棄!!! 一般出錯的情況下會轉(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é)束。一般出錯或者客戶顯示結(jié)束服務(wù)的情況下回轉(zhuǎn)換到此狀態(tài) 
  44.         case conn_closing: 
  45.         } 
  46.     } 
  47.     return

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

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

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

客戶connect()后,memcached 服務(wù)器主線程被喚醒,接下來的調(diào)用鏈是event_handler()->drive_machine()被調(diào)用,此時主線程對應(yīng) conn 狀態(tài)為 conn_listining,接受請求

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

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

thread_libevent_process()調(diào)用conn_new()新建 struct conn 結(jié)構(gòu)體,且狀態(tài)為 conn_new_cmd,其對應(yīng)的就是剛才accept()的連接套接字.conn_new()最關(guān)鍵的任務(wù)是將剛才接受的套接字在 libevent 中注冊一個事件,回調(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)退出。

此后,如果客戶端不請求服務(wù),那么主線程和工作線程都會沉睡,注意這些機制都是由 libevent 提供的。

客戶敲擊命令「get key」后,工作線程會被喚醒,event_handler()被調(diào)用了??? 又被調(diào)用了.event_handler()->drive_machine(),此時 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)下的操作就是嘗試解析命令: 可能是較為簡單的命令,就直接回復(fù),狀態(tài)轉(zhuǎn)換為 conn_close,循環(huán)接下去就結(jié)束了; 涉及存取操作的請求會導(dǎo)致 conn_parse_cmd 狀態(tài)轉(zhuǎn)換為 conn_nread。

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

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

#p#

memcached 的分布式

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

320131223103255

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

原文鏈接:https://github.com/daoluan/decode-memcached

譯文鏈接:http://blog.jobbole.com/53861/

責任編輯:陳四芳 來源: 伯樂在線
相關(guān)推薦

2014-07-03 15:40:09

Apache Spar

2021-11-22 16:12:34

Axios Axios-Retry前端

2021-11-19 07:54:59

Axios網(wǎng)絡(luò)源碼

2018-11-16 16:35:19

Java源碼編程語言

2022-10-08 08:01:17

Spring源碼服務(wù)

2012-02-14 14:05:59

JavaSpring

2017-04-05 16:40:45

2017-03-16 11:39:33

Openstack源碼姿勢

2018-03-28 16:10:23

閱讀源碼境界

2021-08-02 09:50:47

Vetur源碼SMART

2017-04-13 19:26:21

2014-07-29 09:44:58

jQuery源碼

2021-03-13 14:08:00

Hadoop 源碼HDFS

2021-12-20 07:58:59

GitHub源碼代碼

2018-12-18 13:54:57

MVPGoogleAndroid

2020-12-07 11:29:24

ReactVueVue3

2017-03-27 15:15:43

Hive源碼編譯

2014-12-30 11:04:39

懶人筆記

2021-01-06 05:45:58

Dubbo源碼高并發(fā)

2021-03-13 11:23:51

源碼邏輯框架
點贊
收藏

51CTO技術(shù)棧公眾號