【雜談】如何對Redis進行原子操作


什么時候需要進行需要原子操作?

很常見的例子,就是利用Redis實現分布式鎖。

實現鎖需要哪些條件?

我們知道要實現鎖,就需要一個改變鎖狀態的方法。這個方法能原子地對鎖的狀態進行檢查並修改。如果修改成功,則意味着獲得了鎖。對於硬件,它提供的就是test-and-set,compare-and-swap等原語。

Redis有沒有提供類似的原語呢?

有的。Redis有提供setnx(),它會提供這樣的原子操作:如果key沒有值,則將值設置進去,如果已有值就不做處理,提示失敗。

這樣就可以基於這個原語來實現鎖,簡單原理就是:key就是對應的鎖,如果key有值就說明鎖被占用。刪除值代表釋放鎖。如果插入值成功,則代表獲得鎖。再加上過期時間,基本就可以滿足分布式鎖的需求了。

除了鎖,還有哪些地方需要原子操作?

假如我們在操作Redis數據的時候,需要判斷Redis中某個值是否滿足條件,只有滿足條件才做這個操作

我隨便舉個例子,例如:如果key-xxx的值不為0,則加1,如果為0,則刪除。

這種情況Redis可以處理嗎?

可以,Lua腳本。Redis支持Lua腳本。針對上面的問題,我們只要寫這樣的Lua腳本就可以了。

local a = redis.call('get', 'xxx') //調用redis的get方法,key為'xxx'
if(tonumber(a) > 0) then  //redis都是以String進行存儲的,需要轉型
    redis.call('incr', 'xxx') //調用redis的incr方法,key為'xxx'
    return 'OK'
else
    return 'FAIL'

為什么Lua腳本可以實現原子操作, 看不出來它有用鎖啊?

這與Redis的請求處理有關。Redis只用一個線程來處理客戶端的請求。所以在執行lua腳本的時候,沒有其他客戶端的請求在處理。所以在lua腳本中的對redis數據的修改操作就是原子的。

只用一個線程處理的過來嗎?

Redis的請求處理線程,利用Select和事件循環進行處理,大概就是下面這樣:

while(1) {
    events = getEvents(); //先利用SELECT拿到最近的請求
    for(e in events)  //然后逐個處理
        processEvent(e);
}

由於使用的是select()來處理網絡I/O,所以線程不會在一個socket連接上阻塞。另一方面因為redis的操作的數據都在內存中。處理起來也很快,所以也不會出現響應時間太長的情況。

萬一這個線程阻塞了怎么辦?

一般情況下,阻塞不了。前面也說了,客戶端上來的請求都是操作內存的,不會有其他調用(例如文件I/O這樣的調用)。但是,這是一般情況。別忘了lua,lua腳本里面是可以寫阻塞操作的(比如文件I/O,或者最簡單的死循環)。實測發現,如果往Redis中提交一個死循環的lua腳本,Redis就掛了。所以寫lua腳本的時候要小心。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM