1. 程式人生 > >併發情況下引發的血案

併發情況下引發的血案

首先澄清一下,最近更博比較少,最近在研究新的東西的同時還有大量的任務在做,這個月會繼續的更新,把rabbitmq的系列更新完成,同時把我研究的新的東西的完整的系列也整理髮布出來,大家一起學習進步。

一、問題描述:

很多時候面試都會被問到併發的問題,那個時候我們總覺的遇不到這種情況,併發多執行緒就是一個屠龍之術,不幸的是本人負責的一個專案中,出現了這個問題。最開始體量很少使用者少,看不出問題更加測不出問題,最終還是使用者給我們暴漏了出來。還好這是一個模擬的貴金屬交易系統(給各大銀行交易大賽使用的)

下面先給一段虛擬碼來描述一下邏輯片段:

if( userHoldPosition >= entrust.getNum){
  執行平倉程式  中間過程大概 1-3s的耗時
  // 更新使用者的倉位
  updateUserPosition(userNo,entrustNum);
}

這段程式碼正常跑起來沒問題吧,是的我們測試過很多遍,畢竟銀行大佬們如果程式稍有問題,他們是不會用的心情真是誠惶誠恐呀。

下面給出一種在多執行緒下編髮的問題,

  • A請求A 執行緒通過判斷正在執行平倉程式,但是此時還沒有走到更新倉位的程式碼
  • 此時B請求B執行緒也來到了判斷使用者倉位是否足夠平倉,由於A執行緒還未更新使用者持倉,所以此時B執行緒判斷成功通過,
  • A執行緒更新使用者持倉,B執行緒完成平倉程式,更新使用者倉位

二、分析

2.1 問題推廣
  • 1.在這個併發的情況下,加入使用者的倉位不足,是不是會出現負倉位的情況出現。
  • 2.把這種情況推廣,電商中下單扣減庫存的問題上、領取優惠券的問題上
2.2 解決方案:

要想解決問題,必須要找到問題的根源:這種情況出現的原因①:請求同時到達,判斷與更新之間時間跨度大,例如開平倉的計算問題,以及訂單的下單、支付中間都需要很長的時間跨度,所以下面針對這種情況給出解決方案。

  • 針對電商下單的問題,一般是下單就立即鎖庫存,注意這種方法可以抵禦併發量不大的情況,如果是阿里、京東這種體量依舊抵擋不住,目前題主沒有太好的辦法,慢慢的積累看以後有什麼更好的辦法吧。
  • 雙重檢查(double check相信大家在單例的七種實現方法中看到過),這種針對我目前遇到的問題是可以解決的,就是在更新使用者的倉位的時候在做一次判斷,當然極端的情況依舊會有問題(大體量的使用者的時候),這中情況類似於樂觀鎖,大家也可以使用樂觀鎖來實現

ps:說加鎖的朋友,我就不想在說什麼了,如果什麼地方都可以通過加鎖搞定,多執行緒和併發就不會那麼折磨人了。

三、介紹我在網上看到的一種血案(需要通過鎖來實現的)

一張優惠券引發的血案原文連結
https://juejin.im/post/5a5182986fb9a01ca5604a8d

image.png
我想大部分沒有併發思維的同學都是這樣寫出來的,然而問題來了,當某個時間點快取不存在,請求量比較大的時候,同時出現多個執行緒去查詢DB並進行快取,改進使用鎖
image.png
這裡是使用到了鎖,但是在上文中為何我又說鎖不能解決問題呢,這裡是因為更新快取的資料一定只能是序列的一定不能出現多個執行緒去查詢資料庫然後更新快取的情況,這樣的情況的話快取就失去了意義了。然而下單程式本來就是一個多執行緒的高併發的應用,如果這個時候你去加鎖,豈不是讓下單交易程式變成了一個序列話的程式了嗎,這種情況肯定是解決了不一致的問題,但是你的吞吐量會嚴重的下降。

這裡已經加鎖了,進行到這裡應該就可以高枕無憂了吧,只能too young too simple,忘了上面我們遇到的同時通過判斷的情況了嗎,假如A執行緒在獲取鎖的時候,B執行緒在判斷優惠券列表是否存在,A更新釋放鎖,此時B獲取鎖又會在進行一次更新,所以問題依然存在,下面看解決方案:

image.png

這就是我遇到的和網上看到的,在高併發下,看起來測試起來都沒有問題的程式發生血案的故事,這裡面鎖和雙重檢查的思想一定是要有的,以及常見的解決手段樂觀鎖都是需要具備的,所以我們在做這種帶有併發的程式的時候一定要有併發思想在裡面,對於可能出現的極端的併發的情況進行提前的處理,以防引發事故。
部落格首發地址,另外也有群570980002,希望大家能一起在技術上進行探討,共同成長共同進步