Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

滴滴技術發表於2019-01-29

出品 | 滴滴技術

作者 | 楊亞洲

1.開發背景

現有開源快取代理中介軟體有twemproxy、codis等,其中twemproxy為單程式單執行緒模型,只支援memcache單機版和redis單機版,都不支援叢集版功能。

由於twemproxy無法利用多核特性,因此效能低下,短連線QPS大約為3W,長連線QPS大約為13W; codis起幾十個執行緒,短連線qps不超過10萬;同時某些場景這些開源軟體時延抖動厲害。

為了適應公有云平臺上業務方的高併發需求,因此決定藉助於twemproxy來做二次開發,把nginx的高效能、高可靠、高併發機制引入到twemproxy中,通過master+多worker程式來實現七層轉發功能。

2.Twemproxy

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)連線複用,記憶體複用,提高效率


2.2 滴雲memcache快取叢集拓撲結構

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

如上圖所示,實際應用中業務程式通過輪詢不同的twemproxy來提高qps,同時實現負載均衡。


2.3 推特原生twemproxy瓶頸

如今twemproxy憑藉其高效能的優勢, 在很多網際網路公司得到了廣泛的應用,已經佔據了其不可動搖的地位, 然而在實際的生產環境中, 存在以下缺陷,如下:

i)單程式單執行緒, 無法充分發揮伺服器多核cpu的效能

ii)當twemproxy qps短連線達到8000後,消耗cpu超過70%,時延陡增。

iii)大流量下造成IO阻塞,無法處理更多請求,qps上不去,業務時延飆升

iiii)維護成本高,如果想要充分發揮伺服器的所有資源包括cpu、 網路io等,就必須建立多個twemproxy例項,維護成本高

iiiii)擴容、升級不便

原生twemproxy程式呈現了下圖現象:一個人幹活,多個人圍觀。多核伺服器只有一個cpu在工作,資源沒有得到充分利用。

       Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

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多程式提供服務過程如下圖所示:

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程式主迴圈工作流程圖如下:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

從上圖可以看出,worker程式藉助epoll來實現網路非同步收發,客戶端連線twemproxy的時候,worker程式迴圈檢測客戶端的各種網路事件和後端memcached的網路事件,並進行相應的處理。


twemproxy各個程式整體網路i/o處理過程圖如下:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

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處理,在高併發情況下,往往這就是效能瓶頸。其網路模型如下:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

在Linux kernel 3.9帶來了reuseport特性,它可以解決上面的問題,其網路模型如下:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

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程式收到配置資訊後應答給工作程式。流程如下:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

獲取監控資訊流程和配置下發流程基本相同,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程式,實現瞬間無感知”拉起”恢復。以下為模擬觸發異常段錯誤流程:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

如上圖所示,殺掉31420 worker程式後,master程式會立馬在拉起一個31451工作程式,實現了快速恢復。


多程式異常,自動”拉起”功能原始碼,可以參考如下demo:

https://github.com/y123456yz/reading-code-of-nginx-1.9.2/blob/master/nginx-1.9.2/src/demo.c

5.網路優化

5.1 網路卡多佇列

在實際上線後,發現軟中斷過高,幾乎大部分都集中在一個或者幾個CPU上,嚴重影響客戶端連線和資料轉發,qps上不去,時延抖動厲害。


RSS(Receive Side Scaling)是網路卡的硬體特性,實現了多佇列,可以將不同的流分發到不同的CPU上。支援RSS的網路卡,通過多佇列技術,每個佇列對應一箇中斷號,通過對每個中斷的繫結,可以實現網路卡中斷在cpu多核上的分配,最終達到負載均衡的作用。


5.2 可怕的40ms

原生twemproxy線上上跑得過程中,發現時延波動很大,抓包發現其中部分資料包應答出現了40ms左右的時延,拉高了整體時延抓包如下(藉助tcprstat工具):

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

解決辦法如下:在recv系統呼叫後,呼叫一次setsockopt函式,設定TCP_QUICKACK。程式碼修改如下:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

6.Twemproxy改造前後效能對比 (時延、qps對比)

6.1 線上真實流量時延對比

6.1.1 改造前線上twemproxy叢集時延

線上叢集完全採用開源twemproxy做代理,架構如下:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

未改造前線上twemproxy+memcache叢集,qps=5000~6000,長連線,客戶端時延分佈如下圖所示:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

在twemproxy機器上使用tcprstat監控到的網路卡時延如下:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

從上面兩個圖可以看出,採用原生twemproxy,時延高,同時抖動厲害。


6.1.2 參照nginx改造後的twemproxy時延

線上叢集一個twemproxy採用官方原生twemproxy,另一個為改造後的twemproxy,其中改造後的twemproxy配置worker程式數為1,保持和原生開源twemproxy程式數一致,架構如下:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

替換線上叢集兩個代理中的一個後(影響50%流量),長連線,qps=5000~6000,客戶端埋點監控時延分佈如下:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

替換兩個proxy中的一個後,使用tcprstat在代理叢集上面檢視兩個代理的時延分佈如下:


原生twemproxy節點機器上的時延分佈:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

另一個改造後的twemproxy節點機器上的時延分佈:

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

總結:替換線上兩個proxy中的一個後,客戶端時間降低了一倍,如果線上叢集兩個代理都替換為改造後的twemproxy,客戶端監控時延預計會再降低一倍,總體時延降低3倍左右。

此外,從監控可以看出,改造後的twemproxy時延更低,更加穩定,無任何波動。


6.2 參考nginx多程式改造後的twemproxy線下壓測結果(開啟reuseport功能)

監聽同一個埠,資料長度100位元組,壓測結果如下:

linux核心版本:linux-3.10

物理機機型: M10(48 cpu)

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

多程式監聽同一個埠,資料長度150位元組,壓測結果如下:

linux核心版本:linux-3.10

物理機機型: TS60 (24 cpu)

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用

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協議,把這個抽象出來,開發成本會更低。

參考資料:github.com/y123456yz/r…

  • 亞洲,中介軟體技術專家工程師,2017年加入滴滴,負責mongodb資料庫核心開發

Nginx多程式高併發、低時延、高可靠機制在滴滴快取代理中的應用



相關文章