很多優(yōu)化考慮都不是局部的芝薇,是全流程的優(yōu)化济竹,關于
高并發(fā)流量(查詢)的應對
可以看我之前寫的http://www.reibang.com/p/f5271c825eb9
本文聚焦下單轿秧、回滾钙姊、定時取消
本文全是我本人生產環(huán)境沉淀下來的干活,沒有圖(內部產出不能放)须妻,都是字可能有點枯燥仔蝌,請見諒,后面會考慮加些圖解
零 補充一個常規(guī)下單模式的選型以及優(yōu)缺點分析
-
下單減庫存荒吏,即當買家下單后敛惊,在商品的總庫存中減去買家購買數量。下單減庫存是
最簡單,最精確
的一種绰更,下單時直接通過數據庫的事務機制控制商品庫存瞧挤,這樣一定不會出現超賣的情況。
缺點:有些人下完單可能不付款,尤其是惡意下單的人是不會真正付款的,那么既有可能出現想買的人買不成,最終活動也過期了,商家也很煩~
-
付款減庫存儡湾,即買家下單后特恬,并不立即減庫存,而是等到有用戶
付款后才真正減庫存
盒粮,否則庫存一直保留給其他買家鸵鸥。
缺點:因為下單時不會減庫存,所以也就可能出現下單數遠遠超過真正庫存數
的情況丹皱,尤其會發(fā)生在做活動的熱門商品上妒穴。這樣一來,就會導致很多買家下單成功但是付不了款摊崭,買家的購物體驗自然比較差
讼油。 -
預扣庫存,這種方式相對復雜一些呢簸,買家下單后矮台,庫存為其保留一定的時間(如 10 分鐘),超過這個時間根时,庫存將會自動釋放瘦赫,釋放后其他買家就可以繼續(xù)購買。在買家
付款前
蛤迎,系統(tǒng)會校驗
該訂單的庫存是否還有保留
:如果沒有保留确虱,則再次嘗試預扣
;如果庫存不足(也就是預扣失斕骜伞)則不允許繼續(xù)付款校辩;如果預扣成功,則完成付款并實際地減去庫存辆童。
缺點:這種方案確實可以在一定程度上緩解上面的問題宜咒。但是否就徹底解決了呢?其實沒有把鉴!針對惡意下單這種情況故黑,雖然把有效的付款時間設置為 10 分鐘,但是惡意買家完全可以在 10 分鐘后再次下單,或者采用一次下單很多件的方式把庫存減完倍阐。針對這種情況概疆,解決辦法還是要結合安全和反作弊的措施來制止逗威。比如給用戶打標,惡意用戶進入蜜罐(后面會說)
一 下單中的優(yōu)化
- 前端進行庫存可選項的限制,避免用戶大部分的
無效庫存嘗試請求
在涉及到庫存的商品一般在進入詳情頁面或者下單頁面的時候把庫存給查出來,這時候我們可選的商品數量必然要小于這個庫存,不要等用戶點了無數次下單,一個個嘗試庫存是否充足,另外呢即使這樣,由于秒殺的高并發(fā)高流量,依然可能用戶拿到的庫存大于服務端庫存,因此當用戶下單的時候,如果因為庫存原因下單失敗呢,我們就進行前端拿到的庫存值的更新,另外如果庫存不足了,直接由下單頁返回到詳情頁,前端置灰下單按鈕阻止用戶的無效請求; -
風控峰搪、網絡安全
有時候我們會判斷出用戶是有問題的,比如某個用戶開開團前一個小時開始極其高頻的請求數據(1S刷新10次以上或者更高),那這個時候如果我們進行攔截,那他肯定會想各種各樣的方法去破解,最后可能又得手了,那么其實我們這里可以誘導防御的方式,比如說有一種叫做蜜罐
的技術,可以理解為直接提供一個仿真環(huán)境用于引流惡意用戶,給他提供一個啥玩意都有的數據環(huán)境,有服務器有緩存有數據庫,你可以看詳情,然后當他進行下單的時候,咱們沙盒
仿真
環(huán)境
也讓他下單成功,但是呢 他一看訂單(查真實庫)就啥也沒有,就支付不了,用不了咱們真正庫存;
再者呢,可以用多重史詩級難度驗證碼攔擊,咱們可以讓網關層在給用戶打標簽為惡意用戶之后呢,咱們讓他下單的時候就輸入驗證碼,而且給他的是史詩級難度的驗證碼,干擾線比字母多多了的那種,而且呢還不止讓他驗證一次,讓他驗證個兩三次,如果這都難不倒他,就算了; -
庫存緩存化
,我們可以把庫存做到redis里凯旭,我們先采用lua(預查redis庫存->再預扣redis的方式)->樂觀鎖扣除mysql庫存- 為什么這里不直接decr預扣redis要用lua先查redis再預扣概耻?主要是考慮到由于秒殺團購等屬于賠錢引流的生意,所以大部分請求都是遠遠大于庫存的,因此極有可能買不到罐呼,如果我們直接扣redis鞠柄,那扣失敗了,咱們不還是要還庫存加回去嗎
- 另外呢嫉柴,要注意由于庫存的特殊性厌杜,我們要保證redis里的數據和mysql 的數據的一致性,因此我們要使用一些事務來保證计螺,比如TCC或者MQ事務實現夯尽;
- 這里咱們將mysql庫存扣減放到了最后一步也減少了mysql鎖競爭的過程了,這性能飛升暗锹匙握;
-
下單異步化 or 限流,其實在我們前面講了數據全部進緩存了,那么其實普通業(yè)務僅僅是查詢,我們redis集群抗個幾十萬qps并發(fā)不存在任何問題了.
但是下單接口不一樣,下單的流程需要操作數據庫,那么數據庫如果存在大量的行鎖必然會造成大量的等待和超時問題,這種情況解決方案也有;
第一種方案: 我們可以采用異步化下單接口,讓下單走隊列,根據自己系統(tǒng)能力去設置消費線程數,然后輪詢下單狀態(tài)做最終結果,或者直接采用線程池去下單,利用去callback接口帶回下單結果.
第二種方案: 限流,這里的限流不是直接做到接口層面
,因為我們是采用是redis預扣庫存方式,我們其實不是怕大量流量過來,我們是怕大量流量+大量庫存造成了我們redis這時候形同虛設,大量的DML操作在mysql被阻塞,那么這里我們可以進行限流,突破了redis防線的真正扣減mysql操作需要先申請資源,拿到資源再進行下面的操作,其他的進行攔截或者等待,這個操作可以用哨兵來做;并且這里需要做個稍微精準點的限流,比如做到商品層面,每個商品每秒可下單多少多少...這樣,因為我們其實不太怕大量DML,而是怕同一行大量DML;
第三種方案: 據我所知目前有些公司會再mysql層面再做一層開發(fā),他會有行鎖競爭的時候在行后追加一個隊列,把行鎖轉換為隊列,這樣其實也可以很大程度上解決性能問題,排隊效率必然比并發(fā)競爭阻塞要高得多得多(鎖競爭情況下 InnoDB 內部的死鎖檢測,以及 MySQL Server 和 InnoDB 的切換會比較消耗性能); - 下單服務單獨集群,盡管我們上面已經做了重重保障,但是我們還是比較擔心下單造成阻塞,大量tomcat連接數被下單請求使用,其他正常業(yè)務請求無法進來,造成整個系統(tǒng)不可用,那我們就可以讓下單服務器單獨隔離開,其他的服務器用于服務大量的查詢接口;
二 訂單定時取消的優(yōu)化
訂單定時取消是一個非常常見的需求,尤其是上面說到的下單減庫存模式,因為我們有時候會比較擔心用戶下單了,但是不支付,這時候又鎖住了庫存,那其他用戶就一直沒法購買了,所以我們其實就需要進行訂單的自動取消功能,避免長期鎖住庫存讓其他人無法購買;
訂單超時取消存在一個
無法在過期的一瞬間即時處理超時訂單
的問題
舉個例子,比如團購下單接口有個訂單15分鐘超時取消訂單的操作,但是呢我們有時候沒有辦法一下子處理那么多訂單,讓他過期,比如有*十萬個訂單同一時刻過期,不論咋樣我們肯定沒有辦法同時處理完的.但是呢這種超時訂單是絕對不能繼續(xù)讓他進行支付的,咋辦呢?
我們可以進行數據的另類同步
,我們可以在任何查出到這個訂單的地方都對狀態(tài)進行兩種判斷,比如1 訂單狀態(tài)為超時取消,2 訂單狀態(tài)為下單中,但是訂單下單時間距離現在超過了15min,這兩種狀態(tài)咱們都認定他為超時,這樣呢我們就可以做到一定程度上的訂單同一時刻超時了.所謂,所有人都認為你牛逼,你就真的牛逼了,毫無破綻-
這種固定15min超時取消的業(yè)務,咱們可以直接用市面上常用的MQ進行異步化定時處理
- 一方面進行限流了,我們可以根據系統(tǒng)服務能力,調整消費線程數;
- 另外一方面不需要自己寫任務導讀框架,不需要去避免重復處理了,如果系統(tǒng)是集群的,也不需要考慮多個系統(tǒng)直接的并發(fā)競爭問題,省時省力;
- 至于做法可以給大家提供一種方案,RabbitMQ TTL隊列+死信隊列實現;
三 回滾邏輯優(yōu)化
- 把子訂單做的稍寬些,把一些信息放到訂單表里尤其一些強關聯性信息,最好做到一張表內,比如庫存主鍵,商品購買數量,這樣在回滾的時候一方面可以精準命中目標,另外一方面減少許多額外的查詢操作;
- 加分布式串行鎖(如redlock) + 樂觀鎖保障回滾不會被多次回滾,其實秒殺下單一般稍微多考慮考慮都不會出現超賣情況,但是回滾這個邏輯需要好好考慮,這個極易造成超賣,普通業(yè)務單一產品單一庫存還好,像我的業(yè)務涉及到周期性庫存,其實很容易涉及到超賣;
- 異步化,在以下情況下可以采用異步化回滾的方式
- 如果我們對上游的調用量沒有一個很好的預估或者上游的取消訂單流量極其不規(guī)律
- 上游業(yè)務不關心返回值或者上游業(yè)務不需要立即知曉回滾結果
那么這里我們可以采用異步MQ進行接收回滾,如果上游需要知曉回滾結果,可能會高頻查狀態(tài)那么可以將回滾狀態(tài)都存入redis
回滾接口我這里優(yōu)化的比較少
- 一方面是由于其功能確實簡單,只需要保障回滾別造成超賣即可
- 另外一方面是因為大部分商品都是優(yōu)惠力度極大,一般不會取消訂單,回滾庫存;
目前就到這里了,后面有空我會再補充一些
秒殺一般是大流量少庫存,像我目前營銷活動這塊設計到商品庫存的周期庫存,設計理念就是想讓商品慢慢賣,平均到指定周期的指定時段,一般單商品單個周期多了也就200左右并發(fā)的樣子,一般主要設計的好下單的時候沒啥問題;但是呢,這里存在一個未來可能的問題,那就是商品流量確實很大,商品庫存也很多,比如100萬人搶1W個小米手機,好家伙,完全是真實情況啊,這個問題其實是一個很現實的問題,在真實的做電商的互聯網公司其實都會遇到這個問題,但是呢你看很多人的博客都寫的比較夸張,標題都是啥10W并發(fā),一看庫存幾十個,玩呢老哥,真實有那么大流量的陈轿,庫存不至于這么低圈纺,萬一搞個大庫存,用你這方案妥妥的宕機啊;
所以這里對于有效訂單的大流量麦射,大庫存問題的解決也做出了我的一些總結蛾娶,可以看http://www.reibang.com/p/552c4093832e