日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

浅谈分布式锁

發(fā)布時(shí)間:2023/12/10 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 浅谈分布式锁 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

概述

為了防止分布式系統(tǒng)中的多個(gè)進(jìn)程之間相互干擾,我們需要一種分布式協(xié)調(diào)技術(shù)來(lái)對(duì)這些進(jìn)程進(jìn)行調(diào)度。而這個(gè)分布式協(xié)調(diào)技術(shù)的核心就是來(lái)實(shí)現(xiàn)這個(gè)分布式鎖。

為什么要使用分布式鎖

成員變量 A 存在 JVM1、JVM2、JVM3 三個(gè) JVM 內(nèi)存中
成員變量 A 同時(shí)都會(huì)在 JVM 分配一塊內(nèi)存,三個(gè)請(qǐng)求發(fā)過(guò)來(lái)同時(shí)對(duì)這個(gè)變量操作,顯然結(jié)果是不對(duì)的
不是同時(shí)發(fā)過(guò)來(lái),三個(gè)請(qǐng)求分別操作三個(gè)不同 JVM 內(nèi)存區(qū)域的數(shù)據(jù),變量 A 之間不存在共享,也不具有可見(jiàn)性,處理的結(jié)果也是不對(duì)的
注:該成員變量 A 是一個(gè)有狀態(tài)的對(duì)象
如果我們業(yè)務(wù)中確實(shí)存在這個(gè)場(chǎng)景的話(huà),我們就需要一種方法解決這個(gè)問(wèn)題,這就是分布式鎖要解決的問(wèn)題

分布式鎖應(yīng)該具有哪些特點(diǎn)

在分布式系統(tǒng)環(huán)境下,一個(gè)方法在同一時(shí)間只能被一個(gè)機(jī)器的一個(gè)線(xiàn)程執(zhí)行;
高可用的獲取鎖與釋放鎖;
高性能的獲取鎖與釋放鎖;
具備可重入特性;
具備鎖失效機(jī)制,防止死鎖;
具備非阻塞鎖特性,即沒(méi)有獲取到鎖將直接返回獲取鎖失敗。

實(shí)現(xiàn)分布式鎖的N種方式

  • 數(shù)據(jù)庫(kù)實(shí)現(xiàn)排他鎖
  • Redis:和 Memcached 的方式類(lèi)似,利用 Redis 的 setnx 命令。此命令同樣是原子性操作,只有在 key 不存在的情況下,才能 set 成功。
  • Zookeeper:利用 Zookeeper 的順序臨時(shí)節(jié)點(diǎn),來(lái)實(shí)現(xiàn)分布式鎖和等待隊(duì)列。Zookeeper 設(shè)計(jì)的初衷,就是為了實(shí)現(xiàn)分布式鎖服務(wù)的。
  • Memcached:利用 Memcached 的 add 命令。此命令是原子性操作,只有在 key 不存在的情況下,才能 add 成功,也就意味著線(xiàn)程得到了鎖。
  • Chubby:Google 公司實(shí)現(xiàn)的粗粒度分布式鎖服務(wù),底層利用了 Paxos 一致性算法。
    接下來(lái),介紹三種常用的方式
  • 1.基于Mysql實(shí)現(xiàn)分布式鎖

    表結(jié)構(gòu)

    -- auto-generated definition create table distributed_lock (id int auto_incrementprimary key,method_name varchar(100) not null comment '獲取鎖的方法名',remark varchar(100) null comment '備注信息',status int not null comment '分配狀態(tài): 1-未分配,2-已分配',update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP,version int not null comment '版本號(hào)',constraint uidx_method_nameunique (method_name) )comment '分布式鎖表';

    先獲取鎖的信息

    select id, method_name, status,version from distributed_lock where status=1 and method_name='methodName';```### 占有鎖

    update t_resoure set status=2, version=2, update_time=now() where method_name=‘methodName’ and status=1 and version=2;```

    如果沒(méi)有更新影響到一行數(shù)據(jù),則說(shuō)明這個(gè)資源已經(jīng)被別人占位了。
    以上也是cap理論的悲觀(guān)鎖,在數(shù)據(jù)庫(kù)上面的應(yīng)用。

    缺點(diǎn):

    1、這把鎖強(qiáng)依賴(lài)數(shù)據(jù)庫(kù)的可用性,數(shù)據(jù)庫(kù)是一個(gè)單點(diǎn),一旦數(shù)據(jù)庫(kù)掛掉,會(huì)導(dǎo)致業(yè)務(wù)系統(tǒng)不可用。
    2、這把鎖沒(méi)有失效時(shí)間,一旦解鎖操作失敗,就會(huì)導(dǎo)致鎖記錄一直在數(shù)據(jù)庫(kù)中,其他線(xiàn)程無(wú)法再獲得到鎖。
    3、這把鎖只能是非阻塞的,因?yàn)閿?shù)據(jù)的insert操作,一旦插入失敗就會(huì)直接報(bào)錯(cuò)。沒(méi)有獲得鎖的線(xiàn)程并不會(huì)進(jìn)入排隊(duì)隊(duì)列,要想再次獲得鎖就要再次觸發(fā)獲得鎖操作。
    4、這把鎖是非重入的,同一個(gè)線(xiàn)程在沒(méi)有釋放鎖之前無(wú)法再次獲得該鎖。因?yàn)閿?shù)據(jù)中數(shù)據(jù)已經(jīng)存在了。

    解決方案:

  • 數(shù)據(jù)庫(kù)是單點(diǎn)?搞兩個(gè)數(shù)據(jù)庫(kù),數(shù)據(jù)之前雙向同步。一旦掛掉快速切換到備庫(kù)上。

  • 沒(méi)有失效時(shí)間?只要做一個(gè)定時(shí)任務(wù),每隔一定時(shí)間把數(shù)據(jù)庫(kù)中的超時(shí)數(shù)據(jù)清理一遍。

  • 非阻塞的?搞一個(gè)while循環(huán),直到insert成功再返回成功。

  • 非重入的?在數(shù)據(jù)庫(kù)表中加個(gè)字段,記錄當(dāng)前獲得鎖的機(jī)器的主機(jī)信息和線(xiàn)程信息,那么下次再獲取鎖的時(shí)候先查詢(xún)數(shù)據(jù)庫(kù),如果當(dāng)前機(jī)器的主機(jī)信息和線(xiàn)程信息在數(shù)據(jù)庫(kù)可以查到的話(huà),直接把鎖分配給他就可以了。

  • 2.基于Redis實(shí)現(xiàn)分布式鎖

    首先要弄清楚幾個(gè)redis命令的概念

    setnx()

    setnx 的含義就是 SET if Not Exists,其主要有兩個(gè)參數(shù) setnx(key, value)。該方法是原子的,如果 key 不存在,則設(shè)置當(dāng)前 key 成功,返回 1;如果當(dāng)前 key 已經(jīng)存在,則設(shè)置當(dāng)前 key 失敗,返回 0。

    expire()

    expire 設(shè)置過(guò)期時(shí)間,要注意的是 setnx 命令不能設(shè)置 key 的超時(shí)時(shí)間,只能通過(guò) expire() 來(lái)對(duì) key 設(shè)置。

    getset()

    這個(gè)命令主要有兩個(gè)參數(shù) getset(key,newValue)。該方法是原子的,對(duì) key 設(shè)置 newValue 這個(gè)值,并且返回 key 原來(lái)的舊值。假設(shè) key 原來(lái)是不存在的,那么多次執(zhí)行這個(gè)命令,會(huì)出現(xiàn)下邊的效果:
    getset(key, “value1”) 返回 null 此時(shí) key 的值會(huì)被設(shè)置為 value1
    getset(key, “value2”) 返回 value1 此時(shí) key 的值會(huì)被設(shè)置為 value2
    依次類(lèi)推!

    使用步驟:

    1.setnx(lockkey, 當(dāng)前時(shí)間 過(guò)期超時(shí)時(shí)間),如果返回 1,則獲取鎖成功;如果返回 0 則沒(méi)有獲取到鎖,轉(zhuǎn)向 2。

    2.get(lockkey) 獲取值 oldExpireTime ,并將這個(gè) value 值與當(dāng)前的系統(tǒng)時(shí)間進(jìn)行比較,如果小于當(dāng)前系統(tǒng)時(shí)間,則認(rèn)為這個(gè)鎖已經(jīng)超時(shí),可以允許別的請(qǐng)求重新獲取,轉(zhuǎn)向 3。

    3.計(jì)算 newExpireTime = 當(dāng)前時(shí)間 過(guò)期超時(shí)時(shí)間,然后 getset(lockkey, newExpireTime) 會(huì)返回當(dāng)前 lockkey 的值currentExpireTime。

    4.判斷 currentExpireTime 與 oldExpireTime 是否相等,如果相等,說(shuō)明當(dāng)前 getset 設(shè)置成功,獲取到了鎖。如果不相等,說(shuō)明這個(gè)鎖又被別的請(qǐng)求獲取走了,那么當(dāng)前請(qǐng)求可以直接返回失敗,或者繼續(xù)重試。

    5.在獲取到鎖之后,當(dāng)前線(xiàn)程可以開(kāi)始自己的業(yè)務(wù)處理,當(dāng)處理完畢后,比較自己的處理時(shí)間和對(duì)于鎖設(shè)置的超時(shí)時(shí)間,如果小于鎖設(shè)置的超時(shí)時(shí)間,則直接執(zhí)行 delete 釋放鎖;如果大于鎖設(shè)置的超時(shí)時(shí)間,則不需要再鎖進(jìn)行處理。

    話(huà)不多說(shuō),上代碼:

    public final class RedisLockUtil { ?private static final int defaultExpire = 60; ?private RedisLockUtil() {//} ?/*** 加鎖* @param key redis key* @param expire 過(guò)期時(shí)間,單位秒* @return true:加鎖成功,false,加鎖失敗*/public static boolean lock(String key, int expire) { ?RedisService redisService = SpringUtils.getBean(RedisService.class);long status = redisService.setnx(key, "1"); ?if(status == 1) {redisService.expire(key, expire);return true;} ?return false;} ?public static boolean lock(String key) {return lock2(key, defaultExpire);} ?/*** 加鎖* @param key redis key* @param expire 過(guò)期時(shí)間,單位秒* @return true:加鎖成功,false,加鎖失敗*/public static boolean lock2(String key, int expire) { ?RedisService redisService = SpringUtils.getBean(RedisService.class); ?long value = System.currentTimeMillis() expire;long status = redisService.setnx(key, String.valueOf(value)); ?if(status == 1) {return true;}long oldExpireTime = Long.parseLong(redisService.get(key, "0"));if(oldExpireTime < System.currentTimeMillis()) {//超時(shí)long newExpireTime = System.currentTimeMillis() expire;long currentExpireTime = Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));if(currentExpireTime == oldExpireTime) {return true;}}return false;} ?public static void unLock1(String key) {RedisService redisService = SpringUtils.getBean(RedisService.class);redisService.del(key);} ?public static void unLock2(String key) { RedisService redisService = SpringUtils.getBean(RedisService.class); long oldExpireTime = Long.parseLong(redisService.get(key, "0")); if(oldExpireTime > System.currentTimeMillis()) { redisService.del(key); }} }

    3.基于Zookeeper實(shí)現(xiàn)分布式鎖

    首先,我們先來(lái)看看zookeeper的相關(guān)知識(shí)。

    zk 一般由多個(gè)節(jié)點(diǎn)構(gòu)成(單數(shù)),采用 zab 一致性協(xié)議。因此可以將 zk 看成一個(gè)單點(diǎn)結(jié)構(gòu),對(duì)其修改數(shù)據(jù)其內(nèi)部自動(dòng)將所有節(jié)點(diǎn)數(shù)據(jù)進(jìn)行修改而后才提供查詢(xún)服務(wù)。

    zk 的數(shù)據(jù)以目錄樹(shù)的形式,每個(gè)目錄稱(chēng)為 znode, znode 中可存儲(chǔ)數(shù)據(jù)(一般不超過(guò) 1M),還可以在其中增加子節(jié)點(diǎn)。
    子節(jié)點(diǎn)有三種類(lèi)型。序列化節(jié)點(diǎn),每在該節(jié)點(diǎn)下增加一個(gè)節(jié)點(diǎn)自動(dòng)給該節(jié)點(diǎn)的名稱(chēng)上自增。臨時(shí)節(jié)點(diǎn),一旦創(chuàng)建這個(gè) znode 的客戶(hù)端與服務(wù)器失去聯(lián)系,這個(gè) znode 也將自動(dòng)刪除。最后就是普通節(jié)點(diǎn)。
    Watch 機(jī)制,client 可以監(jiān)控每個(gè)節(jié)點(diǎn)的變化,當(dāng)產(chǎn)生變化會(huì)給 client 產(chǎn)生一個(gè)事件。

    zookeeper基本鎖:

    **原理:**利用臨時(shí)節(jié)點(diǎn)與 watch 機(jī)制。每個(gè)鎖占用一個(gè)普通節(jié)點(diǎn) /lock,當(dāng)需要獲取鎖時(shí)在 /lock 目錄下創(chuàng)建一個(gè)臨時(shí)節(jié)點(diǎn),創(chuàng)建成功則表示獲取鎖成功,失敗則 watch/lock 節(jié)點(diǎn),有刪除操作后再去爭(zhēng)鎖。臨時(shí)節(jié)點(diǎn)好處在于當(dāng)進(jìn)程掛掉后能自動(dòng)上鎖的節(jié)點(diǎn)自動(dòng)刪除即取消鎖。

    **缺點(diǎn):**所有取鎖失敗的進(jìn)程都監(jiān)聽(tīng)父節(jié)點(diǎn),很容易發(fā)生羊群效應(yīng),即當(dāng)釋放鎖后所有等待進(jìn)程一起來(lái)創(chuàng)建節(jié)點(diǎn),并發(fā)量很大。

    zookeeper分布式鎖實(shí)現(xiàn):

    **原理:**上鎖改為創(chuàng)建臨時(shí)有序節(jié)點(diǎn),每個(gè)上鎖的節(jié)點(diǎn)均能創(chuàng)建節(jié)點(diǎn)成功,只是其序號(hào)不同。只有序號(hào)最小的可以擁有鎖,如果這個(gè)節(jié)點(diǎn)序號(hào)不是最小的則 watch 序號(hào)比本身小的前一個(gè)節(jié)點(diǎn) (公平鎖)。

    步驟:

  • 在 /lock 節(jié)點(diǎn)下創(chuàng)建一個(gè)有序臨時(shí)節(jié)點(diǎn) (EPHEMERAL_SEQUENTIAL)。

  • 判斷創(chuàng)建的節(jié)點(diǎn)序號(hào)是否最小,如果是最小則獲取鎖成功。不是則取鎖失敗,然后 watch 序號(hào)比本身小的前一個(gè)節(jié)點(diǎn)。

  • 當(dāng)取鎖失敗,設(shè)置 watch 后則等待 watch 事件到來(lái)后,再次判斷是否序號(hào)最小。

  • 取鎖成功則執(zhí)行代碼,最后釋放鎖(刪除該節(jié)點(diǎn))。

  • 代碼如下:

    public final class RedisLockUtil { ?private static final int defaultExpire = 60; ?private RedisLockUtil() {//} ?/*** 加鎖* @param key redis key* @param expire 過(guò)期時(shí)間,單位秒* @return true:加鎖成功,false,加鎖失敗*/public static boolean lock(String key, int expire) { ?RedisService redisService = SpringUtils.getBean(RedisService.class);long status = redisService.setnx(key, "1"); ?if(status == 1) {redisService.expire(key, expire);return true;} ?return false;} ?public static boolean lock(String key) {return lock2(key, defaultExpire);} ?/*** 加鎖* @param key redis key* @param expire 過(guò)期時(shí)間,單位秒* @return true:加鎖成功,false,加鎖失敗*/public static boolean lock2(String key, int expire) { ?RedisService redisService = SpringUtils.getBean(RedisService.class); ?long value = System.currentTimeMillis() expire;long status = redisService.setnx(key, String.valueOf(value)); ?if(status == 1) {return true;}long oldExpireTime = Long.parseLong(redisService.get(key, "0"));if(oldExpireTime < System.currentTimeMillis()) {//超時(shí)long newExpireTime = System.currentTimeMillis() expire;long currentExpireTime = Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));if(currentExpireTime == oldExpireTime) {return true;}}return false;} ?public static void unLock1(String key) {RedisService redisService = SpringUtils.getBean(RedisService.class);redisService.del(key);} ?public static void unLock2(String key) { RedisService redisService = SpringUtils.getBean(RedisService.class); long oldExpireTime = Long.parseLong(redisService.get(key, "0")); if(oldExpireTime > System.currentTimeMillis()) { redisService.del(key); }} }

    優(yōu)缺點(diǎn):

    優(yōu)點(diǎn):

    有效的解決單點(diǎn)問(wèn)題,不可重入問(wèn)題,非阻塞問(wèn)題以及鎖無(wú)法釋放的問(wèn)題。實(shí)現(xiàn)起來(lái)較為簡(jiǎn)單。

    缺點(diǎn):

    性能上可能并沒(méi)有緩存服務(wù)那么高,因?yàn)槊看卧趧?chuàng)建鎖和釋放鎖的過(guò)程中,都要?jiǎng)討B(tài)創(chuàng)建、銷(xiāo)毀臨時(shí)節(jié)點(diǎn)來(lái)實(shí)現(xiàn)鎖功能。ZK 中創(chuàng)建和刪除節(jié)點(diǎn)只能通過(guò) Leader 服務(wù)器來(lái)執(zhí)行,然后將數(shù)據(jù)同步到所有的 Follower 機(jī)器上。還需要對(duì) ZK的原理有所了解。

    三種方案的比較

    上面幾種方式,哪種方式都無(wú)法做到完美。就像CAP一樣,在復(fù)雜性、可靠性、性能等方面無(wú)法同時(shí)滿(mǎn)足,所以,根據(jù)不同的應(yīng)用場(chǎng)景選擇最適合自己的才是王道。

    從理解的難易程度角度(從低到高)
    數(shù)據(jù)庫(kù) > 緩存 > Zookeeper

    從實(shí)現(xiàn)的復(fù)雜性角度(從低到高)
    Zookeeper >= 緩存 > 數(shù)據(jù)庫(kù)

    從性能角度(從高到低)
    緩存 > Zookeeper >= 數(shù)據(jù)庫(kù)

    從可靠性角度(從高到低)
    Zookeeper > 緩存 > 數(shù)據(jù)庫(kù)

    獲取更多互聯(lián)網(wǎng)知識(shí)請(qǐng)關(guān)注公眾號(hào):

    總結(jié)

    以上是生活随笔為你收集整理的浅谈分布式锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。