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

y123456yzzyz發表於2020-10-09

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

關於作者

前滴滴出行技術專家,現任OPPO 文件資料庫 mongodb 負責人,負責 oppo 千萬級峰值 TPS/ 十萬億級資料量文件資料庫 mongodb 核心研發及運維工作,一直專注於分散式快取、高效能服務端、資料庫、中介軟體等相關研發。 Github 賬號地址 : https://github.com/y123456yz

1. 開發背景

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

      由於 twemproxy 無法利用多核特性,因此效能低下,短連線 QPS 大約為 3W ,長連線 QPS 大約為 13W ,同時某些場景時延抖動厲害。

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

2    Twemproxy

2.1  Twemproxy簡介

Twemproxy  是一個快速的單執行緒代理程式,支援  Memcached  ASCII 協議和更新的 Redis 協議。它全部用 C 寫成,使用 Apache 2.0 License 授權。支援以下特性:

i) 速度快

ii) 輕量級

iii) 維護持久的伺服器連線

iiii) 啟用請求和響應的管道

iiiii) 支援代理到多個後端快取伺服器

iiiii) 同時支援多個伺服器池

iiiiii) 多個伺服器自動分享資料

iiiiiii) 可同時連線後端多個快取叢集

iiiiiiii) 實現了完整的  memcached ascii    redis  協議 .

iiiiiiiii) 伺服器池配置簡單,通過一個  YAML  檔案即可

iiiiiiiiii) 一致性 hash

iiiiiiiiii) 詳細的監控統計資訊

iiiiiiiiiii) 支援  Linux, *BSD, OS X and Solaris (SmartOS)

iiiiiiiiiiii) 支援設定 HashTag

iiiiiiiiiiiiiii) 連線複用,記憶體複用,提高效率

2.2  memcache快取叢集拓撲結構

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

      圖 1 twemproxy 快取叢集拓撲圖

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

      說明:官方memcache沒有叢集版和持久化功能,叢集版和持久化功能由我們自己內部開發完成。

2.3 推特原生twemproxy瓶頸

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

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

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

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

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

iiiii) 擴容、升級不便

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

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

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

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

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

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

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

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多程式高併發、低時延、高可靠機制在twemproxy快取代理中介軟體中的應用

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

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

      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多程式高併發、低時延、高可靠機制在twemproxy快取代理中介軟體中的應用

      獲取監控資訊流程和配置下發流程基本相同,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多程式高併發、低時延、高可靠機制在twemproxy快取代理中介軟體中的應用

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

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

nginx 工作程式異常自動拉起功能 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工具):

tcprstat 時延分析工具原始碼實現

https://github.com/y123456yz/tcprstat

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

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

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

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

6.1  線上真實流量時延對比

6.1.1  改造前線上twemproxy叢集時延

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

Nginx多程式高併發、低時延、高可靠機制在twemproxy快取代理中介軟體中的應用       未改造前線上twemproxy+memcache叢集,qps=5000~6000,長連線,客戶端時延分佈如下圖所示:

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

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

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

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

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

6.1.2 參照nginx改造後的twemproxy時延

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

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

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

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

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

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

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

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

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

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

總結:替換線上兩個proxy中的一個後,客戶端時間降低了一倍,如果線上叢集兩個代理都替換為改造後的twemproxy,客戶端監控時延預計會再降低一倍,總體時延降低3倍左右。 Nginx多程式高併發、低時延、高可靠機制在twemproxy快取代理中介軟體中的應用

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

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

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

   linux核心版本:linux-3.10

   物理機機型: M10(48 cpu)

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


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

   linux核心版本:linux-3.10

   物理機機型: TS60 (24 cpu)

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

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

nginx 有興趣的可以原始碼分析參考:

https://github.com/y123456yz/reading-code-of-nginx-1.9.2

核心網路卡時延分析工具 :

https://github.com/y123456yz/tcprstat

twemproxy 原始碼分析 :

https://github.com/y123456yz/Reading-and-comprehense-twemproxy0.4.1

核心協議棧延遲確認機制:

https://github.com/y123456yz/Reading-and-comprehense-linux-Kernel-network-protocol-stack

手把手教你做中介軟體、分散式儲存、高效能服務端開發等開發:

https://github.com/y123456yz/middleware_development_learning

  




來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69984922/viewspace-2725707/,如需轉載,請註明出處,否則將追究法律責任。

相關文章