并發隊列的(de)選擇
Java的(de)并發包提供了(le)三種常見的(de)并發隊列實現:arrayblockingqueue、concurrentlinkedqueue和(hé)linkedblockingqueue。
ArrayBlockingQueue是一個(gè)具有固定初始容量的(de)阻塞隊列,它可(kě)以作爲數據庫模塊(如10項)的(de)成功投标隊列,因此我們建立了(le)一個(gè)10大(dà)小的(de)數組隊列。
Concurrentlinkedqueue是一個(gè)由CAS原語實現的(de)無鎖異步隊列。進入隊列的(de)速度很快(kuài),鎖定隊列後性能稍慢(màn)。
LinkedBlockingQueue也(yě)是一個(gè)阻塞隊列,無論是進出隊列都被鎖定,當團隊爲空時(shí),線程将暫時(shí)阻塞。
在請求預處理(lǐ)階段,由于系統對(duì)進入隊列的(de)需求遠(yuǎn)遠(yuǎn)大(dà)于離開隊列的(de)需求,因此不會出現空隊列,因此可(kě)以選擇并發鏈接隊列作爲請求隊列的(de)實現
1.請求接口的(de)合理(lǐ)設計
seckill或snap-up頁面通(tōng)常分(fēn)爲兩部分(fēn),一部分(fēn)是靜态HTML和(hé)其他(tā)内容,另一部分(fēn)是參與seckill的(de)web後台請求接口。
一般情況下(xià),靜态HTML等内容都是通(tōng)過CDN部署的(de),一般壓力并不大(dà),核心瓶頸實際上是在後台請求接口上。這(zhè)個(gè)後端接口必須能夠支持高(gāo)并發請求。同時(shí),盡快(kuài)返回用(yòng)戶的(de)請求結果非常重要。爲了(le)盡可(kě)能快(kuài)地實現,接口的(de)後端存儲将與存儲器級操作更好。不适合直接面對(duì)MySQL等存儲。如果有如此複雜(zá)的(de)業務需求,建議(yì)使用(yòng)異步編寫。
當然,也(yě)有一些秒殺和(hé)閃購(gòu)使用(yòng)了(le)“滞後反饋”,即秒殺目前還(hái)不知道結果,從頁面上可(kě)以看出用(yòng)戶一段時(shí)間後秒殺是否成功。但這(zhè)種行爲屬于“偷懶”行爲,用(yòng)戶體驗不好,很容易被認爲是“暗箱操作”。
高(gāo)并發下(xià)的(de)數據安全
我們知道,當多(duō)線程寫入同一文件時(shí),有一個(gè)"螺紋安全"問題(多(duō)個(gè)線程同時(shí)運行相同的(de)代碼,如果每個(gè)運行的(de)結果與單個(gè)線程的(de)結果相同),結果是線程安全(預期)。如果是MySQL數據庫,可(kě)以使用(yòng)自己的(de)鎖定機制來(lái)解決這(zhè)個(gè)問題。但是,在大(dà)規模并發場(chǎng)景中,不建議(yì)使用(yòng)MySQL。在殺人(rén)搶奪的(de)場(chǎng)面中,還(hái)有另一個(gè)問題,那就是“超調”,如果在這(zhè)方面不小心,就會造成太多(duō)的(de)送人(rén)。我們也(yě)聽(tīng)說一些電子商務公司從事閃購(gòu)活動。買家成功拍(pāi)照(zhào)後,商家不承認訂單有效,拒絕送貨。這(zhè)裏的(de)問題可(kě)能不一定是企業是奸詐的(de),但在系統的(de)技術水(shuǐ)平上存在超限風險。
1. 超發的(de)原因
假設在搶購(gòu)的(de)情況下(xià),我們總共隻有100種産品。在最後一刻,我們已經消費了(le)99種産品,隻剩下(xià)最後一種。此時(shí),系統發送多(duō)個(gè)并發請求,這(zhè)些請求讀取的(de)貨物(wù)的(de)剩餘量爲99,然後全部通(tōng)過該餘量判斷,最終導緻溢出。(與文章(zhāng)前面提到的(de)場(chǎng)景相同)
在上面的(de)圖中,并發用(yòng)戶B也(yě)被“搶占”,讓另一個(gè)人(rén)訪問該産品。在高(gāo)并發的(de)情況下(xià),這(zhè)種場(chǎng)景很容易出現。
2. 悲觀鎖思路
解決線程安全問題的(de)思路很多(duō),可(kě)以從悲觀鎖的(de)角度進行討(tǎo)論。
悲觀鎖,即在修改數據時(shí),使用(yòng)鎖狀态排除外部請求的(de)修改。如果您遇到鎖定狀态,您必須等待。
盡管上述解決方案确實解決了(le)線程安全問題,但不要忘記我們的(de)場(chǎng)景是“高(gāo)并發性”。換句話(huà)說,會有很多(duō)這(zhè)樣的(de)修改請求,每個(gè)請求都必須等待一個(gè)"鎖",一些線程可(kě)能永遠(yuǎn)不會有機會抓取"鎖",這(zhè)樣的(de)請求就會在那裏死去。同時(shí),會有很多(duō)這(zhè)樣的(de)請求,這(zhè)會在瞬間增加系統的(de)平均響應時(shí)間。因此,可(kě)用(yòng)連接數将耗盡,系統将陷入異常。
3. FIFO隊列思路
好吧,讓我們修改一下(xià)上面的(de)方案,我們直接在隊列中使用(yòng)FIFO(先進先出),這(zhè)樣我們就不會引起一些請求永遠(yuǎn)得(de)不到鎖。看這(zhè)裏,把多(duō)線程變成一個(gè)線程不是有點強迫嗎。
然後,我們現在解決了(le)鎖定問題,并且在"先進先出"隊列中處理(lǐ)了(le)所有請求。所以新問題來(lái)了(le)。在高(gāo)并發場(chǎng)景中,由于請求較多(duō),隊列内存很可(kě)能在一瞬間突發,然後系統進入異常狀态。或者大(dà)内存隊列的(de)設計也(yě)是一種解決方案,但是系統請求在一個(gè)隊列内的(de)速度不能與瘋狂湧流隊列的(de)數量相比較。也(yě)就是說,隊列中的(de)請求将越來(lái)越多(duō)地累積起來(lái)。最後,web系統的(de)平均響應時(shí)間會急劇下(xià)降,系統仍然會陷入異常狀态。
4. 樂(yuè)觀鎖思路
在這(zhè)一點上,我們可(kě)以討(tǎo)論樂(yuè)觀鎖的(de)想法。樂(yuè)觀鎖是一種比悲觀鎖更爲寬松的(de)鎖機制,大(dà)多(duō)數悲觀鎖都是用(yòng)版本更新的(de)。該實現是所有對(duì)該數據的(de)請求都有資格被修改,但是隻有在版本号匹配時(shí)才會獲得(de)數據的(de)版本号,而其他(tā)的(de)返回将失敗。這(zhè)樣就不需要考慮隊列問題,但會增加CPU的(de)計算(suàn)成本。然而,從全面的(de)角度來(lái)看,這(zhè)是一個(gè)很好的(de)解決辦法。
有許多(duō)軟件和(hé)服務支持樂(yuè)觀鎖功能。例如,redis的(de)手表就是其中之一。通(tōng)過此次實施,我們保證了(le)數據的(de)安全