- HikariCP數據庫連接池實戰
- 朱政科
- 6064字
- 2019-09-02 17:55:02
1.2 阿里中間件TCP四次揮手性能調優實戰
重用數據庫連接最主要的原因是可以減少應用程序與數據庫之間創建或銷毀TCP連接的開銷,數據庫連接池的概念應運而生。如果不使用連接池,TCP四次揮手過程中TIME_WAIT的性能調優是相對比較復雜的,請看下面這個小例子。
1.2.1 億級消息網關Rowan架構
Rowan是一個億級企業消息網關中間件服務,如圖1-1所示。它在業務上為B2B、Aliexpress、集團安全、共享事業部、淘寶等大部門提供郵件、短信、旺旺、站內信、釘釘等消息的持續發送能力,支持的業務包括會員注冊、評價、仲裁、用戶觸達EDM、資金中心對賬、交易、營銷、物流追蹤、賣家認證、CRM、風控合規、反欺詐、風險評測、處罰等。

圖1-1 Rowan介紹
技術上,Rowan由多個服務和中間件構成,具有模版管理、用戶觸達、消息管理、EDM無線引流、打點追蹤等功能,每天產生的消息過千萬,僅僅2016年“雙十一大促”當天產生的消息,光郵件類就超過6億。
以郵件模塊為例,Rowan技術架構有過兩個時期。第1個時期,業務方請求通過HSF(一種阿里內部類似DUBBO的RPC)請求調用Rowan, Rowan再透傳調用阿里云郵SMTP服務。當業務洪峰抵達的時候,海外集群會經常出現超時的情況。這是因為,當時阿里云國內集群的系統設計能力比較強,基本上可以支撐國內的郵件發送;相對于國內有5個數據庫集群,國外阿里云集群則顯得有些薄弱,僅有一個數據庫集群。國內集群的系統設計能力是1億/天,日常2000多萬/天;海外日常1000多萬/天,峰值1500多萬/天。阿里云使用寫磁盤的方式,若投遞信息失敗,重試5次,大致時間點是2分鐘、5分鐘、30分鐘、1小時、2小時,若2小時仍發送失敗則丟棄信息。但是,只要是被阿里云SMTP擋住的,如返回給Rowan的connection reset、451、526等異常,不進入阿里云隊列,這就會造成只有Rowan這里會打印出異常信息,而阿里云服務端則打印不出具體異常信息,排查問題相當棘手。
由于國內外知名的郵件服務器,如亞馬遜AWS、搜狐SENDCLOUD等都是支持抗堆積的,在無法推動阿里云從SMTP支持到HTTP的背景下,我進行了第2個時期的技術改造設計,如圖1-2所示。

圖1-2 改造后Rowan技術架構圖
在這個架構中,所有外部服務通過Rowan Service調用對應的服務,如“郵件”等。首先會根據Cache緩存中的模版信息組裝成消息體,并存儲在HBASE中進行發送狀態的記載;其次請求的消息會直接丟到對應的中美MQ(阿里內部叫Metaq,對外開源叫RocketMQ)消息隊列集群中,在線流式實時計算Jstorm會分別處理MQ中的郵件、短信、站內信、HTTP、旺旺、釘釘等消息,來進行消息的投遞。
由于杭州的阿里云郵系統能力遠遠高于美國集群,所以美國集群若命中了國內郵箱,比如126、163、QQ等,會直接路由到國內阿里云網關進行發送。在圖1-2中,Rowan服務是MQ的生產者,Jstorm是MQ的消費者,故流控和暫停功能放在了消費者這里。流控功能是基于阿里配置中心Diamond定制化開發的,Jstorm啟動時直接從配置項讀取MQ最大線程、最小線程數。在Diamond中還可以配置海外集群的壓力疏導功能,支持按照百分比將流量轉移到國內。利用Diamond配置中心實時更新的特性,當Diamond修改時可以實時推送給對應機房以指定的流控線程數重啟MQ消費者客戶端。
阿里云可以配置發件賬號的優先級,并暫停低優先級的賬號。Rowan這套新架構在此基礎上額外支持兩個類似的擴展功能:自帶無效地址,可以關掉對指定收件人的發送;模版禁用也可以直接關掉此類模版的發送。
這套架構采用疏導的策略,旨在將海外集群的壓力轉移到國內分擔,從而大幅度提高系統的整體QPS。這套架構需要業務進行配合,通過統計國內外郵件賬號的QPS指標、進行國內外發件賬號的優先級排序、準備發送故障預案、精簡合并系統通知郵件、均勻化編排大促營銷郵件發送等一系列措施,來保障整體系統“雙十一大促”的穩定性。這也是我在阿里巴巴公司工作期間學會的,技術驅動業務,團隊合作,“貼著業務走,以結果為導向”是中間件團隊的職責。
1.2.2 人臉識別服務:異曲同工的架構
提到疏導的架構和QPS的提升,順便提一個我在2019年工作中設計的大幅度提升QPS的異曲同工的技術方案。
如圖1-3所示,FaceServer是基于Java的人臉識別服務,對業務服務提供人臉識別驗證的Dubbo接口,同時調用基于Python的算法人臉識別服務。算法服務包括多個原子服務,如人臉、質量、屬性、活體、特征等。據統計,FaceServer服務調用算法人臉識別服務,平均每天有40~50次超時異常,最多的時候有100次。技術團隊因此對算法服務器單機進行了壓測,單機10個線程,平均響應時間在2.878秒左右(FaceServer認為3秒即超時),最長響應時間為13.814秒,QPS為1。

圖1-3 歷史架構
FaceServer在物聯網時代,就類似于淘寶網等網站的會員登錄,是互聯網的門面,QPS為1顯然不能滿足商業化需求。很多做研發的資深人士可能會想,可以堆機器來提高QPS呀!這其實是不可行的,因為算法服務器非常特別,不同于普通的服務器,它是昂貴的GPU密集型服務器:一臺阿里云GPU服務器單機每月要6000元,一年7.2萬元,按照圖1-3采購兩臺,一年總計14.4萬元。也就是說,目前單機只有1QPS,兩臺機器2QPS,要支持200QPS,如果堆機器,一年就需要花1400多萬元。為了QPS達到200,一年1400多萬元的開銷,絕對不是中小型創業公司能夠承擔的,在降成本的大型BAT,諸如阿里巴巴這樣的公司,想必也是不能接受的。
目前的算法服務器是單點,另一臺算法GPU服務器由于歷史原因一直沒有啟用,所以第1步改造我決定先高效利用好兩臺已有的算法GPU服務器。在進行了將單機部署提升為集群部署、算法服務內部的串行改并行、耗時原子服務多開進程等工作后,FaceServer的維護者表示再也沒有出現過線上的超時異常的情況,此時的架構如圖1-4所示。經團隊壓測后,單機的QPS已經從1提升到了7,由于線上正在使用人臉識別服務,整體集群QPS沒有進行壓測,估計應該是1+1>2的效果(集群14QPS)。

圖1-4 第1版優化架構
基于百度人臉識別算法,對于企業認證的用戶可以直接免費提供每月15QPS的服務推測,百度應該也是有一些性能優化的技巧來高效利用GPU服務器的,因此目前這樣的架構還是可以繼續提高QPS的。于是,我又和算法團隊進行了進一步的溝通,有收獲的是,據算法團隊反饋,算法服務器內部單獨處理一張人臉圖片和批量處理其實是一樣的處理。算法團隊表示,在單模型情況下,當前人臉特征模型單機進程如果使用批處理至少可以將QPS提高到35,而這恰恰就是可以大幅度提升QPS的切入點。根據算法團隊批處理的需求,我于是想到了一個請求合并的架構。
如圖1-5所示就是第2版優化架構。這是一種請求合并的處理架構,也是中間件性能提升中較為常見的方案。對于業務服務來說,依然是通過Dubbo同步調用FaceServer服務的,然而FaceServer則是將每次請求丟到一個隊列中去,這個隊列當請求收集到一定數量(比如算法服務的35QPS)時則進行提交或者每秒進行提交,這種同步轉異步的方式可以充分利用算法GPU服務器的資源,從而大幅度提升QPS。在圖1-5的架構中,我采用了Guava而沒有采用Redis,原因是Redis進行人臉圖片的讀寫會有網絡開銷,采用Guava會在更大程度上提升響應時間。另外,關于圖1-5重試的部分我也使用了GuavaRetry,充分利用了Guava的緩存和重試工具特性。

圖1-5 第2版優化架構
這種Dubbo同步轉異步、請求合并的方案,其實和Rowan的抗堆積、流控與暫停有著異曲同工之妙,都是在有限的資源(人力、物力)的基礎上充分合理利用已有資源,從而達到更為極致的QPS。
1.2.3 “雙十一大促”全鏈路壓測發現TCP問題
聊完架構,我們繼續回到Rowan和TCP的話題。
Rowan郵件功能使用的是SMTP協議,SMTP在七層協議中屬于應用層協議,屬TCP/IP協議簇,它幫助每臺計算機在發送或中轉信件時找到下一個目的地。Rowan對接阿里云云郵網關,阿里云云郵再對接Hotmail、Gmail、Yahoo等郵件服務商。在我日常運維的過程中,會偶發性地出現“Could not connect to SMTP host”異常提示,尤其是在業務方進行EDM(Email Direct Marketing)郵件大規模營銷時會頻繁發生。
備戰“雙十一”過程中,我對Rowan進行了全鏈路技術改造和壓測,在進行線上壓測郵件服務過程中發現美國集群(US代表美國,HZ代表杭州)在達到1萬QPS壓力時,Rowan服務端大量產生60多萬次的SMTP連接不上阿里云網關的異常,如圖1-6所示。

圖1-6 全鏈路壓測發現TCP問題
阿里的中間件存在百花齊放、百家爭鳴的歷史,也可以說是野蠻生長、重復造輪子、適者生存的歷史。當時還有一款同樣的功能的中間件叫EVE,這是一款從阿里巴巴公司B2B時代就存在的歷史悠久的中間件,而Rowan則是一款年輕的中間件。和EVE不同的是,Rowan并不是通過堆機器和增加系統復雜度來換高吞吐量的,只使用了中美各4臺共計8臺Server服務器,Rowan引入Apache的Jstorm技術,SMTP的郵件發送邏輯放在了Jstorm中,引入Jstorm就是想用最少的機器做極致的事。在Jstorm的日志中,我們又拿到了更加詳細的異常信息。
Caused by: javax.mail.MessagingException: Could not connect to SMTP host: smtp- usa.ocm.aliyun.com, port: 25; nested exception is: java.net.NoRouteToHostException: Cannot assign requested address at com.sun.mail.smtp.AliyunSMTPTransport. openServer(AliyunSMTPTransport.java:1764) at com.sun.mail.smtp.AliyunSMTPTransport. protocolConnect(AliyunSMTPTransport.java:587) Caused by: java.net. NoRouteToHostException: Cannot assign requested address at java.net.PlainSocketImpl. socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractP lainSocketImpl.java:339)
然而登錄阿里云的服務器查看系統指標,以及阿里云云郵系統的神農監控、盤古等指標都沒有問題。查看Jstorm服務器套接字socket使用概況,可以根據上述異常SMTP 25端口用netstat -tn |grep :25 |wc -l命令,或者使用ss -s(Socket Statistics縮寫)命令。如圖1-7和圖1-8所示是連續兩次截圖記錄,可以看出timewait非常多。

圖1-7 第1次查看結果

圖1-8 第2次查看結果
種種跡象表明,問題發生在Client端而不是Server端,這是一起典型的TCP調優案例。
1.2.4 Linux內核網絡參數調優
首先我們調整一下Linux內核參數來提高服務器并發處理能力。一般來說,這種方式可以不用升級服務器硬件就能最大程度地提高服務器性能,是一種節省成本的做法。內核參數修改了以下4個配置:
?ulimit -n。該文件表示系統里打開文件描述符的最大值,查看之后,發現是1024,這個值偏小,我們調大一些。
?somaxconnSocket。cat /proc/sys/net/core/somaxconn,該文件表示等待隊列的長度,默認是128,我們調大到1000。
?netdev_max_backlog。cat /proc/sys/net/core/netdev_max_backlog,該文件表示在每個網絡接口接收數據包的速率比內核處理這些包的速率快時,允許送到隊列的數據包的最大數目,我們將該值調整到1000。
?tcp_max_syn_backlog。cat /proc/sys/net/ipv4/tcp_max_syn_backlog,該文件表示SYN隊列的長度,默認為1024,加大隊列長度為8192,以容納更多等待連接的網絡連接。
修改完畢以后,重新進行線上壓測,然而問題沒有解決,QPS也沒有得到提升。
小竅門
本節主要是對/proc/sys/net/進行優化,該目錄下的配置文件主要用來控制內核和網絡層之間的交互行為,一些技巧補充如下:
1)/proc/sys/net/core/message_burst該文件表示寫新的警告消息所需的時間(以1/10秒為單位),在這個時間內系統接收到的其他警告消息會被丟棄。這用于防止某些企圖用消息“淹沒”系統的人所使用的拒絕服務(Denial of Service)攻擊。缺省:50(5秒)。
2)/proc/sys/net/core/message_cost該文件表示寫每個警告消息相關的成本值。該值越大,越有可能忽略警告消息。缺省:5。
3)/proc/sys/net/core/netdev_max_backlog該文件表示當每個網絡接口接收數據包的速率比內核處理這些包的速率快時,允許送到隊列的數據包的最大數目。缺省:300。
4)/proc/sys/net/core/optmem_max該文件表示每個套接字所允許的最大緩沖區的大小。缺省:10240。
5)/proc/sys/net/core/rmem_default該文件指定了接收套接字緩沖區大小的缺省值(以字節為單位)。缺省:110592。
6)/proc/sys/net/core/rmem_max該文件指定了接收套接字緩沖區大小的最大值(以字節為單位)。缺省:131071。
7)/proc/sys/net/core/wmem_default該文件指定了發送套接字緩沖區大小的缺省值(以字節為單位)。缺省:110592。
8)/proc/sys/net/core/wmem_max該文件指定了發送套接字緩沖區大小的最大值(以字節為單位)。缺省:131071。
1.2.5 Linux TCP參數調優
TIME_WAIT是TCP中一個很重要的狀態,在大并發的短連接下,會產生很多TIME_WAIT,這會消耗很多系統資源;端口的數量只有65535,占用一個就會少一個,進而嚴重影響新連接。所以需要調優TCP參數,從而讓系統更快地釋放TIME_WAIT的連接。TCP的傳輸連接有連接建立、數據傳送和連接釋放3個階段,多年前在與阿里巴巴公司葉軍博士一次閑聊中得知,作為阿里巴巴公司面試官他經常會考察應聘者TCP的3次握手和4次揮手這個知識點。
對于TIME_WAIT,我們主要進行如下的修改:
net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_tw_reuse = 1
這兩個參數默認是關閉的,但是不建議開啟該設置,NAT模式下可能引起連接RST。這兩個參數的作用是主動斷連接,它由于違反了TCP協議(RFC 1122),在其官方文檔
中也強調“It should not be changed without advice/request of technical”。
下面4個是TCP主要內核參數的說明:
?net.ipv4.tcp_syncookies = 1表示開啟SYN Cookies。當出現SYN等待隊列溢出時,啟用cookies來處理,可防范少量SYN攻擊。默認為0,表示關閉。
?net.ipv4.tcp_tw_reuse = 1表示開啟重用。允許將TIME-WAIT sockets重新用于新的TCP連接。默認為0,表示關閉。
?net.ipv4.tcp_tw_recycle = 1表示開啟TCP連接中TIME-WAIT sockets的快速回收。默認為0,表示關閉。
?net.ipv4.tcp_fin_timeout修改系統默認的TIMEOUT時間。
我們可以再繼續進行并發連接backlog調優,并對可用端口調優范圍、TCP Socket讀寫Buffer、TIME-WAIT Socket最大數量、FIN-WAIT-2 Socket超時設置等進行優化。特別說明一下,tcp_max_tw_buckets用于控制并發時,TIME_WAIT的數量默認值是180000,如果超過,系統會清除多余的,并警告“time wait bucket table overflow”。這些參數的調優不但能夠提升服務器的負載,還可以在一定程度上防御DDoS等攻擊。
在參數調優之后,又上了一道雙保險,重新修改了com.sun.mail.smtp源碼的SO_REUSEADDR,通過serverSocket.setReuseAddress(true)讓其在服務器綁定端口前生效,目的是讓端口釋放后立即就可以被再次使用。
但是發布到線上進行壓測以后,卻依然沒有效果。
1.2.6 一行代碼大幅提升QPS
經過了反復的調優以后,真正解決問題的其實只有一行代碼“TCP option SO_LINGER”。在上節中socket.setReuseAddress(true)代碼之后我增加了一行代碼“socket. setSoLinger(true,0)”,重新進行線上壓測,壓測過程中啟用setSoLinger前后數據結果如圖1-9、圖1-10、圖1-11所示。

圖1-9 啟用setSoLinger前

圖1-10 啟用setSoLinger后驗證一

圖1-11 啟用setSoLinger后驗證二
從圖1-9可以發現,啟用l_linger前連接很難超過700, timewait狀態的很多;啟用socket.setSoLinger(true,0)后連接數到1045, timewait狀態沒有了(見圖1-11)。正是因為放棄了TCP中的4次揮手,所以客戶端(Rowan)會給服務端發出RST(阿里云)很多RST。記得當時和阿里資深技術專家葉軍博士交流過,他告訴我說,關閉4次揮手這個案例他以前調優時也做過類似處理,有需要的場景可以將4次揮手改成3次揮手,不過要經過專業大規模測試,一旦TCP層面的Buffer數據丟掉,還是存在一些隱患的。SO_LINGER雖然可以讓服務器性能提升不少,但是在《UNIX網絡編程》卷1中提到TIME_WAIT的作用是在主動關閉端口后,保證數據讓對端收到,Richard.Steven的原話是:“TIME_WAIT是我們的朋友。”如果服務存在大量或者多個通訊,而且之間還有一些時序關系,那么我們就不能使用這種讓用戶丟棄一部分數據的方式,否則可能因為最后階段丟失一些服務器的返回命令而造成程序的錯誤。
那什么是SO_LINGER呢?它是一個可以通過API設置的socket選項,僅僅適用于TCP和SCTP。它主要由on和linger兩個屬性決定,其數據結構如下所示:
struct linger { int l_onoff; /* 0 = off, nozero = on */開關,零或者非零 int l_linger; /* linger time */優雅關閉最長時間 };
如表1-1所示,socket.setSoLinger(true,0)對應的是立即返回,“強制關閉失效”的情況。
表1-1 linger理解圖

如圖1-12所示,這種情況并不是4次揮手,TCP不會進入TIME_WAIT狀態。在send buffer中的數據都發送完之前,close就返回,client向SERVER發送一個RST信息。

圖1-12 l_linger設置值太小
為了避免使用socket.setSoLinger(true,0)可能導致的應用數據包丟失問題,在如圖1-13所示的javamail官方文檔中,SMTP啟用quitwait參數,可以保證應用層消息完整地發完并收到服務端的發送響應。

圖1-13 javamail API
所以,關閉Socket連接部分的代碼調整為,如果quiteWait為true,則需要接收響應。修改完之后,當天13:00以后進行了多次壓測,Rowan服務端中“Could not connect to SMTP host”這個異常再也沒有出現,如圖1-14所示。

圖1-14 13:00以后異常消失
同一時間,阿里云神農監控顯示收到郵件的QPS比之前的壓測要略大,曲線上升波動更快,完全符合預期,如圖1-15所示。

1-15 阿里云13:00開始收到大量的請求
至此,整體TCP調優結束。
思考
SMTP也支持一個連接多封郵件,發完后rset命令,再重新認證發送就可以了,TCP方面并不是與阿里云服務器建立的連接,而是與阿里云的LVS建立的TCP連接。
阿里云使用的還是SMTP的協議,其實我們可以看一下業界的郵件服務,如AWS、SENDCLOUD等,不僅僅支持SMTP,也同樣支持HTTP等。其實真正解決問題的方法是將連接方式改進為長連接,或者實現client的連接復用(連接池的理念)。所以推動阿里云從SMTP改為HTTP也是一個方向。
- Getting Started with Citrix XenApp? 7.6
- Learning Python Web Penetration Testing
- Advanced Quantitative Finance with C++
- Getting Started with Gulp(Second Edition)
- Reporting with Visual Studio and Crystal Reports
- Testing with JUnit
- Android開發精要
- Python自動化運維快速入門
- Neo4j Essentials
- 深度學習:算法入門與Keras編程實踐
- 精通網絡視頻核心開發技術
- Windows Forensics Cookbook
- SQL Server 2016數據庫應用與開發習題解答與上機指導
- Serverless架構
- Yocto for Raspberry Pi