【Redis學習筆記】2018-05-30 Redis源碼學習之Ziplist、Server

做者:施洪寶 順風車運營研發團隊
一. 壓縮列表
壓縮列表是Redis的關鍵數據結構之一。目前已經有大量的相關資料,下面幾個連接都已經對Ziplist進行了詳細的介紹。html

http://origin.redisbook.com/c...
https://segmentfault.com/a/11...
本文只對其進行整體上的介紹,更多詳細內容能夠參考上面3個連接以及Redis源碼。redis

  1. Ziplist的總體結構

Ziplist總體結構圖以下:編程

clipboard.png

上圖中,每一個域的具體功能爲:segmentfault

clipboard.png

2.節點結構
Ziplist的節點結構以下:api

clipboard.png

(1) pre_entry_length數組

pre_entry_length字段記錄了上一個節點的長度,經過這個值,咱們能夠很容易的從當前節點跳轉到上一節點。例如:服務器

clipboard.png

根據編碼方式的不一樣,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的內容爲整形
具體的規則以下:

clipboard.png

(3) content

content爲所存儲的數據,其類型和長度由encoding 和 length 決定。

(4) Example

clipboard.png

3.基本操做
數據結構的基本操做有:增長節點、刪除節點、查找節點等。因爲Ziplist在內存上是連續存儲的,故而在特定位置插入或者刪除操做的複雜度較高。Ziplist定義了下面的幾種操做:

clipboard.png

值得一提的是,在特定位置插入或者刪除時,程序須要進行一種稱之爲連鎖更新的操做以維持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)。

  1. Redis

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函數),等待事件發生,處理事件。

相關文章
相關標籤/搜索