標籤:
我們都知道redis追求的是簡單,快速,高效,在這種情況下也就拒絕了支援window平台,學sqlserver的時候,我們知道事務還算是個比較複雜的東西,
所以這吊毛要是照搬到redis中去,理所當然redis就不是那麼簡單純碎的東西了,但是呢,事務是我們寫程式無法逃避的情境,所以redis作者折衷的寫了個簡
化版的事務機制,下面我來扯一下它的蛋蛋。
一: 事務實戰
具體到事務是什麼,要保證什麼。。。這個我想沒必要說了,先不管三七二十一,看一下redis手冊,領略下它的魔力。
1. multi,exec
還記得sqlserver是怎麼玩的嗎?一般都是這樣的三個步驟,產生事務,產生命令,執行事務,對吧,而對應redis呢??multi就是產生事務,然後
輸入redis命令,最後用exec執行命令,就像下面這樣:
可以看到,我set完命令之後,反饋資訊是QUEUED,最後我再執行exec,這些命令才會真正的執行,就是這麼的簡單,一切執行的就是那麼的順利,
一點都不拖泥帶水,牛逼的不要不要的,可能有些人說,其實事務中還有一個rollback操作,但好像在redis中沒有看到,哈哈,牛逼哈,很遺憾是
redis中沒有rollback操作,比如下面這樣。
在圖中我故意用lpush命令去執行string,可想而知自然不會執行成功,但從結果中,你看到什麼了呢?兩個OK,一個Error,這就是違反了事務
的原子性,對吧,但是我該怎麼反駁呢??? 我會說,錯你妹啊。。。連個基本的命令都寫錯了,你搞個毛啊。。。還寫個吊毛代碼,reids僅僅
是個資料結構伺服器,多簡單的一件事情,退一萬步說,很明顯的錯誤命令它會直接返回的,比如我故意把lpush寫成lpush1:
2. watch
不知道你看完multi後面的三條set命令之後,有沒有一種心虛的感覺,怎麼說呢,就是只要命令是正確的,redis保證會一併執行,誓死完成
任務,雖然說命令是一起執行的,但是誰可以保證我在執行命令的過程中,其他client不會修改這些值呢???如果修改了這些值,那我的exec
還有什麼意義呢???沒關係,這種爛大街的需求,redis怎可能袖手旁觀???這裡的watch就可以助你一臂之力。
WATCHWATCH key [key ...]監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那麼事務將被打斷。
上面就是redis手冊中關於watch的解釋,使用起來貌似很簡單,就是我在multi之前,用watch去監視我要修改的key,如果說我在exec之前,
multi之後的這段時間,key被其他client修改,那麼exec就會執行失敗,返回(nil),就這麼簡單,我還是來舉個例子:
二:原理探索
關於事務操作的原始碼,大多都在redis源碼中的multi.c 檔案中,接下來我會一個一個的簡單剖析一下:
1. multi
在redis的原始碼中,它大概是這麼寫的:
1 void multiCommand(redisClient *c) {2 if (c->flags & REDIS_MULTI) {3 addReplyError(c,"MULTI calls can not be nested");4 return;5 }6 c->flags |= REDIS_MULTI;7 addReply(c,shared.ok);8 }
從這段代碼中,你可以看到multi只是簡單的把redisClient的REDIS_MULTI狀態開啟,告訴這個redis用戶端已經進入事務模式了,對吧。
2. 產生命令
在redisClient中,裡面有一個multiState命令:
typedef struct redisClient { 。。。 multiState mstate; /* MULTI/EXEC state */ 。。。} redisClient;
從注釋中你大概也看到了這個命令和multi/exec肯定有關係,接下來我很好奇的看看multiState的定義:
typedef struct multiState { multiCmd *commands; /* Array of MULTI commands */ int count; /* Total number of MULTI commands */ int minreplicas; /* MINREPLICAS for synchronous replication */ time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */} multiState;
從multiState這個枚舉中,你可以看到下面有一個*command命令,從注釋中可以看到它其實指向的是一個數組,這個數組我想你閉著眼睛都
能想得到吧。。。它就是你的若干條命令啦。。。下面還有一個count,可以看到是實際的commands的總數。
3. watch
為了方便說到後面的exec,這裡想說一下watch大概是怎麼實現的,在multi.c原始碼中是這樣寫的。
1 typedef struct watchedKey { 2 robj *key; 3 redisDb *db; 4 } watchedKey; 5 6 void watchCommand(redisClient *c) { 7 int j; 8 9 if (c->flags & REDIS_MULTI) {10 addReplyError(c,"WATCH inside MULTI is not allowed");11 return;12 }13 for (j = 1; j < c->argc; j++)14 watchForKey(c,c->argv[j]);15 addReply(c,shared.ok);16 }17 18 /* Watch for the specified key */19 void watchForKey(redisClient *c, robj *key) {20 list *clients = NULL;21 listIter li;22 listNode *ln;23 watchedKey *wk;24 25 /* Check if we are already watching for this key */26 listRewind(c->watched_keys,&li);27 while((ln = listNext(&li))) {28 wk = listNodeValue(ln);29 if (wk->db == c->db && equalStringObjects(key,wk->key))30 return; /* Key already watched */31 }32 /* This key is not already watched in this DB. Let‘s add it */33 clients = dictFetchValue(c->db->watched_keys,key);34 if (!clients) {35 clients = listCreate();36 dictAdd(c->db->watched_keys,key,clients);37 incrRefCount(key);38 }39 listAddNodeTail(clients,c);40 /* Add the new key to the list of keys watched by this client */41 wk = zmalloc(sizeof(*wk));42 wk->key = key;43 wk->db = c->db;44 incrRefCount(key);45 listAddNodeTail(c->watched_keys,wk);46 }
這段代碼中大概最核心的一點就是:
/* This key is not already watched in this DB. Let‘s add it */ clients = dictFetchValue(c->db->watched_keys,key);
就是通過dicFetchValue這個字典方法,從watched_keys中找到指定key的value,而這個value是一個clients的鏈表,說明人家其實是想找到
關於這個key的所有client,對吧,最後還會將本次key塞入到redisclient的watched_keys字典中,如下代碼:
/* Add the new key to the list of keys watched by this client */ wk = zmalloc(sizeof(*wk)); wk->key = key; wk->db = c->db; incrRefCount(key); listAddNodeTail(c->watched_keys,wk);
如果非要畫圖,大概就是這樣:
其中watched_key是個字典結構,字典的鍵為上面的key1,key2。。。,value為client的鏈表,這樣的話,我就非常清楚某個key
中是被哪些client監視著的,對吧。
4.exec
這個命令裡面大概做了兩件事情:
<1>: 判斷c->flags=REDIS_DIRTY_EXEC 開啟與否,如果是的話,取消事務discardTransaction(c),也就是說這個key已經
被別的client修改了。
<2>: 如果沒有修改,那麼就for迴圈執行comannd[]中的命令,如中的兩處資訊:
好了,大概就這麼說了,希望對你有協助哈~~~
15天玩轉redis —— 第八篇 你不得不會的事務玩法