出品 | 滴滴技術
做者 | 亞洲linux
1、開發背景nginx
現有開源緩存代理中間件有twemproxy、codis等,其中twemproxy爲單進程單線程模型,只支持memcache單機版和redis單機版,都不支持集羣版功能。git
因爲twemproxy沒法利用多核特性,所以性能低下,短鏈接QPS大約爲3W,長鏈接QPS大約爲13W; codis起幾十個線程,短鏈接qps不超過10萬;同時某些場景這些開源軟件時延抖動厲害。github
爲了適應公有云平臺上業務方的高併發需求,所以決定藉助於twemproxy來作二次開發,把nginx的高性能、高可靠、高併發機制引入到twemproxy中,經過master+多worker進程來實現七層轉發功能。web
2、Twemproxyredis
2.1 Twemproxy簡介後端
Twemproxy 是一個快速的單線程代理程序,支持 Memcached ASCII協議和更新的Redis協議。它所有用C寫成,使用Apache 2.0 License受權。支持如下特性:
1)速度快
2)輕量級
3)維護持久的服務器鏈接
4)啓用請求和響應的管道
5)支持代理到多個後端緩存服務器
6)同時支持多個服務器池
7)多個服務器自動分享數據
8)可同時鏈接後端多個緩存集羣
9)實現了完整的 memcached ascii 和 redis 協議.
10)服務器池配置簡單,經過一個 YAML 文件便可
11)一致性hash
12)詳細的監控統計信息
13)支持 Linux, *BSD, OS X and Solaris (SmartOS)
14)支持設置HashTag
15)鏈接複用,內存複用,提升效率centos
2.2 滴雲memcache緩存集羣拓撲結構設計模式
如上圖所示,實際應用中業務程序經過輪詢不一樣的twemproxy來提升qps,同時實現負載均衡。緩存
2.3 推特原生twemproxy瓶頸
現在twemproxy憑藉其高性能的優點, 在不少互聯網公司獲得了普遍的應用,已經佔據了其不可動搖的地位, 然而在實際的生產環境中, 存在如下缺陷,以下:
i)單進程單線程, 沒法充分發揮服務器多核cpu的性能
ii)當twemproxy qps短鏈接達到8000後,消耗cpu超過70%,時延陡增。
iii)大流量下形成IO阻塞,沒法處理更多請求,qps上不去,業務時延飆升
iiii)維護成本高,若是想要充分發揮服務器的全部資源包括cpu、 網絡io等,就必須創建多個twemproxy實例,維護成本高
iiiii)擴容、升級不便
原生twemproxy進程呈現了下圖現象:一我的幹活,多我的圍觀。多核服務器只有一個cpu在工做,資源沒有獲得充分利用。
3、Nginx
nginx是俄羅斯軟件工程師Igor Sysoev開發的免費開源web服務器軟件,聚焦於高性能,高併發和低內存消耗問題,所以成爲業界公認的高性能服務器,並逐漸成爲業內主流的web服務器。主要特色有:
i)徹底藉助epoll機制實現異步操做,避免阻塞。
ii)重複利用現有服務器的多核資源。
iii)充分利用CPU 親和性(affinity),把每一個進程與固定CPU綁定在一塊兒,給定的CPU 上儘可能長時間地運行而不被遷移到其餘處理器的傾向性,減小進程調度開銷。
iiii)請求響應快
iiiii)支持模塊化開發,擴展性好
iiiii)Master+多worker進程方式,確保worker進程可靠工做。當worker進程出錯時,能夠快速拉起新的worker子進程來提供服務。
iiiiii)內存池、鏈接池等細節設計保障低內存消耗。
iiiiii)熱部署支持,master與worker進程分離設計模式,使其具備熱部署功能。
iiiiiii)升級方便,升級過程不會對業務形成任何傷害。
Nginx多進程提供服務過程以下圖所示:
4、Nginx master+worker多進程機制在twemproxy中的應用
4.1 爲何選擇nginx多進程機制作爲參考?
Twemproxy和nginx都屬於網絡io密集型應用,都屬於七層轉發應用,時延要求較高,應用場景基本相同。
Nginx充分利用了多核cpu資源,性能好,時延低。
4.2 Master-worker多進程機制原理
Master-worker進程機制採用一個master進程來管理多個worker進程。每個worker進程都是繁忙的,它們在真正地提供服務,master進程則很「悠閒」,只負責監控管理worker進程, 包含:接收來自外界的信號,向各worker進程發送信號,監控worker進程的運行狀態,當worker進程退出後(異常狀況下),會自動從新啓動新的worker進程。
worker進程負責處理客戶端的網絡請求,多個worker進程同時處理來自客戶端的不一樣請求,worker進程數可配置。
4.3 多進程關鍵性能問題點
master-worker多進程模式須要解決的問題主要有:
i)linux內核低版本(2.6如下版本), 「驚羣」問題
ii) linux內核低版本(2.6如下版本),負載均衡問題
iii)linux內核高版本(3.9以上版本)新特性如何利用
iii)如何確保進程見高可靠通訊
iiii)如何減小worker進程在不一樣cpu切換的開銷
iiiii)master進程如何彙總各個工做進程的監控數據
iiiiii)worker進程異常,如何快速恢復
4.3.1 linux內核低版本關鍵技術問題
因爲linux低內核版本缺陷,所以存在」驚羣」、負載不均問題,解決辦法徹底依賴應用層代碼保障。
4.3.1.1 如何解決「驚羣」問題
當客戶端發起鏈接後,因爲全部的worker子進程都監聽着同一個端口,內核協議棧在檢測到客戶端鏈接後,會激活全部休眠的worker子進程,最終只會有一個子進程成功創建新鏈接,其餘子進程都會accept失敗。
Accept失敗的子進程是不該該被內核喚醒的,由於它們被喚醒的操做是多餘的,佔用本不該該被佔用的系統資源,引發沒必要要的進程上下文切換,增長了系統開銷,同時也影響了客戶端鏈接的時延。
「驚羣」問題是多個子進程同時監聽同一個端口引發的,所以解決的方法是同一時刻只讓一個子進程監聽服務器端口,這樣新鏈接事件只會喚醒惟一正在監聽端口的子進程。
所以「驚羣」問題經過非阻塞的accept鎖來實現進程互斥accept(),其原理是:在worker進程主循環中非阻塞trylock獲取accept鎖,若是trylock成功,則此進程把監聽端口對應的fd經過epoll_ctl()加入到本進程自由的epoll事件集;若是trylock失敗,則把監聽fd從本進程對應的epoll事件集中清除。
Nginx實現了兩套互斥鎖:基於原子操做和信號量實現的互斥鎖、基於文件鎖封裝的互斥鎖。考慮到鎖的平臺可移植性和通用性,改造twemproxy選擇時,選擇文件鎖實現。
若是獲取accept鎖成功的進程佔用鎖時間過長,那麼其餘空閒進程在這段時間內沒法獲取到鎖,從而沒法接受新的鏈接。最終形成客戶端鏈接相應時間變長,qps低,同時引發負載嚴重不均衡。爲了解決該問題,選擇經過post事件隊列方式來提升性能,trylock獲取到accept鎖成功的進程,其工做流程以下:
1.trylock獲取accept鎖成功
2.經過epoll_wait獲取全部的事件信息,把監聽到的全部accept事件信息加入accept_post列表,把已有鏈接觸發的讀寫事件信息加入read_write_post列表。
3.執行accept_post列表中的全部事件
4.Unlock鎖
5.執行read_write_post列表中的事件。
Worker進程主循環工做流程圖以下:
從上圖能夠看出,worker進程藉助epoll來實現網絡異步收發,客戶端鏈接twemproxy的時候,worker進程循環檢測客戶端的各類網絡事件和後端memcached的網絡事件,並進行相應的處理。
twemproxy各個進程總體網絡i/o處理過程圖以下:
4.3.1.2 如何解決「負載均衡「問題
在多個子進程爭搶處理同一個新鏈接事件時,必定只有一個worker子進程最終會成功創建鏈接,隨後,它會一直處理這個鏈接直到鏈接關閉。這樣,若是有的子進程「運氣」很好,它們搶着創建並處理了大部分鏈接,其餘子進程就只能處理少許鏈接,這對多核cpu架構下的應用很不利。理想狀況下,每一個子進程應該是平等的,每一個worker子進程應該大體平均的處理客戶端鏈接請求。若是worker子進程負載不均衡,必然影響總體服務的性能。
nginx經過鏈接閾值機制來實現負載均衡,其原理以下:每一個進程都有各自的最大鏈接數閾值max_threshold和當前鏈接閾值數local_threshold,和當前鏈接數閾值,進程每接收一個新的鏈接,local_threshold增一,鏈接斷開後,local_threashold減一。若是local_threshold超過max_threshold,則不去獲取accept鎖,把accept機會留給其餘進程,同時把local_threshold減1,這樣下次就有機會獲取accept鎖,接收客戶端鏈接了。
在實際業務應用中,有的業務採用長鏈接和twemproxy創建鏈接,鏈接數最大可能就幾百鏈接,若是設置max_threshold閾值過大,多個鏈接若是同時壓到twemproxy,則很容易引發全部鏈接被同一個進程獲取從而形成不均衡。
爲了儘可能減小負載不均衡,在實際應用中,新增了epoll_wait超時時間配置選項,把該超時時間設短,這樣減小空閒進程在epoll_wait上的等待事件,從而能夠更快相應客戶端鏈接,並有效避免負載不均衡。
4.3.2 Linux內核高版本TCP REUSEPORT特性如何利用
4.3.2.1 什麼是reuseport?
reuseport是一種套接字複用機制,它容許你將多個套接字bind在同一個IP地址/端口對上,這樣一來,就能夠創建多個服務來接受到同一個端口的鏈接。
4.3.2.2 支持reuseport和不支持reuseport的區別
若是linux內核版本小於3.9,則不支持reuseport(注:部分centos發行版在低版本中已經打了reuseport patch,因此部分linux低版本發行版本也支持該特性)。
不支持該特性的內核,一個ip+port組合,只能被監聽bind一次。這樣在多核環境下,每每只能有一個線程(或者進程)是listener,也就是同一時刻只能由一個進程或者線程作accept處理,在高併發狀況下,每每這就是性能瓶頸。其網絡模型以下:
在Linux kernel 3.9帶來了reuseport特性,它能夠解決上面的問題,其網絡模型以下:
reuseport是支持多個進程或者線程綁定到同一端口,提升服務器程序的吞吐性能,其優勢體如今以下幾個方面:
i)容許多個套接字 bind()/listen() 同一個TCP/UDP端口
ii)每個線程擁有本身的服務器套接字
iii)在服務器套接字上沒有了鎖的競爭,由於每一個進程一個服務器套接字
iiii)內核層面實現負載均衡
iiiii)安全層面,監聽同一個端口的套接字只能位於同一個用戶下面
4.3.3 Master進程和worker進程如何通訊?
因爲master進程須要實時獲取worker進程的工做狀態,並實時彙總worker進程的各類統計信息,因此選擇一種可靠的進程間通訊方式必不可少。
在twemproxy改造過程當中,直接參考nginx的信號量機制和channel機制(依靠socketpair)來實現父子進程見通訊。Master進程經過信號量機制來檢測子進程是否異常,從而快速直接的反應出來;此外,藉助socketpair,封裝出channel接口來完成父子進程見異步通訊,master進程依靠該機制來統計子進程的各類統計信息並彙總,經過獲取來自master的彙總信息來判斷整個twemproxy中間件的穩定性、可靠性。
配置下發過程:主進程接收實時配置信息,而後經過channel機制發送給全部的worker進程,各個worker進程收到配置信息後應答給工做進程。流程以下:
獲取監控信息流程和配置下發流程基本相同,master進程收到各個工做進程的應答後,由master進程作統一彙總,而後發送給客戶端。
4.3.4 如何減小worker進程在不一樣cpu切換的開銷
CPU 親和性(affinity) 就是進程要在某個給定的 CPU 上儘可能長時間地運行而不被遷移到其餘處理器的傾向性。
Linux 內核進程調度器天生就具備被稱爲 軟 CPU 親和性(affinity) 的特性,這意味着進程一般不會在處理器之間頻繁遷移。這種狀態正是咱們但願的,由於進程遷移的頻率小就意味着產生的負載小。具體參考sched_setaffinity函數。
4.3.5 worker進程異常如何減小對業務的影響?
在實際線上環境中,常常出現這樣的狀況:某個多線程服務跑幾個月後,由於未知緣由進程掛了,最終形成整個服務都會不可用。
這時候,master+多worker的多進程模型就體現了它的優點,若是代碼有隱藏的而且不容易觸發的bug,某個時候若是某個請求觸發了這個bug,則處理這個請求的worker進程會段錯誤退出。可是其餘worker進程不會收到任何的影響,也就是說若是一個改造後的twemproxy起了20個worker進程,某個時候一個隱藏bug被某個請求觸發,則只有處理該請求的進程段錯誤異常,其餘19個進程不會受到任何影響,該隱藏bug觸發後影響面僅爲5%。若是是多線程模型,則影響面會是100%。
若是某個worker進程掛了,master父進程會感知到這個信號,而後從新拉起一個worker進程,實現瞬間無感知」拉起」恢復。如下爲模擬觸發異常段錯誤流程:
如上圖所示,殺掉31420 worker進程後,master進程會立馬在拉起一個31451工做進程,實現了快速恢復。
多進程異常,自動」拉起」功能源碼,能夠參考以下demo:
https://github.com/y123456yz/...
5、網絡優化
5.1 網卡多隊列
在實際上線後,發現軟中斷太高,幾乎大部分都集中在一個或者幾個CPU上,嚴重影響客戶端鏈接和數據轉發,qps上不去,時延抖動厲害。
RSS(Receive Side Scaling)是網卡的硬件特性,實現了多隊列,能夠將不一樣的流分發到不一樣的CPU上。支持RSS的網卡,經過多隊列技術,每一個隊列對應一箇中斷號,經過對每一箇中斷的綁定,能夠實現網卡中斷在cpu多核上的分配,最終達到負載均衡的做用。
5.2 可怕的40ms
原生twemproxy在線上跑得過程當中,發現時延波動很大,抓包發現其中部分數據包應答出現了40ms左右的時延,拉高了總體時延抓包以下(藉助tcprstat工具):
解決辦法以下:在recv系統調用後,調用一次setsockopt函數,設置TCP_QUICKACK。代碼修改以下:
6、Twemproxy改造先後性能對比 (時延、qps對比)
6.1 線上真實流量時延對比
6.1.1 改造前線上twemproxy集羣時延
線上集羣徹底採用開源twemproxy作代理,架構以下:
未改造前線上twemproxy+memcache集羣,qps=5000~6000,長鏈接,客戶端時延分佈以下圖所示:
在twemproxy機器上使用tcprstat監控到的網卡時延以下:
從上面兩個圖能夠看出,採用原生twemproxy,時延高,同時抖動厲害。
6.1.2 參照nginx改造後的twemproxy時延
線上集羣一個twemproxy採用官方原生twemproxy,另外一個爲改造後的twemproxy,其中改造後的twemproxy配置worker進程數爲1,保持和原生開源twemproxy進程數一致,架構以下:
替換線上集羣兩個代理中的一個後(影響50%流量),長鏈接,qps=5000~6000,客戶端埋點監控時延分佈以下:
替換兩個proxy中的一個後,使用tcprstat在代理集羣上面查看兩個代理的時延分佈以下:
原生twemproxy節點機器上的時延分佈:
另外一個改造後的twemproxy節點機器上的時延分佈:
總結:替換線上兩個proxy中的一個後,客戶端時間下降了一倍,若是線上集羣兩個代理都替換爲改造後的twemproxy,客戶端監控時延預計會再下降一倍,整體時延下降3倍左右。
此外,從監控能夠看出,改造後的twemproxy時延更低,更加穩定,無任何波動。
6.2 參考nginx多進程改造後的twemproxy線下壓測結果(開啓reuseport功能)
監聽同一個端口,數據長度100字節,壓測結果以下:
linux內核版本:linux-3.10
物理機機型: M10(48 cpu)
多進程監聽同一個端口,數據長度150字節,壓測結果以下:
linux內核版本:linux-3.10
物理機機型: TS60 (24 cpu)
7、總結
7.1 多進程、多線程機制選擇
選擇參照nginx多進程機制,而不選擇多線程實現緣由主要有:
1) 多進程機制無鎖操做,實現更容易
2) 多進程的代理,整個worker進程無任何鎖操做,性能更好
3) 若是是多線程方式,若是代碼出現bug段錯誤,則整個進程掛掉,整個服務不可用。而若是是多進程方式,由於bug觸發某個worker進程段錯誤異常,其餘工做進程不會受到如何影響,20個worker進程,若是觸發異常,同一時刻只有有1/20的流量受到影響。而若是是多線程模式,則100%的流量會受到影響。
4) worker進程異常退出後,master進程立馬感知拉起一個新進程提供服務,可靠性更高。
5) 配置熱加載、程序熱升級功能實現更加容易
7.2 參照nginx改造後的twemproxy特性
支持nginx幾乎全部的優秀特性,同時也根據本身實際狀況新增長了自有特性:
1) master+多worker進程機制
2) 適配全部linux內核版本,內核低版本驚羣問題避免支持
3) quic_ack支持
4) reuser_port適配支持
5) worker進程異常,master進程自動拉起功能支持
6) 90%、95%、98%、100%平均時延統計功能支持
7) memcache單機版、集羣版支持
8) redis單機版、集羣版支持
9) 二進制協議、文本協議同時支持
10) redis、memcache集羣在線擴容、縮容、數據遷移支持,擴縮容、數據遷移過程對業務無任何影響。
11) 多租戶支持,一個代理能夠接多個memcache、redis集羣,並支持混部。
12) mget、gets、sets等批量處理命令優化處理
13) 慢響應日誌記錄功能支持
14) 內存參數實時修改支持
15) 詳細的集羣監控統計功能
16) CPU親緣性自添加
17)內存配置動態實時修改
7.3後期計劃
添加以下功能:
i) 配置文件熱加載支持。 ii) 代碼熱升級功能支持。
7.4 長遠規劃展望
抽象出一款相似nginx的高性能代理軟件,nginx支持http協議,咱們的支持tcp協議代理,覆蓋nginx全部功能,包括前面提到的全部功能,同時支持模塊化開發。這樣,不少的tcp協議代理就無需關心網絡架構底層實現,只須要根據須要開發對應的協議解析模塊,和本身關心的統計、審計等功能功能,下降開發成本。現有開源的中間件,很大一部分都是tcp的,有本身的私有tcp協議,把這個抽象出來,開發成本會更低。