單體應(yīng)用分布式部署產(chǎn)生的并發(fā)和事務(wù)問題

一坤溃、問題描述

現(xiàn)有一個電商項目ac-mall-buy涤妒,該項目是單體應(yīng)用架構(gòu)而非微服務(wù)架構(gòu)单雾,但為了支撐更大的用戶量,將該項目部署在3臺web服務(wù)器上(服務(wù)A她紫、服務(wù)B硅堆、服務(wù)C),并用Nginx做反向代理贿讹。

該項目有下單功能渐逃,下單后要更新產(chǎn)品庫存,更新庫存?zhèn)未a如下

@Transactional(rollbackFor = Exception.class)
public void updateProductStore(String productId){
        //操作數(shù)據(jù)庫民褂,更新商品庫存
}

由于在更新庫存的方法上加了事務(wù)注解@Transactional(rollbackFor = Exception.class)茄菊,在單體應(yīng)用,單體部署的時候是沒問題的赊堪,即使出現(xiàn)并發(fā)的情況面殖,事務(wù)控制也能保證產(chǎn)品庫存的一致性。

但如果是分布式部署哭廉,則會出現(xiàn)分布式事務(wù)的問題脊僚,事務(wù)注解@Transactional(rollbackFor = Exception.class) 只針對本地服務(wù)有效。如果現(xiàn)在服務(wù)A遵绰、服務(wù)B同時更新某一產(chǎn)品庫存辽幌,就會出現(xiàn)數(shù)據(jù)不一致的問題。

二椿访、并發(fā)的控制策略

控制并發(fā)采用的策略通常分為樂觀鎖和悲觀鎖乌企。

樂觀鎖的定義: 顧名思義,對加鎖持有一種樂觀的態(tài)度成玫,即先進行業(yè)務(wù)操作加酵,不到最后一步不進行加鎖,樂觀地認為加鎖一定會成功的哭当,在最后一步更新數(shù)據(jù)的時候再進行加鎖猪腕。樂觀鎖的核心算法是CAS(Compare And Swap,比較并交換)荣病,它涉及到三個操作數(shù):內(nèi)存值码撰、預(yù)期值渗柿、新值个盆。當(dāng)且僅當(dāng)預(yù)期值和內(nèi)存值相等時才將內(nèi)存值修改為新值脖岛。

悲觀鎖的定義: 正如其名字一樣,悲觀鎖對數(shù)據(jù)加鎖持有一種悲觀的態(tài)度颊亮。因此,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)脚祟。悲觀鎖的實現(xiàn)感帅,往往依靠數(shù)據(jù)庫提供的鎖機制(也只有數(shù)據(jù)庫層提供的鎖機制才能真正保證數(shù)據(jù)訪問的排他性,否則雹有,即使在本系統(tǒng)中實現(xiàn)了加鎖機制偿渡,也無法保證外部系統(tǒng)不會修改數(shù)據(jù))。

簡言之霸奕,
樂觀鎖: 不是在數(shù)據(jù)庫端鎖住的溜宽,而是程序端控制的。此時可以在mybatis中實現(xiàn)樂觀鎖機制质帅。
悲觀鎖: 在數(shù)據(jù)庫里面鎖住适揉,類似for update查詢。

三煤惩、解決方案

3.1 采用樂觀鎖

如果是一些普通的非高并發(fā)的場景嫉嘀,可以使用樂觀鎖。樂觀鎖的實現(xiàn)通常有兩種方式:版本號字段和時間戳字段魄揉。

補充:為了更好的用戶體驗剪侮,當(dāng)發(fā)生并發(fā)更新失敗后,可以加上重試機制繼續(xù)完成業(yè)務(wù)什猖。

3.1.1 版本(version)字段:

更新的時候給版本號字段加上1票彪,然后UPDATE會返回一個更新結(jié)果的行數(shù),通過這個行數(shù)去判斷不狮,如下所示:

UPDATE T_USER u
SET u.userName = #userName#, u.version = u.version + 1
WHERE u.userId = #userId# AND u.version = #version#

程序?qū)崿F(xiàn)邏輯為:

if(rowsUpdated= =0)
{
    throws new OptimisticLockingFailureException();
}

如果更新執(zhí)行返回的數(shù)量是 0 表示產(chǎn)生并發(fā)問題了降铸,則拋出樂觀鎖并發(fā)修改異常,需要重新獲得最新的數(shù)據(jù)后再進行更新操作摇零。

使用樂觀鎖方案的好處是推掸,mybatis中已提供了實現(xiàn)樂觀鎖的插件 ,進行全局配置即可驻仅,及其簡單方便谅畅。

3.1.2 時間戳(timestamps):

第二種實現(xiàn)方式和第一種差不多,同樣是在需要樂觀鎖控制的table中增加一個字段噪服,名稱無所謂毡泻,字段類型使用時間戳(timestamp),和上面的version類似粘优,也是在更新提交的時候檢查當(dāng)前數(shù)據(jù)庫中數(shù)據(jù)的時間戳和自己更新前取到的時間戳進行對比仇味,如果一致則OK呻顽,否則就是版本沖突。

此方案有缺點丹墨,就是當(dāng)并發(fā)事務(wù)時間間隔小于當(dāng)前系統(tǒng)平臺的最小時間單位時廊遍,會發(fā)生覆蓋前一個事務(wù)結(jié)果的問題。

3.2 用Redis做分布式鎖

分布式鎖本質(zhì)上要實現(xiàn)的目標就是在 Redis 里面占一個“位”贩挣,當(dāng)別的進程也要來占時喉前,發(fā)現(xiàn)已經(jīng)有人坐在那里了,就只好放棄或者稍后再試王财。

占位一般是使用 setnx(set if not exists) 指令卵迂,只允許被一個客戶端占位。先來先占绒净, 用完了狭握,再調(diào)用 del 指令釋放位置。

127.0.0.1:6379> setnx userName alanchen
(integer) 1
127.0.0.1:6379> del userName
(integer) 1

但是有個問題疯溺,如果邏輯執(zhí)行到中間出現(xiàn)異常了论颅,可能會導(dǎo)致 del 指令沒有被調(diào)用,這樣就會陷入死鎖囱嫩,鎖永遠得不到釋放恃疯。于是我們在拿到鎖之后,再給鎖加上一個過期時間墨闲,比如 5s今妄,這樣即使中間出現(xiàn)異常也可以保證 5 秒之后鎖會自動釋放。

127.0.0.1:6379> setnx userName alanchen
(integer) 1
127.0.0.1:6379> expire userName 5
(integer) 1
127.0.0.1:6379> del userName
(integer) 1

但是以上邏輯還有問題鸳碧。如果在 setnx 和 expire 之間服務(wù)器進程突然掛掉了盾鳞,可能是因為機器掉電或者是被人為殺掉的,就會導(dǎo)致 expire 得不到執(zhí)行瞻离,也會造成死鎖腾仅。

這種問題的根源就在于 setnx 和 expire 是兩條指令而不是原子指令。如果這兩條指令可以一起執(zhí)行就不會出現(xiàn)問題套利。也許你會想到用 Redis 事務(wù)來解決推励。但是這里不行,因為 expire 是依賴于 setnx 的執(zhí)行結(jié)果的肉迫,如果 setnx 沒搶到鎖验辞,expire 是不應(yīng)該執(zhí)行的。事務(wù)里沒有 if-else 分支邏輯喊衫,事務(wù)的特點是一口氣執(zhí)行跌造,要么全部執(zhí)行要么一個都不執(zhí)行。

Redis 2.8 版本中作者加入了 set 指令的擴展參數(shù)族购,使得 setnx 和 expire 指令可以一起執(zhí)行壳贪,解決了分布式鎖的問題财著。

127.0.0.1:6379> setex userName 5 alanchen
OK
127.0.0.1:6379> get userName
(nil)

超時問題
Redis 的分布式鎖不能解決超時問題,如果在加鎖和釋放鎖之間的邏輯執(zhí)行的太長撑碴,以至于超出了鎖的超時限制,就會出現(xiàn)問題朝墩。因為這時候第一個線程持有的鎖過期了醉拓,臨界區(qū)的邏輯還沒有執(zhí)行完,這個時候第二個線程就提前重新持有了這把鎖收苏,導(dǎo)致臨界區(qū)代碼不能得到嚴格的串行執(zhí)行亿卤。

為了避免這個問題,Redis 分布式鎖不要用于較長時間的任務(wù)鹿霸。如果真的偶爾出現(xiàn)了排吴,數(shù)據(jù)出現(xiàn)的小波錯亂可能需要人工介入解決。

補充:Java可以直接用Redisson框架實現(xiàn)Redis分布式鎖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末懦鼠,一起剝皮案震驚了整個濱河市钻哩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肛冶,老刑警劉巖街氢,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異睦袖,居然都是意外死亡珊肃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門馅笙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伦乔,“玉大人,你說我怎么就攤上這事董习×液停” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵皿淋,是天一觀的道長斥杜。 經(jīng)常有香客問我,道長沥匈,這世上最難降的妖魔是什么蔗喂? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮高帖,結(jié)果婚禮上缰儿,老公的妹妹穿的比我還像新娘。我一直安慰自己散址,他們只是感情好乖阵,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布宣赔。 她就那樣靜靜地躺著,像睡著了一般瞪浸。 火紅的嫁衣襯著肌膚如雪儒将。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天对蒲,我揣著相機與錄音钩蚊,去河邊找鬼。 笑死蹈矮,一個胖子當(dāng)著我的面吹牛砰逻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泛鸟,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼蝠咆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了北滥?” 一聲冷哼從身側(cè)響起刚操,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎再芋,沒想到半個月后赡茸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡祝闻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年占卧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片联喘。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡华蜒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出豁遭,到底是詐尸還是另有隱情叭喜,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布蓖谢,位于F島的核電站捂蕴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闪幽。R本人自食惡果不足惜啥辨,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盯腌。 院中可真熱鬧溉知,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至玫荣,卻和暖如春甚淡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捅厂。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工贯卦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恒傻。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像建邓,于是被迫代替她去往敵國和親盈厘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

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

  • 1. 背景 對于鎖大家肯定不會陌生官边,在Java中synchronized關(guān)鍵字和ReentrantLock可重入鎖...
    請不要問我是誰閱讀 340評論 0 0
  • 隨著互聯(lián)網(wǎng)信息技術(shù)的飛速發(fā)展沸手,數(shù)據(jù)量不斷增大,業(yè)務(wù)邏輯也日趨復(fù)雜注簿,對系統(tǒng)的高并發(fā)訪問契吉、海量數(shù)據(jù)處理的場景也越來越多...
    風(fēng)平浪靜如碼閱讀 152評論 0 1
  • 轉(zhuǎn)載自(Tank丶Farmer) 在很多場景中,我們?yōu)榱吮WC數(shù)據(jù)的最終一致性诡渴,需要很多的技術(shù)方案來支持捐晶,比如分布式...
    可爸閱讀 334評論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭妄辩,有人歡樂有人憂愁惑灵,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,544評論 28 53
  • 信任包括信任自己和信任他人 很多時候眼耀,很多事情英支,失敗、遺憾哮伟、錯過干花,源于不自信,不信任他人 覺得自己做不成楞黄,別人做不...
    吳氵晃閱讀 6,193評論 4 8