自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

老板問我分布式鎖,結(jié)果悲劇了......

開發(fā) 架構(gòu) 分布式
公司交給了萌新小猿一個(gè)光榮而艱巨的項(xiàng)目,該項(xiàng)目需要使用分布式鎖,這可難道了小猿。

公司交給了萌新小猿一個(gè)光榮而艱巨的項(xiàng)目,該項(xiàng)目需要使用分布式鎖,這可難到了小猿。

[[321687]]

圖片來自 Pexels

只是聽說過分布式鎖很牛掰,其他就一概不知了,唉,不懂就問唄,遂向老板請教。

老板:我們每天不都在經(jīng)歷分布式鎖嗎,我來給你回憶回憶。

小猿:好勒,瓜子板凳已備好。

本文結(jié)構(gòu):

  • 為什么要使用分布式鎖
  • 分布式鎖有哪些特點(diǎn)
  • 分布式鎖流行算法及其優(yōu)缺點(diǎn)
  • 總結(jié)

為什么要使用分布式鎖

這個(gè)問題應(yīng)該拆分成以下 2 個(gè)問題回答。

①為什么使用鎖

保證在同一時(shí)刻共享資源只能被一個(gè)客戶端訪問;根據(jù)鎖用途分為以下兩種:

  • 共享資源只允許一個(gè)客戶端操作
  • 共享資源允許多個(gè)客戶端操作

僅允許一個(gè)客戶端訪問:共享資源的操作不具備冪等性。常見于數(shù)據(jù)的修改、刪除操作。

在上面的例子中:

允許多個(gè)客戶端操作:主要應(yīng)用場景是共享資源的操作具有冪等性;如數(shù)據(jù)的查詢。

既然都具有冪等性了,為什么還需要分布式鎖呢,通常是為了效率或性能,避免重復(fù)操作(尤其是消耗資源的操作)。

例如我們常見的緩存方案:

在上面的例子中:

由于此處的資源是冪等的,通常會(huì)將這類資源做緩存,這就是常見的鎖+緩存架構(gòu)。

常適用于獲取較為消耗資源(時(shí)間、內(nèi)存、CPU 等)的冪等資源,如:

  • 查詢用戶信息
  • 查詢歷史訂單

當(dāng)然,如果資源僅在一段時(shí)間范圍內(nèi)具有冪等性,這時(shí)候,架構(gòu)就應(yīng)該升級(jí)了:

鎖+緩存+緩存失效/失效重新獲取/緩存定時(shí)更新。

②鎖為什么需要分布式的?

還是以上面的緩存方案為例,此處略作變化:

在上面的例子中:

分布式鎖有哪些特點(diǎn)?

①互斥性

在任意時(shí)刻,僅允許有一個(gè)客戶端獲得鎖。

PS:如果多個(gè)客戶端都能同時(shí)獲得鎖,那鎖就沒意義了,共享資源的安全性也就無法保證了。

老板:當(dāng)我在會(huì)議室接待客戶 A 時(shí),其他客戶只有等待,你需要等到我空閑了才能把其他人帶到我辦公室。

小猿:明白。

接待客戶(非冪等共享資源);等到老板空閑(獲取鎖)。

②可重入性

客戶端 A 獲得了鎖,只要鎖沒有過期,客戶端 A 可以繼續(xù)獲得該鎖。鎖在我這里,我還要繼續(xù)使用,其他人不準(zhǔn)搶。

這種特性可以很好的支持【鎖續(xù)約】功能。例如:客戶端 A 獲取鎖,鎖釋放時(shí)間為 10S,即將到達(dá) 10S 時(shí),客戶端 A 未完成任務(wù),需要再申請 5S。若鎖沒有可重入性,客戶端 A 將無法續(xù)約,導(dǎo)致鎖可能被其他客戶端搶走。

小猿:受教了,老板 3 分鐘后你還有一場面試。

老板:小猿啊,難得你這么好學(xué),我很欣慰,我們的交流時(shí)間延10分鐘吧,其他會(huì)議延后。

③高性能

獲取鎖的效率應(yīng)該足夠高;總不能讓業(yè)務(wù)阻塞在獲取鎖上面吧?

小猿:好的,我已在釘釘申請將會(huì)議延長 10 分鐘了。

老板:嗯,我已經(jīng)接受會(huì)議邀請了;

小猿:老板你真高效。

④高可用

分布式、微服務(wù)環(huán)境下,必須保證服務(wù)的高可用,否則輕則影響其他業(yè)務(wù)模塊,重則引發(fā)服務(wù)雪崩。

老板:我手機(jī) 24 小時(shí)開機(jī),有會(huì)議時(shí)聯(lián)系不上我也可以聯(lián)系我秘書。

⑤支持阻塞和非阻塞式鎖

獲取鎖失敗,是直接返回失敗,還是一直阻塞知道獲取成功?不同的業(yè)務(wù)場景有不同的答案。

例如:

⑥解鎖權(quán)限

客戶端僅能釋放(解鎖)自己加的鎖。常見的解決方案是,給鎖加隨機(jī)數(shù)(或 ThreadID)。

老板:小猿啊,給你講了這么多,都明白了嗎?

籠子里的鸚鵡:明白啦,明白啦。

老板:閉嘴,我問的是小猿,只有小猿自己有資格回答。

⑦避免死鎖

加鎖方異常終止無法主動(dòng)釋放鎖;常規(guī)做法是 加鎖時(shí)設(shè)置超時(shí)時(shí)間,如果未主動(dòng)釋放鎖,則利用 Redis 的自動(dòng)過期被動(dòng)釋放鎖。

秘書破門而入:老板,你們 10 分鐘的會(huì)議已經(jīng)到點(diǎn)了,隔壁的李總已經(jīng)等不及了。

老板:一不留神就忘記時(shí)間了,我得去見李總了。

小猿:老板,我們還沒聊完呢...

⑧異常處理

常見的異常情況有 Redis 宕機(jī)、時(shí)鐘跳躍、網(wǎng)絡(luò)故障等。

小猿:不管出現(xiàn)哪種情況,我獲取鎖都會(huì)失敗啊,這可怎么辦呢?

PS:這就復(fù)雜了,需要根據(jù)具體的業(yè)務(wù)場景分析。對于必須同步處理的業(yè)務(wù),則必須失敗告警,對于允許延遲處理的業(yè)務(wù)可以考慮記錄失敗信息待其他系統(tǒng)處理。

分布式鎖流行算法

基本方案 SETNX

基于 Redis 的 SETNX 指令完成鎖的獲取。

①獲取鎖 SET lock:resource_name random_value NX PX 30000

lock:resource_name:資源名字,加鎖對象的唯一標(biāo)記。

random_value:通常存儲(chǔ)加鎖方的唯一標(biāo)記,如“UUID+ThreadID”。

NX:Key 不存在才設(shè)置,即鎖未被其他人加鎖才能加鎖。

PX:鎖超時(shí)時(shí)間。

當(dāng)然,此種加鎖方式是不支持“鎖重入性”的。

②釋放鎖(LUA 腳本)

checkValueThenDelete:檢查解鎖方是否是加鎖方,是則允許解鎖,否則不允許解鎖。

偽代碼是:

  1. public class RedisTool { 
  2.     // 釋放鎖成功標(biāo)記 
  3.     private static final Long RELEASE_LOCK_SUCCESS = 1L; 
  4.  
  5.     /** 
  6.      * 釋放分布式鎖 
  7.      * 
  8.      * @param jedis     Redis客戶端 
  9.      * @param lockKey   鎖標(biāo)記 
  10.      * @param lockValue 加鎖方標(biāo)記 
  11.      * @return 是否釋放成功 
  12.      */ 
  13.     public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String lockValue) { 
  14.         String script = "" + 
  15.                 "if redis.call('get', KEYS[1]) == ARGV[1] then" + 
  16.                 "    return redis.call('del', KEYS[1]) " + 
  17.                 "else" + 
  18.                 "    return 0 " + 
  19.                 "end"
  20.         // Collections.singletonList():用于只有一個(gè)元素的場景,減少內(nèi)存分配 
  21.         Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue)); 
  22.         if (RELEASE_LOCK_SUCCESS.equals(result)) { 
  23.             return true
  24.         } 
  25.         return false
  26.     } 

Redlock 算法

此算法由 Redis 作者 antirez 提出,作為一種分布式場景下的鎖實(shí)現(xiàn)方案。

Redlock 算法原理:【核心】大多數(shù)節(jié)點(diǎn)獲取鎖成功且鎖依舊有效。

Step 1:獲取當(dāng)前時(shí)間(毫秒數(shù))。

Step 2:按序想 N 個(gè) Redis 節(jié)點(diǎn)獲取鎖。設(shè)置隨機(jī)字符串 random_value;設(shè)置鎖過期時(shí)間:

  • Note 1:獲取鎖需設(shè)置超時(shí)時(shí)間(防止某個(gè)節(jié)點(diǎn)不可用),且 timeout 應(yīng)遠(yuǎn)小于鎖有效時(shí)間(幾十毫秒級(jí))。
  • Note 2:某節(jié)點(diǎn)獲取鎖失敗后,立即向下一個(gè)節(jié)點(diǎn)獲取鎖(任何類型失敗,包含該節(jié)點(diǎn)上的鎖已被其他客戶端持有)。

Step 3:計(jì)算獲取鎖的總耗時(shí) totalTime。

Step 4:獲取鎖成功。

獲取鎖成功:客戶端從大多數(shù)節(jié)點(diǎn)(>=N/2+1)成功獲取鎖,且 totalTime 不超過鎖的有效時(shí)間。

重新計(jì)算鎖有效時(shí)間:最初鎖有效時(shí)間減 3.1 計(jì)算的獲取鎖消耗的時(shí)間。

Step 5:獲取鎖失敗。

獲取失敗后應(yīng)立即向【所有】客戶端發(fā)起釋放鎖(Lua 腳本)。

Step 6:釋放鎖。

業(yè)務(wù)完成后應(yīng)立即向【所有】客戶端發(fā)起釋放鎖(Lua 腳本)。

Redlock 算法優(yōu)點(diǎn):

  • 可用性高,大多數(shù)節(jié)點(diǎn)正常即可。
  • 單 Redis 節(jié)點(diǎn)的分布式鎖在 failover 時(shí)鎖失效問題不復(fù)存在。

Redlock 算法問題點(diǎn):

  • Redis 節(jié)點(diǎn)崩潰將影響鎖安全性:節(jié)點(diǎn)崩潰前鎖未持久化,節(jié)點(diǎn)重啟后鎖將丟失;Redis 默認(rèn) AOF 持久化是每秒刷盤(fsync)一次,最壞情況將丟失 1 秒的數(shù)據(jù)。
  • 需避免始終跳躍:管理員手動(dòng)修改時(shí)鐘;使用[不會(huì)跳躍調(diào)整系統(tǒng)時(shí)鐘]的 ntpd(時(shí)鐘同步)程序,對時(shí)鐘修改通過多次微調(diào)實(shí)現(xiàn)。
  • 客戶端阻塞導(dǎo)致鎖過期,導(dǎo)致共享資源不安全。
  • 如果獲取鎖消耗時(shí)間較長,導(dǎo)致效時(shí)間很短,是否應(yīng)該立即釋放鎖?多段才算短?

帶 fencing token 的實(shí)現(xiàn)

分布式系統(tǒng)專家 Martin Kleppmann 討論提出 RedLock 存在安全性問題。

神仙之戰(zhàn):Martin Kleppmann 認(rèn)為 Redis 作者 antirez 提出的 RedLock 算法有安全性問題,雙方在網(wǎng)絡(luò)上多輪探討交鋒。

Martin 指出 RedLock 算法的核心問題點(diǎn)如下:

  • 鎖過期或者網(wǎng)絡(luò)延遲將導(dǎo)致鎖沖突:客戶端 A 進(jìn)程 pause→鎖過期→客戶端 B 持有鎖→客戶端 A 恢復(fù)并向共享資源發(fā)起寫請求;網(wǎng)絡(luò)延遲也會(huì)產(chǎn)生類似效果。
  • RedLock 安全性對系統(tǒng)時(shí)鐘有強(qiáng)依賴。

fencing token 算法原理:

  • fencing token 是一個(gè)單調(diào)遞增的數(shù)字,當(dāng)客戶端成功獲取鎖時(shí)隨同鎖一起返回給客戶端。
  • 客戶端訪問共享資源時(shí)帶上 token。
  • 共享資源服務(wù)檢查 token,拒絕延遲到來的請求。

fencing token 算法問題點(diǎn):

  • 需要改造共享資源服務(wù)。
  • 如果資源服務(wù)也是分布式,如何保證 token 在多個(gè)資源服務(wù)節(jié)點(diǎn)遞增。
  • 2 個(gè) fencing token 到達(dá)資源服務(wù)的順序顛倒,服務(wù)檢查將異常。
  • 【antirez】既然存在 fencing 機(jī)制保持資源互斥訪問,為什么還需要分布式鎖且要求強(qiáng)安全性呢。

其他分布式鎖

數(shù)據(jù)庫排它鎖:

  • 獲取鎖(select for update ,悲觀鎖)。
  • 處理業(yè)務(wù)邏輯。
  • 釋放鎖(connection.commit())。

注意:InnoDB 引擎在加鎖的時(shí)候,只有通過索引進(jìn)行檢索的時(shí)候才會(huì)使用行級(jí)鎖,否則會(huì)使用表級(jí)鎖。So 必須給 lock_name 加索引。

ZooKeeper 分布式鎖:

  • 客戶端創(chuàng)建 znode 節(jié)點(diǎn),創(chuàng)建成功則獲取鎖成功。
  • 持有鎖的客戶端訪問共享資源完成后刪除 znode。
  • znode 創(chuàng)建成 ephemeral(znode 特性),保證創(chuàng)建 znode 的客戶端崩潰后,znode 會(huì)被自動(dòng)刪除。
  • 【問題】Zookeeper 基于客戶端與 Zookeeper 某臺(tái)服務(wù)器維護(hù) Session,Session 依賴定期心跳(heartbeat)維持。

Zookeeper 長時(shí)間收不到客戶端心跳,就任務(wù) Session 過期,這個(gè) Session 所創(chuàng)建的所有 ephemeral 類型的 znode 節(jié)點(diǎn)都將被刪除。

Google 的 Chubby 分布式鎖:

  • sequencer 機(jī)制(類似 fencing token)緩解延遲導(dǎo)致的問題。
  • 鎖持有者可隨時(shí)請求一個(gè) sequencer。
  • 客戶端操作資源時(shí)將 sequencer 傳給資源服務(wù)器。
  • 資源服務(wù)器檢查 sequencer 有效性:①調(diào)用 Chubby 的 API(CheckSequencer)檢查。②對比檢查客戶端、資源服務(wù)器當(dāng)前觀察到的 sequencer(類似 fencing token)。③lock-delay:允許客戶端為持有鎖指定一個(gè) lock-delay 延遲時(shí)間,Chubby 發(fā)現(xiàn)客戶端失去聯(lián)系時(shí),在 lock-delay 時(shí)間內(nèi)組織其他客戶端獲取鎖;

總結(jié)

我們該使用怎樣的分布式鎖算法?

  • 技術(shù)都是為業(yè)務(wù)服務(wù)的,避免選擇“高大上”的炫技;
  • 依托業(yè)務(wù)場景,盡可能選擇最簡單的做法;
  • 最簡單的分布式鎖導(dǎo)致偶發(fā)性異常如何處理呢?建議增加額外的機(jī)制甚至人工介入保證業(yè)務(wù)準(zhǔn)確性,通常這部分成本低于復(fù)雜的分布式鎖的開發(fā)、運(yùn)維成本。

分布式鎖的另類玩法,“分而治之”經(jīng)久不衰:

  • 如果共享資源本身可以拆分,那就分開處理吧。
  • 比如電商系統(tǒng)防止超賣,假設(shè)有 10000 個(gè)口罩將被秒殺,常規(guī)做法是一個(gè)鎖控制所有資源。另類玩法就是將 10000 個(gè)口罩交由 20 個(gè)鎖控制,整體性能瞬間提升幾十倍。

PS:此處超賣僅是舉例,真實(shí)場景下的秒殺超賣有更加復(fù)雜的場景,慎重。

 

責(zé)任編輯:武曉燕 來源: zxiaofan
相關(guān)推薦

2019-06-19 15:40:06

分布式鎖RedisJava

2018-07-17 08:14:22

分布式分布式鎖方位

2021-07-16 07:57:34

ZooKeeperCurator源碼

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2022-08-04 08:45:50

Redisson分布式鎖工具

2018-11-27 16:17:13

分布式Tomcat

2021-11-26 06:43:19

Java分布式

2021-07-06 08:37:29

Redisson分布式

2023-09-22 08:00:00

分布式鎖Redis

2017-10-24 11:28:23

Zookeeper分布式鎖架構(gòu)

2023-08-21 19:10:34

Redis分布式

2022-01-06 10:58:07

Redis數(shù)據(jù)分布式鎖

2021-10-25 10:21:59

ZK分布式鎖ZooKeeper

2021-06-03 08:55:54

分布式事務(wù)ACID

2024-11-28 15:11:28

2020-06-15 08:15:47

分布式鎖系統(tǒng)

2021-07-02 08:51:09

Redisson分布式鎖公平鎖

2023-01-13 07:39:07

2021-06-30 14:56:12

Redisson分布式公平鎖

2024-10-07 10:07:31

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)