:從負載均衡原理到ACL動態(tài)路由配置詳解)
1. 七層代理到底解決了什么問題先說個實際場景。之前幫某公司做一套內部業(yè)務系統后端就三臺應用服務器一開始用的輪詢DNS哪臺掛了不知道流量還經常打偏一到高峰期總有那么一兩臺撐不住另外兩臺閑著看熱鬧。后來就是靠部署HAProxy七層代理把這些亂象一次性理順的。HAProxy是目前生產環(huán)境里用得最多的高性能負載均衡軟件之一所謂“七層代理”指的是它工作在OSI模型的第七層也就是HTTP/HTTPS應用層。跟四層代理最大的區(qū)別在于四層代理只看數據包里的IP和端口拿到連接就轉發(fā)不關心你訪問的是哪個域名、哪條路徑、帶了什么Cookie七層代理則會把HTTP請求完整拆開來看拿到URL、Header、Cookie、請求方法甚至請求體里的關鍵字段然后根據這些信息決定把這個請求轉發(fā)給哪個后端。說白了一句四層代理是快遞分揀員只看包裹上的地址七層代理是掛號導診臺會問你是哪里疼、掛哪個科把你送到對應的醫(yī)生面前。那這個能力到底適合誰如果你只是兩臺服務器做高可用四層方案其實也夠用但如果你的業(yè)務里面有多個域名共用一臺入口、有不同路徑要轉發(fā)給不同服務、有像 /api 和 /static 這樣的動靜分離、需要根據請求頭里的版本號做灰度路由這時候七層代理基本就是最優(yōu)解。HAProxy在這塊的優(yōu)勢一是性能極強單實例輕松扛住每秒幾萬請求二是配置靈活ACL規(guī)則寫起來像寫業(yè)務代碼一樣順手三是穩(wěn)定性經過大規(guī)模生產環(huán)境檢驗我用了這么多年還沒見過HAProxy自己崩掉的場景。2. 方案選型為什么是HAProxy而不是其他負載均衡器2.1 HAProxy、Nginx、LVS三者怎么選很多剛接觸負載均衡的讀者都會問這個問題同樣是做流量分發(fā)Nginx不是也能做七層代理嗎LVS在四層領域不也很強嗎為什么偏偏選HAProxy我自己的使用體會是這樣的。LVS工作在四層性能確實是天花板級別的但它有個天然短板不解析HTTP層想做七層路由、域名分發(fā)、路徑匹配這些業(yè)務邏輯基本無能為力。而且LVS配置多、規(guī)則多架構上還要引入額外的Director節(jié)點日常維護成本比HAProxy高不少。Nginx做七層代理當然可以很多團隊也確實在用。但Nginx本身的定位是Web服務器代理能力是它的擴展技能。在負載均衡這個專項場景里HAProxy有幾點是Nginx比不了的HAProxy原生支持配置熱加載改完配置直接執(zhí)行reload命令幾乎零中斷地切換配置不需要像Nginx那樣小心地執(zhí)行nginx -s reload還要擔心worker進程的日志句柄問題。HAProxy的統計信息頁面開箱即用還能通過socket接口實時調整權重、下線后端、查看連接數運維操作非常方便。健康檢查類型豐富可以配置幾十種檢查方式從簡單的TCP探測到復雜的HTTP關鍵字匹配、LDAP檢查、Redis協議檢查都能做。會話保持機制做得細基于Cookie的會話粘滯、基于來源IP的哈希、基于URL的哈希選擇面遠比Nginx寬。當然Nginx在緩存、靜態(tài)文件處理、腳本擴展這些領域有自己的優(yōu)勢。我通常的建議是如果你需要的是緩存或復雜的請求重寫可以考慮Nginx如果核心訴求就是高并發(fā)流量分發(fā)給后面的應用服務器HAProxy是更純粹的“專業(yè)選手”配置更清晰排查問題也更容易。2.2 版本選擇與部署方式的影響HAProxy從2.0開始引入了不少重要特性比如HTTP/2支持、PROMEX指標暴露、多線程模型優(yōu)化等。到2.4、2.6、2.8這些版本七層代理能力和性能一直在增強。我跟你說句實在話對于生產環(huán)境建議直接選2.4以上的穩(wěn)定版本別在老版本上糾結。一個很重要的原因是2.4之后配置里支持了純環(huán)境變量替換可以在global段用setenv/getenv指令配合運行時環(huán)境變量來管理配置差異這讓配置的復用和環(huán)境遷移方便了一大截。部署方式上最常見的兩種是裸機直接裝二進制包或系統包以及用容器跑HAProxy。容器化部署最大的好處是配置變更可以通過鏡像版本控制回滾很直接但要注意容器里跑HAProxy時一定要把系統文件描述符限制打開很多人在容器里遇到文件句柄耗盡就是因為沒做這個操作。裸機部署則更適合那種追求極致性能、網絡綁定比較復雜的場景。另外一個常被忽略的點是HAProxy處理七層代理時最核心的優(yōu)化點不在HAProxy進程本身而在它后面的操作系統網絡棧。比如tcp_tw_reuse、tcp_fin_timeout、somaxconn這些內核參數直接決定了HAProxy能否扛住高并發(fā)短連接的壓力。簡單說配置層面做好還不夠系統層面同樣得配合這部分我會在后面的實操段落里給出具體的參數。3. 核心配置術語與參數深解先花點篇幅講清楚HAProxy配置里的幾個核心“區(qū)塊”這是所有七層代理配置的基礎你熟悉了之后再看任何一份HAProxy配置都能快速上手。3.1 frontend、backend、listen的關系HAProxy七層代理配置通常分成三段global段、defaults段以及frontend/backend段落。global段是進程級配置比如日志、線程數、運行用戶、最大連接數。defaults段是默認參數的模板后面所有frontend和backend都會繼承這里的設置適合統一設置超時時間、模式等。frontend段定義入口監(jiān)聽客戶端連接解析HTTP然后根據ACL規(guī)則決定把請求轉發(fā)到哪個backend。backend段定義后端服務器池包括服務器列表、負載均衡算法、健康檢查方式等。實際配置里還有一種listen段它是frontend和backend的結合體適合端口和用途都比較單一的簡單場景。但做七層代理尤其是有多個域名、多個路徑、多個后端池的復雜業(yè)務我更推薦把frontend和backend分開寫邏輯清晰得多。當你后面需要新增一個域名路由時只需要在frontend里加幾條ACL然后加一個backend段不需要動其他配置。3.2 mode http與七層代理模式的關鍵設置HAProxy最核心的一句配置就是mode。默認情況下如果沒寫mode會繼承defaults里的設置很多新手容易踩這個坑——defaults里忘了寫mode結果前置的七層ACL規(guī)則完全不生效。做七層代理必須明確設置mode http。mode http會啟用HTTP解析引擎這時候你可以用path、hdr、url、method這些關鍵字來寫ACL規(guī)則。比如frontend web_front bind *:80 mode http use_backend app_backend if { path_beg /api/ }這里path_beg就是路徑前綴匹配意思是請求路徑以 /api/ 開頭時轉發(fā)到app_backend后端。沒有mode http這種規(guī)則是寫不出來的因為四層模式下HAProxy根本不解析HTTP報文。還有一個容易被忽略的配置option httplog。這個選項會讓HAProxy按照HTTP格式輸出訪問日志里面包含請求方法、路徑、狀態(tài)碼、響應時間、前后端連接信息這些信息在排查問題的時候價值極高。我見過有些部署為了省日志空間把這個關掉的真出問題時完全無從下手我強烈建議必須開著。通過log global配置把日志交給系統syslog然后用httplog格式輸出排查問題時你會感謝當初開啟了這個選項。3.3 超時參數與連接復用七層代理里最容易讓人困惑的就是各種超時時間。HAProxy里的超時主要分三類timeout connect連接后端服務器的超時時間一般設置5秒左右比較合理。如果后端在網絡層面就比較慢可以適當調大但不要超過10秒否則用戶體驗會很差。timeout client與客戶端連接之間空閑狀態(tài)的超時時間默認可以設60秒配合HTTP長連接使用。timeout server與后端服務器連接之間空閑狀態(tài)的超時時間通常要跟后端的處理節(jié)奏匹配比如后端要跑大查詢、導出報表這個就適當調大比如300秒。超時設置的核心原則客戶端超時要比服務器超時長否則后端還在處理請求客戶端這邊等不及先斷了后端白忙活一場。典型的問題場景是應用導出報表后端要算兩分鐘客戶端超時設了30秒結果前端一直報網關超時后端實際還在算。這就是典型的超時配置未對齊問題。另外如果想優(yōu)化連接性能可以開啟option http-keep-alive并配合connection復用參數。默認情況下HAProxy每處理完一個請求就可能關閉連接如果業(yè)務里大量請求是短連接、請求頻率高頻繁握手會浪費大量CPU和網絡資源開啟連接復用之后性能提升非常明顯。4. 七層代理核心玩法ACL規(guī)則與動態(tài)路由4.1 ACL到底怎么用ACL全稱Access Control List在HAProxy里就是一組匹配規(guī)則用來描述“什么樣的請求滿足什么樣的條件”。七層代理的靈魂就在這——ACL規(guī)則寫得好不好直接決定你的路由邏輯是否靈活。常見的ACL類型包括基于路徑匹配path_beg前綴、path_end后綴、path_reg正則基于域名匹配hdr(host) 精確匹配、hdr_beg(host) 前綴匹配、hdr_reg(host) 正則基于方法匹配method GET / POST / PUT基于來源IPsrc 匹配來源地址基于請求頭hdr_user-agent、hdr_referer等等基于Cookiecookie(name) 匹配Cookie字段一個典型的場景某個業(yè)務系統要按請求路徑分流前端靜態(tài)資源走靜態(tài)服務器后端API走應用服務器上傳管理功能走單獨的文件服務。配置長這樣frontend web_front bind *:80 mode http option httplog acl is_static path_beg /static /images /css /js acl is_api path_beg /api/ acl is_upload path_beg /upload use_backend static_pool if is_static use_backend api_pool if is_api use_backend upload_pool if is_upload default_backend web_pool這一段配置讀起來幾乎跟自然語言一樣維護成本極低。新同事接手也只要照著原有的ACL樣式加幾行就行不需要理解復雜的路由表邏輯。ACL還能組合判斷比如“來源IP是內網網段并且路徑是 /admin/ 的請求才允許訪問管理后臺”寫法是acl is_admin_net src 10.0.0.0/8 acl is_admin_path path_beg /admin/ use_backend admin_pool if is_admin_net is_admin_path這里兩個ACL之間是“與”關系都滿足才命中。如果想要“或”關系就用or連接HAProxy的ACL組合邏輯非常靈活這也是它相比其他方案的優(yōu)勢所在。4.2 請求重寫與Header操作七層代理不僅要會“轉發(fā)”還要能“改造”。實際業(yè)務里最常見的三個改造需求改寫Host頭、插入客戶端真實IP、添加安全頭。先說Host頭。當后端多個虛擬主機共用一個IP時后端服務器根據Host頭來區(qū)分。如果你的前端HAProxy和后面的Nginx之間沒有做Host頭透傳后端拿到的Host就是前端代理的地址或默認的IP業(yè)務可能直接報錯。HAProxy里配置是backend app_backend server web1 10.0.1.11:8080 check http-request set-header Host myapp.example.com再就是X-Forwarded-For頭。后端應用經常要記錄客戶端真實IP而代理轉發(fā)之后默認TCP連接里看不到原始來源IP。要在HAProxy層開啟option forwardfor讓HAProxy在請求頭中插入X-Forwarded-For后端應用取這個Header就能拿到真實IP。需要提醒的是option forwardfor默認會保留客戶端已經存在的X-Forwarded-For字段如果客戶端偽造了這個頭后端拿到的可能不是真實IP。生產環(huán)境如果對來源IP要求比較嚴格建議用if-none參數強制覆蓋option forwardfor except 127.0.0.1 header X-Real-IP這句的意思是除了本機訪問之外統一覆蓋X-Real-IP頭的值避免偽造。4.3 灰度發(fā)布和AB測試的天然幫手HAProxy的ACL規(guī)則直接支持灰度發(fā)布。最常用的兩種方法按來源IP的網段灰度以及按URL參數或Header標記灰度。比如要上線新版本的訂單系統老版本是old_pool新版本是new_pool希望先把公司內部員工和某個測試合作方的流量導入新版本。配置是這樣acl is_employee src 10.0.10.0/24 acl is_partner src 192.168.88.0/24 acl is_new_ver hdr(beta-version) -i v2 use_backend new_pool if is_employee use_backend new_pool if is_partner use_backend new_pool if is_new_ver default_backend old_pool這種做法的好處是當新版本出問題時只需要把這幾條ACL注釋掉執(zhí)行一次reload流量就全部切回老版本。灰度發(fā)布的核心訴求就是“快上快下”HAProxy這種方式比改應用代碼、改DNS都可靠得多。按比例灰度也可以用HAProxy實現最簡單的方式是用random算法配合weight權重。比如新版本后端server只分配5的權重老版本分配95權重balance random server old1 10.0.1.10:8080 weight 95 server new1 10.0.1.20:8080 weight 5這種按權重的灰度方案適合那種無法用IP或Header精準圈定用戶范圍的場景操作直觀便于隨時調整。5. 完整實操一個可落地的七層代理配置5.1 場景設定與整體拓撲為了把這個技術講透我拿一個實際項目來做示例。假設有個電商業(yè)務系統后端有三組服務靜態(tài)資源服務圖片、CSS、JS跑在三個Nginx節(jié)點上端口80API服務HTTP接口跑在兩個Tomcat節(jié)點上端口8080管理后臺獨立的Web應用跑在兩個節(jié)點上端口7001還有一個需求出于審計要求管理后臺只能允許內網IP訪問。同時需要把客戶端真實IP傳給后端API服務。這個場景基本把七層代理的常見需求都覆蓋了域名轉發(fā)、路徑分流、IP限制、Header改寫、健康檢查、負載均衡算法。5.2 完整配置與每段含義解析直接上完整配置我每一步都加了注釋和說明global log /dev/log local0 info maxconn 100000 nbthread 4 user haproxy group haproxy stats socket /run/haproxy/admin.sock mode 660 level admin defaults mode http timeout connect 5s timeout client 60s timeout server 300s option httplog option dontlognull option forwardfor frontend web_front bind *:80 bind *:443 ssl crt /etc/haproxy/cert.pem maxconn 30000 default_backend static_pool # 按Host頭識別不同域名 acl api_host hdr(host) -i api.example.com acl admin_host hdr(host) -i admin.example.com acl static_host hdr(host) -i static.example.com # 按路徑識別API acl api_path path_beg /api/ # 管理后臺限制內網 acl admin_net src 10.0.0.0/8 192.168.0.0/16 # 路由決策 use_backend api_pool if api_host OR api_path use_backend admin_pool if admin_host admin_net use_backend static_pool if static_host backend api_pool balance roundrobin option httpchk GET /healthz server api1 10.0.2.11:8080 check inter 3s fall 2 rise 3 server api2 10.0.2.12:8080 check inter 3s fall 2 rise 3 backend admin_pool balance leastconn option httpchk GET /admin/login server admin1 10.0.3.11:7001 check inter 5s fall 3 rise 2 weight 1 server admin2 10.0.3.12:7001 check inter 5s fall 3 rise 2 weight 1 backend static_pool balance source hash-type consistent option httpchk GET / server static1 10.0.4.11:80 check inter 2s fall 2 rise 2 server static2 10.0.4.12:80 check inter 2s fall 2 rise 2 server static3 10.0.4.13:80 check inter 2s fall 2 rise 2 listen stats bind 127.0.0.1:8404 stats enable stats uri /stats stats refresh 5s stats auth admin:yourpassword這個配置里幾個關鍵點需要展開講一下。首先是global段的nbthread 4指的是啟動4個線程來處理請求。注意這個值不是越大越好建議跟機器的物理核數對齊或者略微小于核心數。之前在生產上遇到過一個案例一臺32核機器把nbthread設置成32結果CPU跑滿但吞吐率反而下降因為線程切換開銷過高了后來調整到16才恢復正常。然后是defaults里的option dontlognull這個選項很有用它會讓HAProxy不記錄“健康檢查”之類的空連接日志減少日志噪音。如果不加這個健康檢查產生的刷屏日志會淹沒真正有價值的訪問日志查問題時特別煩。再說到frontend里的bind *:443 ssl crt這是HTTPS終止。HAProxy可以直接終止SSL然后以HTTP方式轉發(fā)給后端這樣后端的應用就只需要處理HTTP請求SSL證書的管理集中在前端一個入口省事很多。正式環(huán)境證書需要注意定期續(xù)期我用的是腳本配合定時任務檢查證書過期時間過期前自動重新加載HAProxy配置。5.3 負載均衡算法的選擇邏輯配置里三個后端池我用了三種不同的算法這不是隨便選的背后各有道理。api_pool用的是roundrobin也就是輪詢每個請求按順序輪流分發(fā)到后端節(jié)點。這個算法適合后端各節(jié)點性能相近、接口耗時相對平均的場景實現簡單、分布均勻度好。admin_pool用的是leastconn最小連接數。管理后臺有個特點用戶登錄后長時間保持HTTP連接會話停留在某臺節(jié)點上。如果還沿用輪詢新建連接會固定散到不同節(jié)點上但舊用戶的連接全集中在一臺機器上就會造成負載不均。leastconn會優(yōu)先把新連接分配到當前活躍連接數最少的節(jié)點對長連接場景非常友好。這里管理員用戶量不算大用leastconn基本是最佳選擇。static_pool用的是source算法加一致性哈希。source算法是按客戶端IP哈希同一個IP的請求始終落到同一臺后端節(jié)點這對靜態(tài)資源來說能充分利用節(jié)點本地緩存緩存命中率明顯上升。加hash-type consistent是為了避免節(jié)點增減時大量請求重新映射。靜態(tài)資源場景下緩存命中率直接影響響應速度和后端壓力所以這個選擇是從實戰(zhàn)出發(fā)的。如果你拿不準該用哪種算法默認從roundrobin開始是比較穩(wěn)妥的。等觀察一段時間訪問日志和節(jié)點負載分布后再根據實際情況調整成leastconn或source。5.4 健康檢查參數詳解健康檢查是負載均衡里絕對不能省略的環(huán)節(jié)。沒有健康檢查某臺后端宕機了流量照樣發(fā)過去用戶的請求直接超時等到你發(fā)現線上問題業(yè)務早就受了損失。配置里我寫了option httpchk GET /healthz意思是HAProxy定期以GET方式請求這個路徑只要返回狀態(tài)碼在2xx—3xx范圍就認為節(jié)點健康。這個健康檢查路徑很有講究建議后端專門實現一個輕量級健康檢查接口這個接口不僅要檢查進程還活著還要檢查依賴的數據庫連接池、緩存連接是否正常。有些團隊直接用首頁當作健康檢查路徑這其實隱患很大——進程還在但數據庫連接池滿了首頁還能返回健康檢查卻誤判為健康流量照樣灌進去直到用戶側真正報錯才發(fā)現問題。再說inter、fall、rise這三個參數。inter是健康檢查間隔我API池設了3秒。間隔太短會增加后端不必要的壓力一個健康檢查請求雖然輕量但節(jié)點多的時候累計起來也很可觀間隔太長又會讓故障感知變慢。生產經驗是2~5秒之間比較合理。fall是連續(xù)幾次失敗就判定節(jié)點故障rise是連續(xù)幾次成功就判定恢復。這兩個參數需要平衡設得太小容易誤判比如節(jié)點一次GC停頓時間稍長觸發(fā)了超時就立刻被摘除影響穩(wěn)定性設得太大又會讓故障轉移變慢。常規(guī)推薦是fall 2~3rise 2~3。5.5 reload與平滑切換HAProxy做大版本配置修改后不需要重啟進程只需要執(zhí)行reload命令。這是HAProxy相比其他同類型工具很省心的地方之一。在systemd環(huán)境下執(zhí)行haproxy -f /etc/haproxy/haproxy.cfg -c systemctl reload haproxy第一行是檢查配置語法如果配置有問題會直接報錯此時不能執(zhí)行第二行。實際生產操作中我通常把這兩句放在一個腳本里先檢查配置語法通過后再執(zhí)行reload。這樣無論誰去操作服務器都不會因為手誤把錯誤配置加載上去導致服務中斷。reload過程的原理是HAProxy主進程會啟動一個新的子進程加載新配置老的子進程繼續(xù)處理已有連接等所有老連接處理完畢后才退出。這意味著reload操作對用戶幾乎是無感知的不會造成在線連接的閃斷。這里有個細節(jié)要注意如果老的子進程有大量長連接一直不關閉它就會一直不退出導致系統里堆積大量殘留進程。這種場景下建議配合timeout server http-keep-alive設置一個合理的超時避免連接無限期占用。6. 高級功能與常見問題排查6.1 會話保持的三種方式HTTP業(yè)務里經常需要把同一個用戶的請求固定分發(fā)到同一臺后端服務器特別是登錄狀態(tài)存在本地內存里的老系統。HAProxy實現會話保持有幾種方式我按推薦程度排序介紹。第一種是基于Cookie插入。HAProxy會在響應里給客戶端種一個帶路由信息的Cookie后續(xù)請求帶上這個Cookie時HAProxy就根據Cookie里的路由標記把請求轉發(fā)到最初處理會話的那臺后端。配置是這樣的backend api_pool cookie SERVERID insert indirect nocache server api1 10.0.2.11:8080 check cookie s1 server api2 10.0.2.12:8080 check cookie s2這種方式的優(yōu)勢是精準不受IP變化影響劣勢是需要后端應用配合Cookie處理有些前后端分離的應用會自己管理Cookie需要溝通排除沖突。第二種是IP哈希就是前面static_pool用過的source算法。只需要配置balance source一鍵生效。缺點是客戶端出口IP如果變化比如4G網絡切換基站導致IP變化會話就會漂移。所以這種方案適合局域網內的B端業(yè)務不太適合移動互聯網場景。第三種是基于Cookie的值做哈希比如按用戶ID字段做哈希。這種方式不需要后端顯式管理路由Cookie但要求請求里有可用的標識字段比如通過hdr(cookie)提取用戶ID。如果用戶清掉了Cookie哈希值就沒了會話也就保不住了。生產里怎么選核心看業(yè)務對“會話丟失”的容忍度。如果登錄狀態(tài)丟失會造成明確損失建議優(yōu)先用Cookie插入如果只是希望同一用戶的靜態(tài)請求盡量落在同一臺節(jié)點上IP哈希就夠用了。6.2 后端獲取不到客戶端真實IP怎么辦這個坑我在排查線上問題時遇到太多次了?,F象是后端應用查看訪問日志時REMOTE_ADDR全是HAProxy節(jié)點的內網IP看不到真實用戶IP導致風控和用戶行為分析的數據全部失效。原因很簡單HAProxy做完代理轉發(fā)之后和后端建立TCP連接的是HAProxy自己的IP不是客戶端IP。要解決這個問題有三個層次的做法。最標準的做法是在HAProxy里開啟option forwardfor讓HAProxy在轉發(fā)HTTP請求時插入X-Forwarded-For頭。后端應用需要配置信任這個頭比如Tomcat的RemoteIpValve、Nginx的set_real_ip_from real_ip_headerSpring Boot也可以通過server.forward-headers-strategyNATIVE開啟。這里有個安全小細節(jié)X-Forwarded-For客戶端是可以偽造的。如果后端不加區(qū)分直接信任會帶來IP偽造風險。所以更穩(wěn)妥的做法是在HAProxy層對源IP做一次過濾只信任從前端代理過來的請求也就是配置option forwardfor except 127.0.0.1或者設定trusted proxies的白名單。我在前面配置示例里用到了header X-Real-IP的寫法就是給指定Header賦值并把原有內容覆蓋掉從源頭上杜絕偽造。6.3 504/502錯誤排查七層代理環(huán)境下最常見的兩類錯誤是502和504。502 Bad Gateway代表HAProxy成功接收了客戶端請求但無法從后端服務器獲得有效響應??赡苁呛蠖藳]有進程在監(jiān)聽端口、網絡不通、后端服務器直接掛了也可能是健康檢查已經標記該節(jié)點宕機但仍有請求被轉發(fā)到一個已經被摘除的節(jié)點這種情況極少見往往是健康檢查配置異常。排查思路一般是這樣先看stats頁面里后端節(jié)點的狀態(tài)是否全部DOWN如果是直接用curl測試后端curl -v http://后端IP:端口/healthz看后端本身能不能通。如果后端本身一切正常再查HAProxy到后端服務器之間是否有防火墻攔截。504 Gateway Timeout則代表后端響應超時也就是說HAProxy已經連上后端服務器、也發(fā)出了請求但后端在timeout server設置的時間內沒返回。這種問題一要檢查后端是否真的有慢查詢或外部接口超時二要看timeout server是不是設置得太短。有個經典案例后端處理圖片上傳大圖片壓縮需要一分多鐘前端界面一直報504后端明明還在正常處理。處理方式是把timeout server從60秒調到300秒問題立即消失。我在生產環(huán)境里還遇到過一個比較隱蔽的問題后端應用在壓測中出現周期性的504排查了很久發(fā)現是數據庫連接池在預熱階段連接建立過慢導致個別請求首次處理超過了3秒的connect timeout。后來把timeout connect稍微調大并讓后端應用啟動時做一次連接池預熱徹底解決了。6.4 統計頁面的正確用法HAProxy自帶的統計頁面是個非常好用的線上診斷工具配置也簡單listen stats bind *:8404 stats enable stats uri /stats stats refresh 3s stats admin if LOCALHOST要注意的是stats頁面必須做訪問控制。不要把8404端口暴露到公網至少要把它綁到內網或本機然后用防火墻限制訪問來源。如果確實需要遠程訪問給stats auth設置強密碼是底線。頁面上最有價值的幾個字段每個后端的Status狀態(tài)UP/DOWN、Sessions Current當前連接數、Response Time響應時間、Session Rate連接速率。排查問題時我一般先看Status字段是不是全UP如果有DOWN的節(jié)點再看是主動下線還是被動檢查失敗然后回到第六節(jié)說的健康檢查邏輯去分析原因。Sessions Current字段如果長時間逼近maxconn上限說明后端擴容已經迫在眉睫或者是負載均衡算法選擇不合理導致節(jié)點分配不均需要結合連接數分布和響應時間一起判斷。7. 性能與安全調優(yōu)心得7.1 內核參數、連接隊列與文件描述符配置層面的調優(yōu)做完了還差系統層面的調優(yōu)。這里我整理了一份常用的內核參數參考清單注意這是基于常見實踐的補充生產環(huán)境還是要先在測試環(huán)境驗證再上線net.ipv4.tcp_tw_reuse 1 net.ipv4.tcp_fin_timeout 30 net.ipv4.tcp_max_syn_backlog 65535 net.core.somaxconn 65535 net.core.netdev_max_backlog 65535 fs.file-max 65536000tcp_tw_reuse和tcp_fin_timeout解決的是高并發(fā)短連接場景下TIME_WAIT堆積的問題。HAProxy做七層代理時每個普通請求都會消耗一個端口和連接請求處理完畢后連接立即關閉會留下大量TIME_WAIT狀態(tài)的連接。如果等待時間去到默認的60秒大量連接資源被占據后續(xù)新連接就會受影響。開啟tcp_tw_reuse后內核可以安全地復用這些TIME_WAIT連接效果非常顯著。somaxconn和tcp_max_syn_backlog則決定了系統在請求風暴下能承載多少未處理的連接請求。HAProxy的maxconn是一個全局限制超出了這個數字的請求會被拒絕這其實是一種保護機制——寧可排不上隊也不能拖著大量連接拖垮整個進程。之前給一個高并發(fā)項目壓測一開始沒有調somaxconn壓測剛開始階段就出現大量連接失敗操作系統的半連接隊列直接滿了。調大之后壓測曲線立即變得平滑。另外必須提到ulimit。HAProxy進程能打開的文件描述符數量直接受系統硬限制約束。在systemd服務里可以通過LimitNOFILE設置[Service] LimitNOFILE1048576這是高并發(fā)環(huán)境下HAProxy啟動之后“莫名”報too many open files時最常見的解法。注意不止systemd的unit文件要改還要同步檢查一下/etc/security/limits.conf里的nofile設置兩邊要一致。7.2 安全加固IP黑白名單與其他防護措施七層代理天然處在一個可以介入安全策略的位置。我這邊至少保留了以下幾類措施。最簡單的是IP封禁。比如某個惡意IP持續(xù)掃描或高頻請求直接在frontend里加一條ACL攔截acl banned_ip src 1.2.3.4 http-request deny if banned_ip比直接在防火墻封禁更靈活的地方在于你不需要動系統防火墻改配置reload一下就生效了還可以配合stats頁面觀察被攔截的請求有沒有降下來。更精細的防護手段包括限制請求速率。HAProxy自帶stick table可以做速率統計backend rate_limit stick-table type ip size 100k expire 30m store http_req_rate(10s) backend api_pool http-request track-sc0 src table rate_limit acl too_fast sc_http_req_rate(0) ge 50 http-request deny if too_fast這段配置的意思是為每個來源IP維護一個10秒窗口內的HTTP請求計數如果超過50次直接拒絕。這個機制特別適合在惡意爬蟲、刷接口攻擊來臨時做快速止血。帶寬防護、單IP并發(fā)數限制也可以通過類似方式實現。當然要注意的是一旦啟用了這些規(guī)則誤殺的可能性也存在。比如公司出口IP可能是公網NAT共享的幾十個人共享一個IP出口如果把閾值設得太低正常同事的請求也會被攔截引發(fā)“上不了系統”的工單轟炸。所以閾值的設置一定要結合業(yè)務實際情況統計一段時間內的訪問分布后再定別拍腦袋。7.3 配置規(guī)范與變更發(fā)布的經驗關于HAProxy配置本身我最后分享幾條實踐經驗。每次做變更前一定要用-c參數做語法檢查這個習慣能攔截掉90%的低級錯誤。配置文件的版本管理要用Git這是最基本的底線這樣任何一次變更都可以追溯是誰改的、為什么改、改了什么。reload之前把當前生效配置備份一次出問題時能用最快的速度回滾。還有一個容易忽略的環(huán)節(jié)HAProxy的知識庫和社區(qū)非常成熟遇到沒見過的報錯先把報錯信息原樣搜索一遍通常都能找到遇到過同樣問題的人。我自己比較偏愛的排查順序是先看stats頁面、再看日志、然后看后端節(jié)點狀態(tài)、最后才看配置和代碼這樣能最大化縮小排查范圍。8. 一條實戰(zhàn)鏈路從壓測到上線最后講一個完整案例。某套電商促銷系統上線前我負責整個入口的流量驗證。當時用壓測工具模擬了1萬并發(fā)客戶端請求直接懟到HAProxy七層代理入口。第一輪壓測的結果很不理想節(jié)點2的負載明顯高于節(jié)點1響應時間也不穩(wěn)定。通過stats頁面看到節(jié)點2的Sessions Current長期高于節(jié)點1于是判斷負載均衡算法可能不均衡但配置里用的是roundrobin啊。仔細檢查后發(fā)現健康檢查里配置的權重沒有對齊節(jié)點2回頭查配置發(fā)現它的server行多了一個weight 2導致流量分配比重是2:1輪詢被權重干擾了。把權重統一改為1之后第二輪壓測負載曲線基本重合。接著又出現了一個新問題壓測過程中大量請求報504。通過查看后端應用日志發(fā)現后端節(jié)點有慢SQL單接口耗時最長的到了8秒。臨時調大timeout server到10秒后504消失但整條鏈路的響應時間依然偏高。最后通過優(yōu)化SQL索引和緩存方案后端耗時降到了100毫秒左右壓測數據才算真正合格。整個過程下來的經驗是性能瓶頸往往不是單一因素造成的代理層、網絡層、后端應用、數據庫可能同時存在隱患。而七層代理作為流量入口恰好是一個天然的“觀測點”通過它你能像看實時交通路況一樣看到每條鏈路的健康狀況然后再順著線索一層層往下查。這也是我一直覺得HAProxy七層代理值得認真對待的原因——它不僅能幫你把流量分發(fā)好更能在關鍵時刻幫你快速定位問題到底出在哪一環(huán)。