一個操做要修改用戶的狀態,修改狀態須要先讀出用戶的狀態,在內存裏進行修改,改完了再存回去。若是這樣的操做同時進行了,就會出現併發問題,由於讀取和保存狀態這兩個操做不是原子的。這個時候就要使用到分佈式鎖來限制程序的併發執行。redis
通常是使用 setnx(set if not exists) 指令佔坑, 用完再調用 del 指令釋放茅坑。若是邏輯執行到中間出現異常了,可能會致使 del 指令沒有被調用,這樣就會陷入死鎖,鎖永遠得不到釋放。因而咱們在拿到鎖以後,再給鎖加上一個過時時間,好比 5s,這樣即便中間出現異常也能夠保證 5 秒以後鎖會自動釋放。算法
/** * @Auther: majx2 * @Date: 2019-3-21 16:02 * @Description: */ public class DistributedLockTest { Jedis jedis = RedisDS.create().getJedis(); final static String KEY = "KEY"; @Test public void testLock() throws InterruptedException { new Thread(new Runnable() { @Override public void run() { Assert.assertTrue(exec()); } }).start(); Thread.sleep(1000); Assert.assertFalse(exec()); Thread.sleep(3000); } public boolean exec(){ return new RedLock().trylock(KEY, new LockWrap() { @Override public boolean invoke() { try { Thread.sleep(2000); } catch (InterruptedException e) {} return true; } }); } public class RedLock{ public boolean trylock(String key,LockWrap wrap){ Long result = jedis.setnx(key, KEY);// 佔坑 if(result == 1L){ jedis.expire(key,5000); // 避免沒有刪除 boolean invoke = wrap.invoke(); jedis.del(key); return invoke; } return false; } } public interface LockWrap{ boolean invoke(); } }
可是以上邏輯還有問題。若是在 setnx 和 expire 之間服務器進程忽然掛掉了,多是由於機器掉電或者是被人爲殺掉的,就會致使 expire 得不到執行,也會形成死鎖。這種問題的根源就在於 setnx 和 expire 是兩條指令而不是原子指令。 解決這些問題,可使用開源分佈式組建redission。緩存
Redis 的分佈式鎖不能解決超時問題,若是在加鎖和釋放鎖之間的邏輯執行的太長,以致於超出了鎖的超時限制,就會出現問題。由於這時候鎖過時了,第二個線程從新持有了這把鎖,第二個線程就會在第一個線程邏輯執行完之間拿到了鎖;緊接着第一個線程執行完了業務邏輯,就把鎖給釋放了,第三個線程就會在第二個線程邏輯執行完之間拿到了鎖。爲了不這個問題,Redis 分佈式鎖不要用於較長時間的任務。若是真的偶爾出現了,數據出現的小波錯亂可能須要人工介入解決。安全
在 Sentinel 集羣中,主節點掛掉時,從節點會取而代之,客戶端上卻並無明顯感知。原先第一個客戶端在主節點中申請成功了一把鎖,可是這把鎖尚未來得及同步到從節點,主節點忽然掛掉了。而後從節點變成了主節點,這個新的節點內部沒有這個鎖,因此當另外一個客戶端過來請求加鎖時,當即就批准了。這樣就會致使系統中一樣一把鎖被兩個客戶端同時持有,不安全性由此產生。服務器
若是你很在意高可用性,但願掛了一臺 redis 徹底不受影響,那就應該考慮 redlock算法
。不過代價也是有的,須要更多的 redis 實例,性能也降低了。併發
注: Redlock算法,須要提供多個 Redis 實例,這些實例以前相互獨立沒有主從關係。加鎖時,它會向過半節點發送 set(key, value, nx=True, ex=xxx) 指令,只要過半節點 set 成功,那就認爲加鎖成功。釋放鎖時,須要向全部節點發送 del 指令。不過, Redlock 算法還須要考慮出錯重試、時鐘漂移等不少細節問題,同時由於 Redlock 須要向多個節點進行讀寫,意味着相比單實例 Redis 性能會降低一些。分佈式
本文基於《Redis深度歷險:核心原理和應用實踐》一文的JAVA實踐。更多文章請參考:高性能緩存中間件Redis應用實戰(JAVA)ide