Upsync:微博开源基于Nginx容器动态流量管理方案
編者按:高可用架構分享及傳播在架構領域具有典型意義的文章,本文由姚四芳在高可用架構群分享。轉載請注明來自高可用架構公眾號「 ArchNotes 」。
姚四芳,新浪微博高級技術專家,微博平臺架構組技術負責人。2012 年加入新浪微博,參與微博 Feed 架構升級,平臺服務化改造,混合云等多個重點項目,現任微博平臺架構組技術負責人,負責平臺公共基礎組建的研發工作。曾在 QCon 進行《新浪微博高性能架構》技術分享,專注高性能架構及服務中間件方向。
Nginx 使用的業務背景與問題
Nginx 以其超高的性能與穩定性,在業界獲得了廣泛的使用,微博的七層就大量使用了 Nginx 。結合 Nginx 的健康檢查模塊,以及動態 reload 機制,可以近乎無損的服務的升級上線與擴容。這個時候擴容的頻次比較低,大多數情況下是有計劃的擴容。
微博的業務場景有非常顯著的峰值特征。既有例行的晚高峰,也有像元旦、春晚、紅包飛這樣的預期內的極端流量峰值。更有#周一見# #我們#等明星/社會事件引發的偶發峰值。之前通常的做法就是 buffer + 降級。在不考慮降級時(會影響用戶體驗)buffer 小了無法承擔峰值大了則成本無法承受。因此,從 2014 年開始,就在嘗試利用容器化來實現 buffer 的動態調整,從而實現依據流量對 buffer 按需擴/縮容,以節省成本開銷。
在這種場景下,會有持續的大量的擴/縮容操作。業界基于 Nginx 的 backend 變更有兩種常用的解決方案。一種是 Tengine 提供的基于 DNS 的,一種是基于 consul-template 的 backend 服務發現。下面的表格簡單對比了兩種方案的特點。
基于 DNS : 該模塊由 Tengine 團隊開發,可以動態的解析 upstream conf 下的域名。這種方式操作簡單,只要修改 dns 下掛載的 server 列表便可。
缺點?:?
-
DNS 定期輪詢解析 ( 30s ),若配置的時間過短如 1s,則對 DNS server 形成壓力,配置的時間過長,則時效性會受影響。
-
基于 DNS 的服務下面不能掛過多的 server,會發生截斷 ( UDP 協議 ),也會對帶寬造成壓力。
基于 consul-template 與 consul : 作為一個組合,consul 作為 db,consul-template 部署于 Nginx server 上,consul-template 定時向 consul 發起請求,發現 value 值有變化,便會更新本地的 Nginx 相關配置文件,發起 reload 命令。但是在流量比較重的情況下,發起 reload 會對性能造成影響。reload 的同時會引發新的 work 進程的創建,在一段時間內新舊 work 進程會同時存在,并且舊的 work 進程會頻繁的遍歷 connection 鏈表,查看是否請求已經處理結束,若結束便退出進程;另 reload 也會造成 Nginx 與 client 和 backend 的長鏈接關閉,新的 work 進程需要創建新的鏈接。
reload 造成的性能影響 :
reload 時 Nginx 的請求處理能力會下降( 注:Nginx 對于握手成功的請求不會丟失 )
reload 時耗時會發生波動,波動幅度甚至達 50%+ ( 不同的業務耗時,波動幅度會有差異 )
每一次的 reload 對 Nginx 的 QPS 與耗時的影響通常會持續 8~10s,考慮到一次擴容會有頻繁的變更,這對在線業務來說,是不堪承受之重。因此,要避免對 Nginx 進行 reload。
基于動態路由的方案設計
在 Nginx 的設計中,每一個 upstream 維護了一張靜態路由表,存儲了 backend 的 ip、port 以及其他的 meta 信息。每次請求到達后,會依據 location 檢索路由表,然后依據具體的調度算法(如 round robin )選擇一個 backend 轉發請求。但這張路由表是靜態的,如果變更后,則必須 reload,通過上圖就可以發現 SLA 受到較大影響。?
最直觀的想法就是,每次更新后 backend 后,動態更新/創建一張路由表,從而避免 reaload。通過 Nginx 擴展一個模塊 [ nginx-upsync-module ] ( https://github.com/weibocom/nginx-upsync-module ) 來動態更新維護路由表。通常路由表的維護的 push 與 pull 兩種方式。
push 方案
此方案中 Nginx 提供 http api 接口,通過 api 添加/刪除 server 時,通過調用 api 向 Nginx 發出請求,操作簡單、便利。
架構圖如下:
http api 除了操作簡單、方便,而且實時性好;缺點是有多臺 Nginx 時,不同 Nginx 路由表的一致性難于保證,如果某一條注冊失敗,便會造成服務配置的不一致,容錯復雜。另外擴容 Nginx 服務器,需要從其他的 Nginx 中同步路由表。
pull 方案
考慮到 push 方案中路由表維度中存在的一致性待問題,引入了第三方組件 consul 解決這一問題。
架構圖如下:
路由表中所有的 backend 信息( 含 meta )存儲到 consul ,所有的 Nginx 從 consul 拉取相關信息,有變更則更新路由表,利用 consul 解決一致性問題,同時利用 consul 的 wait 機制解決實時性問題,利用 consul 的 index ( 版本號 ) 進行增量摘取,解決帶寬占用問題。
在 consul 中,一個 k/v 對代表一個 backend 信息,增加一個即視作擴容,減少一個即為縮容。調整 meta 信息,如權重,也可以達到動態流量調整的目的。
下面的實現基于 consul 進行介紹。
基于動態路由的方案實現
基于 upsync 方式,開發了模塊 nginx-upsync-module,它的功能是拉取 consul 的后端 server 的列表,并更新 Nginx 的路由信息。此模塊不依賴于任何第三方模塊。
路由表更新方式
consul 作為 Nginx 的 db,利用 consul 的 KV 服務,每個 Nginx work 進程獨立的去拉取各個 upstream 的配置,并更新各自的路由。
流程圖如下:
每個 work 進程定時的去 consul 拉取相應 upstream 的配置,定時的間隔可配;其中 consul 提供了 time_wait 機制,利用 value 的版本號,若 consul 發現對應 upstream 的值沒有變化,便會 hang 住這個請求 5 分鐘(默認),在這五分鐘內對此 upstream 的任何操作,都會立刻返回給 Nginx,對相應路由進行更新。對于拉取的間隔可以結合場景的需要進行配置,基本可以實現所要求的實時性。upstream 變更后,除了更新 Nginx 的緩存路由信息,還會把本 upstream 的后端 server 列表 dump 到本地,保持本地 server 信息與 consul 的一致性。
除了注冊/注銷后端的 server 到 consul,會更新到 Nginx 的 upstream 路由信息外,對后端 server 屬性的修改也會同步到nginx的upstream路由。當前本模塊支持修改的屬性有 weight、max_fails、fail_timeout、down,修改 server 的權重可以動態的調整后端的流量,若想要臨時移除server,可以把 server 的 down 屬性置為 1( 當前 down 的屬性暫不支持 dump 到本地的 server 列表內 ?),流量便會停止打到該 server,若要恢復流量,可重新把 down 置為 0。
另外每個 work 進程各自拉取、更新各自的路由表,采用這種方式的原因:一是基于 Nginx 的進程模型,彼此間數據獨立、互不干擾;二是若采用共享內存,需要提前預分配,靈活性可能受限制,而且還需要讀寫鎖,對性能可能存在潛在的影響;三是若采用共享內存,進程間協調去拉取配置,會增加它的復雜性,拉取的穩定性也會受到影響。基于這些原因,便采用了各自拉取的方式。
高可用性
Nginx 的后端列表更新依賴于 consul,但是不強依賴于它,表現在:一是即使中途consul意外掛了,也不會影響 Nginx 的服務,Nginx 會沿用最后一次更新的服務列表繼續提供服務;二是若 consul 重新啟動提供服務,這個時候 Nginx 會繼續去 consul 探測,這個時候 consul 的后端服務列表發生了變化,也會及時的更新到 Nginx。
另一方面,work 進程每次更新都會把后端列表 dump 到本地,目的是降低對 consul 的依賴性,即使在 consul 不可用之時,也可以 reload Nginx。Nginx 啟動流程圖如下:
Nginx 啟動時,master 進程首先會解析本地的配置文件,解析完成功,接著進行一系列的初始化,之后便會開始 work 進程的初始化。work 初始化時會去 consul 拉取配置,進行 work 進程 upstream 路由信息的更新,若拉取成功,便直接更新,若拉取失敗,便會打開配置的 dump 后端列表的文件,提取之前 dump 下來的 server 信息,進行 upstream 路由的更新,之后便開始正常的提供服務。
每次去拉取 consul 都會設置連接超時,由于 consul 在無更新的情況下默認會 hang 五分鐘,所以響應超時配置時間應大于五分鐘。大于五分鐘之后,consul 依舊沒有返回,便直接做超時處理。
兼容性
整體上講本模塊只是更新后端的 upstream 路由信息,不嵌入其它模塊,同時也不影響其它模塊的功能,亦不會影響 Nginx-1.9.9 的幾種負載均衡算法:least_conn、hash_ip 等。
除此之外,模塊天然支持健康監測模塊,若 Nginx 編譯時包含了監測模塊,會同時調用健康監測模塊的接口,時時更新健康監測模塊的路由表。
性能測試
nginx-upsync-module 模塊,潛在的帶來額外的性能開銷,比如間隔性的向 consul 發送請求,由于間隔比較久,且每個請求相當于 Nginx 的一個客戶端請求,所以影響有限。基于此,在相同的硬件環境下,使用此模塊和不使用此模塊簡單做了性能對比。
基本環境
硬件環境:Intel(R) Xeon(R) CPU E5645 @ 2.40GHz 12 核 ??
系統環境:centos6.5; ?
work進程數:8個; ? ??
壓測工具:wrk; ?
壓測命令:./wrk -t8 -c100 -d5m --timeout 3s http://$ip:8888/proxy_test ?
壓測數據 :?
其中 Nginx ( official ) 是官方 Nginx,不執行 reload 下的測試數據。Nginx ( upsync ) 是基于 upsync 模塊,每隔 10s 鐘向 consul 注冊/注銷一臺機器的數據;從數據可以看出,通過 upsync 模塊做擴縮容操作,對性能的影響有限,可以忽略不計。
應用案例
模塊已經應用在微博的各類業務中,下面圖表對比分析使用模塊前后的 QPS 與耗時變化。
從數據可以得出,reload 操作時造成 nginx 的請求處理能力下降約 10%,Nginx 本身的耗時會增長 50%+。若是頻繁的擴容縮容,reload 操作造成的開銷會更加明顯。
在 2016 年元旦期間,針對不同時間段流量特點,進行了上百次的擴/縮容,整體服務在擴容過程中的 SLA 未受影響。
官方商業版對 Nginx plus 支持了 DNS 與 push 版本提供了支持。
在使用過程中因為數據一致性等問題,擴展支持了基于 consul 的 pull 版本
https://github.com/weibocom/nginx-upsync-module 目前在完善 wiki 與文檔 點擊閱讀原文可以進入。
Q & A
1、consul 中的機器配置信息注冊是微博系統根據流量自動調整嗎 ?
這是兩個問題,在擴容時向節點注冊 backend 信息的過程是自動的,已經整合到上線系統中。另外,目前微博在進行在線容量評估系統開發與評測,目前是處理半自動調整。
2、請問下當初為何不考慮 zk 呢? 如果換做 zk 不用輪訓 pull 改為長連會有什么不一樣嗎?
目前模塊已經在添加類似 etcd、zk 類的支持。當初使用 consul 是因為公司內已經有 consul 集群與運維人員。zk 與 etcd、consul 對于模塊而言在本質上是一樣的
3、為什么不要 Nginx 的 master 去 pull ,然后分發給各個 work ,而用 work 進程 pull ?前者可以減少網絡交互和提升一個 Nginx 內部多 work 的一致性
如果使用 master 去 pull,需要修改 core 模塊,在進行模塊設計時,一個大的原則就是想盡量確保模塊的 0 依賴。總的來說,這也是一個 trade off。
4、consul 中的機器配置信息注冊是微博系統根據流量自動調整嗎 ?
這個問題與問題 1 類似。
5、基于什么考慮選用了 consul 做配置管理?
與問題 2 類似。
6、能否設計一套 API,給 Nginx 去 pull ,源是 consul 還是某個 Java 服務都沒關系,感覺更通用些
模塊的設計思路與提到的類似,里面設計了一個 upsync type,不同的源進行不同的 type 實現即可。
7、另外不知道有沒有解決過 Nginx 路由信息太多占用內存過多的問題,我們現在用 LRU 的方式淘汰,不知道微博有沒有這樣的場景
在設計的時候考慮過這一塊。通常我們只會保持當前路由表與一個過期路由表,在過期路由表支撐的所有請求處理完成后,這一部分內存會被釋放掉。
8、在流量低谷的時候,微博未使用的機器用來做什么呢?如果這批機器還在給別的服務使用,當微博動態加載這批機器,那其它服務怎么辦
目前我們在進行混合云部署,buffer 池子在公有云上創建。流量低谷的時候,直接刪除即可。自有機房的機器在流量低谷時,通常可以跑一些離線業務,在線與離線混跑的策略目前在開發中。
9、ab 和 work 的實際測試結果表明 在高壓力下頻繁更新 consul 列表會出現failed的情況? 這個問題你們是怎么處理的?
我們有測試每秒鐘變更上千臺機器,并不會出現問題。這個已經能夠支撐絕大部分(包括大鱷們)的擴容訴求。我們在壓測 consul 的時候,的確出現 consul 的 failed 情況(單主提供服務),這個和模塊本身并無關聯。需要提升 consul 集群的性能與配置。
10、請教老師,想對 consul 做個簡單了解,consul 是在內存中存儲的路由信息嗎?如何區分不同服務的路由信息?只能通過鍵值命名還是每個服務分別部署 consul ?
路由表信息會存儲到 3 個地方。1. Nginx 的內存,直接提升路由服務;2. 存儲在 consul 上,存儲每一個節點就是一個 backend key 為 /$consul_path/$upstream/ip:port。 3. Nginx 所在服務器的文件上,以 snapshot 的方式存儲,避免 consulg 與 Nginx 同時宕機。
點擊閱讀原文可以訪問 nginx-upsync-module 在 github 上的開源地址。
微博技術團隊在高可用架構分享的部分文章
-
支撐微博千億調用的輕量級RPC框架:Motan
-
微博基于Docker的混合云平臺設計與實踐
-
微博“異地多活”部署經驗談
-
微博基于Docker容器的混合云遷移實戰
-
單表60億記錄的MySQL優化和運維之道
-
微博在大規模、高負載系統問題排查方法
-
總結
以上是生活随笔為你收集整理的Upsync:微博开源基于Nginx容器动态流量管理方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 创建标签
- 下一篇: Nginx —— 检查配置文件ngi