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

DDD實(shí)戰(zhàn):應(yīng)對(duì)并發(fā)挑戰(zhàn),五個(gè)技巧讓你輕松應(yīng)對(duì)

開(kāi)發(fā) 前端
并發(fā)管理是一個(gè)高級(jí)話題,也是設(shè)計(jì)中的難點(diǎn),一不小心就會(huì)出問(wèn)題。讓每個(gè)開(kāi)發(fā)人員都成為并發(fā)高手又是一件不太現(xiàn)實(shí)的事,但,好在存在很多并發(fā)管理的成熟方案,業(yè)務(wù)開(kāi)發(fā)者按照?qǐng)鼍斑M(jìn)行落地即可。

在業(yè)務(wù)開(kāi)發(fā)中,事務(wù)一致性核心在于“原子性”,則并發(fā)管理的核心在于“隔離性”。

  1. 原子性:一個(gè)業(yè)務(wù)操作被視為一個(gè)不可分割的邏輯單元,要么全部執(zhí)行成功,要么全部失敗回滾;
  2. 隔離性:并發(fā)業(yè)務(wù)操作之間要相互隔離,不能互相干擾;

1. 無(wú)處不在的并發(fā)

并發(fā)管理是指在多個(gè)用戶同時(shí)訪問(wèn)、修改同一數(shù)據(jù)時(shí),如何保證數(shù)據(jù)的準(zhǔn)確性、一致性和完整性的一系列管理措施。

并發(fā)無(wú)處不在是指在當(dāng)前的業(yè)務(wù)系統(tǒng)和應(yīng)用程序中,幾乎所有的操作都是并發(fā)的。無(wú)論是網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)操作、I/O讀寫(xiě)操作等,都可能在同一時(shí)刻被多個(gè)線程或進(jìn)程同時(shí)執(zhí)行。這意味著在業(yè)務(wù)開(kāi)發(fā)中,必須充分考慮并發(fā)處理問(wèn)題,避免出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)、死鎖等問(wèn)題,同時(shí)合理利用多線程、協(xié)程等技術(shù)來(lái)提高系統(tǒng)的性能和處理能力。

1.1. 常見(jiàn)業(yè)務(wù)流程

首先看以下流程:

圖片圖片

這是一個(gè)聚合根更新操作,包括:

  1. 從 DB 中加載數(shù)據(jù);
  2. 修改內(nèi)存中的數(shù)據(jù);
  3. 將變更更新到 DB;

也許還沒(méi)有使用DDD,對(duì)聚合根不太熟悉,那再看一個(gè)流程:

圖片圖片

這是一個(gè)更為通用的數(shù)據(jù)編輯流程,包括:

  1. 打開(kāi)編輯頁(yè)面,從 DB 中加載數(shù)據(jù),完成數(shù)據(jù)展示;
  2. 通過(guò) UI 界面對(duì)數(shù)據(jù)進(jìn)行編輯;
  3. 點(diǎn)擊保存按鈕,將新的變更保存到 DB;

仔細(xì)對(duì)比這兩張圖,其實(shí)他們都在做同樣的事情:

  1. 加載數(shù)據(jù);
  2. 修改數(shù)據(jù);
  3. 更新數(shù)據(jù);

在這里便存在并發(fā)問(wèn)題。

1.2. 并發(fā)問(wèn)題

上面所提到的流程是否存在并發(fā)問(wèn)題,仔細(xì)看下圖:

圖片圖片

同一個(gè)流程,操作同一數(shù)據(jù),只是操作順序不同,也會(huì)出現(xiàn)并發(fā)安全問(wèn)題:

  1. Action2 首先加載數(shù)據(jù) V1;
  2. Action1 其次也加載數(shù)據(jù) V1;
  3. Action2 對(duì)數(shù)據(jù)進(jìn)行修改,并成功保存變更后的數(shù)據(jù) V2(V1 + Action2 = V2);
  4. Action1 對(duì)數(shù)據(jù)進(jìn)行修改,并成功保存變更后的數(shù)據(jù) V3 (V1 + Action1 = V3);

看起來(lái)沒(méi)什么問(wèn)題,但 V3 是業(yè)務(wù)期望的嗎?V2 的變更又去哪里了呢?

此時(shí),V2 被 V3 覆蓋,V2 的變更丟失了。

如果還不清楚,明確業(yè)務(wù)操作為 count++,如下圖所示:

圖片圖片

對(duì)數(shù)據(jù)庫(kù)的 count 進(jìn)行累加操作

  1. Action2 首先加載數(shù)據(jù) count: 1;
  2. Action1 其次也加載數(shù)據(jù) count: 1;
  3. Action2 對(duì)count:1進(jìn)行累加,獲得新值 2,并成功保存 count:2;
  4. Action1 對(duì)count:1進(jìn)行累加,獲得新值 2,并成功保存 count:2;

操作完成后,最終結(jié)果為2。實(shí)際期望結(jié)果為3,Action2 的修改被 Action1 覆蓋,導(dǎo)致一次累加操作被覆蓋。

當(dāng)然,這僅僅是同一流程下的并發(fā)問(wèn)題,多流程間也存在并發(fā)問(wèn)題:

圖片圖片

對(duì)于同一記錄,自增流程和設(shè)置流程并發(fā)執(zhí)行,同樣發(fā)生了寫(xiě)覆蓋。

2. 局部串行

并發(fā)問(wèn)題,只有在并發(fā)執(zhí)行的情況下才會(huì)發(fā)生,對(duì)于同一數(shù)據(jù)如果不存在并發(fā)就不會(huì)出問(wèn)題。

2.1. 線程方案

如下圖所示:

圖片圖片

訂單流程中的核心操作:

  1. 扣庫(kù)存
  2. 下單
  3. 支付成功

由于多個(gè)訂單間不存在關(guān)系,可以并發(fā)執(zhí)行;但同一訂單,必須保障業(yè)務(wù)執(zhí)行順序。

什么是“局部串行”:

  1. 對(duì)于同一訂單,需要保障順序性;
  2. 對(duì)于不同訂單可以并行執(zhí)行;

其中分發(fā)器是核心,它連接訂單事件和后臺(tái)線程:

  1. 收到訂單事件后,從消息體中獲取訂單號(hào);
  2. 通過(guò) 訂單號(hào) % 線程數(shù)量,計(jì)算出事件運(yùn)行的線程;
  3. 將事件提交到對(duì)應(yīng)線程的處理隊(duì)列進(jìn)行處理;
  4. 這樣統(tǒng)一訂單只會(huì)由同一線程進(jìn)行處理;

這樣,相同訂單號(hào)的訂單事件均由同一個(gè)線程處理,從而保證局部串行化。不同訂單之間,不存在相互影響,可以在多個(gè)線程中并行執(zhí)行。

2.2. MQ 方案

當(dāng)然,內(nèi)存操作存在數(shù)據(jù)安全問(wèn)題(重啟任務(wù)會(huì)丟失),不少M(fèi)Q也提供了相關(guān)功能,以 RocketMQ 的順序消息為例,如下圖所示:

圖片圖片

  1. RocketMQ 將相同 shardingKey 的消息發(fā)送至固定的 partition;
  2. 后臺(tái)處理線程從 partition 中獲取消息并執(zhí)行處理邏輯;
  3. 從而保證相同 shardingKey 的消息均由同一線程處理;

局部串行對(duì)性能存在一定影響,系統(tǒng)最大的并發(fā)量為 partition 數(shù)量。如果出現(xiàn)增加 Worker 節(jié)點(diǎn)無(wú)法提升系統(tǒng)吞吐時(shí),需要擴(kuò)展 partition 數(shù)量。

【備注】在系統(tǒng)做 rebalance 時(shí),可能會(huì)出現(xiàn)短暫的消息混亂,通常情況下,業(yè)務(wù)是可接受的。如果必須保障強(qiáng)順序,如 binlog 場(chǎng)景,只能使用一個(gè) partition,但會(huì)極大的影響性能。

3. 最后寫(xiě)勝出

有些時(shí)候,寫(xiě)更新不依賴于之前的數(shù)據(jù)狀態(tài),只需使用最新數(shù)據(jù)進(jìn)行覆蓋即可,此時(shí),并發(fā)管理也就變的非常簡(jiǎn)單。

如下圖所示:

圖片圖片

  1. Action1 將 name 更新為 “精英英語(yǔ)”;
  2. Action2 將 status 更新為 Enable;
  3. 兩者對(duì)不同字段進(jìn)行更新,并且相互間沒(méi)有交集;

此時(shí),不會(huì)出現(xiàn)并發(fā)問(wèn)題。但由于時(shí)序問(wèn)題,數(shù)據(jù)的最終狀態(tài)以“最后更新”為準(zhǔn)。

4. 原子指令

許多存儲(chǔ)引擎對(duì)單條記錄提供了原子操作,對(duì)于簡(jiǎn)單的場(chǎng)景,可以將并發(fā)控制委托給存儲(chǔ)引擎進(jìn)行管理。

比如在庫(kù)存扣減的場(chǎng)景,可以使用 Redis 或 DB 的原子指令進(jìn)行操作。

4.1. Redis

使用 Redis 的 incr 指令:

圖片圖片

由于 redis 指令是單線程處理不存在并發(fā)問(wèn)題,直接使用 incr key -1 質(zhì)量對(duì)數(shù)量進(jìn)行扣減。當(dāng)然,這樣可能會(huì)出現(xiàn)數(shù)量為負(fù)值情況,此時(shí)可以引入 LUA 腳本進(jìn)行保障:

-- KEYS[1]: 庫(kù)存鍵的名稱,例如 stock:1001
-- ARGV[1]: 要扣減的數(shù)量
local stock = tonumber(redis.call('GET', KEYS[1]))

-- 判斷扣減的數(shù)量是否大于庫(kù)存數(shù)量
if stock < tonumber(ARGV[1]) then
    return -1
end

-- 扣減庫(kù)存,并返回剩余的庫(kù)存數(shù)量
stock = stock - tonumber(ARGV[1])
redis.call('SET', KEYS[1], stock)

-- 返回剩余的庫(kù)存數(shù)量
return stock

4.2. MySQL

同樣的操作也可以在 MySQL 中操作,如下圖所示:

圖片圖片

也可避免扣減為 負(fù)值的情況,如下圖所示:

圖片圖片

新增對(duì) count 的條件判斷,通過(guò)操作結(jié)果控制不同的流程:

  1. 影響行數(shù)為1,代表操作成功;
  2. 影響函數(shù)為0,代表操作失?。?/span>

5. 樂(lè)觀鎖

當(dāng)一個(gè)事務(wù)(線程)修改一個(gè)數(shù)據(jù)時(shí),先記錄下該數(shù)據(jù)的版本號(hào),其他事務(wù)(線程)修改該數(shù)據(jù)時(shí)必須先檢查版本號(hào),只有版本號(hào)相同的事務(wù)(線程)才能修改數(shù)據(jù)。樂(lè)觀鎖通常使用CAS(Compare and Swap)操作實(shí)現(xiàn),對(duì)并發(fā)性能影響較小,但是需要開(kāi)發(fā)人員在代碼中增加版本號(hào)檢查的代碼。

業(yè)務(wù)中使用最多的場(chǎng)景仍舊是 讀-改-寫(xiě),此時(shí)最佳處理方案便是樂(lè)觀鎖。

圖片圖片

相對(duì)于數(shù)據(jù)更新,樂(lè)觀鎖方案只是增加了 version 判斷,并未引入其他復(fù)雜性,對(duì)性能影響非常小。

  1. 在加載數(shù)據(jù)時(shí)獲取當(dāng)前的數(shù)據(jù)版本 vsn1;
  2. 操作完成后,將數(shù)據(jù)更新到DB時(shí),指定更新的數(shù)據(jù)版本為 vsn1,并將最新的 vsn 更新為 vsn1+1;
  3. 根據(jù)操作結(jié)果進(jìn)行判斷:
  1. 更新成功,數(shù)據(jù)庫(kù)數(shù)據(jù)未發(fā)生變化,不存在并發(fā)問(wèn)題;
  2. 更新失敗,數(shù)據(jù)庫(kù)數(shù)據(jù)已經(jīng)發(fā)生變化,此時(shí)可以告知用戶對(duì)數(shù)據(jù)進(jìn)行重新加載,并進(jìn)行修改;

對(duì)于聚合根來(lái)說(shuō),這是數(shù)據(jù)更新最常見(jiàn)的并發(fā)保障機(jī)制。

6. 悲觀鎖

當(dāng)一個(gè)事務(wù)(線程)正在使用某個(gè)數(shù)據(jù)時(shí),其他事務(wù)(線程)就不能訪問(wèn)該數(shù)據(jù),必須等待鎖釋放后才能訪問(wèn)。悲觀鎖能夠保證數(shù)據(jù)的一致性,但是對(duì)并發(fā)性能影響比較大。

悲觀鎖是最后的辦法,由于其對(duì)性能沖擊較大,不到萬(wàn)不得已不要隨便使用。

6.1. 數(shù)據(jù)庫(kù)悲觀鎖

MySQL 提供 for update 指令,可以在查詢數(shù)據(jù)時(shí)獲取寫(xiě)鎖,從而保證數(shù)據(jù)不會(huì)被破壞。

使用 for update 加載數(shù)據(jù),操作如下:

圖片圖片

for update 語(yǔ)句將對(duì)數(shù)據(jù)進(jìn)行強(qiáng)制加鎖,只有在事務(wù)提交后,鎖才會(huì)釋放。如圖所示,for update 會(huì)對(duì)操作進(jìn)行強(qiáng)制排序,最終使單條操作變成串行化,從而影響并發(fā)度最終影響系統(tǒng)性能。

6.2. 分布式鎖

通常情況下分布式鎖是一種特殊的悲觀鎖,在一些數(shù)據(jù)添加場(chǎng)景非常重要。

比如,在訂單系統(tǒng)中,對(duì)于特價(jià)商品一個(gè)用戶只能購(gòu)買(mǎi)一次,如下圖所示:

圖片圖片

該流程存在并發(fā)問(wèn)題,可能導(dǎo)致一個(gè)用戶下單多次:

  1. 兩個(gè)線程都成功加載用戶的歷史訂單;
  2. 進(jìn)行重復(fù)性校驗(yàn),發(fā)現(xiàn)都沒(méi)有購(gòu)買(mǎi)該商品,從而進(jìn)入生單流程;
  3. 兩個(gè)線程完成訂單對(duì)象構(gòu)建,將數(shù)據(jù)保存到數(shù)據(jù)庫(kù);
  4. 最終,同一用戶生成了兩個(gè)訂單,與業(yè)務(wù)預(yù)期不符;

由于是新增場(chǎng)景,沒(méi)有什么資源可鎖定,所以樂(lè)觀鎖方案無(wú)法落地,此時(shí)就需要引入分布式鎖,如下圖所示:

圖片圖片

以 user 為單位申請(qǐng)分布式鎖,保證同一用戶只有一個(gè)線程能進(jìn)行被保護(hù)流程,從而保證同一用戶不會(huì)購(gòu)買(mǎi)多次。

4. 小結(jié)

并發(fā)管理是一個(gè)高級(jí)話題,也是設(shè)計(jì)中的難點(diǎn),一不小心就會(huì)出問(wèn)題。讓每個(gè)開(kāi)發(fā)人員都成為并發(fā)高手又是一件不太現(xiàn)實(shí)的事,但,好在存在很多并發(fā)管理的成熟方案,業(yè)務(wù)開(kāi)發(fā)者按照?qǐng)鼍斑M(jìn)行落地即可:

  1. 局部串行:適用于同一數(shù)據(jù)的修改需要串行處理;不同數(shù)據(jù)間可并行處理的場(chǎng)景;
  2. 最后寫(xiě)勝出:適用于不依賴于前值狀態(tài)的更新操作,對(duì)數(shù)據(jù)進(jìn)行全量覆蓋的場(chǎng)景;
  3. 原子指令:適用于通過(guò)原子指令能完成業(yè)務(wù)場(chǎng)景,并且存儲(chǔ)引擎也提供了對(duì)應(yīng)支持;
  4. 樂(lè)觀鎖:適用于聚合根的更新場(chǎng)景,對(duì)性能影響極小,可以作為框架默認(rèn)配置;
  5. 悲觀鎖:適用于最為嚴(yán)格的場(chǎng)景,需要強(qiáng)制串行,對(duì)性能影響極大,需謹(jǐn)慎選擇;


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

2024-07-03 08:49:32

2020-07-14 07:12:19

云安全AI無(wú)服務(wù)器

2012-05-09 09:25:19

云計(jì)算云服務(wù)中斷

2023-03-30 14:14:45

Kubernetes

2024-03-28 08:41:10

高并發(fā).NET異步編程

2024-01-31 08:50:41

Guava并發(fā)工具

2024-02-01 18:06:04

Python編程系統(tǒng)

2020-11-23 09:21:09

開(kāi)源項(xiàng)目

2024-01-03 10:03:26

PythonTCP服務(wù)器

2025-03-26 01:25:00

Spring開(kāi)發(fā)JSON

2020-04-13 09:55:45

CIOIT預(yù)算首席信息官

2011-11-14 15:38:41

2012-10-11 10:37:34

集成系統(tǒng)IBMPureFlex

2021-01-22 15:25:42

數(shù)據(jù)科學(xué)數(shù)據(jù)分析IT

2025-02-28 13:00:00

JavaScrip開(kāi)發(fā)語(yǔ)言

2015-06-29 16:16:58

云計(jì)算PaaS云安全

2023-11-03 08:32:53

Flask高并發(fā)

2023-04-28 11:08:21

供應(yīng)商企業(yè)

2023-08-26 09:20:23

2021-05-14 14:52:59

高并發(fā)TPSQPS
點(diǎn)贊
收藏

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