什么時候需要進行需要原子操作?
很常見的例子,就是利用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腳本的時候要小心。