微服務(wù)架構(gòu)設(shè)計(jì)- 13分布式緩存

鎖是一種在并發(fā)編程中廣泛使用的工具计贰,用于保護(hù)共享資源掖棉,防止多個(gè)線程同時(shí)訪問而引起的競爭問題墓律。在JVM的發(fā)展中,鎖機(jī)制逐漸演化幔亥,提供了多種鎖類型和優(yōu)化方式耻讽。

當(dāng)前,我們可以通過不同類型的鎖來滿足不同的需求:

  • 公平鎖:用于按照請(qǐng)求的順序分配鎖帕棉,實(shí)現(xiàn)線程的順序排隊(duì)针肥。
  • 非公平鎖:允許線程在沒有按照請(qǐng)求順序的情況下獲取鎖,以提高執(zhí)行效率香伴。
  • 可重入鎖:減少死鎖的風(fēng)險(xiǎn)慰枕,同一線程可以多次獲取同一把鎖。
  • 讀寫鎖:通過分離讀取和寫入操作即纲,提高并發(fā)讀取性能捺僻。

JVM還通過引入偏向鎖、輕量級(jí)鎖和重量級(jí)鎖等機(jī)制來優(yōu)化鎖的性能崇裁,以滿足不同并發(fā)場景的需求匕坯。

此外,通過使用CAS原子操作包(java.util.concurrent.atomic)拔稳,我們可以實(shí)現(xiàn)無鎖編程葛峻,也稱為樂觀鎖,以提高并發(fā)性能巴比。

在并發(fā)編程中术奖,我們經(jīng)常需要確保多個(gè)線程安全地訪問共享資源礁遵。在一個(gè)簡單的扣款示例中,我們首先檢查賬戶余額是否足夠以執(zhí)行扣款操作采记。這種情況下佣耐,我們可以使用不同類型的鎖來確保線程安全性,如公平鎖唧龄、非公平鎖兼砖、可重入鎖等。另外既棺,使用CAS原子操作可以實(shí)現(xiàn)無鎖編程讽挟,提高并發(fā)性能。這種多樣性的鎖機(jī)制和優(yōu)化方式使得我們能夠根據(jù)具體需求選擇最適合場景的鎖策略丸冕,以保證程序的正確性和性能耽梅。

def balance = db.account.getBalance(id)
if (balance < amount) {
    return error("余額少于扣款金額")
}
db.account.updateBalance(id, -amount)

假定賬戶余額100元,有兩個(gè)并發(fā)請(qǐng)求同時(shí)扣款胖烛,在沒有鎖的情況可能會(huì)導(dǎo)致余額為負(fù)數(shù):


image.png

簡單做法使用synchronized加鎖眼姐,如:

// 定義一個(gè)對(duì)象用于作為鎖
def lock = new Object()
// ...
// 在需要同步的地方使用 synchronized 塊
synchronized (lock) {
    def balance = db.account.getBalance(id)
    if (balance >= amount) {
        db.account.updateBalance(id, -amount)
    } else {
        return error("余額少于扣款金額")
    }
}

在分布式系統(tǒng)中,跨多個(gè)節(jié)點(diǎn)實(shí)現(xiàn)分布式鎖確實(shí)面臨更大的挑戰(zhàn)佩番,包括性能众旗、一致性、可靠性等方面的考慮答捕。在探討分布式鎖之前,強(qiáng)調(diào)了使用鎖時(shí)需要明確其必要性屑那,因?yàn)殒i可能導(dǎo)致并行邏輯轉(zhuǎn)化為串行拱镐,從而影響性能,同時(shí)還需要考慮鎖的容錯(cuò)性持际,防止死鎖的發(fā)生沃琅。
以下是兩種替代分布式鎖方案:

  • Set化后的MQ替代分布式鎖:
    將分布式鎖的邏輯遷移到消息隊(duì)列(MQ)上。例如蜘欲,按用戶ID進(jìn)行Set化(用戶ID % Set數(shù))益眉,將用戶分散到不同的組,為每個(gè)組創(chuàng)建獨(dú)立的MQ隊(duì)列姥份。這樣郭脂,一個(gè)用戶在同一時(shí)間只能在一個(gè)隊(duì)列中進(jìn)行處理,實(shí)現(xiàn)了鎖的功能澈歉。每個(gè)隊(duì)列的處理是串行化的展鸡,但通過多個(gè)Set,可以在性能上實(shí)現(xiàn)一定程度的并行化埃难。這種方式在性能上可能優(yōu)于傳統(tǒng)的分布式鎖莹弊,并且在代碼上的改動(dòng)相對(duì)較小涤久。
  • 使用樂觀鎖:
    引入樂觀鎖的機(jī)制,例如為account添加一個(gè)更新版本字段(update_version)忍弛,每次更新時(shí)遞增版本號(hào)响迂。更新時(shí)的條件是版本號(hào)必須等于傳入的版本號(hào)。這種樂觀鎖機(jī)制可以避免并發(fā)更新問題细疚。雖然在高并發(fā)情況下可能會(huì)導(dǎo)致版本沖突蔗彤,但可以通過額外的處理機(jī)制來解決這些沖突。
// 從數(shù)據(jù)庫中獲取賬戶余額和版本號(hào)
def (balance, currentVersion) = db.account.getBalanceAndVersion(id)
// 檢查余額是否足夠扣款
if (balance < amount) {
    return error("余額少于扣款金額")
}
// 執(zhí)行扣款操作惠昔,更新余額和版本號(hào)
// 對(duì)應(yīng)的SQL: UPDATE account SET balance = balance - <amount>, update_version = update_version + 1 WHERE id = <id> AND update_version = <currentVersion>
if (db.account.updateBalance(id, -amount, currentVersion) == 0) {
    return error("扣款失敗") // 或遞歸執(zhí)行此代碼進(jìn)行重試
}

在某些情況下幕与,可能不得不使用鎖,尤其是在需要同時(shí)鎖定多個(gè)對(duì)象(例如用戶镇防、訂單啦鸣、商品、SKU等)時(shí)来氧,鎖可能成為更好的選擇诫给。當(dāng)然,前提是需要審查業(yè)務(wù)操作的合理性啦扬,以及系統(tǒng)設(shè)計(jì)是否存在缺陷中狂。在這種情況下,鎖可以用來確保操作的原子性和一致性扑毡。
此外胃榕,在高并發(fā)的場景中,悲觀鎖可能比樂觀鎖更有效率瞄摊。悲觀鎖在整個(gè)操作期間鎖住資源勋又,防止其他線程進(jìn)行并發(fā)修改,適用于并發(fā)量較高且需要保持?jǐn)?shù)據(jù)一致性的情況换帜。

如果需要引入分布式鎖楔壤,必須注意以下問題:

  1. 鎖的范圍: 確定鎖的粒度和范圍,以防止過度鎖定或過度細(xì)粒度的問題惯驼。
  2. 分布式鎖的實(shí)現(xiàn): 選擇合適的分布式鎖實(shí)現(xiàn)蹲嚣,例如基于數(shù)據(jù)庫、ZooKeeper祟牲、Redis等的分布式鎖隙畜。
  3. 性能和延遲:分布式鎖引入了網(wǎng)絡(luò)通信和遠(yuǎn)程節(jié)點(diǎn)的延遲,可能影響系統(tǒng)的性能说贝。需要仔細(xì)考慮性能和延遲的平衡禾蚕。
  4. 死鎖和容錯(cuò):考慮鎖的容錯(cuò)機(jī)制,防止死鎖的發(fā)生狂丝,并確保系統(tǒng)在各種異常情況下的可靠性换淆。

1. 鎖釋放與超時(shí)

正常情況下哗总,只要我們?cè)谑褂猛赕i后在finally中加上鎖釋放的代碼就可以了,比如下面的代碼:

val lock=new Lock()
  if(lock.tryLock()){
      try{
          // 業(yè)務(wù)處理
      }catch(Exception ex){
          // 業(yè)務(wù)異常處理
      }finally{
          //釋放鎖
          lock.unlock();
      }
  }

在單機(jī)環(huán)境中倍试,我們通常會(huì)使用try-finally塊確保每次加鎖都能正確釋放讯屈,即使在業(yè)務(wù)處理或異常處理中發(fā)生了OOM也不會(huì)導(dǎo)致死鎖。然而县习,在分布式環(huán)境中涮母,由于網(wǎng)絡(luò)不可靠、節(jié)點(diǎn)宕機(jī)等原因躁愿,可能出現(xiàn)無法正常釋放鎖的情況叛本。例如,OOM發(fā)生時(shí)可能導(dǎo)致鎖對(duì)象被持有彤钟,正常執(zhí)行了unlock代碼但網(wǎng)絡(luò)傳輸時(shí)丟失了unlock信號(hào)也可能導(dǎo)致死鎖来候。

為了在分布式環(huán)境中更可靠地處理鎖的釋放,我們需要考慮以下因素:

  1. 超時(shí)機(jī)制:
    在分布式環(huán)境中逸雹,引入超時(shí)機(jī)制是很常見的做法营搅。通過使用tryLock(<等待鎖的時(shí)長>, <鎖占用的最大時(shí)長>),我們可以設(shè)置一個(gè)等待鎖的時(shí)長和鎖占用的最大時(shí)長梆砸。這樣即使鎖的釋放發(fā)生異常转质,也能在一定時(shí)間后自動(dòng)釋放,避免死鎖的發(fā)生帖世。需要注意平衡等待時(shí)間和最大占用時(shí)間的設(shè)定休蟹,以兼顧性能和可靠性。
  2. 心跳超時(shí)設(shè)置:
    更優(yōu)雅但復(fù)雜的方法是使用心跳機(jī)制日矫。占有鎖的服務(wù)與鎖服務(wù)保持心跳赂弓,一旦心跳超時(shí),說明鎖服務(wù)可能存在問題搬男,占有鎖的服務(wù)可以主動(dòng)釋放鎖拣展。這樣的機(jī)制可以更精準(zhǔn)地檢測到鎖的狀態(tài)彭沼,但需要額外的實(shí)現(xiàn)和管理缔逛。

這兩種方式都旨在提高在分布式環(huán)境中處理鎖釋放的可靠性,以防止死鎖的發(fā)生姓惑。在選擇適當(dāng)?shù)姆绞綍r(shí)褐奴,需要根據(jù)系統(tǒng)需求、性能要求以及維護(hù)成本來做出權(quán)衡于毙。

2. 性能及高可用

在分布式系統(tǒng)中敦冬,通常會(huì)選擇非公平鎖以提高性能。然而唯沮,如果需要保證加鎖順序并選擇使用公平鎖脖旱,就需要謹(jǐn)慎考慮對(duì)性能的影響堪遂。加解鎖操作本身必須保證高性能和可用性,避免成為系統(tǒng)的單點(diǎn)故障萌庆。此外溶褪,鎖的信息應(yīng)當(dāng)被持久化,而使用自旋時(shí)需要慎重践险,以避免浪費(fèi)CPU資源猿妈。

  • 公平鎖和性能考慮:
    一般而言,分布式鎖為了提高性能會(huì)選擇非公平鎖巍虫。然而彭则,如果需要確保加鎖的順序,可能會(huì)考慮使用公平鎖占遥。但要注意俯抖,公平鎖可能導(dǎo)致性能下降,因?yàn)樗枰S護(hù)一個(gè)隊(duì)列來記錄等待鎖的順序筷频,而非公平鎖則可以允許新來的線程插隊(duì)蚌成,更迅速地獲得鎖。
  • 加解鎖操作的性能和可用性:
    加解鎖操作是分布式鎖的核心凛捏,必須保證高性能和可用性担忧。這包括在高并發(fā)情況下迅速完成加鎖和解鎖操作,同時(shí)避免成為系統(tǒng)的單點(diǎn)故障坯癣。
  • 持久化鎖信息:
    為了防止鎖信息的丟失瓶盛,特別是在節(jié)點(diǎn)故障或重啟的情況下,分布式鎖的信息應(yīng)當(dāng)被持久化示罗。這確保了即使系統(tǒng)經(jīng)歷了故障惩猫,鎖的狀態(tài)也能夠得到正確地恢復(fù)。
  • 自旋的慎用:
    自旋是為了避免線程阻塞而不斷嘗試獲取鎖的一種機(jī)制蚜点。然而轧房,過度的自旋可能導(dǎo)致CPU資源的浪費(fèi)。因此绍绘,在使用自旋時(shí)需要謹(jǐn)慎奶镶,要考慮自旋次數(shù)和時(shí)長,避免不必要的性能開銷陪拘。

3. 數(shù)據(jù)一致性

確保數(shù)據(jù)一致性在分布式鎖的設(shè)計(jì)中至關(guān)重要厂镇。為了實(shí)現(xiàn)這一目標(biāo),需要合理設(shè)置鎖標(biāo)記以區(qū)分不同實(shí)例和線程的操作左刽。對(duì)于可重入鎖捺信,必須確保計(jì)數(shù)正確,并在每一次解鎖時(shí)適當(dāng)減少計(jì)數(shù)欠痴。此外迄靠,為了維護(hù)數(shù)據(jù)的一致性秒咨,選擇支持CP特性(一致性和分區(qū)容忍性)的服務(wù)作為分布式鎖的中間件是至關(guān)重要的。

1. 設(shè)置鎖標(biāo)記以區(qū)分實(shí)例和線程:
在設(shè)計(jì)分布式鎖時(shí)掌挚,必須合理設(shè)置鎖標(biāo)記拭荤,以便清楚地區(qū)分是哪個(gè)實(shí)例、哪個(gè)線程在進(jìn)行操作疫诽。這樣可以確保鎖的正確性和精確性舅世。
2. 可重入鎖計(jì)數(shù)和解鎖次數(shù):
對(duì)于可重入鎖,需要做好計(jì)數(shù)奇徒,確保每一次加鎖都能正確計(jì)數(shù)雏亚,而每一次解鎖都能適當(dāng)減少計(jì)數(shù)。這是為了防止在嵌套調(diào)用中出現(xiàn)錯(cuò)誤摩钙。
3. 選擇CP特性的服務(wù)作為中間件:
為了保證數(shù)據(jù)一致性罢低,選擇支持CP特性(一致性和分區(qū)容忍性)的服務(wù)作為分布式鎖的中間件是至關(guān)重要的。CP特性確保了在網(wǎng)絡(luò)分區(qū)的情況下仍能保持一致性胖笛,盡管可能會(huì)犧牲可用性网持。

目前,主流的分布式鎖實(shí)現(xiàn)有以下幾種:

1. 關(guān)系型數(shù)據(jù)庫:
使用關(guān)系型數(shù)據(jù)庫的某些特性來實(shí)現(xiàn)分布式鎖长踊,比如利用主鍵的唯一性約束和數(shù)據(jù)一致性來確保在同一時(shí)間只有一個(gè)請(qǐng)求能夠獲得鎖功舀。雖然這種方案實(shí)現(xiàn)簡單,但在高并發(fā)場景或可重入場景中可能存在較大的性能瓶頸身弊。

2. Redis:
利用Redis的單線程執(zhí)行和原子操作(例如setnx)來實(shí)現(xiàn)分布式鎖辟汰。這種方案相對(duì)簡單,但由于缺乏原子化的值比較方法阱佛,難以實(shí)現(xiàn)對(duì)鎖的占用者是否是當(dāng)前實(shí)例的當(dāng)前線程的原子確認(rèn)帖汞,因此較難實(shí)現(xiàn)重入鎖。此外凑术,Redis單節(jié)點(diǎn)存在高可用問題翩蘸,而引入RedLock多節(jié)點(diǎn)方案也引起了一些爭議。然而淮逊,在絕大多數(shù)情況下催首,Redis仍然是一個(gè)被廣泛使用且可靠的分布式鎖解決方案。

3. ZooKeeper:
利用ZooKeeper的特性壮莹,如持久節(jié)點(diǎn)(PERSISTENT)翅帜、臨時(shí)節(jié)點(diǎn)(EPHEMERAL)姻檀、時(shí)序節(jié)點(diǎn)(SEQUENTIAL)以及Watcher接口來實(shí)現(xiàn)分布式鎖命满。這種方案能夠保證最為嚴(yán)格的數(shù)據(jù)一致性,在性能和高可用性方面也表現(xiàn)出色绣版。推薦在對(duì)一致性要求極高胶台、并發(fā)量大的場景中使用歼疮。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市诈唬,隨后出現(xiàn)的幾起案子韩脏,更是在濱河造成了極大的恐慌,老刑警劉巖铸磅,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赡矢,死亡現(xiàn)場離奇詭異,居然都是意外死亡阅仔,警方通過查閱死者的電腦和手機(jī)吹散,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來八酒,“玉大人空民,你說我怎么就攤上這事⌒呙裕” “怎么了界轩?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長衔瓮。 經(jīng)常有香客問我浊猾,道長,這世上最難降的妖魔是什么热鞍? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任与殃,我火速辦了婚禮,結(jié)果婚禮上碍现,老公的妹妹穿的比我還像新娘幅疼。我一直安慰自己,他們只是感情好昼接,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布爽篷。 她就那樣靜靜地躺著,像睡著了一般慢睡。 火紅的嫁衣襯著肌膚如雪逐工。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天漂辐,我揣著相機(jī)與錄音泪喊,去河邊找鬼。 笑死髓涯,一個(gè)胖子當(dāng)著我的面吹牛袒啼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼蚓再,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼滑肉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起摘仅,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤靶庙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后娃属,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體六荒,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年矾端,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晤碘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宏蛉。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拢驾,死狀恐怖噪叙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情豺旬,我是刑警寧澤钠惩,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站族阅,受9級(jí)特大地震影響篓跛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坦刀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一愧沟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鲤遥,春花似錦沐寺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至钢坦,卻和暖如春究孕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爹凹。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國打工厨诸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人禾酱。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓微酬,卻偏偏與公主長得像绘趋,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子得封,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容