分布式鎖一般有三種實現方式:
1. 數據庫樂觀鎖;
2. 基於ZooKeeper的分布式鎖;
3. 基於Redis的分布式鎖;
這里大概說一下三種方式的優缺點,數據庫樂觀鎖優點是實現簡單,只需要for update關鍵詞就可以實現,缺點是無法滿足高並發量以及數據庫讀寫頻繁的系統
ZooKeeper分布式鎖無論是從性能以及實現的功能來說都是非常優秀,只是在開發起來需要一定的基礎,對新手可能不是很友好
而本文主要講第三種利用redis實現分布式鎖,優點是開發相對簡單,能滿足一定並發量的系統,缺點是存在線程爭搶鎖的問題,當並發量到達一定級別,多個線程去爭搶同一個鎖,對性能的影響較大
事務以及原子性
雖然Redis是單線程運行,但是在分布式的情況下對同一資源進行操作還是會出現問題,下圖是一個簡單的例子
所以一定要保證tomcat1以及tomcat2讀寫的原子性,既讀與寫要么都執行,要么都不執行。關於事務的原子性可以查詢這里
那么如何保證呢,redis在2.6中加入了lua腳本功能可以輕松的解決這個問題,下面是一個簡單的例子實現了上述的加100操作
Jedis jedis = jedisPool.getResource(); String script = "local a = redis.call('get', KEYS[0]) a = a + 100 redis.call('set', a)";
jedis.eval(script, 1, rname+"Lock",RedisCacheFactory.FactoryUUID,"1000");
分布式鎖的具體實現
大概講一下思路:首先加鎖的方式是向redis里存入一個KEY-VALUE,KEY存入的加鎖對象可以是方法、類、數據等等,VALUE存入持有鎖的節點(例如tomcat1)
大概整理了一下幾個問題:
Q:為什么VALUE存入持有鎖的節點
A:為的是防止A加的鎖被B給解除,保證只有持有鎖的節點才能解鎖
Q:怎么存入持有鎖的節點
A:這里只是我的思路是在tomcat啟的時候生成一個uuid作為該tomcat的token存入到VALUE中
Q:怎么防止死鎖
A:利用Redis設置鍵的過期時間
下面貼出部分代碼,僅供參考
加鎖
JedisPool jedisPool = new JedisPool(new JedisPoolConfig(),RedisInstance.hostName,Integer.parseInt(RedisInstance.port),5000,password); Jedis jedis = jedisPool.getResource(); // key1 : key值 argv1 :value值 argv2 :過期時間 String script = "if redis.call('EXISTS',KEYS[1]) ==0 then redis.call('set',KEYS[1],ARGV[1]) redis.call('EXPIRE',KEYS[1],ARGV[2]) return 1 else return 0 end"; long result = (long) jedis.eval(script, 1, rname+"Lock",RedisCacheFactory.FactoryUUID,"1000"); jedis.close(); jedisPool.close();
解鎖
JedisPool jedisPool = new JedisPool(new JedisPoolConfig(),RedisInstance.hostName,Integer.parseInt(RedisInstance.port),5000,password); Jedis jedis = jedisPool.getResource(); String script = "if redis.call('EXISTS',KEYS[1]) ==1 and redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; long result = (long) jedis.eval(script, 1, rname+"Lock",RedisCacheFactory.FactoryUUID); jedis.close(); jedisPool.close();
以上僅個人意見,如有錯誤的地方,還請各位海涵。
后面可能會整合這次緩存改造的所有環節發出來給大家參考 一下
補充說明一下:lua操作redis時如果操作多個key不在同一節點下會出錯,原因是因為Cluster會將數據自動分布到不同的節點(虛擬的16384個slot,具體看這里)。
解決辦法 后面會貼出詳細教程