來源:rrd.me/fS8yz
一. 項(xiàng)目思考
由于項(xiàng)目發(fā)起了一個(gè)抽獎(jiǎng)活動(dòng)瓤球,發(fā)起活動(dòng)之前給所有用戶發(fā)短信提示他們購(gòu)買了我們的產(chǎn)品有抽獎(jiǎng)權(quán)益。然后用戶上來進(jìn)入抽獎(jiǎng)頁面點(diǎn)擊爆增耳胎,過了一會(huì)兒頁面就打不開了悼粮。后面查看了下各種日志掀淘,發(fā)現(xiàn)了瓶頸在數(shù)據(jù)庫竖席,由于讀寫沖突嚴(yán)重耘纱,導(dǎo)致響應(yīng)變慢,有不少連接都超時(shí)了毕荐。后面看到監(jiān)控和日志留下的數(shù)據(jù)揣炕,發(fā)現(xiàn)負(fù)責(zé)抽獎(jiǎng)的微服務(wù)集群qps暴漲12倍,db的qps也漲了10倍东跪。這很明顯是一個(gè)高并發(fā)下如何擺脫數(shù)據(jù)庫讀寫,I/O瓶頸的問題。
整點(diǎn)開搶后瞬時(shí)巨量的請(qǐng)求同時(shí)涌入虽填,即使我們Nginx端做過初步限流丁恭,整個(gè)業(yè)務(wù)邏輯校驗(yàn)階段運(yùn)作良好,但是系統(tǒng)的瓶頸就轉(zhuǎn)移到其他環(huán)節(jié):大量的讀寫請(qǐng)求斋日,導(dǎo)致后面的請(qǐng)求全部排隊(duì)等待牲览,等前面一個(gè)update完成釋放行鎖后才能處理下一個(gè)請(qǐng)求,大量請(qǐng)求等待恶守,占用了數(shù)據(jù)庫的連接第献。一旦數(shù)據(jù)庫同一時(shí)間片內(nèi)的連接數(shù)被打滿,就會(huì)導(dǎo)致這個(gè)時(shí)間片內(nèi)其他后來的全部請(qǐng)求因拿不到連接而超時(shí)兔港,導(dǎo)致訪問此數(shù)據(jù)庫的其他環(huán)節(jié)也出現(xiàn)問題庸毫,所以RT就會(huì)異常飆高
于是我們?cè)谒伎贾趺磧?yōu)化這個(gè)高并發(fā)下的抽獎(jiǎng)問題
二. 優(yōu)化思路
聽了經(jīng)驗(yàn)豐富的師兄的經(jīng)驗(yàn),也借鑒了下網(wǎng)上的一些思路衫樊,能采用的有效措施主要是:降級(jí)飒赃,限流,緩存科侈,消息隊(duì)列载佳。主要原則是:盡量不暴露db,把大部分請(qǐng)求在服務(wù)的系統(tǒng)上層處理了。
三. 優(yōu)化細(xì)節(jié)
\1. 抽獎(jiǎng)詳情頁
a. 線上開啟緩存
線上已寫緩存邏輯臀栈,但是沒有用switch開啟蔫慧。開啟后可以減少數(shù)據(jù)庫的并發(fā)IO壓力,減少鎖沖突权薯。
b. 關(guān)于本地緩存淘汰策略的細(xì)節(jié)處理
緩存超過或等于限制大小全部清空姑躲。建議等于時(shí)不清空,而使用緩存淘汰算法:比如LRU,LFU,NRU等,這樣不會(huì)出現(xiàn)緩存過大清空后崭闲,從數(shù)據(jù)庫更新數(shù)據(jù)到緩存肋联,緩存里數(shù)據(jù)依舊很大。導(dǎo)致緩存清空頻率過高刁俭,反而降低系統(tǒng)的吞吐量橄仍。例如guava cache中的參數(shù)是
//設(shè)置緩存容器的初始容量為10
initialCapacity(10)
//設(shè)置緩存最大容量為100,超過100之后就會(huì)按照LRU最近雖少使用算法來移除緩存項(xiàng)
maximumSize(100)
\2. 抽獎(jiǎng)邏輯
a.隊(duì)列削峰
用額外的單進(jìn)程處理一個(gè)隊(duì)列牍戚,下單請(qǐng)求放到隊(duì)列里侮繁,一個(gè)個(gè)處理,就不會(huì)有qps的高并發(fā)問題了如孝。場(chǎng)景中抽獎(jiǎng)用戶會(huì)在到點(diǎn)的時(shí)間涌入宪哩,DB瞬間就接受暴擊壓力,hold不住就會(huì)宕機(jī)第晰,然后影響整個(gè)業(yè)務(wù)锁孟。隊(duì)列的長(zhǎng)度保持固定彬祖,對(duì)于如果請(qǐng)求排隊(duì)在隊(duì)伍中靠后,比如獎(jiǎng)品100個(gè)的情況下品抽,中獎(jiǎng)率10%储笑,隊(duì)列里請(qǐng)求任務(wù)超過1000時(shí),就直接將后續(xù)的抽獎(jiǎng)?wù)埱蠓祷夭恢歇?jiǎng)圆恤。用tair記錄排隊(duì)數(shù)突倍,如果獎(jiǎng)品沒發(fā)完,再請(qǐng)空tair,允許請(qǐng)求繼續(xù)入隊(duì)列盆昙。這樣隊(duì)列起到了降級(jí)和削峰的作用羽历。
b.將事務(wù)和行級(jí)悲觀鎖改成樂觀鎖
原來的代碼是通過悲觀鎖來控制超發(fā)的情況。(比如一共有100個(gè)商品淡喜,在最后一刻秕磷,我們已經(jīng)消耗了99個(gè)商品,僅剩最后一個(gè)拆火。這個(gè)時(shí)候跳夭,系統(tǒng)發(fā)來多個(gè)并發(fā)請(qǐng)求,這批請(qǐng)求讀取到的商品余量都是99個(gè)们镜,然后都通過了這一個(gè)余量判斷币叹,最終導(dǎo)致超發(fā)。)
在原來的代碼中用的是for update行鎖模狭,在高并發(fā)的情況下會(huì)很多這樣的修改請(qǐng)求颈抚,每個(gè)請(qǐng)求都需要等待鎖,某些線程可能永遠(yuǎn)都沒有機(jī)會(huì)搶到這個(gè)鎖嚼鹉,這種請(qǐng)求就會(huì)死在那里贩汉。同時(shí),這種請(qǐng)求會(huì)很多锚赤,瞬間增大系統(tǒng)的平均響應(yīng)時(shí)間匹舞,結(jié)果是可用連接數(shù)被耗盡,系統(tǒng)陷入異常线脚。
可以采用樂觀鎖赐稽,是相對(duì)于“悲觀鎖”采用更為寬松的加鎖機(jī)制,大都是采用帶版本號(hào)(Version)更新浑侥。實(shí)現(xiàn)就是姊舵,這個(gè)數(shù)據(jù)所有請(qǐng)求都有資格去修改,但會(huì)獲得一個(gè)該數(shù)據(jù)的版本號(hào)寓落,只有版本號(hào)符合的才能更新成功括丁,其他的返回?fù)屬?gòu)失敗。
c.對(duì)于與抽獎(jiǎng)無直接關(guān)系的流程采用異步
比如抽獎(jiǎng)成功之后的發(fā)短信功能另起一個(gè)線程池專門處理伶选。這樣可以提高請(qǐng)求的處理速率史飞,提高qps上升后的乘載能力尖昏。
d.數(shù)據(jù)庫的讀寫分離
現(xiàn)在的數(shù)據(jù)庫查詢都是讀的主庫。將數(shù)據(jù)庫的大量查詢改為從庫祸憋,減輕主庫的讀寫壓力会宪。主服務(wù)器進(jìn)行寫操作時(shí),不影響查詢應(yīng)用服務(wù)器的查詢性能蚯窥,降低阻塞,提高并發(fā)塞帐。
e.同一時(shí)間片內(nèi)拦赠,采用信號(hào)量機(jī)制
確保進(jìn)來的人數(shù)不會(huì)過多導(dǎo)致系統(tǒng)響應(yīng)超時(shí):信號(hào)量的采用,能夠使得抽獎(jiǎng)高峰期內(nèi)葵姥,同一時(shí)間片內(nèi)不會(huì)進(jìn)入過多的用戶荷鼠,從底層實(shí)現(xiàn)上規(guī)避了系統(tǒng)處理大數(shù)據(jù)量的風(fēng)險(xiǎn)。這個(gè)可以配合隊(duì)列進(jìn)行限流處理榔幸。
f. 消息存儲(chǔ)機(jī)制
將數(shù)據(jù)請(qǐng)求先添加到信息隊(duì)列中(比如Tair存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)中)允乐,然后再寫工具啟動(dòng)定時(shí)任務(wù)從Tair中取出數(shù)據(jù)去入庫,這樣對(duì)于db的并發(fā)度大大降低到了定時(shí)任務(wù)的頻率削咆。但是問題可能會(huì)出在保持?jǐn)?shù)據(jù)的一致性和完整性上牍疏。
g.必要時(shí)候采用限流降級(jí)的測(cè)流
當(dāng)并發(fā)過多時(shí)為了保證系統(tǒng)整體可用性,拋棄一些請(qǐng)求拨齐。對(duì)于被限流的請(qǐng)求視為抽不到獎(jiǎng)鳞陨。
3.額外考慮
a.防止黑客刷獎(jiǎng)
防止黑客惡意攻擊(比如cc攻擊)導(dǎo)致qps過高,可以考慮策略在服務(wù)入口為相同uid的賬戶請(qǐng)求限制每秒鐘的最高訪問數(shù)瞻惋。
b. 中獎(jiǎng)數(shù)據(jù)預(yù)熱
中獎(jiǎng)只是少數(shù)厦滤,大部分人并不會(huì)中獎(jiǎng),所以可以在第一步便限制只有少數(shù)用戶的請(qǐng)求能夠打到真正抽獎(jiǎng)邏輯上歼狼。是否可以考慮在抽獎(jiǎng)之前先用隨機(jī)算法生成一批中獎(jiǎng)候選人掏导。然后當(dāng)用戶請(qǐng)求過來時(shí)如果其中絕大多數(shù)請(qǐng)求都非中獎(jiǎng)候選人,則直接返回抽獎(jiǎng)失敗羽峰,不走抽獎(jiǎng)拿獎(jiǎng)品的流程趟咆。少部分用戶請(qǐng)求是中獎(jiǎng)候選人,則進(jìn)入隊(duì)列限寞,排在隊(duì)列前面的獲得獎(jiǎng)品忍啸,發(fā)完為止,先到先得履植。
舉個(gè)例子:10萬個(gè)用戶抽獎(jiǎng)计雌,獎(jiǎng)品100個(gè),先隨機(jī)選出中獎(jiǎng)候選人500個(gè)玫霎。用戶請(qǐng)求過來時(shí)凿滤,不走抽獎(jiǎng)查庫邏輯的用戶過濾掉99500個(gè)妈橄,剩余的候選人的請(qǐng)求用隊(duì)列處理,先到先得。這樣可以把絕大多數(shù)的請(qǐng)求攔截在服務(wù)上游不用查庫翁脆,但是缺點(diǎn)是不能保證獎(jiǎng)品一定會(huì)被抽完(可能抽獎(jiǎng)候選人只有不到100人參與抽獎(jiǎng))眷蚓。
四.設(shè)計(jì)架構(gòu)圖