1,使用Redis做分佈式鎖:
利用SETNX添加一個鎖,並設置鎖的釋放時間。
問題:
a,某個機器實例的任務執行時長超時了,超過了鎖釋放的時間,會造成其他機器實例獲取到該鎖並執行任務。任務被同時執行。
b,Redis的部署模式:如果是單實例,或者是master-slave模式。 Redis可能會掛(概率很小),或者只是針對master節點加鎖,如果master節點故障,發生master,slave切換,鎖丟失。
解決方案:
1,針對問題a
* 如果任務時間超時,可以設置告警,人工進行干預。
* 或者在當前機器實例上,頻繁的去get鎖,如果鎖屬於自己,則延長鎖的釋放時間。
2,針對問題b
* 如果是master-slave模式,在每個節點都建立鎖,如果是Cluster模式,在超過一半的節點上都建立鎖。
如果業務對分佈式鎖出錯可以容忍,不是那麼強烈的一致性,那就使用Redis的簡單實現,因爲Redis可以支撐
很高的併發。
比如一些週期性定時任務,例如同步數據的,處理異常情況等等,避免多個實例跑浪費機器資源的。
如果業務對分佈式鎖有一定要求,當然不是100%的強一致性,而且併發特別高,可以考試使用
Redisson, 他具有很高的數據一致性,可以達到99.99%,而且性能特別好。
可以使用Redisson的多個Redis實例的分佈式鎖。
Config config = new Config();
config.useClusterServers().addNodeAddress("redis://192.168.31.101:7001")
.addNodeAddress("redis://192.168.31.101:7002")
.addNodeAddress("redis://192.168.31.101:7003")
.addNodeAddress("redis://192.168.31.102:7001")
.addNodeAddress("redis://192.168.31.102:7002")
.addNodeAddress("redis://192.168.31.102:7003");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("anyLock");
lock.lock();
lock.unlock();
它的 API 中的 Lock 和 Unlock 即可完成分佈式鎖:
Redisson 中有一個 Watchdog 的概念,翻譯過來就是看門狗,它會在你獲取鎖之後,每隔 10s 幫你把 Key 的超時時間設爲 30s。
Redisson 的“看門狗”邏輯保證了沒有死鎖發生。(如果機器宕機了,看門狗也就沒了。此時就不會延長 Key 的過期時間,到了 30s 之後就會自動過期了,其他線程可以獲取到鎖)
2,基於Zookeeper實現分佈式鎖
ZK:提供配置管理,分佈式協同,命名的中心化服務。
ZK的節點類型:持久節點,臨時節點
順序節點:持久順序節點,臨時順序節點
分佈式鎖實現原理:爲鎖創建一個持久化節點,例如:/lock
在這個節點下,每個Client創建臨時順序節點,如:/lock/client-001,/lock/client-002,/lock/client-003
臨時節點,如果Client會話結束,或者斷開,ZK自動刪除臨時節點。
Client獲取/lock下所有的子節點列表,判斷當前創建的子節點列表序號是不是最小,如果是,認爲獲取該鎖。
如果不是,監聽比自己小一個的節點。直到獲得節點變更通知後重複檢查節點序號。
這裏,爲什麼監聽比自己小一個的節點,而不是最小序號的節點,因爲,如果/lock下有1000個節點的話,
當最小節點有變更通知,ZK需要通知其它999個節點。ZK會發生阻塞,Client也沒有必要同時去爭搶鎖。
3,基於Mysql數據庫實現分佈式鎖:
悲觀鎖:select ... for update
首先設置Mysql爲非auto commit 模式,放在一個事務執行。
- 第一步查詢鎖定一行數據:select * from table where id = 1 for update 鎖定 id=1 的行。
- 第二步:操作和這一行數據關聯的業務,比如:insert, 或者 update 等等。
- 第三步:提交事務。id=1 的行解鎖。
當然,在事務中,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一筆數據時會等待其它事務結束後才執行,一般SELECT … 則不受此影響。
4,基於Memcached的分佈式鎖實現,和Redis最簡單的分佈式鎖實現功能效果一致。
Memcached的分佈式完全是依賴客戶端的一致性哈希算法來達到分佈式的存儲,在Memcached服務端,所有的操作都是原子性的。
我們利用add函數,add會添加第一個到達的值,並返回true,後續的添加則都會返回false。
利用這個原理,可以先定義一個 鎖 LockKEY ,add 成功的認爲是得到鎖。並且設置[過期超時] 時間,保證宕機後,也不會死鎖。