那些你不得不知的搶購業務要點
剛入門的產品小朋友們,或許你們只知道有搶購、團購、閃購等名詞,但是你們有具體了解過這其中的業務要點是什么嗎?下面就跟著我來具體了解一下吧!
案例:馬上就到雙十一了,上級給我安排了一個秒殺搶購的活動,讓我設計一個方案,那我應該如何下手呢?
頁面上面的設計,這里我就不多說了,各大網站上都有很多案例了!
現在我重點來講一下需要注意的幾點:
一、超賣問題
假如你的庫存有10,現在3個用戶來購買,a用戶購買3個,b用戶購買5個,c用戶購買3個,合起來就是準備購買11個。
如果三個用戶是同時并發購買,會出現怎樣的情況呢?
每個用戶進行減庫存的時候,數據庫都會去修改一下數據,如下:update goods set amount=amount-購買數量 where goods_id=xxx。
mysql會鎖定這一行數據(使用innodb存儲引擎),數據庫加的是排他鎖。根據排他鎖的特點:其他線程不能讀、不能寫此行數據。
排他鎖情況下,那么其他用戶就是等待狀態了。
- a用戶執行update的時候,鎖定庫存數據。update執行完畢后,減去了3個后,mysql自動釋放鎖。
- b用戶執行,減去了5個。此時,已經賣掉8個庫存了,庫存數為2了。
- 但是c用戶接著執行,Update goods set amount=amount-1 where goods_id=xxx。
結果庫存數量變成-1了。
思考:把庫存數量字段的類型,設計成正數類型,不允許出現負數,會怎么樣呢?
測驗結果:數據庫會直接報錯,通不過。
解決辦法:只有庫存數量,大于或等于購買數量的時候,才能去減庫存。其他情況,提示信息,庫存不足。
二、并發的問題
為了更好的理解并發和同步,我們需要先明白兩個重要的概念:同步和異步。
同步和異步的區別和聯系
- 所謂同步,可以理解為在執行完一個函數或方法之后,一直等待系統返回值或消息,這時程序是出于阻塞的,只有接收到返回的值或消息后才往下執行其它的命令。
- 異步,執行完函數或方法后,不必阻塞性地等待返回值或消息,只需要向系統委托一個異步過程,那么當系統接收到返回值或消息時,系統會自動觸發委托的異步過程,從而完成一個完整的流程。
同步在一定程度上可以看做是單線程,這個線程請求一個方法后就待這個方法給他回復,否則他不往下執行。異步在一定程度上可以看做是多線程的,請求一個方法后,就不管了,繼續執行其他的方法。
如何處理并發和同步?
首先需要明白,鎖機制有兩個層面:
- 一種是代碼層次上的,如:java中的同步鎖,典型的就是同步關鍵字synchronized,這里我不在做過多的講解;
- 另外一種是數據庫層次上的,比較典型的就是悲觀鎖和樂觀鎖。這里我們重點講解的就是悲觀鎖(傳統的物理鎖)和樂觀鎖。
悲觀鎖(Pessimistic Locking):
悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其他事務,以及來自?外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處于鎖定狀態。
悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能?真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系?統不會修改數據)。
第一種問題中描述的超賣現象,其實是并發搶購時出現的情況。用到的是數據庫內帶的加排他鎖方式,阻止了其他線程讀取、訪問數據,這樣等待的時間就比較長。而業界一般的解決是使用樂觀鎖的辦法來解決:使用數據庫的樂觀鎖是通用解決辦法。
樂觀鎖:
相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨占性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。
如:一個金融系統,當某個操作員讀取用戶的數據,并在讀出的用戶數據的基礎上進?行修改時(如更改用戶帳戶余額),如果采用悲觀鎖機制,也就意味著整個操作過程中(從操作員讀出數據、開始修改直至提交修改結果的全過程,甚至還包括操作員中途去煮咖啡的時間),數據庫記錄始終處于加鎖狀態??梢韵胍姡绻鎸装偕锨€并發,這樣的情況將導致怎樣的后果。
樂觀鎖機制在一定程度上解決了這個問題。
通俗說就是:修改數據的時候,不給數據加鎖。
樂觀鎖意思是不鎖定表的情況下,利用業務的控制來解決并發問題,這樣即保證數據的并發可讀性又保證保存數據的排他性,保證性能的同時解決了并發帶來的臟數據問題。
所以很多情況下都會采用樂觀鎖來解決業務上的問題。
高并發的解決方法主要有以下幾點:
(1)前臺優化
- 減少http請求——css文件合并? ,js文件合并;
- 壓縮js,css文件;
- 使用雪碧圖;
- 懶加載(只加載看到的第一屏內容,下拉之后看到其他的內容);
- 預加載(只加載默認圖);
- cdn 加速。
(2)服務端優化:
- 頁面靜態化;
- 負載均衡、集群;
- 分布式;
- 使用隊列。
(3)MySQL優化:
- 查詢優化,能單表的單表
- 查詢一條數據使用limit
- 生成查詢緩存
- 使用索引
- 多表查詢使用id進行關聯
- 數據庫分表
- 數據庫分區
- 數據庫集群
- 要查詢的字段避免使用*號,指定需要的字段
- 避免使用%前綴的模糊查詢
- 避免使用負向查詢
- 避免使用or查詢
- 避免使用子查詢
- 避免使用MySQL自帶函數
- 不要是rand
- 有順序的讀取
- 設置合適的數據類型
- 避免使用text類型
- 避免使用null
(4)代碼優化:
- 用單引號代替雙引號,雙引號會查詢變量;
- 避免使用require_once require_once會判斷文件是否加載過;
- 使用靜態方法代替普通方法,靜態方法速度比普通方法快4倍;
- 變量使用完之后需要銷毀;
- 盡量不要使用@;
- include用絕對路徑,不要使用相對路徑,相對路徑會有查詢的過程;
- 避免使用__SET __GET __AUTOLOAD;
- 循環的時候先確定循環次數,不要每次循環都要計算;
- 避免循環查庫;
- 避免多層foreach嵌套;
- 避免使用遞歸 ,遞歸比較浪費資源。
三、下單和減庫存要在一個事務中
如果不在一個事務內,可能出現兩種現象:
- 訂單入庫失敗、減庫存成功。發現訂單入庫失敗,減庫存就不要繼續進行下去了。
- 訂單入庫成功、減庫存失敗。實際下了20個訂單,庫存卻沒有減。數據不一致了。
四、設計虛擬庫存和真實庫存兩套方案
有些人下單完后,最終并不會去付款。如果一下單就馬上減庫存,很多人下單,最終并不會去付款,可能導致庫存數最后為0,別的用戶無法下單了。而實際中倉庫中卻有庫存在,這樣庫存數據是不準確的。
什么時候減庫存?是下單完成減庫存、還是付款完后減庫存呢?
付款后,才減庫存,可能出現的現象:用戶下完單,接著去付款,結果庫存不夠了,這樣用戶體驗很不好。但是淘寶的設計是3天(大廠就是比較任性哦)
買家拍下商品后,“等待買家付款”的狀態下系統會給予買家3天的時間進行付款,此時的付款動作是將錢款支付到支付寶公司。
此付款時間無法延長,若逾期未付款,交易將自動關閉,如您仍想購買,建議重新購買并及時付款。但是如果下完單就減庫存,并能夠保證用戶下單只要付款,就一定能買到這個商品。這樣的用戶體驗會較好。
具體技術實現辦法:下單后,馬上減去庫存。另外設置一個定時腳本,掃描超過30分未支付的訂單,把訂單中的商品數量返回到庫存中去,訂單關閉。
如唯品會的購物下單:
為什么使用虛擬庫存和真實庫存兩套方案?
假設庫存數是50,a訂單購買了5個件商品,支付完畢,庫存數減去5,庫存數變成了45件。由于還沒有發貨,實際庫存中還有50件商品,這樣會出現混淆了。
使用兩套庫存記錄方案是有必要的!
- 下單-操作虛擬庫存數
- 商品發貨出庫-操作真實庫存數
五、減少頻繁讀數據庫的壓力
用戶每次點擊一個商品詳情頁面,都要讀取庫存,判斷:有沒有庫存。如果讀庫存走的是數據庫判斷,很多人來搶購的情況下,數據庫的壓力會很大。
假設是1萬個用戶同時訪問搶購頁面,數據庫接受的訪問次數是1萬個并發。
用戶還要進行刷新頁面操作,由于每次刷新都會走數據庫判斷庫存。數量會更大,數據庫的壓力就更大了。所以最好是,把庫存總數,緩存在redis中去。
內存中緩存的庫存數量,只用來做讀判斷,這樣壓力扛住了。而更改數據庫的庫存總數了,程序馬上要把庫存總數,同步到緩存中去。
系統抗壓力問題:
- 如何限流?
- 如何防止惡意刷數據?
?防止限流就是寫代碼去阻止部分人進行頻繁請求,為了識別是機器還是人工。加一些友好一點的驗證碼,這樣不管是從體驗上還是從系統的穩定性方面都是比較好的。
如下圖淘寶做的驗證:
滑塊驗證碼方案,驗證碼后臺針對用戶產生的行為軌跡數據進行機器學習建模,結合訪問頻率、地理位置、歷史記錄等多個維度信息,快速、準確的返回人機判定結果。
攻與防技術都是在對抗中不斷升級的,無解的驗證碼還不存在,但防的一方可以不斷提升破解成本。應用選擇滑塊驗證,也有部分因素是因為競爭激烈的互聯網很看重用戶體驗。拖動畢竟是趣味性交互且容易完成,而圖形驗證碼既容易被黑客攻破,對用戶也并沒有那么友好——肉眼識別無趣(可能還很艱難),鍵盤手動輸入更浪費時間,體驗不太好。
總結
本文介紹了產品在設計搶購閃購商品活動中應該注意的幾點事項,在設計閃購活動時,會圍繞如何處理并發、扣除庫存、防止惡意刷數據等問題。
各公司可以先根據自己的業務情況來設計相對應的方案,然后再用成本計算法,反算開發時間與成本,這樣既保證了項目進度,性能體驗等也不錯。
本文由 @香魚 原創發布于人人都是產品經理。未經許可,禁止轉載
題圖來自 Pexels,基于 CC0 協議
- 目前還沒評論,等你發揮!