15天玩轉redis —— 第八篇 你不得不會的事務玩法

來源:互聯網
上載者:User

標籤:

   我們都知道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 —— 第八篇 你不得不會的事務玩法

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.