“秒殺”成了現在互聯網的新寵,動不動就來個秒殺或搶購之類的活動,特別是近期的雙101或雙102。甚么“秒殺”“搶購”這類型活動對系統來講還真是1個考驗,不過BOSS們就是喜歡,隨意1句“他們能做,為何我們不能做”。我還嘴賤地問了1下"想怎樣秒殺?",得到的答復是“像搶小米1樣就好了”。
......
我怕我再多問兩句就連淘寶都要我做出來。為了不影響正常服務的,只能從現有機器上單獨抽1臺刀片機出來“玩玩”了。
“秒殺”考驗的就是系統確保事務安全情況下的并發處理能力,小米的搶購應當異步的,先給你進來排隊,稍后再給你答復那種,相對用戶體驗沒那末好。面對這樣的領導,出來后肯定是1大堆問題“為何要讓我等那末久才給我答復”,“為何不立刻就給我知道有無成功”等等。為了封住他的嘴巴,只能做同步的了。
問題:安全性、穩定性、性能消耗、速度、防御。
安全性:主要指的是線程安全和事務安全
穩定性:能確保在秒殺進程中不要宕機就好了
性能消耗:越簡單越好,保證最大并發數下資源足夠支持,包括JVM大小、系統相干參數、web容器線程數、數據庫連接數等。
速度:說白了,就是甚么都不要做,就告知用戶秒殺成功與否就能夠,剩下的事情(例以下單之類的)秒殺后再做。越簡單越好,不過常常越簡單的事情就越復雜。
防御:就是防范那些“黃牛黨”用那些搶購工具發起無窮攻擊的那種。
防御:
安全性
Java支持多線程,這不用多說,反正不要用到全局變量就行。在事物方面,我用到oracle數據庫的“行鎖”:select for update nowait。nowait相當重要,當獲得不到鎖的時候就是立刻拋異常直接返回“秒殺失敗”響應。不要在web容器或數據庫里面多待1好秒。如果不用nowait關鍵字就跟Java里面的synchronized或lock性能1樣:等待鎖。1等的話,就準備“收尸”了。當用戶線程獲得到行鎖了就能夠留下“腳印”成功解鎖走人。為了確保數據的準確性,還可以通過用戶ID為中獎名單表的主鍵,就算“不謹慎”讓它溜進來了,還可以以主鍵束縛限制他重復秒殺成功。
穩定性
多大的獨自就得有多大的嘴巴,通過評估,我們這個“秒殺”系統約支持每秒100個并發吧,也足夠應付用戶了,那我們就根據這個并發數做好入口的限制,例如web服務器入口我就開個500吧(由于還有靜態文件)。多了的會直接返回500的,為了確保系統穩定,還真得需要那末做。接下來就是web容器線程數了,基于漏斗形原則,設置肯定要比前面的少,例如測試計算1個線程1秒能處理3個線程,那我們就給他設置個50左右就能夠了。接下來的就是數據庫連接數了,基于我們數據庫服務器能力還是挺強的,處理能力不比JVM處理得慢,為了不讓web容器線程等待,設置了與并發1樣的線程數量100。
性能消耗
由于只支持秒殺功能,所以JVM的消耗還是可以控制的,對4C、16G服務器來講應當沒甚么問題。IO開消不算太大。不過1切還得活動過后再說。
速度
秒殺1條路下來其實就是獲得活動信息和秒殺兩個步奏,活動信息已初始化在JVM內存里面了,速度沒甚么問題。秒殺通過上面所說的數據庫行鎖確保了線程不會等待,直接返回結果。還有秒殺成功名單也用戶“留腳印”的時候同步到JVM內存的全局變量里面。所以,如果成功秒殺過的用戶會在JVM判斷里面直接返回結果了。就算1個不謹慎,在同步成功名單與獲得行鎖之間的時間差里面逃過1劫,上面提到的成功搶購名單的主鍵還是可以避免他屢次成功搶購的。
其實數據庫里面的行鎖決定了秒殺還只是1個單線程,由于大家就是為了爭取1行的鎖,如果秒殺商品數量眾多,我們可以分多行鎖,就是我們把商品平分了,例如我有1000個商品可以秒殺,我分出100行,每行負責10個商品,在系統初始化的時候把100行的行ID初始化到JVM,用戶進來的時候隨機獲得行ID進行鎖行和秒殺。當某行秒殺完了以后就在JVM剔除,直到所有行都秒殺完,不過半分鐘的事情。
防御
這里的防御主要是防御那些通過秒殺工具搶購的用戶,通過工具他們可以大批量的并發進攻。通過IP限制是不可能的了,由于IP會誤殺很多無辜用戶,現在只有通過用戶ID限制了。限制有很多層面的限制,越是在系統的外圍就越能為系統減輕負擔。例如在路由、防火墻等做攔截最少比web容器層面攔截好。想象是美好的,由于企業的復雜關系與權利的限制,自己只能控制在自己的利用里面了,所以對我來講最外層也就是web服務器了。只能盡可能在這里攔截了,每一個用戶ID只能同時進入1次。為了能應付大并發的負荷,在這選用了redis的swatch命令機制,詳情可參考(http://book.2cto.com/201306/25048.html)。用了redis 也有1段時間了,通過網上的資料也能夠看得到它的出色的地方,也經過了我們系統的考驗,還是不錯的。
結果
活動前期,秒殺的前5分鐘,通過對端口的監控,線程急劇飆升,看來“洪水”開始來臨。
活動開始,約經過半分鐘,web容器線程爆滿,服務器直接返回500。等了約1分鐘,線程還是不降,商品已秒殺終了了,通過對javacore文件分析,大部份線程在wait,1部份block了,數據庫服務器性能消耗不高,導出AWR文件,漸漸分析。經過對web服務重啟,1切恢復正常。
分析
事后通過對各種日志的分析,發現活動開始的最高并發有500訪問量/秒,這個500還只是web容器的并發,在端口處的實際并發肯定不止。還有對javacore文件的分析,發現很多線程在等待數據的資源,難道100個數據庫連接數都還不夠,再跟蹤數據庫日志,發現有1條SQL語句寫得還不是很好,以后再接再礪。
總結
秒殺真不好服侍,領導更不好服侍,不過經過了,學習了,就是自己的了。