初一七月

          你必須承認,我們生存的這個世界取決于自身的能力而非別人的保護(AW)
          隨筆 - 23, 文章 - 0, 評論 - 11, 引用 - 0
          數(shù)據(jù)加載中……

          Redis并發(fā)問題

          Redis為單進程單線程模式,采用隊列模式將并發(fā)訪問變?yōu)榇性L問。Redis本身沒有鎖的概念,Redis對于多個客戶端連接并不存在競爭,但是在Jedis客戶端對Redis進行并發(fā)訪問時會發(fā)生連接超時、數(shù)據(jù)轉(zhuǎn)換錯誤、阻塞、客戶端關(guān)閉連接等問題,這些問題均是由于客戶端連接混亂造成。對此有2種解決方法:

          1.客戶端角度,為保證每個客戶端間正常有序與Redis進行通信,對連接進行池化,同時對客戶端讀寫Redis操作采用內(nèi)部鎖synchronized。

          2.服務(wù)器角度,利用setnx實現(xiàn)鎖。

          對于第一種,需要應(yīng)用程序自己處理資源的同步,可以使用的方法比較通俗,可以使用synchronized也可以使用lock;第二種需要用到Redis的setnx命令,但是需要注意一些問題。

          SETNX命令(SET if Not eXists)

          語法:
          SETNX key value

          功能:
          將 key 的值設(shè)為 value ,當(dāng)且僅當(dāng) key 不存在;若給定的 key 已經(jīng)存在,則 SETNX 不做任何動作。

          時間復(fù)雜度:
          O(1)
          返回值:
          設(shè)置成功,返回 1 。
          設(shè)置失敗,返回 0 。

          模式:將 SETNX 用于加鎖(locking)

          SETNX 可以用作加鎖原語(locking primitive)。比如說,要對關(guān)鍵字(key) foo 加鎖,客戶端可以嘗試以下方式:

          SETNX lock.foo <current Unix time + lock timeout + 1>

          如果 SETNX 返回 1 ,說明客戶端已經(jīng)獲得了鎖, key 設(shè)置的unix時間則指定了鎖失效的時間。之后客戶端可以通過 DEL lock.foo 來釋放鎖。

          如果 SETNX 返回 0 ,說明 key 已經(jīng)被其他客戶端上鎖了。如果鎖是非阻塞(non blocking lock)的,我們可以選擇返回調(diào)用,或者進入一個重試循環(huán),直到成功獲得鎖或重試超時(timeout)。

          但是已經(jīng)證實僅僅使用SETNX加鎖帶有競爭條件,在特定的情況下會造成錯誤。

          處理死鎖(deadlock)

          上面的鎖算法有一個問題:如果因為客戶端失敗、崩潰或其他原因?qū)е聸]有辦法釋放鎖的話,怎么辦?

          這種狀況可以通過檢測發(fā)現(xiàn)——因為上鎖的 key 保存的是 unix 時間戳,假如 key 值的時間戳小于當(dāng)前的時間戳,表示鎖已經(jīng)不再有效。

          但是,當(dāng)有多個客戶端同時檢測一個鎖是否過期并嘗試釋放它的時候,我們不能簡單粗暴地刪除死鎖的 key ,再用 SETNX 上鎖,因為這時競爭條件(race condition)已經(jīng)形成了:

          C1 和 C2 讀取 lock.foo 并檢查時間戳, SETNX 都返回 0 ,因為它已經(jīng)被 C3 鎖上了,但 C3 在上鎖之后就崩潰(crashed)了。
          C1 向 lock.foo 發(fā)送 DEL 命令。
          C1 向 lock.foo 發(fā)送 SETNX 并成功。
          C2 向 lock.foo 發(fā)送 DEL 命令。
          C2 向 lock.foo 發(fā)送 SETNX 并成功。
          出錯:因為競爭條件的關(guān)系,C1 和 C2 兩個都獲得了鎖。



          幸好,以下算法可以避免以上問題。來看看我們聰明的 C4 客戶端怎么辦:

          C4 向 lock.foo 發(fā)送 SETNX 命令。
          因為崩潰掉的 C3 還鎖著 lock.foo ,所以 Redis 向 C4 返回 0 。
          C4 向 lock.foo 發(fā)送 GET 命令,查看 lock.foo 的鎖是否過期。如果不,則休眠(sleep)一段時間,并在之后重試。
          另一方面,如果 lock.foo 內(nèi)的 unix 時間戳比當(dāng)前時間戳老,C4 執(zhí)行以下命令:
          GETSET lock.foo <current Unix timestamp + lock timeout + 1>


          因為 GETSET 的作用,C4 可以檢查看 GETSET 的返回值,確定 lock.foo 之前儲存的舊值仍是那個過期時間戳,如果是的話,那么 C4 獲得鎖。
          如果其他客戶端,比如 C5,比 C4 更快地執(zhí)行了 GETSET 操作并獲得鎖,那么 C4 的 GETSET 操作返回的就是一個未過期的時間戳(C5 設(shè)置的時間戳)。C4 只好從第一步開始重試。
          注意,即便 C4 的 GETSET 操作對 key 進行了修改,這對未來也沒什么影響。

          這里假設(shè)鎖key對應(yīng)的value沒有實際業(yè)務(wù)意義,否則會有問題,而且其實其value也確實不應(yīng)該用在業(yè)務(wù)中。

          為了讓這個加鎖算法更健壯,獲得鎖的客戶端應(yīng)該常常檢查過期時間以免鎖因諸如 DEL 等命令的執(zhí)行而被意外解開,因為客戶端失敗的情況非常復(fù)雜,不僅僅是崩潰這么簡單,還可能是客戶端因為某些操作被阻塞了相當(dāng)長時間,緊接著 DEL 命令被嘗試執(zhí)行(但這時鎖卻在另外的客戶端手上)。


          GETSET命令

          語法:
          GETSET key value

          功能:
          將給定 key 的值設(shè)為 value ,并返回 key 的舊值(old value)。當(dāng) key 存在但不是字符串類型時,返回一個錯誤。

          時間復(fù)雜度:
          O(1)

          返回值:
          返回給定 key 的舊值;當(dāng) key 沒有舊值時,也即是, key 不存在時,返回 nil 。

          ref by
          http://blog.csdn.net/hpb21/article/details/7893013
          http://redis.readthedocs.org/en/latest/string/setnx.html


          posted on 2013-01-28 19:23 初一七月 閱讀(36592) 評論(0)  編輯  收藏 所屬分類: Web

          主站蜘蛛池模板: 天门市| 崇阳县| 正蓝旗| 宕昌县| 长海县| 南和县| 军事| 益阳市| 麦盖提县| 仙游县| 淮阳县| 府谷县| 洮南市| 桐庐县| 余庆县| 榆中县| 灵寿县| 黑龙江省| 静乐县| 巴彦县| 海盐县| 四子王旗| 都昌县| 枞阳县| 崇仁县| 武强县| 卢龙县| 柳江县| 金溪县| 万荣县| 雅江县| 彭泽县| 临西县| 嘉禾县| 霸州市| 嘉黎县| 敖汉旗| 鄢陵县| 沐川县| 综艺| 东辽县|