高并发下的抽奖优化
點擊上方“朱小廝的博客”,選擇“設為星標”
后臺回復”1024“獲取公眾號專屬1024GB資料
來源:rrd.me/fS8yz
一. 項目思考
由于項目發起了一個抽獎活動,發起活動之前給所有用戶發短信提示他們購買了我們的產品有抽獎權益。然后用戶上來進入抽獎頁面點擊爆增,過了一會兒頁面就打不開了。后面查看了下各種日志,發現了瓶頸在數據庫,由于讀寫沖突嚴重,導致響應變慢,有不少連接都超時了。后面看到監控和日志留下的數據,發現負責抽獎的微服務集群qps暴漲12倍,db的qps也漲了10倍。這很明顯是一個高并發下如何擺脫數據庫讀寫,I/O瓶頸的問題。
整點開搶后瞬時巨量的請求同時涌入,即使我們Nginx端做過初步限流,整個業務邏輯校驗階段運作良好,但是系統的瓶頸就轉移到其他環節:大量的讀寫請求,導致后面的請求全部排隊等待,等前面一個update完成釋放行鎖后才能處理下一個請求,大量請求等待,占用了數據庫的連接。一旦數據庫同一時間片內的連接數被打滿,就會導致這個時間片內其他后來的全部請求因拿不到連接而超時,導致訪問此數據庫的其他環節也出現問題,所以RT就會異常飆高
于是我們在思考著怎么優化這個高并發下的抽獎問題
二. 優化思路
聽了經驗豐富的師兄的經驗,也借鑒了下網上的一些思路,能采用的有效措施主要是:降級,限流,緩存,消息隊列。主要原則是:盡量不暴露db,把大部分請求在服務的系統上層處理了。
三. 優化細節
1. 抽獎詳情頁
a. 線上開啟緩存
線上已寫緩存邏輯,但是沒有用switch開啟。開啟后可以減少數據庫的并發IO壓力,減少鎖沖突。
b. 關于本地緩存淘汰策略的細節處理
緩存超過或等于限制大小全部清空。建議等于時不清空,而使用緩存淘汰算法:比如LRU,LFU,NRU等,這樣不會出現緩存過大清空后,從數據庫更新數據到緩存,緩存里數據依舊很大。導致緩存清空頻率過高,反而降低系統的吞吐量。例如guava cache中的參數是
//設置緩存容器的初始容量為10 initialCapacity(10)//設置緩存最大容量為100,超過100之后就會按照LRU最近雖少使用算法來移除緩存項maximumSize(100)2. 抽獎邏輯
a.隊列削峰
用額外的單進程處理一個隊列,下單請求放到隊列里,一個個處理,就不會有qps的高并發問題了。場景中抽獎用戶會在到點的時間涌入,DB瞬間就接受暴擊壓力,hold不住就會宕機,然后影響整個業務。隊列的長度保持固定,對于如果請求排隊在隊伍中靠后,比如獎品100個的情況下,中獎率10%,隊列里請求任務超過1000時,就直接將后續的抽獎請求返回不中獎。用tair記錄排隊數,如果獎品沒發完,再請空tair,允許請求繼續入隊列。這樣隊列起到了降級和削峰的作用。
b.將事務和行級悲觀鎖改成樂觀鎖
原來的代碼是通過悲觀鎖來控制超發的情況。(比如一共有100個商品,在最后一刻,我們已經消耗了99個商品,僅剩最后一個。這個時候,系統發來多個并發請求,這批請求讀取到的商品余量都是99個,然后都通過了這一個余量判斷,最終導致超發。)
在原來的代碼中用的是for update行鎖,在高并發的情況下會很多這樣的修改請求,每個請求都需要等待鎖,某些線程可能永遠都沒有機會搶到這個鎖,這種請求就會死在那里。同時,這種請求會很多,瞬間增大系統的平均響應時間,結果是可用連接數被耗盡,系統陷入異常。
可以采用樂觀鎖,是相對于“悲觀鎖”采用更為寬松的加鎖機制,大都是采用帶版本號(Version)更新。實現就是,這個數據所有請求都有資格去修改,但會獲得一個該數據的版本號,只有版本號符合的才能更新成功,其他的返回搶購失敗。
c.對于與抽獎無直接關系的流程采用異步
比如抽獎成功之后的發短信功能另起一個線程池專門處理。這樣可以提高請求的處理速率,提高qps上升后的乘載能力。
d.數據庫的讀寫分離
現在的數據庫查詢都是讀的主庫。將數據庫的大量查詢改為從庫,減輕主庫的讀寫壓力。主服務器進行寫操作時,不影響查詢應用服務器的查詢性能,降低阻塞,提高并發。
e.同一時間片內,采用信號量機制
確保進來的人數不會過多導致系統響應超時:?信號量的采用,能夠使得抽獎高峰期內,同一時間片內不會進入過多的用戶,從底層實現上規避了系統處理大數據量的風險。這個可以配合隊列進行限流處理。
f. 消息存儲機制
將數據請求先添加到信息隊列中(比如Tair存儲的數據結構中),然后再寫工具啟動定時任務從Tair中取出數據去入庫,這樣對于db的并發度大大降低到了定時任務的頻率。但是問題可能會出在保持數據的一致性和完整性上。
g.必要時候采用限流降級的測流
當并發過多時為了保證系統整體可用性,拋棄一些請求。對于被限流的請求視為抽不到獎。
3.額外考慮
a.防止黑客刷獎
防止黑客惡意攻擊(比如cc攻擊)導致qps過高,可以考慮策略在服務入口為相同uid的賬戶請求限制每秒鐘的最高訪問數。
b. 中獎數據預熱
中獎只是少數,大部分人并不會中獎,所以可以在第一步便限制只有少數用戶的請求能夠打到真正抽獎邏輯上。是否可以考慮在抽獎之前先用隨機算法生成一批中獎候選人。然后當用戶請求過來時如果其中絕大多數請求都非中獎候選人,則直接返回抽獎失敗,不走抽獎拿獎品的流程。少部分用戶請求是中獎候選人,則進入隊列,排在隊列前面的獲得獎品,發完為止,先到先得。
舉個例子:10萬個用戶抽獎,獎品100個,先隨機選出中獎候選人500個。用戶請求過來時,不走抽獎查庫邏輯的用戶過濾掉99500個,剩余的候選人的請求用隊列處理,先到先得。這樣可以把絕大多數的請求攔截在服務上游不用查庫,但是缺點是不能保證獎品一定會被抽完(可能抽獎候選人只有不到100人參與抽獎)。
四.設計架構圖
想知道更多?掃描下面的二維碼關注我
【精彩推薦】
混沌工程的陷阱
張一鳴:為什么BAT挖不走我們的人才?
Github 開源了新型肺炎防疫項目,助力抗擊疫情!
遠程辦公,為什么一直不被公司普遍接受?
朕已閱?
總結
- 上一篇: 武汉疫情之后,中国即将发生的10大变化!
- 下一篇: 6 款代码对比工具