最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

聊聊Java三種常見的分布式鎖

 更新時間:2023年06月28日 11:36:49   作者:橘子的后端面試手記  
目前分布式鎖的實現(xiàn)方案主要包括三種,本文就來介紹一下這三種常見的分布式鎖以及這三種鎖的性能等,具有一定的參考價值,感興趣的可以了解一下

平時我們在 APP 上搶某件商品時,手指瘋狂點擊下單,但是只會生成一個訂單,為什么呢?

因為對用戶下單加了一個鎖,避免用戶重復(fù)下單。而在分布式場景,訂單可以看作一個共享資源,所以這個鎖可以叫做分布式鎖。

一般來講,分布式鎖有三種實現(xiàn)方式:

  • 基于數(shù)據(jù)庫的分布式鎖
  • 基于 redis 的分布式鎖
  • 基于 Zookeeper 的分布式鎖

1 基于數(shù)據(jù)庫的分布式鎖

基于數(shù)據(jù)庫實現(xiàn)分布式鎖有很多實現(xiàn),這里介紹其中一種。其核心思想是:

在數(shù)據(jù)庫中創(chuàng)建一個表,表中添加 方法名 等字段,并 對方法名字段添加唯一索引,如果要對某個方法加鎖,則使用這個方法名向表中插入數(shù)據(jù),成功插入則加鎖成功,方法執(zhí)行完后刪除對應(yīng)的行數(shù)據(jù)釋放鎖。[1]

1.1 創(chuàng)建表

CREATE TABLE `t_database_lock` (
  `id` bigint NOT NULL COMMENT '主鍵',
  `method_name` varchar(50) COLLATE utf8_bin NOT NULL DEFAULT '方法名字',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最近更新時間'
  PRIMARY KEY(`id`),
  UNIQUE INDEX `uk_t_database_lock_mname`(`method_name`) USING BTREE COMMENT '方法名唯一索引',
  INDEX `idx_t_accounting_balance_account_utime`(`update_time`) USING BTREE COMMENT '更新時間索引',
  INDEX `idx_t_accounting_balance_account_ctime`(`create_time`) USING BTREE COMMENT '創(chuàng)建時間索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin

1.2 偽代碼實現(xiàn)

主流程如下:

public void execute(String methodName) {
    try {
        if (lock(methodName)) { // 加鎖
            // 你的業(yè)務(wù)邏輯
            run();
        }
    } finally {
        unlock(); // 釋放鎖
    }
}

lock() 方法的實現(xiàn)的偽代碼如下:

private boolean lock(String methodName) {
    try {
        // 插入一條數(shù)據(jù)
        int count = "INSERT INTO method_lock (method_name, desc) VALUES ('methodName', '需要加鎖的methodName');";
        if (count == 1) { // 1 表示插入成功
            return true;
        }
        return false;
    } catch (Exception e) {
        return false;
    }
    return false;
}

unlock() 方法實現(xiàn)的偽代碼如下:

private boolean lock(String methodName) {
    try {
        // 插入一條數(shù)據(jù)
        int count = "INSERT INTO method_lock (method_name, desc) VALUES ('methodName', '需要加鎖的methodName');";
        if (count == 1) { // 1 表示插入成功
            return true;
        }
        return false;
    } catch (Exception e) {
        return false;
    }
    return false;
}

它的優(yōu)點是實現(xiàn)簡單,但是有以下缺點:

  • 讀取寫入性能低,資源開銷較大,不適合并發(fā)量很高的場景。
  • 可靠性較低,因為數(shù)據(jù)庫的可用性會直接影響到分布式鎖的可用性,所以數(shù)據(jù)庫需要雙機部署、數(shù)據(jù)同步、主備切換。
  • 沒有鎖失效機制。如果在成功插入一條數(shù)據(jù)后,服務(wù)器宕機了,導(dǎo)致這條數(shù)據(jù)沒有刪除,服務(wù)恢復(fù)后一直獲取不到鎖。解決方式是可以在表中新增一例失效時間,定時清除這些失效數(shù)據(jù)。
  • 鎖邏輯簡單,不具備阻塞鎖等高級功能。

2 基于 redis 的分布式鎖

redis 分布式鎖的好處是性能好,實現(xiàn)也較為方便。

2.1 setnx + expire

  • setnx 即 Set if Not Exists,如果 redis 存在這個 key,setnx 返回0,加鎖失敗;否則返回1,加鎖成功。
  • expire 用來設(shè)置 redis key 的過期時間,避免這個鎖一直存在,造成無法加鎖的問題。

實現(xiàn)偽代碼如下:

if (jedis.setnx(key) == 1) { // 加鎖成功
    expire(key, 100);
    try {
        doSomething(); //業(yè)務(wù)處理
    } catch (Exception e) {
    } finally {
        jedis.del(key); // 釋放鎖
    }
}

這個是基本的實現(xiàn),但是會有一個問題:如果 setnx 執(zhí)行結(jié)束,進(jìn)程中斷了,沒有執(zhí)行 expire 代碼,導(dǎo)致 key 沒有設(shè)置過期時間,別的線程就永遠(yuǎn)獲取不到這個鎖了。

2.2 setnx 同時設(shè)置過期時間

基于以上問題,我們可以將 redis 的 value 利用起來,將 jedis.setnx(key) 改為 jedis.setnx(key, ${過期時間}),加鎖的偽代碼如下[2]:

public boolean lock(String key, Long expireTime) {
    Jedis jedis = new Jedis();
    //系統(tǒng)時間 + 設(shè)置的過期時間
    long expires = System.currentTimeMillis() + expireTime;
    String expiresStr = String.valueOf(expires);
    // 如果當(dāng)前鎖不存在,返回加鎖成功
    if (jedis.setnx(key, expiresStr) == 1) {
        return true;
    }
    // 如果鎖已經(jīng)存在,獲取鎖的過期時間
    String currentValueStr = jedis.get(key);
    // 鎖過期
    if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
        // 鎖已過期,獲取上一個鎖的過期時間,并設(shè)置現(xiàn)在鎖的過期時間
        String oldValueStr = jedis.getSet(key, expiresStr);
        if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
            // 只有上一個鎖的過期時間和當(dāng)前過期時間相同才可加鎖
            return true;
        }
    }
    return false;
}

以上的實現(xiàn)方式避免了鎖一直存在的問題,但是仍然有缺點:

  • 過期時間使用 System.currentTimeMillis() 生成,意味著客戶端時間必須同步。
  • 沒有辨別客戶端的機制,可能 A 機器加了鎖,被 B 機器解鎖了。
  • 并發(fā)情況下,多個機器同時加鎖,只有一個會加鎖成功。

2.3 將 redis value 設(shè)置為客戶端標(biāo)識

通過以上分析,我們既希望 redis 設(shè)置 key 和 expire 同時進(jìn)行,又希望 redis 加鎖解鎖時能識別客戶端。

因此我們將客戶端加鎖時,生成一個唯一 ID 作為 redis 的 value,在進(jìn)行鎖操作時先校驗一下客戶端是否一致,在進(jìn)行鎖操作。redis 的 set() 方法可以滿足要求。

流程的偽代碼如下:

public void execute() {
    Jedis jedis = new Jedis();
    String key = "key";
    String uuid = "uuid";
    if ("1".equals(jedis.set(key, uuid, "NX", "EX", 100))) { //加鎖
        try {
            doSomething(); //業(yè)務(wù)處理
        } finally {
            //判斷是不是當(dāng)前線程加的鎖,是才釋放
            if (uuid.equals(jedis.get(key))) {
                jedis.del(key); //釋放鎖
            }
        }
    }
}

以上實現(xiàn)已經(jīng)不錯了,可以滿足大部分場景。但是還有一個問題:過期時間是個不確定因素,你很難設(shè)置一個合適的過期時間,使得鎖既在業(yè)務(wù)執(zhí)行后釋放,又不至于太長導(dǎo)致其他線程獲取失敗。

2.4 Redisson

Redisson 可以解決上面的問題,它的原理是在加鎖成功后啟動一個看門狗,在線程持有鎖期間不斷延長 key 的生存時間,如此一來可解決鎖在業(yè)務(wù)執(zhí)行完之前就釋放的問題。

3 Zookeeper 分布式鎖

當(dāng)使用 ZooKeeper 實現(xiàn)分布式鎖時,可以按照以下步驟進(jìn)行操作:

  • 每個客戶端連接到 ZooKeeper 服務(wù)器。
  • 每個客戶端在 ZooKeeper 上創(chuàng)建一個臨時順序節(jié)點作為鎖節(jié)點。
  • 客戶端獲取鎖的方式是判斷自己創(chuàng)建的節(jié)點是否是當(dāng)前所有鎖節(jié)點中最小的。
  • 如果客戶端創(chuàng)建的節(jié)點是最小節(jié)點,表示該客戶端獲得了鎖,可以執(zhí)行關(guān)鍵區(qū)域的代碼。
  • 如果客戶端創(chuàng)建的節(jié)點不是最小節(jié)點,則需要監(jiān)聽比自己創(chuàng)建節(jié)點次小的節(jié)點。
  • 當(dāng)監(jiān)聽到次小節(jié)點被刪除時,客戶端重新檢查自己創(chuàng)建的節(jié)點是否是當(dāng)前所有鎖節(jié)點中最小的。如果是,則獲得了鎖,可以執(zhí)行關(guān)鍵區(qū)域的代碼。
  • 當(dāng)客戶端完成了關(guān)鍵區(qū)域的操作后,釋放鎖,即刪除自己創(chuàng)建的節(jié)點。

這樣,通過 ZooKeeper 的臨時順序節(jié)點和監(jiān)聽機制,可以實現(xiàn)一個簡單的分布式鎖。每個客戶端在創(chuàng)建節(jié)點時,會根據(jù)節(jié)點名稱的順序來確定是否獲得了鎖。如果創(chuàng)建的節(jié)點是最小節(jié)點,則表示該客戶端獲得了鎖,可以執(zhí)行關(guān)鍵區(qū)域的代碼。其他客戶端則通過監(jiān)聽比自己創(chuàng)建的節(jié)點次小的節(jié)點來等待鎖的釋放,從而實現(xiàn)分布式的互斥訪問。

總結(jié)

三種分布式鎖對比

數(shù)據(jù)庫分布式鎖實現(xiàn)

優(yōu)點:簡單,使用方便,不需要引入 Redis、Zookeeper 等中間件。

缺點:

  • 不適合高并發(fā)的場景

  • db 操作性能較差

Redis 分布式鎖實現(xiàn)

優(yōu)點:

  • 性能好,適合高并發(fā)場景

  • 較輕量級

  • 有較好的框架支持,如 Redisson

缺點:

  • 過期時間不好控制

  • 需要考慮鎖被別的線程誤刪場景

Zookeeper 分布式鎖實現(xiàn)

缺點:

  • 性能不如 Redis 實現(xiàn)的分布式鎖

  • 比較重的分布式鎖。

優(yōu)點:

  • 有較好的性能和可靠性

  • 有封裝較好的框架,如 Curator

以上實現(xiàn)方式都不是完美的,在實際生產(chǎn)中要合理評估,選擇適合自己的方案。

性能

redis > zookeeper >= 數(shù)據(jù)庫

可靠性

zookeeper > redis > 數(shù)據(jù)庫

實現(xiàn)復(fù)雜度

zookeeper > redis > 數(shù)據(jù)庫

到此這篇關(guān)于聊聊Java三種常見的分布式鎖的文章就介紹到這了,更多相關(guān)Java 分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java中BorderLayout布局管理器的兩種排列方式

    Java中BorderLayout布局管理器的兩種排列方式

    這篇文章主要介紹了Java中BorderLayout布局管理器的兩種排列方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • 詳解java中的阻塞隊列

    詳解java中的阻塞隊列

    這篇文章主要介紹了java中的阻塞隊列的相關(guān)知識,文中代碼非常詳細(xì),供大家參考和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • scala 操作數(shù)據(jù)庫的方法

    scala 操作數(shù)據(jù)庫的方法

    這篇文章主要介紹了scala 操作數(shù)據(jù)庫的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • SpringCloud消息總線Bus配置中心實現(xiàn)過程解析

    SpringCloud消息總線Bus配置中心實現(xiàn)過程解析

    這篇文章主要介紹了SpringCloud消息總線Bus配置中心實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-03-03
  • Jenkins環(huán)境搭建之下載與安裝過程

    Jenkins環(huán)境搭建之下載與安裝過程

    Jenkins是一個功能強大的應(yīng)用程序,允許持續(xù)集成和持續(xù)交付項目,集成Jenkins可以用于一些測試和部署技術(shù),對Jenkins環(huán)境搭建之下載與安裝過程感興趣的朋友跟隨小編一起看看吧
    2021-12-12
  • Spring延遲Bean初始化的實現(xiàn)示例

    Spring延遲Bean初始化的實現(xiàn)示例

    延遲初始化也叫做惰性初始化,指不提前初始化Bean,而是只有在真正使用時才創(chuàng)建及初始化Bean,本文主要介紹了Spring延遲Bean初始化的實現(xiàn)示例,感興趣的可以了解一下
    2024-06-06
  • 一文詳解前端和后端的數(shù)據(jù)是如何連接的(基于Spring?Boot、Django或Node.js)

    一文詳解前端和后端的數(shù)據(jù)是如何連接的(基于Spring?Boot、Django或Node.js)

    這篇文章主要介紹了前端和后端的數(shù)據(jù)是如何連接的相關(guān)資料,文中通過示例介紹的非常詳細(xì),舉例講解的是基于Spring?Boot、Django或Node.js,需要的朋友可以參考下
    2025-01-01
  • 如何使用stream從List對象中獲取某列數(shù)據(jù)

    如何使用stream從List對象中獲取某列數(shù)據(jù)

    這篇文章主要介紹了如何使用stream從List對象中獲取某列數(shù)據(jù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • SpringBoot實現(xiàn)二維碼掃碼登錄的原理及項目實踐

    SpringBoot實現(xiàn)二維碼掃碼登錄的原理及項目實踐

    本文主要介紹了SpringBoot實現(xiàn)二維碼掃碼登錄的原理及項目實踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Springboot筆記之熱部署及不生效的解決方案

    Springboot筆記之熱部署及不生效的解決方案

    這篇文章主要介紹了Springboot筆記之熱部署及不生效的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12

最新評論

钟山县| 凤城市| 博客| 明溪县| 弥勒县| 喜德县| 获嘉县| 余江县| 阳高县| 石狮市| 开阳县| 博野县| 瑞昌市| 改则县| 东丰县| 西乌珠穆沁旗| 兰州市| 龙山县| 云浮市| 惠水县| 黄大仙区| 和林格尔县| 株洲县| 雅安市| 皋兰县| 开江县| 潮安县| 资溪县| 罗源县| 新宁县| 樟树市| 沂源县| 徐州市| 墨玉县| 瓮安县| 黄山市| 通江县| 永吉县| 新兴县| 滦平县| 武夷山市|