1. 程式人生 > >Redis原始碼分析(十七)--- multi事務操作

Redis原始碼分析(十七)--- multi事務操作

        redis作為一非關係型資料庫,竟然同樣擁有與RDBMS的事務操作,不免讓我覺得比較驚訝。在redis就專門有檔案就是執行事務的相關操作的。也可以讓我們領略一下,在Redis的程式碼中是如何實現事務操作。首先亮出mulic.c下面的一些API。

/* ================================ MULTI/EXEC ============================== */
void initClientMultiState(redisClient *c) /* 初始化客戶端操作 */
void freeClientMultiState(redisClient *c) /* 釋放客戶端所有與multi/exec相關的資源 */
void queueMultiCommand(redisClient *c) /* 客戶端的multi命令佇列新增一條新的命令 */
void discardTransaction(redisClient *c) /* 撤銷事務操作 */
void flagTransaction(redisClient *c) /* 標記一個事物為DIRTY_EXEC狀態,最後這個事物會執行失敗,,此方法調用於插入命令的時候 */
void multiCommand(redisClient *c) /* 加入multi命令 */
void discardCommand(redisClient *c) /* 撤銷命令 */
void execCommandPropagateMulti(redisClient *c) /* 傳送multi命令給所有的從客戶端和aof檔案 */
void execCommand(redisClient *c) /* 客戶單執行Command命令 */
void watchForKey(redisClient *c, robj *key) /* 為客戶端新增key監聽 */
void unwatchAllKeys(redisClient *c) /* 客戶端移除所有的key */
void touchWatchedKey(redisDb *db, robj *key) /* touch key的意思,表示key正在被監聽,下一條執行操作將會失敗 */
void touchWatchedKeysOnFlush(int dbid) /* 根據key所在的的db,把此db下的watched-key統統touch一遍 */
void watchCommand(redisClient *c) /* watch key 的命令方法,通過client中的引數傳值 */
void unwatchCommand(redisClient *c) /* 取消監聽key的命令方法 */
方法不是很多,但是裡面出現了一個出現頻率很高的詞"key"。這個key在這裡的確是起到了關鍵的作用。在muli的程式碼中主要包含了一些,加入命令,執行命令,還有一些撤銷指令的操作,比如下面的撤銷事務的操作。
/* 撤銷事務 */
void discardTransaction(redisClient *c) {
    freeClientMultiState(c);
    initClientMultiState(c);
    c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC);
    //客戶端取消監聽所有的key
    unwatchAllKeys(c);
}
裡面有個unwatchAllKeys()的方法。下面是事務操作的關鍵原理了:
/* 在事務處理中,存在2種mapping對映,key-->client lists ,表示所有列表中的Client都在監聽這個key
	,當這個key的value發生改變了,可以標記這些Client為DIRTY狀態,需要更新了,同時在Client內部也會維護
	一個key of list,表示一個客戶端所監視的所有key,當Client發生free操作等,就要把key裡面維護的Client列表
	做更新*/

/* touch key的意思,表示key正在被監聽,下一條執行操作將會失敗 */
也就是說,正在客戶端正在監聽的key,他的下一步命令將會執行失敗,達到了同步的效果,
/* "Touch" a key, so that if this key is being WATCHed by some client the
 * next EXEC will fail. */
/* touch key的意思,表示key正在被監聽,下一條執行操作將會失敗 */
void touchWatchedKey(redisDb *db, robj *key) {
    list *clients;
    listIter li;
    listNode *ln;

    if (dictSize(db->watched_keys) == 0) return;
    clients = dictFetchValue(db->watched_keys, key);
    if (!clients) return;

    /* Mark all the clients watching this key as REDIS_DIRTY_CAS */
    /* Check if we are already watching for this key */
    listRewind(clients,&li);
    while((ln = listNext(&li))) {
        redisClient *c = listNodeValue(ln);
		
		//遍歷該key擁有的Client,把flag標記為DIRTY_CAS狀態
        c->flags |= REDIS_DIRTY_CAS;
    }
}
當客戶端嘗試用touch的方法去監聽key的時候,Client的flag狀態唄改為了DIRTY_CAS,不禁讓我猜測,同步的方法是用CAS演算法嘛,如果很多客戶端都在用此演算法,的確挺耗CPU的哦。總的來說,key維護了一個Client列表,一個Client同樣擁有它所有watch的key列表,key的結構體很簡單:
/* 定義了watchedKey結構體 */
typedef struct watchedKey {
    robj *key;
    redisDb *db;
} watchedKey;
key包含了它所屬於的哪個資料庫,所以剛剛撤銷事務的操作,就要把客戶端所監聽的key都給移除掉了。