redis學習總結

來源:互聯網
上載者:User

Redis學習總結

redis是一個單線程基於事件機制的一個模型,使用事件處理架構 aeEvent

1.       啟動過程

Reids的啟動過程大致如下:

1.初始化全域struct server資料結構,給每個成員賦予預設值,並且建立命令表,用於尋找相應命令對應的處理函數:initServerConfig()--->populateCommandTable
2.如果指定了設定檔,會讀取redis.conf重新賦值給structserver執行個體:loadServerConfig()
3.初始化其它資料結構,建立db 對應dict資料結構等,bind,listen啟動網路服務:initServer()
該操作同時建立aeEventLoop結構,aeCreateTimeEvent添加定時器事件(serverCron),第一個定時器事件在1ms後被調用,aeCreateFileEvent添加IO事件
這個事件監聽的是伺服器的listen通訊端可讀事件,相應的事件處理函數為acceptTcpHandler
4.載入資料庫:appendfile 載入 OR rdbfile載入。這裡是如果有aof的話先載入,rdb就不進行,否則再判斷是否有rdb。即優先使用aof方式。
5.最後調用aeMain來真正的監聽網路通訊端,並進行事件響應

1.1   timeEven事件

在事件啟動後的下一毫秒調用 serverCron 函數,實際這個serverCron在執行完後會被重新設定加入調度事件中,以後每隔100 毫秒執行一次。
響應處理函數為:serverCron,定時器事件處理流程(該內容參考自http://www.w3ccollege.org/redis/redis-internal/understanding-redis-internal-the-main-structure-and-start-the-process.html)
1. 列印非空db的一些資訊,log功能
2. 如果當前沒有後台運行dump資料儲存到檔案的進程,則可以根據記憶體使用量情況重新調整hashTables大小
3. 日誌輸出一些client的串連資訊
4. 檢測idle的client 串連,關閉idle串連
5. 判斷是否有進行中dump資料到檔案的後台進程
如果有判斷是否已經結束,進行結束後的一些工作(這個跟replication有關,後續詳細說明)沒有則判斷是否需要啟動新的進程dump資料到檔案
6. 處理expired的key
7. vmSwapOut功能,如果啟動了vm選項,則判斷是否超過vmMaxMemory,如果超過進行value的swap
8. 判斷如果server是slave,則串連master,發送sync命令,和master同步資料 

1.2   fileEvent事件

FileEvent在這裡有兩種類型的fd,一個是listen時的fd,另一個是accept返回的fd。顯然第一個fd是一直存在的,它的響應函數為acceptTcpHandler,這個處理函數會調用acceptCommonHandler,該函數為此客戶createClient,並為accept返回的fd createFileEvent(fd),該類型event的響應函數為readQueryFromClient(覺得這函數名取得不怎麼好,起碼後面加個Handler);這樣兩種類型的fd事件都註冊好,並且它們的響應函數分別為:acceptCommonHandler,readQueryFromClient。
注意到這裡還沒有真正的啟動好服務,其底層調用的是epoll_ctl,即添加(註冊)事件。而真正的wait則是在aeMain裡,進入aeMain後,server才進入真正的服務階段。首先遍曆aeTimeEvent鏈表,接到最近的事件,如果該事件的回應時間小於當前(即已經過了這個時間但還沒被處理),所以顯然在調用aeApiPoll進行epoll_wait時timeout為0,即當沒有fileEvent時直接返回,否則epoll_wait的timeout可設計為這個最近時間減去目前時間,即可以等待這麼長的時間,這麼長的時間如果還沒有fileEvent的話就應該返回,因為此時timeEvent已經到點了。如果在這個時間內收到了fileEvent時候,則把這些可用的事件的fd及屬性mask儲存到aeFiredEvent數組中(server的epoll一開始是使用aeEventLoop->apidata(aeApiState)->events[AE_SETSIZE]來儲存這些可用的event結構),從aeApiPoll返回後台是一個for迴圈對所有可用的event進行處理,即調用相應的事件處理函數aeFileEvent->rfileProc,如acceptCommonHandler,readQueryFromClient;aeFileEvent->wFileEvent,sendReplyToClient。最後再判斷是否有timeEvent可以處理。(可以看圖1,圖2來理解整個過程)

以上就是redis的主處理流程。下面一張圖是來自http://www.w3ccollege.org/redis/redis-internal/redis-redis-event-library-source-code-analysis.html
非常清楚的描述了redis的主架構及事件處理流程

圖1 redis主架構及事件流程

2.       redis主要類型關係及查詢過程

2.1   類型關係圖

圖2 redis主要類型關係

通過該圖我們可以看到redis的主要三個構成部分:
第一部分查詢儲存資料時使用的記憶體資料結構(redisDb,dict,dictht,dictEntry,redisObject)及相應資料的儲存方式(string,list,set,zset,hash;它們內部根據element個數,size(這兩個值由設定檔指定)再決定是否使用相應的緊湊型儲存(set,list,hash緊湊儲存類型:ziplist,intset,zipmap));
第二部分就是事件的類型結構(aeEventLoop,aeFileEvent[...],aeTimeEvent[單向鏈表],aeFiredEvent[...](用來存放可用的fileEvent))。
第三部分就是redisClient用來儲存client[雙向鏈表]資訊的:使用者發來的命令,以及對應於這些命令對應的建立redisObject物件類型,命令處理函數等資訊。

 

2.2   尋找過程

通過這張圖我們可以大概的知道redis查詢更新的一個流程:通過FileEvent監聽用戶端的請求,當某個fd可用時,調用readQueryFromClient進行命令解析processInputBuffer(該函數內部根據用戶端(telnet或redis-cli)發送的命令協議格式分別調用processInlineBuffer或processMultibulkBuffer這兩個函數都是完成把儲存在redisClient->querybuf解析為一個個的參數,並且構造為redisObject對象儲存到redisClient->argv[c->argc]中),然後調用processCommand開始執行用戶端發送過來的命令,第一個參數顯示是用戶端想要執行的操作,所以先執行lookupCommand進行命令查詢(使用命令尋找ht表readonlyCommandTable)獲得相應的命令處理函數redisCommand.redisCommandProc,然後調用再call(c)進行真正的命令(增刪尋找等)操作,這裡以get命令為例進行下面的講解,通過查表獲得getCommand函數,然後執行lookupKeyReadOrReply進行查詢與返回操作,首先執行lookupKeyWrite(c->db,key),其最底層還是調用dictFind(db,key):

    if (d->ht[0].size == 0) return NULL; /* We don't have a table at all */

    if (dictIsRehashing(d)) _dictRehashStep(d);

    h = dictHashKey(d, key); //進行真正的hash計算,hashFunction由dictht的dictType指定

    for (table = 0; table <= 1; table++) {

        idx = h & d->ht[table].sizemask;

        he = d->ht[table].table[idx];  //獲得dictEntry*,所有hash

        while(he) {

            if (dictCompareHashKeys(d, key, he->key)) //對key值進行校正

                return he;

            he = he->next;

        }

        if (!dictIsRehashing(d)) return NULL;

    }

得到結果後調用addReply(c,robj)來進入寫返回操作,它首先調用_installWriteEvent向epoll註冊可寫事件,相應的響應函數為sendReplyToClient,然後判斷robj的encoding類型,進行getDecodedObject解碼,把解碼後的內容拷貝到redisClient->buf(_addReplyToBuffer),最後當剛才的事件發生時,由sendReplyToClient進行真正的write(cfd)操作。(真正的wait事件是在最外層的aeMain函數,所以這些內容會被正確拷貝到c->buf裡)。
[注dict裡有個dictht[2],表示兩個hash表,這個是為了實現rehash時使用,當ht[0],連結太長,就應該進行rehash,ht[1].size=ht[0].size*2]這裡只介紹簡單的SDS查詢的過程,該類型其實就是一個動態數組的類型,下面的串連介紹了redis各種記憶體儲存的結構,非常的詳細:
http://www.w3ccollege.org/redis/redis-internal/redis-memory-storage-structure-analysis-2.html

 

3.       持久化:

該章節內容轉自

【http://www.w3ccollege.org/redis/redis-internal/redis-2-2-4-studies-4-persistence.html】

Redis的持久化目前有兩種方式:snapshot與aof方式。

3.1   snapshot(快照)

進行快照的時機是由設定檔的save設定的,如save 900 1;900s有一個更新;或者由client發送save或者bgsave命令來進行快照。其中save操作是在主線程中儲存快照的,由於redis是用一個主線程來處理所有client的請求,這種方式會阻塞所有client請求,所以不推薦使用。bgsave會執行後台dump(建立子進程執行dump)。整個過程如下:
當dump條件滿足(或者接收到bgsave命令)redis調用系統fork,建立子進程
父進程繼續處理client請求,子進程負責將記憶體內容寫入到臨時檔案
由於Liunx的copy on write機制,子進程會在記憶體映射表中建立一個指標,指向父進程相同的記憶體位址,兩個進程會共用相同的記憶體和物理檔案,當父進程處理寫請求時,os會為父進程要修改的頁面棄置站台,而不是寫共用的頁面。所以子進程的地址空間內的資料是fork時刻整個記憶體的快照,當子進程將快照寫入臨時檔案完畢後,用剛才的臨時檔案替換原來的快照檔案,然後子進程退出。
可以看出這裡每次都是把整個記憶體快照全寫到檔案中,而不是唯寫修改的內容。
主要的函數是:static int rdbSave(char *filename)
註:在shutdown server,及執行flushall命令時也要執行dump操作。 

3.2   AOF(Append-Only File)

通過write函數,將每次修改db資料的命令,追加到檔案中,預設是appendonly.aof ,因此粒度是最小的了,跟日誌方式有點類似。所以,可以簡單理解為跟MySQL binlog一樣的東西,作用就是記錄每次的寫操作,在遇到斷電等問題時可以用它來恢複資料庫狀態。但,他不是bin的,而是text的,一行一行寫得很規範,就是說我們也能人工通過它恢複資料當Redis重啟時,會執行.aof 檔案中儲存的命令在記憶體中重建整個資料庫的內容(註:因為啟動時是先載入該檔案的,並且它與dump.rdb兩者只會使用其一,所以如果在啟動之前建立一個空的.aof檔案,那麼啟動後整個redis
instance是空的,即使現在dump.rdb有資料)。但不管如何,總有遺失資料的可能,畢竟整個過程是非同步。為了降低風險,我們可以修改設定檔,選擇調用fsync()的頻率,來保證日誌寫入到磁碟的時機。
這種方式也有弊端,就是任何寫入操作都會進行持久化,那麼,AOF檔案會越來越大,重啟的時候資料初始化要很久,這時候我們可以通過執行bgrewriteaof[background rewrite append only file] 命令在後台重建該檔案。
  收到此命令Redis將使用與快照類似的方式,將記憶體中的資料以命令的方式儲存到臨時檔案中,最後替換原來的檔案。舉例來說就是:之前可能用了10w次操作共改變了100條資料(比如在一條資料上進行了多次操作),那這時AOF中的10w條寫操作記錄就變成了100條記錄。相當於將前面的執行全部合并了,原本很大AOF變小了。[相當於最後只記錄所有key的一次寫操作,數值就是它們當前最新的值]
  需要注意到是重寫AOF檔案的操作,並沒有讀取舊的AOF檔案,而是將整個記憶體中的資料庫內容用命令的方式重寫了一個新的AOF檔案,這點和快照有點類似,(fork子進程,copy-on-write)。過程如下:
  redis調用fork ,現在有父子兩個進程
  子進程根據記憶體中的資料庫快照集,將所有資料產生一條寫入日誌到臨時檔案
  父進程繼續處理client請求,除了把寫命令寫入到原來的aof檔案中。同時把收到的寫命令緩衝起來。這樣就能保證如果子進程重寫失敗的話並不會出問題
  當子進程把快照內容寫入已命令方式寫到臨時檔案中後,子進程發訊號通知父進程。然後父進程把緩衝的寫命令也寫入到臨時檔案
現在父進程可以使用臨時檔案替換老的aof檔案,並重新命名,後面收到的寫命令也開始往新的aof檔案中追加。

 

4.       事務

本章主要參考
【http://www.w3ccollege.org/redis/redis-notes/redis-study-notes-of-the-matters.html】
redis的事務較簡單,並不具備傳統事務的acid的全部特徵。主要原因之一是redis事務中的命令並不是立即執行的,會一直排隊到發布exec命令才執行所有的命令。即redis只能保證一個client發起的事務中的命令可以連續的執行,而中間不會插入其他client的命令。另外面它不支援復原,事務中的命令可以部分成功,部分失敗,命令失敗時跟不在事務上下文執行時返回的資訊類似。其表現方式是使用multi,exec命令對實現事務:當一個client在一個串連中發出multi命令後,這個串連會進入一個事務上下文,該串連後續的命令並不是立即執行,而是先放到一個隊列中,直到此串連受到exec命令後,redis才會順序的執行隊列中的所有命令,並將所有命令的運行結果打包一起返回給client,然後此串連就結束事務上下文。[在redis2.1中加了watch命令來實現樂觀鎖,實現對共用資源的同步,如果watch一個key之後,該key如果在執行串連之外進行了任何更改,都會導致該串連的事務失敗]

 

5.       主從複製

本章節轉自
【http://www.w3ccollege.org/redis/redis-notes/redis-study-notes-of-the-master-slave-replication.html】
當設定好slave伺服器後,slave會建立和master的串連,然後發送sync命令。無論是第一次同步建立的串連還是串連斷開後的重新串連,master都會啟動一個後台進程,將資料庫快照集儲存到檔案中,同時master主進程會開始收集新的寫命令並緩衝起來。後台進程完成寫檔案後,master就傳送檔案給slave,slave將檔案儲存到磁碟上,然後載入到記憶體恢複資料庫快照集到slave上。接著master就會把緩衝的命令轉寄給slave。而且後續master收到的寫命令都會通過開始建立的串連發送給slave。從master到slave的同步資料的命令和從client發送的命令使用相同的協議格式。當master和slave的串連斷開時slave可以自動重建立立串連。如果master同時收到多個
slave發來的同步串連命令,只會使用啟動一個進程來寫資料庫鏡像,然後發送給所有slave。
配置slave伺服器很簡單,只需要在設定檔中加入如下配置:slaveof 192.168.1.1 6379 #指定master的ip和連接埠

 

後話

上面的內容,很多都是總結自網上的資料,有的忘了是來自哪裡的,所以沒有標註。自己也是在看了他們的文章後,再看了server 啟動的代碼(第一章節)及event庫及查詢執行過程的代碼(第二章節),其它的章節由於時間關係就沒去看code,直接轉載別人的。寫這篇文章更多的是讓自己以後需要的時候可以快速查閱,也是對這幾天來學習的總結,總算有個實質性的輸出。感謝互連網以及那些把自己的知識分享出來的同學們。
[註:redis的代碼比較少,並且主體架構也比較清析,所以代碼讀起來比較容易,自己是先從redis-benchmark.c讀起的,因為它的代碼更少,但是它包含了redis的事件驅動機制的實現,所以對於後面讀取redis-server還是有協助的。下面的三個參考串連基本上有了redis的所有主題,講的也非常的詳細和清晰,強烈推薦關注redis的同學關注]

 

參考資料

http://www.w3ccollege.org/category/redis

http://www.hoterran.info/

http://www.petermao.com/

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.