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

聊聊并發(fā)-分布式鎖質(zhì)量保障

開(kāi)發(fā) 架構(gòu)
事前保障的階段發(fā)生在技術(shù)評(píng)審階段,在此階段,我們需要評(píng)估出當(dāng)前業(yè)務(wù)場(chǎng)景下是否存在并發(fā)風(fēng)險(xiǎn);如果存在,確定我們的技術(shù)選型。

一、背景

并發(fā)問(wèn)題是電商系統(tǒng)最常見(jiàn)的問(wèn)題之一,例如庫(kù)存超賣、抽獎(jiǎng)多發(fā)、券多發(fā)放、積分多發(fā)少發(fā)等場(chǎng)景;之所以會(huì)出現(xiàn)上述問(wèn)題,是因?yàn)榇嬖诙鄼C(jī)器多請(qǐng)求同時(shí)對(duì)同一個(gè)共享資源進(jìn)行修改,如果不加以限制,將導(dǎo)致數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)不一致性;解決并發(fā)問(wèn)題的方式有很多,例如:隊(duì)列、異步、響應(yīng)式、鎖都可以;由于當(dāng)前互聯(lián)網(wǎng)都是分布式系統(tǒng),因此本文只針對(duì)使用較為廣泛的分布式鎖的方式來(lái)進(jìn)行敘述如何進(jìn)行質(zhì)量保障。

二、分布式鎖介紹

1.什么是分布式鎖

先了解一下什么是鎖,在單機(jī)系統(tǒng)中,多個(gè)線程同時(shí)改變一個(gè)變量時(shí),需要對(duì)變量或者代碼塊做同步從而保證串行修改變量,該同步實(shí)質(zhì)上就是通過(guò)鎖來(lái)實(shí)現(xiàn)。為了實(shí)現(xiàn)多個(gè)線程在同一個(gè)時(shí)刻針對(duì)同一塊代碼串行執(zhí)行,就需要在某個(gè)地方做個(gè)標(biāo)記,該標(biāo)記必須每個(gè)線程都能看到,當(dāng)標(biāo)記不存在時(shí)可以設(shè)置該標(biāo)記,其余后續(xù)線程發(fā)現(xiàn)已經(jīng)有標(biāo)記了則等待擁有標(biāo)記的線程結(jié)束同步代碼塊取消標(biāo)記后再去嘗試設(shè)置標(biāo)記,此標(biāo)記可以理解為鎖。分布式鎖就是在多機(jī)系統(tǒng)下的該標(biāo)記。

2.實(shí)現(xiàn)分布式鎖的主流方式

目前分布式鎖的實(shí)現(xiàn)方式有3種主流方法,即:

  • 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖,此處的數(shù)據(jù)庫(kù)指的是MySQL關(guān)系型數(shù)據(jù)庫(kù)

基于MySQL鎖表

數(shù)據(jù)庫(kù)版本號(hào)樂(lè)觀鎖

  • 基于緩存實(shí)現(xiàn)分布式鎖,此處的緩存指的是Redis
  • 基于zookeeper/etcd實(shí)現(xiàn)分布式鎖

具體的關(guān)于鎖的實(shí)現(xiàn)方式,已經(jīng)有太多的文章進(jìn)行介紹,本文就不再贅述。

三、質(zhì)量保障

并發(fā)問(wèn)題一旦涉及到錢,通常都會(huì)導(dǎo)致不同程度的資損,而且在我們的功能測(cè)試中是很難發(fā)現(xiàn),因此對(duì)于并發(fā)的質(zhì)量保障顯得尤為的重要,可以抽象為3層來(lái)保障:事前、事中、事后三大步驟;事前保障通過(guò)Review 方式提前規(guī)避技術(shù)上的風(fēng)險(xiǎn),事中保障驗(yàn)證在技術(shù)實(shí)現(xiàn)過(guò)程中是否存在漏洞,事后保障校驗(yàn)數(shù)據(jù)是否符合預(yù)期,對(duì)于有并發(fā)風(fēng)險(xiǎn)的項(xiàng)目上述三個(gè)步驟的保障缺一不可。

1.事前質(zhì)量保障

事前保障的階段發(fā)生在技術(shù)評(píng)審階段,在此階段,我們需要評(píng)估出當(dāng)前業(yè)務(wù)場(chǎng)景下是否存在并發(fā)風(fēng)險(xiǎn);如果存在,確定我們的技術(shù)選型。

評(píng)估并發(fā)風(fēng)險(xiǎn)

評(píng)估并發(fā)風(fēng)險(xiǎn)的關(guān)鍵點(diǎn)在于是否存在多個(gè)進(jìn)程同時(shí)訪問(wèn)共享資源,簡(jiǎn)單來(lái)說(shuō)是否存在多個(gè)進(jìn)程在同一時(shí)間對(duì)同一個(gè)數(shù)據(jù)進(jìn)行更新的操作;例如:電商中的庫(kù)存,多人同時(shí)購(gòu)買同一個(gè)商品,也就是會(huì)存在同一時(shí)間對(duì)同一個(gè)商品的庫(kù)存進(jìn)行更新,此處就存在并發(fā)風(fēng)險(xiǎn)。

技術(shù)選型

要做到正確的技術(shù)選型,我們就需要對(duì)上述3種方式實(shí)現(xiàn)的鎖的優(yōu)缺點(diǎn)以及應(yīng)用場(chǎng)景需要進(jìn)行了解。

實(shí)現(xiàn)方式

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

缺點(diǎn)

應(yīng)用場(chǎng)景

MySQL數(shù)據(jù)庫(kù)表

易于理解/易于實(shí)現(xiàn)

容易出現(xiàn)單點(diǎn)故障、死鎖性能低/可靠性低

適用于并發(fā)量低、性能要求低的場(chǎng)景

Redis分布式鎖

性能高/易于實(shí)現(xiàn)可跨集群部署,無(wú)單點(diǎn)故障

鎖失效時(shí)間的控制不穩(wěn)定穩(wěn)定性低于ZooKeeper

適用于高并發(fā)、高性能場(chǎng)景

ZooKeeper分布式鎖

無(wú)單點(diǎn)故障/可靠性高不可重入/無(wú)死鎖問(wèn)題

實(shí)現(xiàn)復(fù)雜性能低于緩存分布式鎖

適用于大部分分布式場(chǎng)景,除對(duì)性能要求極高的場(chǎng)景

MySQL數(shù)據(jù)庫(kù)表的樂(lè)觀鎖適用于讀多寫少的場(chǎng)景且共享資源為數(shù)據(jù)庫(kù)的單行數(shù)據(jù);MySQL表鎖實(shí)現(xiàn)的鎖一般都不推薦使用;ZooKeeper分布式鎖雖然適用于大部分分布式場(chǎng)景,但是由于其實(shí)現(xiàn)復(fù)雜度相對(duì)較高以及需要額外引入中間件,在大部分業(yè)務(wù)場(chǎng)景中的應(yīng)用比較少,而基于Redis的緩存分布式鎖應(yīng)用較為廣泛;但是具體業(yè)務(wù)實(shí)現(xiàn)采用哪種類型的分布式鎖,還是需要基于當(dāng)前的業(yè)務(wù)特性來(lái)進(jìn)行決定;

在技術(shù)評(píng)審階段,一方面我們要評(píng)估出是否存在并發(fā)風(fēng)險(xiǎn),另外一方面,我們需要識(shí)別開(kāi)發(fā)同學(xué)在技術(shù)的實(shí)現(xiàn)上可能存在的漏洞,針對(duì)分布式鎖的實(shí)現(xiàn)漏洞可參考下文的CodeReview的關(guān)注點(diǎn)。

2.事中保障

CodeReview

1)Redis緩存分布式鎖

Redis通??梢允褂胹etnx(key,value)函數(shù)來(lái)實(shí)現(xiàn)分布式鎖。key和value就是基于緩存的分布式鎖的兩個(gè)屬性,其中key表示鎖id。setnx函數(shù)返回1表示獲得鎖,返回0表示其他服務(wù)器已經(jīng)獲得了鎖;

  • Redis緩存分布式鎖CodeReview注意點(diǎn)

1))Redis Key

  • 全面梳理業(yè)務(wù)場(chǎng)景,對(duì)于同一共同資源,key要保持一致;
  • key是識(shí)別共享資源的唯一鍵,key的設(shè)計(jì)既需要能夠鎖住當(dāng)前共享資源又不能影響到其他資源;

例如:商品庫(kù)存,我們的key應(yīng)該是具體到某個(gè)商品,而不是所有商品,鎖住A商品,不會(huì)影響B(tài)商品。

2))鎖釋放

  • 鎖一定需明確釋放,try/finally 結(jié)構(gòu)加鎖解鎖,finally內(nèi)釋放鎖;
  • 鎖只能被加鎖的對(duì)象釋放,此處是經(jīng)常出問(wèn)題的點(diǎn),如下圖所示,A加鎖被B釋放鎖,導(dǎo)致鎖失效,鎖被C搶占到;

針對(duì)上述問(wèn)題,釋放鎖時(shí)需要先讀取當(dāng)前key的value,再和傳入的value進(jìn)行比較;上述是兩個(gè)步驟一定要保證原子性,如果原生Redis可采用lua腳本保證原子性;如果tair,可采取TairString的cad方法;value必須是一個(gè)唯一值,唯一標(biāo)記是當(dāng)前對(duì)象加的鎖。

3))鎖超時(shí)

  • 一定要設(shè)置key的超時(shí)時(shí)間;例如:客戶端A 搶到鎖后,系統(tǒng)突然異常,A就無(wú)法釋放鎖,變成死鎖;設(shè)置超時(shí)時(shí)間就是為了防止此種情況發(fā)生,在時(shí)間到期后,自動(dòng)刪除key,間接釋放鎖;
  • 超時(shí)時(shí)間的設(shè)置一般來(lái)講大于服務(wù)的最大執(zhí)行時(shí)間即可,但是服務(wù)最大的執(zhí)行時(shí)間會(huì)受很多因素影響,是不可控的;例如:A服務(wù)一般執(zhí)行時(shí)間是30ms,設(shè)置的鎖超時(shí)時(shí)間為100ms,受網(wǎng)絡(luò)影響服務(wù)執(zhí)行時(shí)間變成了200ms,在100ms的時(shí)候鎖就會(huì)被釋放了;在大部分場(chǎng)景下,開(kāi)發(fā)不會(huì)處理此種情況,此種極端情況是否需要處理,需要進(jìn)行協(xié)商;處理方式如下2種:

可以再開(kāi)啟一個(gè)線程,為當(dāng)前超時(shí)時(shí)間續(xù)時(shí),但增加了系統(tǒng)的復(fù)雜度;

將過(guò)期時(shí)間設(shè)置非常長(zhǎng),一定能保證邏輯在鎖釋放之前能夠執(zhí)行完成;此方案簡(jiǎn)單但是有缺陷,當(dāng)遇到系統(tǒng)突發(fā)異常時(shí),鎖無(wú)法被釋放,只能等待redis key超時(shí),而超時(shí)時(shí)間又設(shè)置的較長(zhǎng),因此在當(dāng)前時(shí)間內(nèi)誰(shuí)都無(wú)法獲取到鎖,阻斷業(yè)務(wù)執(zhí)行,很有可能造成故障;

4))鎖粒度

如果針對(duì)某個(gè)共享資源的寫是基于另外一個(gè)共享資源的值計(jì)算而來(lái),那么鎖的范圍必須包含讀共享資源;范圍不包含讀共享資源會(huì)導(dǎo)致臟讀,最終導(dǎo)致數(shù)據(jù)的錯(cuò)誤,如下圖所示,Client B最終計(jì)算的B的結(jié)果就是錯(cuò)誤的。

5))獲取鎖失敗

由于其他線程已經(jīng)獲取到了鎖,當(dāng)前線程獲取鎖失敗后有3種處理方式:異常拋出讓用戶重試;通過(guò)自旋再次進(jìn)行搶鎖;發(fā)布訂閱,訂閱鎖釋放消息;在并發(fā)度低的場(chǎng)景下異常拋出以及自旋搶鎖都可以,在高并發(fā)場(chǎng)景下異常拋出和自旋搶鎖都不可取。

2)MySQL數(shù)據(jù)庫(kù)鎖CR點(diǎn)

  • 數(shù)據(jù)庫(kù)版本號(hào)樂(lè)觀鎖

在數(shù)據(jù)庫(kù)的表中需要包含一個(gè)數(shù)字類型的字段version,讀取數(shù)據(jù)時(shí)把version字段讀出來(lái),更新數(shù)據(jù)時(shí)判斷當(dāng)前version是否等于讀取出來(lái)的version,并對(duì)當(dāng)前version+1;如果等于就更新成功,不等于表示數(shù)據(jù)已過(guò)期更新失敗。例如以積分體系為例,存在多種場(chǎng)景增加積分,通過(guò)樂(lè)觀鎖來(lái)保證數(shù)據(jù)的正確性。

樂(lè)觀鎖CR注意點(diǎn):

where 條件一定要命中索引(最好是主鍵或者唯一索引),否則會(huì)鎖表;where 條件一定要命中索引(最好是主鍵或者唯一索引),否則會(huì)鎖表;

update table set 中必須要包含version = version + 1;

update 返回結(jié)果為0時(shí),一定要根據(jù)業(yè)務(wù)場(chǎng)景進(jìn)行相應(yīng)的處理,自主重試或者拋異常;

  • 基于MySQL鎖表

其實(shí)現(xiàn)原理是:創(chuàng)建一張鎖表,對(duì)臨界資源做唯一性約束,通過(guò)增加一條記錄對(duì)某一資源上鎖,釋放鎖時(shí)刪除記錄;一般不推薦此種用法。

并發(fā)測(cè)試

并發(fā)測(cè)試總體上可以分為3大類:

  • 復(fù)雜的并發(fā)場(chǎng)景,一次請(qǐng)求共享資源存在多個(gè),且前后存在各種依賴關(guān)系,此種場(chǎng)景適合于鏈路級(jí)別壓測(cè),壓測(cè)模型需要精心設(shè)計(jì)。
  • 單一并發(fā)場(chǎng)景,一個(gè)共享資源,可以處理多次,例如:扣除某個(gè)商品的庫(kù)存,可以反復(fù)調(diào)用。

可以通過(guò)接口壓測(cè)的方式進(jìn)行測(cè)試,通過(guò)查看最終數(shù)據(jù)是否會(huì)存在與預(yù)期不一致情況即可;

壓測(cè)工具:jmeter 即可進(jìn)行壓測(cè)(集團(tuán)可直接采用pas-server進(jìn)行壓測(cè),方便快捷);

  • 單一并發(fā)場(chǎng)景,一個(gè)共享資源,且只能處理1次,例如:用戶只有一次抽獎(jiǎng)機(jī)會(huì),連續(xù)點(diǎn)2次會(huì)不會(huì)抽2次;

可以利用JVM的并發(fā)函數(shù)CountDownLatch,CyclicBarrier等,CountDownLatch片段代碼:

public void invokeAllTask(ConcurrencyRequest request, Runnable task) {
final CountDownLatch startCountDownLatch = new CountDownLatch(1);
final CountDownLatch endCountDownLatch = new CountDownLatch(request.getConcurrency());
for (int i = 0; i < request.getConcurrency(); i++) {
Thread t = new Thread(() -> {
try {
startCountDownLatch.await();
try {
task.run();
} finally {
endCountDownLatch.countDown();
}
} catch (Exception ex) {
log.error("異常", ex);
}
});
t.start();
}
startCountDownLatch.countDown();
try {
endCountDownLatch.await();
} catch (InterruptedException ex) {
log.error("線程異常中斷", ex);
}
}

利用jmeter的定時(shí)器 Synchronizing Timer也可以實(shí)現(xiàn)此功能。

3.事后保障

數(shù)據(jù)對(duì)賬

數(shù)據(jù)對(duì)賬(數(shù)據(jù)一致性校驗(yàn))是我們?cè)谙到y(tǒng)上線后對(duì)并發(fā)問(wèn)題的最后一道防線,通過(guò)對(duì)賬來(lái)識(shí)別我們的數(shù)據(jù)的不一致性問(wèn)題;壓測(cè)有成本,且受技巧熟練度和壓測(cè)設(shè)計(jì)的影響,不一定能暴露問(wèn)題;如果被測(cè)場(chǎng)景評(píng)估并發(fā)問(wèn)題的發(fā)生概率極低,即使發(fā)生了影響也比較小,此時(shí)review+對(duì)賬方式也不失為一種好的選擇;

如何進(jìn)行對(duì)賬,不同的業(yè)務(wù)場(chǎng)景有不同的對(duì)賬方法,例如:

  • 互動(dòng)積分體系每個(gè)用戶的扣除以及增加積分都會(huì)落流水表;每個(gè)用戶目前有多少積分都會(huì)放在積分表;只需要把流水表的積分加總和積分表的積分進(jìn)行對(duì)賬;
  • 互動(dòng)任務(wù)體系,一筆訂單只能推進(jìn)一個(gè)任務(wù),對(duì)賬只需要檢查任務(wù)記錄中一筆訂單是否存在多條記錄;
select count(*)  as task_count,
scene_code,
order_id
from task_record
where unique_id is not null
group by scene_code,
order_id
having count(*)> 1

四、總結(jié)

作為質(zhì)量保障同學(xué)一定要時(shí)刻繃著一根弦,當(dāng)前場(chǎng)景下是否會(huì)存在并發(fā)問(wèn)題;并發(fā)問(wèn)題的識(shí)別簡(jiǎn)單而言就是是否存在同時(shí)更新同一個(gè)數(shù)據(jù),如果是就一定要注意開(kāi)發(fā)同學(xué)是否處理了并發(fā),并發(fā)的實(shí)現(xiàn)主要是上面闡述的幾種,然后按照?qǐng)鼍斑M(jìn)行分析即可;關(guān)于并發(fā)場(chǎng)景的質(zhì)量保障,大體原則可以概括為如下:

  • 梳理并發(fā)場(chǎng)景
  • 帶著注意點(diǎn)CR 代碼
  • 并發(fā)測(cè)試(非銀彈,不是所有場(chǎng)景都具備可測(cè)性)
  • 監(jiān)控對(duì)賬進(jìn)行兜底識(shí)別并發(fā)問(wèn)題
責(zé)任編輯:武曉燕 來(lái)源: 阿里技術(shù)
相關(guān)推薦

2022-03-11 10:03:40

分布式鎖并發(fā)

2022-04-08 08:27:08

分布式鎖系統(tǒng)

2021-09-17 07:51:24

RedissonRedis分布式

2019-06-19 15:40:06

分布式鎖RedisJava

2024-07-15 08:25:07

2021-09-26 09:16:45

RedisGeo 類型數(shù)據(jù)類型

2024-05-06 00:00:00

.NET分布式鎖技術(shù)

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2021-07-16 07:57:34

ZooKeeperCurator源碼

2018-07-17 08:14:22

分布式分布式鎖方位

2022-06-13 10:01:36

Apollo攜程框架

2025-03-06 11:30:15

2023-02-10 00:04:53

2022-08-04 08:45:50

Redisson分布式鎖工具

2018-11-27 16:17:13

分布式Tomcat

2021-11-26 06:43:19

Java分布式

2021-07-10 10:02:30

ZooKeeperCurator并發(fā)

2021-12-01 10:13:48

場(chǎng)景分布式并發(fā)

2017-12-20 16:15:30

分布式系統(tǒng)架構(gòu)

2022-01-17 09:18:28

JMeter分布式壓測(cè)
點(diǎn)贊
收藏

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