分布式鎖用Redis好?還是Zookeeper好?
點(diǎn)擊上方“碼農(nóng)突圍”,馬上關(guān)注
這里是碼農(nóng)充電第一站,回復(fù)“666”,獲取一份專屬大禮包 真愛,請(qǐng)?jiān)O(shè)置“星標(biāo)”或點(diǎn)個(gè)“在看

Redis 實(shí)現(xiàn) 基于 Zookeeper 來實(shí)現(xiàn)分布式鎖 總結(jié)



Redis 實(shí)現(xiàn)
SETNX key value 命令,意為 set if not exists(如果不存在該 key,才去 set 值),就比如說是張三去上廁所,看廁所門鎖著,他就不進(jìn)去了,廁所門開著他才去。

SETEX key seconds value 命令,為指定 key 設(shè)置過期時(shí)間,單位為秒。SET key value ex seconds nx,加鎖的同時(shí)設(shè)置過期時(shí)間。
//基于jedis和lua腳本來實(shí)現(xiàn)
privatestaticfinal?String?LOCK_SUCCESS?=?"OK";
privatestaticfinal?Long?RELEASE_SUCCESS?=?1L;
privatestaticfinal?String?SET_IF_NOT_EXIST?=?"NX";
privatestaticfinal?String?SET_WITH_EXPIRE_TIME?=?"PX";
?
@Override
public?String?acquire()?{
????try?{
????????//?獲取鎖的超時(shí)時(shí)間,超過這個(gè)時(shí)間則放棄獲取鎖
????????long?end?=?System.currentTimeMillis()?+?acquireTimeout;
????????//?隨機(jī)生成一個(gè)?value
????????String?requireToken?=?UUID.randomUUID().toString();
????????while?(System.currentTimeMillis()?????????????String?result?=?jedis
????????????????.set(lockKey,?requireToken,?SET_IF_NOT_EXIST,?SET_WITH_EXPIRE_TIME,?expireTime);
????????????if?(LOCK_SUCCESS.equals(result))?{
????????????????return?requireToken;
????????????}
????????????try?{
????????????????Thread.sleep(100);
????????????}?catch?(InterruptedException?e)?{
????????????????Thread.currentThread().interrupt();
????????????}
????????}
????}?catch?(Exception?e)?{
????????log.error("acquire?lock?due?to?error",?e);
????}
?
????returnnull;
}
?
@Override
public?boolean?release(String?identify)?{
????if?(identify?==?null)?{
????????returnfalse;
????}
????//通過lua腳本進(jìn)行比對(duì)刪除操作,保證原子性
????String?script?=?"if?redis.call('get',?KEYS[1])?==?ARGV[1]?then?return?redis.call('del',?KEYS[1])?else?return?0?end";
????Object?result?=?new?Object();
????try?{
????????result?=?jedis.eval(script,?Collections.singletonList(lockKey),
????????????Collections.singletonList(identify));
????????if?(RELEASE_SUCCESS.equals(result))?{
????????????log.info("release?lock?success,?requestToken:{}",?identify);
????????????returntrue;
????????}
????}?catch?(Exception?e)?{
????????log.error("release?lock?due?to?error",?e);
????}?finally?{
????????if?(jedis?!=?null)?{
????????????jedis.close();
????????}
????}
?
????log.info("release?lock?failed,?requestToken:{},?result:{}",?identify,?result);
????returnfalse;
}
思考:加鎖和釋放鎖的原子性可以用 lua 腳本來保證,那鎖的自動(dòng)續(xù)期改如何實(shí)現(xiàn)呢?
????org.redisson
????redisson
????3.13.4
private?void?test()?{
????//分布式鎖名??鎖的粒度越細(xì),性能越好
????RLock?lock?=?redissonClient.getLock("test_lock");
????lock.lock();
????try?{
????????//具體業(yè)務(wù)......
????}?finally?{
????????lock.unlock();
????}
}

//?最常見的使用方法
lock.lock();
?
//?加鎖以后10秒鐘自動(dòng)解鎖
//?無需調(diào)用unlock方法手動(dòng)解鎖
lock.lock(10,?TimeUnit.SECONDS);





小結(jié):雖然 lock() 有自動(dòng)續(xù)鎖機(jī)制,但是開發(fā)中還是推薦使用 lock(time,timeUnit),因?yàn)樗〉袅苏麄€(gè)續(xù)期帶來的性能損,可以設(shè)置過期時(shí)間長(zhǎng)一點(diǎn),搭配unlock()。
public?void?test()?{
????RLock?lock?=?redissonClient.getLock("test_lock");
????lock.lock(30,?TimeUnit.SECONDS);
????try?{
????????//.......具體業(yè)務(wù)
????}?finally?{
????????//手動(dòng)釋放鎖
????????lock.unlock();
????}
}
基于 Zookeeper 來實(shí)現(xiàn)分布式鎖
create [-s] [-e] path [data] 命令,-s 為創(chuàng)建有序節(jié)點(diǎn),-e 創(chuàng)建臨時(shí)節(jié)點(diǎn)。
ls [-w] path 為查看節(jié)點(diǎn)命令,-w 為添加一個(gè) watch(監(jiān)視器),/ 為查看根節(jié)點(diǎn)所有節(jié)點(diǎn),可以看到我們剛才所創(chuàng)建的節(jié)點(diǎn),同時(shí)如果是跟著指定節(jié)點(diǎn)名字的話為查看指定節(jié)點(diǎn)下的子節(jié)點(diǎn)。

當(dāng)?shù)谝粋€(gè)線程進(jìn)來時(shí)會(huì)去父節(jié)點(diǎn)上創(chuàng)建一個(gè)臨時(shí)的順序節(jié)點(diǎn)。 第二個(gè)線程進(jìn)來發(fā)現(xiàn)鎖已經(jīng)被持有了,就會(huì)為當(dāng)前持有鎖的節(jié)點(diǎn)注冊(cè)一個(gè) watcher 監(jiān)聽器。 第三個(gè)線程進(jìn)來發(fā)現(xiàn)鎖已經(jīng)被持有了,因?yàn)槭琼樞蚬?jié)點(diǎn)的緣故,就會(huì)為上一個(gè)節(jié)點(diǎn)去創(chuàng)建一個(gè) watcher 監(jiān)聽器。 當(dāng)?shù)谝粋€(gè)線程釋放鎖后,刪除節(jié)點(diǎn),由它的下一個(gè)節(jié)點(diǎn)去占有鎖。

public?class?ZooKeeperDistributedLock?implements?Watcher?{
?
????private?ZooKeeper?zk;
????private?String?locksRoot?=?"/locks";
????private?String?productId;
????private?String?waitNode;
????private?String?lockNode;
????private?CountDownLatch?latch;
????private?CountDownLatch?connectedLatch?=?new?CountDownLatch(1);
????private?int?sessionTimeout?=?30000;
?
????public?ZooKeeperDistributedLock(String?productId)?{
????????this.productId?=?productId;
????????try?{
????????????String?address?=?"192.168.189.131:2181,192.168.189.132:2181";
????????????zk?=?new?ZooKeeper(address,?sessionTimeout,?this);
????????????connectedLatch.await();
????????}?catch?(IOException?e)?{
????????????throw?new?LockException(e);
????????}?catch?(KeeperException?e)?{
????????????throw?new?LockException(e);
????????}?catch?(InterruptedException?e)?{
????????????throw?new?LockException(e);
????????}
????}
?
????public?void?process(WatchedEvent?event)?{
????????if?(event.getState()?==?KeeperState.SyncConnected)?{
????????????connectedLatch.countDown();
????????????return;
????????}
?
????????if?(this.latch?!=?null)?{
????????????this.latch.countDown();
????????}
????}
?
????public?void?acquireDistributedLock()?{
????????try?{
????????????if?(this.tryLock())?{
????????????????return;
????????????}?else?{
????????????????waitForLock(waitNode,?sessionTimeout);
????????????}
????????}?catch?(KeeperException?e)?{
????????????throw?new?LockException(e);
????????}?catch?(InterruptedException?e)?{
????????????throw?new?LockException(e);
????????}
????}
????//獲取鎖
????public?boolean?tryLock()?{
????????try?{
????????//?傳入進(jìn)去的locksRoot?+?“/”?+?productId
????????//?假設(shè)productId代表了一個(gè)商品id,比如說1
????????//?locksRoot?=?locks
????????//?/locks/10000000000,/locks/10000000001,/locks/10000000002
????????lockNode?=?zk.create(locksRoot?+?"/"?+?productId,?new?byte[0],?ZooDefs.Ids.OPEN_ACL_UNSAFE,?CreateMode.EPHEMERAL_SEQUENTIAL);
?
????????//?看看剛創(chuàng)建的節(jié)點(diǎn)是不是最小的節(jié)點(diǎn)
????????// locks:10000000000,10000000001,10000000002
????????List?locks?=?zk.getChildren(locksRoot,?false);
????????Collections.sort(locks);
?
????????if(lockNode.equals(locksRoot+"/"+?locks.get(0))){
????????????//如果是最小的節(jié)點(diǎn),則表示取得鎖
????????????return?true;
????????}
?
????????//如果不是最小的節(jié)點(diǎn),找到比自己小1的節(jié)點(diǎn)
??????int?previousLockIndex?=?-1;
????????????for(int?i?=?0;?i?????????if(lockNode.equals(locksRoot?+?“/”?+?locks.get(i)))?{
????????????????????previousLockIndex?=?i?-?1;
????????????break;
????????}
???????}
?
???????this.waitNode?=?locks.get(previousLockIndex);
????????}?catch?(KeeperException?e)?{
????????????throw?new?LockException(e);
????????}?catch?(InterruptedException?e)?{
????????????throw?new?LockException(e);
????????}
????????return?false;
????}
?
????private?boolean?waitForLock(String?waitNode,?long?waitTime)?throws?InterruptedException,?KeeperException?{
????????Stat?stat?=?zk.exists(locksRoot?+?"/"?+?waitNode,?true);
????????if?(stat?!=?null)?{
????????????this.latch?=?new?CountDownLatch(1);
????????????this.latch.await(waitTime,?TimeUnit.MILLISECONDS);
????????????this.latch?=?null;
????????}
????????return?true;
????}
?
????//釋放鎖
????public?void?unlock()?{
????????try?{
????????????System.out.println("unlock?"?+?lockNode);
????????????zk.delete(lockNode,?-1);
????????????lockNode?=?null;
????????????zk.close();
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}?catch?(KeeperException?e)?{
????????????e.printStackTrace();
????????}
????}
????//異常
????public?class?LockException?extends?RuntimeException?{
????????private?static?final?long?serialVersionUID?=?1L;
?
????????public?LockException(String?e)?{
????????????super(e);
????????}
?
????????public?LockException(Exception?e)?{
????????????super(e);
????????}
????}
}
總結(jié)
實(shí)現(xiàn)方式的不同,Redis 實(shí)現(xiàn)為去插入一條占位數(shù)據(jù),而 ZK 實(shí)現(xiàn)為去注冊(cè)一個(gè)臨時(shí)節(jié)點(diǎn)。 遇到宕機(jī)情況時(shí),Redis 需要等到過期時(shí)間到了后自動(dòng)釋放鎖,而 ZK 因?yàn)槭桥R時(shí)節(jié)點(diǎn),在宕機(jī)時(shí)候已經(jīng)是刪除了節(jié)點(diǎn)去釋放鎖。 Redis 在沒搶占到鎖的情況下一般會(huì)去自旋獲取鎖,比較浪費(fèi)性能,而 ZK 是通過注冊(cè)監(jiān)聽器的方式獲取鎖,性能而言優(yōu)于 Redis。
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?面試題?資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!
點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取
評(píng)論
圖片
表情

