分布式鎖的三種實(shí)現(xiàn)方式
基于數(shù)據(jù)庫(kù)
- 新建一張表,每次insert 一條記錄猜谚,利用唯一約束授艰,釋放鎖刪除此記錄即可辨嗽。
- for update 利用行級(jí)鎖。
強(qiáng)依賴數(shù)據(jù)庫(kù)淮腾,一但數(shù)據(jù)庫(kù)不可用則系統(tǒng)不可用糟需。
一單獲取鎖失敗,則直接返回失敗谷朝,線程不會(huì)進(jìn)入等待隊(duì)列洲押。
Redis
推薦redission,提供豐富的工具類,支持LUA腳本圆凰,支持spring框架等等(太多杈帐,大家可以度娘下)。
我見過很多的應(yīng)用中都是一種寫法
jedis.set(key, value, "NX", "PX", expireTime);
這種寫法有什么問題专钉?當(dāng)多個(gè)線程同時(shí)獲取鎖失敗時(shí)娘荡,未獲取到鎖的線程依然不能進(jìn)入等待隊(duì)列干旁,直接返回失敗,很多童鞋使用了強(qiáng)大的武器for循環(huán)炮沐,而在redission中的lock就利用了redis的訂閱功能實(shí)現(xiàn)的線程的等待和通知争群,有興趣可以參考https://redisson.org/
zookeeper
今天這里主要分析zookeeper的實(shí)現(xiàn)方式和細(xì)節(jié),以幫助大家在應(yīng)用大年、以及在面試過程當(dāng)中能夠很好的理解和回答分布式鎖的實(shí)現(xiàn)過程换薄。
image.png
- DEMO
package com.jyly.mydubbo.zk;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
/**
* @author 咖啡爺爺
*
*/
public class Lock {
// 根節(jié)點(diǎn)目錄
private String ROOT_LOCK = "/locks";
CountDownLatch countDownLatch = null;
ZkClient zkClient = null;
final LockContext context = new LockContext();
TreeSet<String> treeSet = null;
public static String lockKey = "node";
public Lock() {
// init zkclient
zkClient = new ZkClient("xxx.xxx.xxx.xx:2181");
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Lock lock = new Lock();
try {
if (lock.tryLock(lockKey)) {
System.out.println(Thread.currentThread().getName() + ">>>>獲取到鎖");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + ">>>>釋放鎖");
}
}
}).start();
}
}
/**
* 獲取分布式鎖
* @param key
* @return
*/
public boolean tryLock(String key) {
try {
if (!zkClient.exists(ROOT_LOCK)) {
zkClient.createPersistent(ROOT_LOCK);
}
String seq = zkClient.createEphemeralSequential(ROOT_LOCK.concat("/").concat(key), null);
context.set(seq.substring(seq.lastIndexOf("/") + 1, seq.length()));
if (isMinNode()) {
return true;
} else {
addListenPreNode();
countDownLatch = new CountDownLatch(1);
for(;;) {
countDownLatch.await();
if(isMinNode()) {
return true;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
// 判斷當(dāng)前是否是最小節(jié)點(diǎn)
private boolean isMinNode() {
treeSet = new TreeSet<>();
for (String children : zkClient.getChildren(ROOT_LOCK)) {
treeSet.add(children);
}
String minNode = treeSet.first();
if (context.get().equals(minNode)) {
return true;
}
return false;
}
/**
* 添加監(jiān)聽前驅(qū)節(jié)點(diǎn)
*/
private void addListenPreNode() {
zkClient.subscribeDataChanges(ROOT_LOCK.concat("/").concat(treeSet.lower(context.get())), new IZkDataListener() {
public void handleDataChange(String arg0, Object arg1) throws Exception {
}
public void handleDataDeleted(String arg0) throws Exception {
countDownLatch.countDown();
}
});
}
/**
* 解鎖釋放節(jié)點(diǎn)
*/
public void unlock() {
zkClient.delete(ROOT_LOCK.concat("/").concat(context.get()));
}
}
class LockContext {
ThreadLocal<String> lockContext = new ThreadLocal<>();
public String get() {
return lockContext.get();
}
public void set(String seq) {
lockContext.set(seq);
}
}
-
zk里面的目錄結(jié)構(gòu)
image.png