日常開發(fā)中,針對一些需要鎖定資源的操作,例如商城的訂單超賣問題、訂單重復(fù)提交問題等。
都是為了解決在資源有限的情況限制客戶端的訪問,對應(yīng)的是限流。
單節(jié)點(diǎn)鎖問題
目前針對這種鎖資源的情況采取的往往是互斥鎖,例如 java 里的 synchronized 鎖以及?ReentrantLock,其中?synchronized 的加鎖操作在 jvm 層面實(shí)現(xiàn),會有一個鎖升級(偏向鎖、輕量級鎖、重量級鎖)的問題,ReentrantLock 需要手寫代碼實(shí)現(xiàn),底層是 AQS。但是 java 層面的鎖有一個問題,就是只能在一個進(jìn)程中使用,如果跨進(jìn)程就無能為力了,例如應(yīng)用的集群部署,客戶端請求過來后通過負(fù)載均衡策略轉(zhuǎn)發(fā)到對應(yīng)的實(shí)例上。
分布式鎖
鑒于以上單節(jié)點(diǎn)鎖的問題,就需要通過一個中間介質(zhì)來實(shí)現(xiàn)針對需要訪問的資源進(jìn)行一個資源加鎖和釋放操作的問題,目前有如下方式
數(shù)據(jù)庫
將需要訪問的數(shù)據(jù)可以放到數(shù)據(jù)庫的表中,一般存儲的是確保唯一性的業(yè)務(wù)主鍵,在訪問資源時可將業(yè)務(wù)主鍵插入到表中,每次訪問資源前先查詢數(shù)據(jù)庫判斷數(shù)據(jù)是否存在,如果存在表明資源在被訪問,否則就正常處理。
zookeeper
https://zookeeper.apache.org/doc/r3.9.1/recipes.html#sc_recipes_Locks
通過臨時順序節(jié)點(diǎn)(EPHEMERAL_SEQUENTIAL )來實(shí)現(xiàn),這種類型的節(jié)點(diǎn)的好處是會話級別,如果會話結(jié)束節(jié)點(diǎn)會刪除掉。
官方提供的是 curator 組件庫,在項(xiàng)目的 pom.xml 中引入如下依賴
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.5.0</version>
</dependency>
https://curator.apache.org/docs/getting-started#distributed-lock
內(nèi)部提供了一個分布式鎖接口?InterProcessLock,通過對應(yīng)的實(shí)現(xiàn)類?InterProcessMutex 進(jìn)行加鎖和釋放鎖操作。
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.concurrent.TimeUnit;
public class DistributedLock {
public static void main(String[] args) throws InterruptedException {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(10000, 3);
for (int i = 1; i <= 1; i++) {
new Thread(() -> {
try (CuratorFramework client = CuratorFrameworkFactory.newClient(Constants.CONNECTION_URL, retryPolicy);){
client.start();
InterProcessLock lock1 = new InterProcessMutex(client, "/dlock");
try {
lock1.acquire(5, TimeUnit.SECONDS);
System.out.println("lock1獲取");
TimeUnit.SECONDS.sleep(5);
lock1.release();
System.out.println("lock1釋放");
} catch (Throwable e) {
e.printStackTrace();
}
}
}).start();
}
}
}
鎖的組成如下
/父節(jié)點(diǎn)/_c_+UUID+-lock-+10位數(shù)(從0開始自增,不足10位用0補(bǔ)足)
如果在執(zhí)行過程中進(jìn)行了多次加鎖,具體如下
/dlock/_c_0d4db7a9-5c21-4a57-9904-e42cd970d774-lock-0000000000
/dlock/_c_d3588ec1-981a-41aa-a420-89f7949f94d6-lock-0000000001
這樣就會有一個問題,前面的前綴?PROTECTED_PREFIX 和 UUID 會對節(jié)點(diǎn)產(chǎn)生干擾。
https://github.com/apache/curator/blob/apache-curator-5.5.0/curator-framework/src/main/java/org/apache/curator/framework/imps/ProtectedUtils.java#L65
可以將?ProtectedUtils 整個類復(fù)制一遍在項(xiàng)目中,將?getProtectedPrefix() 的內(nèi)容進(jìn)行修改,如下
修改前
public static String getProtectedPrefix(final String protectedId)
{
return PROTECTED_PREFIX + protectedId + PROTECTED_SEPARATOR;
}
修改后
public static String getProtectedPrefix(final String protectedId)
{
return protectedId;
}
這樣生成的節(jié)點(diǎn)格式為
/父節(jié)點(diǎn)/lock-+10位數(shù)(從0開始自增,不足10位用0補(bǔ)足)
最終生成的節(jié)點(diǎn)如下
/dlock/lock-0000000000
其中對應(yīng)的節(jié)點(diǎn)值為當(dāng)前請求的 ip
[zk: localhost:2181(CONNECTED) 2] get /dlock/lock-0000000000
192.168.106.109
如果有多個客戶端針對同一個節(jié)點(diǎn)進(jìn)行加鎖請求,會按序創(chuàng)建多個節(jié)點(diǎn),但是持有鎖的只是最小的節(jié)點(diǎn),后面的節(jié)點(diǎn)會向獲取鎖的節(jié)點(diǎn)注冊?Watcher 來監(jiān)聽持有鎖的節(jié)點(diǎn)是否存在。
redis
作為一個在內(nèi)存層次的數(shù)據(jù)庫,用處多多,其中可以用于分布式鎖。
redis 提供了 lua 腳本支持,lua 腳本可以做到將操作進(jìn)行打包,確保整個操作的原子性。其中分布式鎖就用到了 lua 腳本。
redis 官方介紹
https://redis.io/docs/manual/patterns/distributed-locks/
該鎖被命名為?Redlock,在不同的語言中有對應(yīng)的實(shí)現(xiàn),在 java 中對應(yīng)的是?redisson。
在項(xiàng)目的 pom.xml 中引入如下依賴
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.26.0</version>
</dependency>
github 項(xiàng)目鏈接
https://github.com/redisson/redisson
與 spring boot 整合
https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter#spring-boot-starter
區(qū)別
分布式鎖建立在共享介質(zhì)上,所以上面的三種方式都需要借助于其他組件來實(shí)現(xiàn)。
數(shù)據(jù)庫層面不適合做高并發(fā)處理。
redis 依賴于全局時間,需要考慮到加鎖時間的問題。
zookeeper 建立在創(chuàng)建節(jié)點(diǎn)的基礎(chǔ)上,屬于重操作,相對于 redis 內(nèi)存操作慢一些。
由于?zookeeper 使用了 zab 協(xié)議,針對寫請求會轉(zhuǎn)發(fā)到領(lǐng)導(dǎo)者,如果領(lǐng)導(dǎo)者節(jié)點(diǎn)宕機(jī),會從跟隨者節(jié)點(diǎn)中選舉出數(shù)據(jù)最完整的節(jié)點(diǎn)晉升為領(lǐng)導(dǎo)者,不會出現(xiàn)類似 redis 異步同步數(shù)據(jù)丟失的問題。
對于一些并發(fā)請求大的應(yīng)用,使用 zookeeper 可能出現(xiàn)鎖獲取失敗的情況,使用 redis 集群不會因?yàn)?,因?yàn)?redis 的單線程高并發(fā)處理一般情況下難以達(dá)到,一般瓶頸在網(wǎng)卡帶寬上。
之前自己寫的文章
https://blog.csdn.net/zlpzlpzyd/article/details/132716450
參考鏈接
https://www.jianshu.com/p/31335efec309
https://www.cnblogs.com/xuwc/p/14019932.html
https://juejin.cn/post/6844903729406148622
https://www.cnblogs.com/crazymakercircle/p/14504520.html
https://blog.csdn.net/qq_26709459/article/details/112770526
https://zhuanlan.zhihu.com/p/639756647
https://zhuanlan.zhihu.com/p/383512946
https://www.cnblogs.com/zwj-199306231519/articles/17411947.html文章來源:http://www.zghlxwxcb.cn/news/detail-810790.html
https://baijiahao.baidu.com/s?id=1784900642230670850文章來源地址http://www.zghlxwxcb.cn/news/detail-810790.html
到了這里,關(guān)于分布式鎖的產(chǎn)生以及使用的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!