做者:施洪寶 順風車運營研發團隊
一. 壓縮列表
壓縮列表是Redis的關鍵數據結構之一。目前已經有大量的相關資料,下面幾個連接都已經對Ziplist進行了詳細的介紹。html
http://origin.redisbook.com/c...
https://segmentfault.com/a/11...
本文只對其進行整體上的介紹,更多詳細內容能夠參考上面3個連接以及Redis源碼。redis
Ziplist總體結構圖以下:編程
上圖中,每一個域的具體功能爲:segmentfault
2.節點結構
Ziplist的節點結構以下:api
(1) pre_entry_length數組
pre_entry_length字段記錄了上一個節點的長度,經過這個值,咱們能夠很容易的從當前節點跳轉到上一節點。例如:服務器
根據編碼方式的不一樣,pre_entry_length可能佔用一個字節,也可能佔用5個字節。具體處理規則以下:網絡
若是前一個節點長度小於254,便使用1個字節記錄上一個節點長度。數據結構
若是前一個字節長度大於254,pre_entry_length便佔用5個字節,第一個字節寫入254,後面4個字節記錄上一個節點的具體長度。多線程
(2) enconding以及length
encoding 和 length 決定了content 保存的數據類型及長度,encodingz佔用2個bit,encoding以及length的總長度多是1個,2個或者5個字節。
00,01,10表示content的內容爲字符數組
11表示content的內容爲整形
具體的規則以下:
(3) content
content爲所存儲的數據,其類型和長度由encoding 和 length 決定。
(4) Example
3.基本操做
數據結構的基本操做有:增長節點、刪除節點、查找節點等。因爲Ziplist在內存上是連續存儲的,故而在特定位置插入或者刪除操做的複雜度較高。Ziplist定義了下面的幾種操做:
值得一提的是,在特定位置插入或者刪除時,程序須要進行一種稱之爲連鎖更新的操做以維持Ziplist結構的性質。以上操做的具體代碼實現,能夠參考Redis源碼,此處再也不贅述。
二. Server
這部分主要是從服務端,分析Redis響應客戶請求的原理。
1.基礎知識
Linux下有「一切皆文件」的思想。咱們能夠將socket、pipe、硬件等資源都看做文件,利用操做文件的方式對其進行操做。具體到網絡通訊中,服務端與客戶端的數據交換能夠看做是對文件的讀寫操做。
例如:服務器端首先監聽端口,具體實現就是新建一個監聽文件描述符,當這個監聽文件可讀時,就是有新的客戶端請求鏈接。
服務端在接受一個新的鏈接請求時,也會新建一個文件描述符,這個描述符即表明這個網絡鏈接。經過對這個文件的讀寫,便可實現與客戶端的通訊。
正常狀況下,一個服務端一般要同時服務多個客戶,服務端處理客戶請求的主邏輯以下:
while(1){ //等待網絡事件發生 //根據每一個事件,調用相關的回調函數。 }
注:客戶端向服務端發送數據時,網卡首先接收到數據,以後向操做系統提出中斷請求,操做系統將通知服務進程處理這些數據。
2.IO多路複用
《UNIX網絡編程》中提到,IO模型能夠分爲5種:同步阻塞、同步非阻塞、IO複用、信號驅動以及異步非阻塞。阻塞、非阻塞是針對調用方而言的。同步、異步是針對被調用方而言的。本節,咱們重點介紹IO多路複用。
IO複用:顧名思義,就是多個IO複用一個端口。常見的IO多路複用的方案有:select, poll, kqueue, epoll等。
(1) Select簡介
select 的函數原型以下:
int select(int maxfdAdd1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout); //maxfdAdd1, 所要監聽的最大文件描述符加1 //readset, writeset, exceptset, 須要監聽讀、寫、錯誤的文件描述符 fd_set 多是由數組,或者是由二進制位構成的數組。這個取決於操做系統的具體實現。 //timeout, 超時時間 struct timeval{ long tv_sec; //秒 long tv_usec; //微妙 };
函數返回值爲發生事件的FD總數。
select的過程能夠簡述以下:應用程序首先調用select函數,經過入口參數將所要監聽的文件描述符傳遞給操做系統,操做系統負責具體監聽這些文件描述符,當有事件發生時,通知進程處理。
(2) Epoll簡介
對於每一個文件描述符,epoll能夠設置2種事件觸發方式,水平觸發與邊沿觸發。
水平觸發:顧名思義,就是當FD有事件時,會一直通知進程。例如:當文件可讀時,操做系統告訴進程,文件可讀,進程處理以後,若是因爲時間緣由,沒法讀取所有數據。此時,操做系統會仍然告訴進程FD可讀。
邊沿觸發:所謂邊沿,就是指事物狀態發生改變。邊沿觸發,就是僅當FD狀態發生改變時,通知進程。若是操做系統通知進程以後,進程並無所有讀取數據,操做系統僅會在FD狀態發生改變時(例如:新數據到來),纔會通知進程去處理。
//函數原型 int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, strcut epoll_event *event); int epoll_wait(int epfd, struct epoll_event *event, int maxEvent, int timeout); //epfd自己會佔用一個文件描述符,不用時應將其關閉。 typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; //簡單的使用用例,限於篇幅,本部分只列出其中的關鍵語句。 int epfd = epoll_create(size); //調用epoll_ctl(epfd, op, fd, *events), 增長或者減小所要監聽的Fd while(1){ int num = epoll_wait(epfd, *events, maxEvent, timeout); //events 記錄全部發生事件的FD for(int i = 0; i < num; ++i){ //根據每一個FD,調用對應的事件處理函數 } }
Epoll和Select相比,效率更高,這主要因爲如下幾個緣由:
Select每次調用,都須要將全部文件描述符從用戶態複製到內核態,Epoll能夠經過epoll_ctl,每一個文件描述符只須要複製一次。
對select而言,操做系統須要遍歷全部文件,從而找出發生事件的文件描述符。操做系統爲每一個epoll維護了一個雙向鏈表,當某個文件可讀或者可寫時,經過回調事先設定的回調函數,將文件描述符寫入這個雙向鏈表。操做系統每次只須要查看這個鏈表,便可知道是否有事件發生。
Select返回時,須要程序遍歷全部監聽的文件描述符,從而找出發生事件的文件。Epoll能夠直接獲得發生事件的文件描述符(epoll_wait函數,結果會被寫入events)。
Redis 是做爲服務端向客戶提供服務的。網絡編程的基本模型有單進程單線程、單進程多線程、多進程單線程以及多進程多線程。Redis在提供服務時,是基於單進程單線程的模型,經過IO複用技術,實現高併發。Redis源碼4.0.9版,已經實現了select, kqueue, epoll以適應不一樣的需求(redis-4.0.9/src/ae_select.c, redis-4.0.9/src/ae_kqueue.c, redis-4.0.9/src/ae_epoll.c)。
(1) 關鍵數據結構
aeEventLoop爲Reids最重要的數據結構之一,表明服務進程的事件處理循環。
/* State of an event based program */ typedef struct aeEventLoop { int maxfd; /* highest file descriptor currently registered */ int setsize; /* max number of file descriptors tracked */ long long timeEventNextId; time_t lastTime; /* Used to detect system clock skew */ aeFileEvent *events; /* Registered events */ aeFiredEvent *fired; /* Fired events */ aeTimeEvent *timeEventHead; int stop; void *apidata; /* This is used for polling API specific data */ aeBeforeSleepProc *beforesleep; aeBeforeSleepProc *aftersleep; } aeEventLoop;
aeFileEvent封裝通常的文件事件
typedef struct aeFileEvent { int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */ aeFileProc *rfileProc; aeFileProc *wfileProc; void *clientData; } aeFileEvent;
aeTimeEvent用來封裝定時器事件
/* Time event structure */ typedef struct aeTimeEvent { long long id; /* time event identifier. */ long when_sec; /* seconds */ long when_ms; /* milliseconds */ aeTimeProc *timeProc; aeEventFinalizerProc *finalizerProc; void *clientData; struct aeTimeEvent *next; } aeTimeEvent;
記錄已經發生事件的文件描述符集合
/* A fired event */ typedef struct aeFiredEvent { int fd; int mask; } aeFiredEvent;
(2)Reis服務端簡要流程Reis服務啓動的入口爲Server.c中的main函數,在main函數中,會進行一些數據初始化、配置初始化、設置回調函數等工做。以後Redis會進入事件主循環(aeMain函數),等待事件發生,處理事件。