大前提:主要針對商城促銷日搶購是10秒以內完成20萬訂單的搶購。
為了這個目標,進行了如下兩個方面的優化:
一 程序tps(每秒處理請求數),程序抗并發能力的優化。
二 集群tps優化,集群抗并發能力優化。
先說單程序tps優化:
影響單個程序tps的關鍵點如下:
- json的序列化與反序列化,非常消耗cpu,尤其是沒有用fastjson的時候,cpu彪的非常高。解決方案:減少序列化和反序列化操作,能緩存的盡量走緩存,盡量把要頻繁使用的序列化或者反序列化好的結果數據存到緩存中,減少序列化和反序列化的執行次數。
- 緩存的使用技巧:
不涉及到全局性的緩存數據,都盡量使用本地緩存(谷歌的開發包提供了很優秀的本地緩存功能)
設計到全局性的緩存,比如統一的庫存,統一的售罄狀態等,可以使用redis等緩存方案。
如果數據源在數據庫中,并且要求實時或者近似實時的查詢,其實也可以通過評估延遲時間來使用緩存。
比如某些查詢操作需要實時去數據庫中查詢,如果有100臺web服務器會查數據庫,每臺的tps是5000的話,總tps也有50萬。
數據庫是絕對扛不住的,即使做了讀寫分離。 而且這種實時查詢,再高并發下主從同步很可能都是分鐘級別的延遲。 對數據庫的持續壓力是非常大的。
此時就可以做一個評估了,比如我們允許接收數據有1秒鐘的延遲,那么我們就可以請求來的時候,先從數據庫中查出數據,然后存到本地緩存中,本地緩存是非??斓?,使用時的時間消耗基本可以忽略不計。 那么 100臺機器 每秒只需要請求數據庫100次就可以了,比50萬節省了太多了。 另外如果想提升redis的性能,第一要有一個足夠高頻的單核cpu(因為redis主要是用單線程模式),外加足夠的內存。 可以給redis部屬自帶的哨兵,讓redis有一定的容災能力。 - 數據庫連接池的優化
在高并發場景下,數據庫是我們永遠的痛, 數據庫有幾個重要的稀缺資源,數據庫連接數(一般的mysql的數據庫實例,最好是2000左右的連接數,如果連接數再提高,會影響數據庫的性能,但也不是絕對的,有些時候可以適當的犧牲連接數換取服務器端更高的并發處理能力來提升tps,這里就需要通過壓測找到那個性能最好的平衡點了)。 另外對于數據庫連接池的配置,一般我們會使用阿帕奇的開源工具dbcp,它可以設置三個數量 初始連接數,最大連接數,最小連接數。 建議把這3個設置成一樣的,這樣會減小高并發時連接池新增或銷毀鏈接帶來的開銷。 dbcp還有很多優化的技巧,這里不再說了,大家可以百度一下。 - 我理解的一個完整的單機性能模型:
當一大批請求訪問到web容器(tomcat或者resion)時, web容器會有兩個隊列,第一個隊列是能夠得到資源執行處理的隊列(可以通過調整web容器的線程數來調整隊列的大?。?,第二個隊列是排隊等待的隊列。 當請求數比第一個隊列承受的并發高的時候,請求會進入到排隊中,當排隊的隊列也滿了之后,請求會直接返回50x錯誤。 web容器還可以調整能夠處理的最大線程數,再壓測的時候我們做過調整,1024,2048,8192. 再我們用恒定高并發壓的時候,如果cpu是瓶頸,那么調這個屬性幾乎對tps沒太多幫助。 【web容器內部的模型純屬猜測,大家有興趣可以細細研究】 數據庫連接數設置多少最合適呢,其實就是能夠與服務所需要的連接數匹配。 什么是匹配呢,就是沒有線程因為拿不到數據庫連接而進行等待。 - 性能分析的工具:jprofiler
它可以分析cpu的高消耗位置,線程等待的位置,內存泄露的位置等。 我們主要優化的其實就是這些點, 不要讓線程消耗太多cpu,不要讓線程因為稀缺資源而等待(用緩存替代數據庫就是干這個的),不要有內存溢出。
(友情提示,這個軟件非常消耗cpu,大概自己就能吃60%的cpu,開啟的時候請讓并發數小一點,50左右就夠了) - 網絡資源:
當我們單機的tps達到5000+的時候,其實我們消耗的網絡帶寬是非常大的,那么此時啟動服務器端的Gzip就非常有用了,它會多消耗一些cpu資源,但是會節省很多網絡帶寬。 我們最終可以根據我們手頭的硬件資源來評估,到底是要cpu還是要帶寬。 - 分庫分表:
分表要解決的問題是mysql單表存儲數據最大量的問題,當單表數據量過大(超過1000萬)時,對表操作時性能會開始下降。 分庫要解決的時硬件性能的問題。 分表的方法更多的需要借助中間鍵,或者代碼中有一些邏輯。 分庫則可以通過部屬的方式簡單實現。 比如同一套代碼, 部屬的時候可以 部屬十組,每組服務器上綁定不同的host去連不同的數據庫。 然后再web服務器之上用nginx來基于某個值做哈希讓請求總能打到唯一的一組服務器上。 這種方式運維的壓力會大,但程序之需要一套代碼。 - 硬件上需要監控的參數:
包量(主要用于計算網卡是否跑滿),連接數(結合服務器端口數來計算是否占滿),各個單體之間的連接是長連接還是短連接。影響集群tps1 在集群環境下,由于resion的海量增加,很多其他節點都可能成為瓶頸, 比如lvs,nginx,網卡,redis,數據庫等。lvs 和 nginx 出現瓶頸后,也是從cpu,網卡,連接數,包量,流量等方向查起。 redis出問題主要看內存是否達到瓶頸, cpu是否達到瓶頸。 redis是很需要一個強勁的cpu的,我們使用的都是單線程模式的redis,比較穩定。數據庫瓶頸:一般數據庫瓶頸有幾個, 連接數資源(一般2000左右最好,超過了就開始有性能損失,達到4000就對數據庫操作有非常大的影響了)
2.2. 分流:把流量轉移,比如把靜態內容發布到cdn中,來分擔服務器端的壓力。 把同一個resion中的服務拆分到其他resion中,分開部署,使用獨立的數據庫資源等。
3.3. 限流:直接對流量進行限制,比如再nginx中可以設置基于ip或者location的限流,訪問頻次控制等,比如一分鐘,某個url下的某個參數(一般是user_id)能訪問服務幾次。
4.4. 降級:直接把服務中不重要的功能去掉,或者直接去掉不重要的服務。系統處于可運行狀態,但是邊緣功能暫時不展示或無法使用。 以保證主交易流程的暢通無阻。
5.5. 容災:對于核心系統來說,容災是一個很重要的話題。 首先部署層面同一個服務不能全部部署在同一個宿主機的虛機上,同業務的宿主機不能再機房再同一個機柜中,同一套服務不能只在一個機房中。 機房間要有快速切換的方案,服務切換時要有有數據同步的機制,保證數據的有效容災。
--------------------------運維視角看性能-------------------------
一 cpu篇
通過監控能夠看到cpu的各項指標,比較重要的有 cpu idel(cpu空閑), 100%-cpuidle=cpu使用量。 cpu io wait: cpu等待磁盤io所消耗的百分比,這個值如果高就說明磁盤可能有問題了。 這個可以和磁盤的swap交換量結合起來一起看。( 命令:free -m 看swap交換) cpu user:程序使用的cpu
二 監控的load性能圖標。
load這個值是用cpu,磁盤,內存等一起計算出來的一個值。
一般load數值cpu核數3>12 就說明性能已經不行了。
三 內存
內存主要就是通過swap置換量來分析,如果成百上千的swap置換,就說明內存已經不足,開始大量使用磁盤代替內存,此時往往 cpu io wait 也會高很多。 (free -m)用來看swap置換量等。
四 網絡
time wait 等待的連接 close 關閉的連接 Estable 目前已經建立的鏈接 y.. 半連接 如果time wait 比較多,可以考慮把短連接改成長連接,當然也是基于具體場景的。 (命令:ss -s 看連接數, ss -an 看其他的)
五 網卡
消耗網卡的主要是兩個東西,1 流量 2 包量 流量消耗的是網卡的吞吐亮,比如千兆網卡, 流量上限就是1G。 包量消耗的時網卡的性能,一般一臺虛機 10萬左右的包量基本就是極限了,再多久可能有丟包。 可以用 w -get命令 來實驗是否丟包。 可以通過給網卡做bading, 把多個網卡綁在一起來提高網卡性能。 理論可以綁定無限多個,目前我們是2個或者4個綁定在一起。
六 磁盤
一般磁盤的性能瓶頸是看磁盤的iops (一秒多少次磁盤讀寫) 磁盤的io wait比較高的時候,就是磁盤有瓶頸的時候。 一般此時,cpu io wait 也會高。
七 端口數
我們的七層代理(nginx) 一個ip可以有65535個端口。 這個量很可能不夠。 解決的辦法是nginx做多ip回源,比如nginx用多個ip吧請求upstream到下面的resion,這樣就解決了端口數不夠的問題。 也可以增加nginx的數量。 (命令 ss -s 可以看有多少端口數)
八 主機虛擬化
一個性能好的主機可以虛擬出很多的虛擬機,一般虛擬化之后,總體的性能損耗是20%-30%左右,但是帶來的好處是可以做隔離,帶來更多端口數,減少硬件資源浪費。 虛擬化之后其實網卡也被虛擬化了,所以虛擬化之后的主機對于包量的處理能力會低,一般10萬左右的包量就基本到上限了。