框架相關(12)-- 面試題補充

1、四層協議和七層協議的負載均衡

所謂四層就是基于IP+端口的負載均衡,通過虛擬IP+端口接收請求,然后再分配到真實的服務器;常見的四層負載均衡器有LVS和F5。

七層通過虛擬的URL或主機名接收請求,然后再分配到真實的服務器七層就是基于URL等應用層信息的負載均衡。常見的七層負載均衡器有haproxy,nginx。

2、庫存大促如何在高并發的時候保證既不會多扣也不會少扣(庫存超賣問題)?

有悲觀鎖,分布式鎖,樂觀鎖,隊列串行化,Redis原子操作幾種解決方案。

1)悲觀鎖、隊列串行化

悲觀鎖,也就是在修改數據的時候,采用鎖定狀態,排斥外部請求的修改。遇到加鎖的狀態,就必須等待。可以采用redis隊列+mysql事務控制的方案。

mysql的執行代碼:

beginTranse(開啟事務)

try{

? ? ? ? ? ?//quantity為請求減掉的庫存數量$dbca->query('update s_store set amount = amount - quantity where postID =? ? ? ? ?12345');? ? ? ??

? ? ? ? ? $result = $dbca->query('select amount from s_store where postID = 12345');

? ? ? ? ? if(result-amount <0){thrownewException('庫存不足'); }

? ? ?}catch($eException){? ??

? ? ? rollBack(回滾)}

commit(提交事務)

先執行update鎖住本條記錄,這樣就能保證其他線程執行不了更新操作,可以避免超扣現象。

上述的方案的確解決了線程安全的問題,但是,別忘記,我們的場景是“高并發”。也就是說,會很多這樣的修改請求,每個請求都需要等待“鎖”,某些線程可能永遠都沒有機會搶到這個“鎖”,這種請求就會死在那里。針對這個問題我們稍微修改一下上面的場景,我們直接將請求放入隊列中的,采用FIFO(First Input First Output,先進先出),這樣的話,我們就不會導致某些請求永遠獲取不到鎖。

2)樂觀鎖

樂觀鎖,是相對于“悲觀鎖”采用更為寬松的加鎖機制,大都是采用帶版本號(Version)更新。實現就是,這個數據所有請求都有資格去修改,但會獲得一個該數據的版本號,只有版本號符合的才能更新成功,其他的返回搶購失敗。這樣的話,我們就不需要考慮隊列的問題,不過,它會增大CPU的計算開銷。但是,綜合來說,這是一個比較好的解決方案。

mysql實現方案:數據庫表增加版本字段如version,每次修改時版本號+1

如果更新操作順序執行,則數據的版本(version)依次遞增,不會產生沖突。但是如果發生有不同的業務操作對同一版本的數據進行修改,那么,先提交的操作會把數據version更新為2,當A在B之后提交更新時發現數據的version已經被修改了,那么A的更新操作會失敗。

PDO update更新后,不但要驗證返回狀態是否為true,并且同時驗證影響行數是否大于0。

Redis實現方案:在 Redis 中使用 watch 命令可以決定事務是執行還是回滾。

一般而言,可以在 multi 命令之前使用 watch 命令監控某些鍵值對,然后使用 multi 命令開啟事務,執行各類對數據結構進行操作的命令,這個時候這些命令就會進入隊列。

當 Redis 使用 exec 命令執行事務的時候,它首先會去比對被 watch 命令所監控的鍵值對,

如果沒有發生變化,那么它會執行事務隊列中的命令,提交事務;

如果發生變化,那么它不會執行任何事務中的命令,而去事務回滾。

無論事務是否回滾 , Redis 都會去取消執行事務前的 watch 命令

while (true) {

? ? ? ? ? ? System.out.println(Thread.currentThread().getName());

? ? ? ? ? ? jedis = RedisUtil.getJedis();

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? jedis.watch("mykey");

? ? ? ? ? ? ? ? int stock = Integer.parseInt(jedis.get("mykey"));

? ? ? ? ? ? ? ? if (stock > 0) {

? ? ? ? ? ? ? ? ? ? Transaction transaction = jedis.multi();

? ? ? ? ? ? ? ? ? ? transaction.set("mykey", String.valueOf(stock - 1));

? ? ? ? ? ? ? ? ? ? List<Object> result = transaction.exec();

? ? ? ? ? ? ? ? ? ? if (result == null || result.isEmpty()) {

? ? ? ? ? ? ? ? ? ? ? ? // 可能是watch-key被外部修改,或者是數據操作被駁回? ? ? ? ? ? ? ? ? ? ? ?

? ? ? ? ? ? ? ? ? ? ? ?System.out.println("Transaction error...");

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? System.out.println("庫存為0");

? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? } catch (Exception e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? ? ? RedisUtil.returnResource(jedis);

? ? ? ? ? ? }finally{

? ? ? ? ? ? ? ? RedisUtil.returnResource(jedis);

? ? ? ? ? ? }

? ? ? ? }

3)分布式鎖

常規做法

首先我們要明確重要的一點是減庫存是需要順序的,而需要順序就意味著不能有并發加減庫存的操作,為了實現順序,一般做法都是將多線程強行變為單線程實現同步操作或者所說的順序,將多線程變為單線程方案普遍做法便是使用鎖或者借助隊列。另一方面由于大型互聯網應用面向大量用戶所以都是大型分布式加集群作為最基礎的架構,而由于架構原因,往常所使用的lock或者Synchronized進程鎖關鍵字失去了意義(只能鎖住當前Web程序代碼塊,但無法鎖住集群中其他Web程序)。此時我們便要借助分布式鎖或者MQ組件來達到跨進程跨主機的單線程效果。

接下來我們以ABC下單減庫為例說明分布式下的減庫存場景

ABC同時發起庫存減1的請求

服務器接收到三個減庫存操作,利用分布式鎖鎖住了減庫存的邏輯,每次只限一個請求操作.對A請求進行庫存減1操作后,再對B進行操作,one by one 以此類推。

目前減庫存操作運行很好,不會發生超賣情況,老板再也不擔心程序員敗家了。但是以上減庫存的邏輯有個很大的問題便是由于強行將多線程請求變為單線程,不可避免的導致排隊的發生,這樣會發生什么情況呢?

越后進分布式鎖或者隊列的請求他需要的響應時間越久因為他的響應時間是前面所有請求的響應時間之和。當然有人會說增加配置或者在redis中減庫存再利用rabbitmq將結果同步到數據庫中,由于操作內存中的數據讓減庫存操作響應加快,這的確對單次的減庫存有效,但是隨著并發提高,單次減庫存響應時間的優化必將遇到瓶頸。依然沒有解決高并發下所有人必須強行排隊導致的問題。那有沒有那種又順序執行又能相對的并行加減庫存操作呢?

并行異步減庫存

減庫存必定是順序排隊的,這毋庸置疑,但是有沒有辦法可以加快這個排隊呢,答案是有的!

只有將同步減庫存邏輯變為異步才能從根本解決排隊問題。但是有人會說這與庫存操作的邏輯(同步順序排隊)沖突。

其實這里所說的異步是相對的,什么意思呢?

首先全局庫存是必須順序操作的,但是如果我們把庫存分割成N塊,每一塊內部是順序的,但是每一塊彼此之間又是異步的。這樣就很好的解決了庫存順序執行的邏輯又減輕了排隊的影響。有人會問這里是如何減輕的呢?首先來給減庫存算一筆響應時間的賬:

假設每個減庫存操作的響應時間優化到50毫秒,并發2000,按照常規做法加全局鎖那第2000個人的響應時間便是前面1999個用戶的響應時間加他自己的50毫秒之和為100秒。100秒的響應可能用戶早就心里默默詛咒你了。而且這已經是非常理想化的單次響應時間了。如果有人說可以優化到2毫秒就不會超時了。。麻煩帶上鍵盤去微博杠吧。。

如果使用第二種方案假設三個用戶請求減庫存操作,完全可以讓三個請求進三個不同的鎖去扣減各自的庫存數,此時三人沒有排隊可以保證他們同時減庫存,而又不影響庫存總數的準確性,因為三個請求操作的是各自鎖所維護的庫存數。隨著業務增長,庫存總數的分割可以不斷細分直到縮短響應時間到合理范圍,而這個庫存總數的分割很好的保證了不會遇到瓶頸。但是由于這種業務架構的設計,導致業務不得不變得復雜,可以看到我們在進入分布式鎖之前有一個稱為庫存總數協調器的模塊,這個模塊是用來做什么的呢?

首先我們把庫存分割成多塊后解決的首要問題便是如何讓請求均勻的依次進入每一個分布式鎖中進而操作當前鎖所負責的庫存數。

庫存協調器的邏輯完全看各位自己業務模型來決定,你可以用雪花算法均勻分布也可使用ip或者用戶標識取余去覆蓋到每一個鎖,總之實現方式看業務情況來決定,當然了很大幾率會出現有的庫存塊內的庫存總數消耗完了但有的還剩余,所以庫存協調器一定要考慮到這類情況及時將庫存較多的庫存塊內的庫存數分散給其他庫存塊,以達到多線程減庫存的效果。

從示例圖中可以看到引入了rabbitmq,他在當前整個業務架構中的作用主要是每一個分布式鎖處理完當前庫存塊的庫存后要將當前加減的數量丟給消息隊列,由消費端慢慢消化這些操作到數據庫。

● 其實這就是分段加鎖。你想,假如你現在iphone有1000個庫存,那么你完全可以給拆成20個庫存段,要是你愿意,可以在數據庫的表里建20個庫存字段,比如stock_01,stock_02,類似這樣的,也可以在redis之類的地方放20個庫存key。

● 總之,就是把你的1000件庫存給他拆開,每個庫存段是50件庫存,比如stock_01對應50件庫存,stock_02對應50件庫存。

● 接著,每秒1000個請求過來了,好!此時其實可以是自己寫一個簡單的隨機算法,每個請求都是隨機在20個分段庫存里,選擇一個進行加鎖。

● bingo!這樣就好了,同時可以有最多20個下單請求一起執行,每個下單請求鎖了一個庫存分段,然后在業務邏輯里面,就對數據庫或者是Redis中的那個分段庫存進行操作即可,包括查庫存 -> 判斷庫存是否充足 -> 扣減庫存。

● 這相當于什么呢?相當于一個20毫秒,可以并發處理掉20個下單請求,那么1秒,也就可以依次處理掉20 * 50 = 1000個對iphone的下單請求了。

● 一旦對某個數據做了分段處理之后,有一個坑大家一定要注意:就是如果某個下單請求,咔嚓加鎖,然后發現這個分段庫存里的庫存不足了,此時咋辦?

● 這時你得自動釋放鎖,然后立馬換下一個分段庫存,再次嘗試加鎖后嘗試處理。這個過程一定要實現。

參考:https://blog.csdn.net/sD7O95O/article/details/101398241

https://blog.csdn.net/u010391342/article/details/84372342

4)Redis原子操作

incr/decr原子性操作,incr增加,decr減少

//此處不可以取出后放入php變量判斷庫存,否則會出現幻讀,導致超賣

if ($redis_client->decrby($key, $parmas['num']) > -1) {? //利用原子操作將庫存數量減去需要的商品數量,如果返回值大于-1則說明庫存量滿足要求

? ? ? ? //減庫存

? ? ? ? $goods = new Goods();

? ? ? ? $parmas['version'] = 1;

? ? ? ? return $goods->subInventory($parmas);

} else {

? ? //購買多個時,如庫存不足,需要把數量加回去,否則會出現庫減庫存,商品并沒有賣出去

? ? $redis_client->incrby($key, $parmas['num']);

? ? return false;

}

使用redis測試時,每次修改完庫存需要刪除KEY:DEL goods_1

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容