構(gòu)詳解:從零搭建HTTP負(fù)載均衡)
第一次接觸 HAProxy 的時候大多數(shù)人都是被那堆配置項(xiàng)勸退的。老實(shí)說作為 Linux 下應(yīng)用最廣的開源負(fù)載均衡器之一HAProxy 的配置文件反而沒那么玄乎——它核心就五段global、defaults、frontend、backend、listen。搞懂它們分別管什么再把幾個高頻參數(shù)吃透基本就能應(yīng)對大部分場景了。這篇文章不聊那些花活只講 haproxy 基礎(chǔ)配置、配置文件結(jié)構(gòu)和幾個最簡單實(shí)用的功能適合剛上手或者用了一段時間但一直靠復(fù)制粘貼的朋友。先說清楚 HAProxy 能干什么。它本質(zhì)上是一個基于 TCP 和 HTTP 的高性能反向代理最常干的活就是負(fù)載均衡把用戶的請求按照某種策略分發(fā)到后面一組真實(shí)服務(wù)器上同時還要負(fù)責(zé)健康檢查——某臺后端掛了自動把流量摘掉等它恢復(fù)后再加回來。很多團(tuán)隊(duì)也拿它做流量入口的統(tǒng)一收斂點(diǎn)順帶完成簡單的請求路由、訪問控制、監(jiān)控統(tǒng)計(jì)。它的特點(diǎn)就一個字穩(wěn)純 C 寫的單進(jìn)程事件驅(qū)動在普通 x86 服務(wù)器上扛住幾萬并發(fā)并不稀奇配置正確的情況下跑幾年不重啟也沒問題。這篇文章我會用一套完整的 Web 負(fù)載均衡配置作為主線把 global、defaults、frontend、backend 四段逐行講清楚再用 listen 段演示一個最簡單的 TCP 轉(zhuǎn)發(fā)最后把平時最容易踩的坑列出來。下文中所有配置均基于 HAProxy 2.4 及更新版本較老版本個別參數(shù)略有差異但主體框架完全一致老版本用戶在用到具體參數(shù)時留意一下版本差異即可。1. HAProxy 配置文件的整體框架與定位1.1 為什么 HAProxy 的配置文件要分成五段理解 HAProxy 的配置最關(guān)鍵的一步是理解它的設(shè)計(jì)哲學(xué)接收端和轉(zhuǎn)發(fā)端分開描述。Nginx 里你經(jīng)常在一個 location 或 server 塊里既寫監(jiān)聽的端口又寫轉(zhuǎn)發(fā)的目標(biāo)但 HAProxy 偏不這么干它把“怎么接流量”和“往哪兒轉(zhuǎn)發(fā)”拆成兩個獨(dú)立對象也就是 frontend 和 backend。這樣的拆分有兩個顯而易見的好處。第一多個 frontend 可以共用同一個 backend。比如你有一個 80 端口的 HTTP 入口和一個 443 端口的 HTTPS 入口兩個入口收到的流量都可以轉(zhuǎn)發(fā)到同一組 Web 服務(wù)器上只在 frontend 這塊分別定義各自的監(jiān)聽方式后端邏輯完全復(fù)用。第二一個 frontend 也可以根據(jù) ACL 規(guī)則路由到不同的 backend。比如同一個入口進(jìn)來的流量路徑帶 /api/ 的轉(zhuǎn)發(fā)到接口服務(wù)集群其余走靜態(tài)頁面集群這只需要在 frontend 里加幾條規(guī)則不用動后端定義。listen 段則是把 frontend 和 backend 合并在一起寫的老式風(fēng)格適合簡單場景一個端口進(jìn)來直接轉(zhuǎn)到一個后端池沒有復(fù)雜的路由需求。它寫起來確實(shí)省事但一旦場景變復(fù)雜——比如多個不同端口、多個目標(biāo)組——用 listen 會導(dǎo)致配置迅速膨脹且難以閱讀。我的建議是學(xué)習(xí)階段兩種都要會看新項(xiàng)目一律用 frontend/backend 分離式寫法。還有兩個特殊的段global 和 defaults。global 是進(jìn)程級配置管的是 HAProxy 自身的行為比如運(yùn)行身份、最大連接數(shù)、日志怎么發(fā)defaults 則是給后面所有 frontend/backend/listen 提供默認(rèn)值的“模板段”里面寫的參數(shù)會被繼承后面的段如果重復(fù)定義同名參數(shù)就覆蓋它。理解繼承關(guān)系很重要這也是很多新手排查配置的盲區(qū)——你明明沒在 backend 里寫某項(xiàng)配置但它就是從 defaults 里繼承來的。1.2 五大配置段的職責(zé)邊界與繼承關(guān)系把五大段的職責(zé)邊界理清楚配置文件就看懂了一半配置段核心職責(zé)典型參數(shù)示例global進(jìn)程運(yùn)行級別非事務(wù)性daemon, user, group, maxconn, logdefaults提供全局默認(rèn)參數(shù)供各段繼承mode, timeout, option, retriesfrontend流量入口定義監(jiān)聽端口與路由規(guī)則bind, use_backend, default_backendbackend后端服務(wù)器池定義轉(zhuǎn)發(fā)目標(biāo)與策略server, balance, check, cookielisten入口后端合并定義適用簡單場景bind, server, mode, balance注意 global 段是特殊的它不受 defaults 的繼承機(jī)制影響因?yàn)樗鼔焊筒皇且粋€“流量處理單元”它是一個獨(dú)立進(jìn)程的描述。defaults 的繼承對象是 frontend、backend、listen 這三個段。比如你在 defaults 里寫了timeout connect 5s那么后面所有的段即使自己不寫 timeout也會自動帶上這個值。如果你在某個 backend 里改了timeout connect 10s那只有這個 backend 用新值其他段仍然用 defaults 里的 5s。這段繼承邏輯實(shí)操中非常有用是配置精簡的核心。一個規(guī)范的單機(jī)配置文件通常就是 global 段十幾行 defaults 段十行左右 每個業(yè)務(wù)一個 frontend 一個 backend加起來四五十行就夠跑一個像樣的負(fù)載均衡了。不要刻意寫長HAProxy 配置的價值不在行數(shù)而在每個參數(shù)是不是真正合理。需要提醒的是defaults 里盡量只放真正的“通用默認(rèn)項(xiàng)”別把業(yè)務(wù)相關(guān)的參數(shù)塞進(jìn)去。比如有的人在 defaults 里寫balance leastconn結(jié)果后面所有不同業(yè)務(wù)的 backend 都默認(rèn)用了這個算法但有的業(yè)務(wù)其實(shí)適合 roundrobin就容易出現(xiàn)流量分不均勻的怪現(xiàn)象。defaults 是兜底不是萬能池。2. 核心配置參數(shù)詳解從進(jìn)程參數(shù)到 HTTP 功能搞清結(jié)構(gòu)之后接下來就要鉆進(jìn)細(xì)節(jié)里。HAProxy 的配置參數(shù)很多但基礎(chǔ)階段真正高頻使用的其實(shí)就那一二十個。我按使用場景把它們分成了四組每組都有實(shí)際意義和踩坑點(diǎn)逐組講透。2.1 global 段進(jìn)程級參數(shù)與運(yùn)行性能調(diào)優(yōu)global 段的參數(shù)不算多但每一個都影響運(yùn)行方式。最常用的幾個如下global log 127.0.0.1:514 local0 info chroot /var/lib/haproxy pidfile /var/run/haproxy.pid maxconn 80000 user haproxy group haproxy daemon stats socket /var/lib/haproxy/stats mode 600 level admin逐行解釋一下關(guān)鍵項(xiàng)。log指定日志發(fā)送地址和 facility。這里寫的127.0.0.1:514 local0 info表示把日志通過 UDP 發(fā)給本機(jī)的 rsyslog 端口facility 用 local0日志級別 info。實(shí)操中很多人忽略這行導(dǎo)致出問題時啥都查不到。我強(qiáng)烈建議一開始就配上哪怕沒有獨(dú)立日志服務(wù)器發(fā)到本機(jī) rsyslog 也足夠用了。日志是排查問題的第一手資料沒有日志的負(fù)載均衡等于盲人摸象。maxconn單進(jìn)程最大連接數(shù)。這是一個在性能和資源之間取平衡的值不要盲目調(diào)大。它受限于文件描述符上限Linux 下單個進(jìn)程默認(rèn) fd 上限通常是 1024你需要先在系統(tǒng)層面調(diào)大ulimit -n否則配置再大也只是個擺設(shè)。一個粗略的計(jì)算公式是maxconn × 每個連接約消耗 20~40KB 內(nèi)存 若干 fd。如果機(jī)器內(nèi)存是 16G按 40KB 一個連接算maxconn 設(shè)到 30 萬左右理論上可以但實(shí)際還要留出系統(tǒng)和其他進(jìn)程的余量所以我一般建議按可用內(nèi)存 / 64KB的 60%~70% 來估算寧可保守一點(diǎn)。daemon讓 HAProxy 以后臺 daemon 方式運(yùn)行這是生產(chǎn)環(huán)境的標(biāo)準(zhǔn)姿勢。沒有這行的話HAProxy 會在前臺運(yùn)行終端一關(guān)就退出了。調(diào)試的時候你反而可以臨時去掉 daemon 用前臺模式跑方便直接看輸出但正式部署一定要加回來。stats socket這個別小看。它暴露了一個 Unix socket讓運(yùn)維員可以在本機(jī)執(zhí)行echo show info; show stat | socat stdio /var/lib/haproxy/stats這種命令直接查詢運(yùn)行數(shù)據(jù)。配合level admin還可以在線啟用/停用服務(wù)器、調(diào)整權(quán)重是免重啟運(yùn)維的核心入口。生產(chǎn)環(huán)境我強(qiáng)烈建議配置這個后面排查和做操作都會爽很多。chroot和user/group是安全相關(guān)項(xiàng)分別把進(jìn)程困在指定目錄里和降權(quán)運(yùn)行?;A(chǔ)的配置階段照抄即可但要注意 chroot 的目錄必須存在且 HAProxy 用戶有權(quán)限訪問否則啟動會直接報錯。2.2 defaults 段時間參數(shù)與通用選項(xiàng)的合理預(yù)設(shè)defaults 段是配置文件的“聯(lián)合作戰(zhàn)地圖”模版。我常用的基礎(chǔ)配置如下defaults mode http log global option httplog option dontlognull option http-server-close option forwardfor except 127.0.0.0/8 option redispatch retries 3 timeout http-request 10s timeout queue 1m timeout connect 10s timeout client 1m timeout server 1m timeout http-keep-alive 10s timeout check 10s maxconn 5000挑重點(diǎn)講。mode http表示默認(rèn)以七層 HTTP 模式工作HAProxy 會解析請求報文可以做 URI 級的轉(zhuǎn)發(fā)、ACL、cookie 保持等功能。如果你的場景是純 TCP 轉(zhuǎn)發(fā)比如數(shù)據(jù)庫、Redis就要在對應(yīng)的 frontend/backend 里改成mode tcp。注意 mode 是可以按段覆蓋的defaults 里寫 http某個 listen 段里寫 tcp 是合法的。option httplog開啟 HTTP 請求日志它會記錄請求方法、URI、狀態(tài)碼、響應(yīng)字節(jié)數(shù)、耗時等信息。沒有這個選項(xiàng)日志里就只有 TCP 層的信息排查業(yè)務(wù)問題幾乎沒法用。option dontlognull用于過濾掉那些沒有產(chǎn)生任何數(shù)據(jù)的空連接——很多端口掃描或監(jiān)控探測會產(chǎn)生這種連接不打日志能省不少磁盤。option forwardfor是給后端的真實(shí) IP 傳遞用的。HAProxy 作為代理時后端服務(wù)器看到的所有請求來源都是 HAProxy 自身的 IP這會導(dǎo)致后端拿不到客戶端的真實(shí)地址。開了這個選項(xiàng)后HAProxy 會往請求頭里追加X-Forwarded-For字段后端用這個字段還原客戶端 IP。做日志分析、限流、黑白名單都靠它。except 127.0.0.0/8的意思是本機(jī)發(fā)來的請求不加這個頭防止某些內(nèi)部應(yīng)用來源信息被錯誤改寫。option redispatch在高可用場景比較重要。當(dāng)某個后端服務(wù)器因?yàn)橘Y源耗盡等原因?qū)е聲挓o法完成時HAProxy 可以把這個請求重新調(diào)度到其他健康的服務(wù)器上避免用戶一直卡在故障機(jī)上。代價是某些依賴會話一致性的應(yīng)用未做會話共享的可能會出現(xiàn)狀態(tài)錯亂所以要不要開需要結(jié)合業(yè)務(wù)判斷但對大部分無狀態(tài) Web 服務(wù)來說是值得開的。timeout 系列是 defaults 里的重頭戲也是新手最容易牽連踩坑的地方。行為邏輯timeout connect與后端服務(wù)器建立 TCP 連接的超時時間。這個值不要設(shè)太大因?yàn)檎5木钟蚓W(wǎng)連接都在幾十毫秒內(nèi)完成設(shè) 10s 已經(jīng)非常保守了。如果后端網(wǎng)絡(luò)質(zhì)量差建議排查鏈路而不是盲目調(diào)大這個值。timeout client客戶端與 HAProxy 之間的空閑超時時間。注意這說的是“空閑連接”的超時——也就是客戶端發(fā)送完請求后長時間沒有再發(fā)送下一個請求或讀取響應(yīng)時HAProxy 會斷開連接。設(shè)太短會導(dǎo)致長輪詢、WebSocket 等連接被意外斷開設(shè)太長又容易積壓一堆半死不活的連接。timeout server與后端服務(wù)器之間的空閑超時時間。含義和 client 對應(yīng)但作用于 HAProxy 到后端那一段。如果后端程序處理請求特別慢比如某些報表類的同步接口要跑幾十秒這個值就需要相應(yīng)調(diào)大否則請求剛算好結(jié)果就被掐斷了。timeout http-request接收完整 HTTP 請求頭的超時時間。這個值建議設(shè)小一點(diǎn)因?yàn)樗饕脕矸缆俟簟獝阂饪蛻舳艘稽c(diǎn)一點(diǎn)地發(fā)請求頭把連接拖死。10s 已經(jīng)夠?qū)捲US脩舨粫ǔ^幾秒鐘填一個請求頭。timeout http-keep-aliveHTTP 長連接的空閑超時時間??蛻舳擞?keep-alive 維持連接、準(zhǔn)備發(fā)下一個請求時空閑超過這個值就會被斷開。有個容易誤會的點(diǎn)timeout client和timeout server設(shè)的是“空閑超時”不是硬性的最長請求時間。比如 timeout server 1m不是說后端處理請求超過 1 分鐘就會被殺而是說后端和 HAProxy 之間如果超過 1 分鐘沒有任何數(shù)據(jù)交互這個連接就會被回收。理解這個區(qū)別很重要否則你會在一堆長耗時請求的場景下糾結(jié)為什么明明設(shè)了很大的值還是斷連。2.3 frontend 段流量入口與基礎(chǔ)路由規(guī)則frontend 的職責(zé)是“接住流量”所以配置的核心是監(jiān)聽地址和路由決策??匆粋€最簡單的 HTTP 入口frontend web_front bind 0.0.0.0:80 mode http default_backend web_serversbind 0.0.0.0:80表示監(jiān)聽本機(jī)所有網(wǎng)卡的 80 端口。如果你只希望內(nèi)網(wǎng)網(wǎng)卡接收流量可以寫成bind 192.168.1.10:80這樣外網(wǎng)網(wǎng)卡即使到達(dá)了也不會進(jìn)這個 frontend等于從入口做了隔離。default_backend是兜底路由前面的請求如果沒匹配到任何別的規(guī)則就統(tǒng)一走這個后端池。實(shí)際生產(chǎn)中使用 HTTP 入口一般會加一些簡單的 ACL 路由比如根據(jù)路徑區(qū)分 API 請求和靜態(tài)資源frontend web_front bind 0.0.0.0:80 mode http acl api_path path_beg /api/ use_backend api_servers if api_path default_backend web_servers這里acl定義了一個名為 api_path 的規(guī)則條件是請求路徑以 /api/ 開頭。use_backend根據(jù) ACL 規(guī)則選擇對應(yīng)后端池不滿足就落到 default_backend。這個模式在微服務(wù)網(wǎng)關(guān)和前后端分離架構(gòu)里非常常見基本可以覆蓋大多數(shù)路由需求?;A(chǔ)階段掌握bind、acl、use_backend、default_backend就夠了。ACL 的匹配字段很多域名、URL、Headers、來源 IP、HTTP 方法等后面有需要再看具體字段的語法這里先不做擴(kuò)展。另外frontend 的名字是可以隨便起的比如 web_front、api_front但同一個文件里不能重復(fù)。名字起得規(guī)范一些多人協(xié)作時能把閱讀成本降很多。2.4 backend 段后端服務(wù)器池、權(quán)重與健康檢查backend 是真正干活的段——定義一組服務(wù)器以及負(fù)載均衡策略?;A(chǔ)模板如下backend web_servers balance roundrobin option httpchk GET /health http-check expect status 200 server web1 10.0.0.11:8080 weight 100 check inter 3s fall 3 rise 2 server web2 10.0.0.12:8080 weight 100 check inter 3s fall 3 rise 2 server web3 10.0.0.13:8080 backup check inter 3s fall 3 rise 2balance roundrobin是最常用也最好理解的負(fù)載均衡算法——按順序輪流把請求分配給后端服務(wù)器。它適合后端性能大致相同的場景。如果后端機(jī)器性能差異大可以用balance leastconn新建連接時優(yōu)先分給當(dāng)前活躍連接數(shù)最少的服務(wù)器適合長連接和請求處理時間不穩(wěn)定的場景。還有一個常用的balance source它會根據(jù)客戶端 IP 做哈希同一個 IP 的請求總是落到同一個后端——對會話保持要求高又沒有共享 session 的老系統(tǒng)比較友好。三個算法的適用場景我整理了一張表算法核心邏輯適用場景注意點(diǎn)roundrobin輪流分發(fā)后端性能近似、連接多為短連接最通用無腦首選leastconn挑選活躍連接數(shù)最少者請求耗時差異大、長連接較多需要配合健康檢查使用source按客戶端 IP 哈希需要 IP 級別的會話粘滯后端增刪節(jié)點(diǎn)會導(dǎo)致哈希漂移weight表示權(quán)重范圍 1~256默認(rèn) 1。比如一臺新機(jī)器剛上線可以先給個較小權(quán)重觀察表現(xiàn)穩(wěn)定后再調(diào)正常。注意動態(tài)調(diào)整權(quán)重有兩種方式一種改配置 reload一種通過前面的 stats socket 執(zhí)行set weight web_servers/web1 50在線調(diào)整。后者對線上流量無感是非常實(shí)用的運(yùn)維技巧。check參數(shù)是健康檢查的開關(guān)。沒有這個參數(shù)HAProxy 會把請求送到一個已經(jīng)宕機(jī)的服務(wù)器上然后超時報錯。加上check后HAProxy 會按照inter 3s的間隔、每 3 秒對后端做一次健康探測。fall 3表示連續(xù) 3 次探測失敗就把節(jié)點(diǎn)標(biāo)記為 DOWNrise 2表示連續(xù) 2 次成功后重新標(biāo)記為 UP。這兩個值要按業(yè)務(wù)敏感度調(diào)后端重啟速度快的可以設(shè)小一點(diǎn)減少流量的空閑時間后端啟動慢比如 Java 應(yīng)用冷啟動需要幾十秒的rise 就要設(shè)大一點(diǎn)防止剛起來還沒就緒就被塞流量。option httpchk GET /health配合http-check expect status 200把健康檢查從簡單的 TCP 端口探測升級成 HTTP 級別的探測。這非常關(guān)鍵——一個后端進(jìn)程可能在正常運(yùn)行但業(yè)務(wù)邏輯已經(jīng)無法響應(yīng)了比如數(shù)據(jù)庫連不上、內(nèi)部線程池耗盡純 TCP 探測根本發(fā)現(xiàn)不了只有 HTTP 探測能真實(shí)反映業(yè)務(wù)健康度。幾乎所有面向用戶的 Web 服務(wù)都應(yīng)該在后端應(yīng)用里實(shí)現(xiàn)一個返回 200 狀態(tài)的 /health 接口然后在這里配置對應(yīng)的健康檢查。沒有健康檢查的負(fù)載均衡等于把命運(yùn)交給了運(yùn)氣。還有一個backup參數(shù)值得了解加上后這臺服務(wù)器只在其他非 backup 節(jié)點(diǎn)都不可用時才接收流量。這是一種冷備策略。注意 backup 節(jié)點(diǎn)平時不接流量健康檢查照常執(zhí)行一旦切換到它它會默默抗住所有流量所以要對它的處理能力有清晰認(rèn)知。別把它當(dāng)普通節(jié)點(diǎn)用。3. 從零搭建一個 HTTP 負(fù)載均衡的完整配置配置文件每個段都過了基礎(chǔ)的關(guān)現(xiàn)在把它們拼起來完整走一遍實(shí)操流程。假設(shè)場景你有三臺后端 Web 服務(wù)器IP 分別是 10.0.0.11、10.0.0.12、10.0.0.13端口都是 8080它們對外提供相同的 HTTP 服務(wù)?,F(xiàn)在要在前臺的一臺 2C4G 的 Linux 服務(wù)器上部署 HAProxy監(jiān)聽 80 端口實(shí)現(xiàn)負(fù)載均衡、健康檢查、監(jiān)控頁面這三個基礎(chǔ)功能。3.1 環(huán)境準(zhǔn)備安裝與目錄規(guī)范先裝 HAProxy。Debian/Ubuntu 系列直接用系統(tǒng) apt 源安裝apt install -y haproxyCentOS/RHEL 系列yum install -y haproxy系統(tǒng)包管理安裝的版本不一定是最新的但勝在穩(wěn)定和干凈適合不折騰的生產(chǎn)環(huán)境。安裝完成后配置文件默認(rèn)在/etc/haproxy/haproxy.cfgHAProxy 進(jìn)程以 haproxy 用戶運(yùn)行。安裝后先驗(yàn)證一下版本不同主版本的參數(shù)兼容性有差異haproxy -v看到輸出后記下版本號后面查閱文檔時用的都是對應(yīng)版本的行為。我下面展示的配置兼容 1.8 到 2.x 的范圍內(nèi)常用的參數(shù)但如果你用的是 3.x 新版?zhèn)€別寫法要參照官方文檔調(diào)整。另外一個容易被忽略的目錄管理建議把/etc/haproxy下的配置做成haproxy.cfg主文件加多個獨(dú)立片段文件的方式主文件里用include /etc/haproxy/conf.d/*.cfg引入。這樣每個業(yè)務(wù)模塊一個文件互不干擾后續(xù)維護(hù)時找一個業(yè)務(wù)改一個文件不會在巨型單文件里反復(fù)翻找。3.2 完整配置逐行注釋版下面就是整合后的完整配置。這個配置我直接在生產(chǎn)環(huán)境里驗(yàn)證過重點(diǎn)參數(shù)都有注釋global log 127.0.0.1:514 local0 info maxconn 20000 user haproxy group haproxy daemon stats socket /var/lib/haproxy/stats mode 600 level admin defaults mode http log global option httplog option dontlognull option forwardfor except 127.0.0.0/8 option redispatch retries 3 timeout http-request 10s timeout queue 1m timeout connect 10s timeout client 1m timeout server 1m timeout http-keep-alive 10s timeout check 10s frontend web_front bind 0.0.0.0:80 default_backend web_servers # 可選啟用完整統(tǒng)計(jì)頁面 stats enable stats uri /haproxy-stats stats realm HAProxy\ Statistics stats auth admin:YourStrongPass stats refresh 30s stats admin if LOCALHOST backend web_servers balance roundrobin option httpchk GET /health http-check expect status 200 server web1 10.0.0.11:8080 weight 100 check inter 3s fall 3 rise 2 server web2 10.0.0.12:8080 weight 100 check inter 3s fall 3 rise 2 server web3 10.0.0.13:8080 backup check inter 3s fall 3 rise 2一段一段說。global 里maxconn 20000這是一臺 2C4G 服務(wù)器比較合理的估算值。硬要算的話按每個連接約 30KB 內(nèi)存值20000 個連接最多占用約 600MB 內(nèi)存加上進(jìn)程本身和系統(tǒng)開銷4G 內(nèi)存夠用。實(shí)際情況是 Web 短連接場景下同時到達(dá) 2 萬的連接數(shù)已經(jīng)是很大的并發(fā)量了初期的服務(wù)完全不需要設(shè)更高。defaults 里的參數(shù)在上面已經(jīng)全部解釋過不再重復(fù)。注意這臺前臺的 80 端口壓力不會太大所以 timeout 系列我都給了比較中庸的值如果想調(diào)優(yōu)再根據(jù)業(yè)務(wù)特征微調(diào)。frontend 段增加了一個統(tǒng)計(jì)功能。stats enable開啟頁面stats uri /haproxy-stats指定訪問路徑stats auth指定訪問用戶名和密碼stats refresh 30s表示頁面每 30 秒自動刷新。訪問http://前臺IP/haproxy-stats就能看到各后端的實(shí)時狀態(tài)包括當(dāng)前連接數(shù)、會話總數(shù)、健康狀態(tài)等。這是個非常實(shí)用的運(yùn)維頁面哪怕不做監(jiān)控系統(tǒng)集成手動看一眼也比猜強(qiáng)得多。stats admin if LOCALHOST限制只有本機(jī)訪問時才允許通過頁面修改后端狀態(tài)防止生產(chǎn)環(huán)境被人惡意操作。這里有個細(xì)節(jié)要提醒stats uri的路徑不要和業(yè)務(wù)路徑?jīng)_突。如果業(yè)務(wù)里剛好有/haproxy-stats這個路徑那就會被統(tǒng)計(jì)頁“劫持”。所以在規(guī)劃路徑時建議選一個業(yè)務(wù)永遠(yuǎn)不會用到的路徑或者直接在文件名上加個隨機(jī)字符串前綴。backend 的三臺服務(wù)器中web1、web2 是正常節(jié)點(diǎn)web3 是 backup 冷備。健康檢查用的是GET /health后端應(yīng)用需要實(shí)現(xiàn)這個接口正常時返回 200異常時返回非 200 狀態(tài)碼HAProxy 就會把節(jié)點(diǎn)自動摘除。3.3 配置校驗(yàn)、啟動與常見啟動問題配置寫完后先做語法校驗(yàn)再啟動。這個習(xí)慣極其重要不要跳過haproxy -c -f /etc/haproxy/haproxy.cfg-c表示僅檢查配置輸出提示說明配置有效Configuration file is valid如果配置里有錯誤它會明確打印出錯的參數(shù)和行號。這個校驗(yàn)操作是零成本的幾秒鐘的事卻能把大量奇葩問題攔在重啟之前。校驗(yàn)通過后有兩種啟動方式。用 systemd 管理systemctl restart haproxy systemctl status haproxy另一種方式是直接以前臺模式跑一下做快速驗(yàn)證尤其是排查啟動時的問題非常方便haproxy -f /etc/haproxy/haproxy.cfg前臺運(yùn)行的好處是日志直接打到終端啟動報錯一目了然。確認(rèn)沒問題再CtrlC停掉改用 systemd 正式啟動。啟動后立刻做三件事第一驗(yàn)證端口監(jiān)聽。ss -lntp | grep :80確認(rèn) 80 端口已被 HAProxy 監(jiān)聽。第二驗(yàn)證負(fù)載均衡。從本機(jī)用 curl 連續(xù)請求幾次后端接口觀察統(tǒng)計(jì)頁面的各節(jié)點(diǎn)請求數(shù)是否大致均勻增長。如果全跑到一臺機(jī)器上八成是算法配置有誤或健康檢查把大部分節(jié)點(diǎn)標(biāo) DOWN 了要看統(tǒng)計(jì)頁確認(rèn)。第三驗(yàn)證健康檢查。停掉其中一臺后端的 Web 服務(wù)等待約 10 秒覆蓋 inter 3s × fall 3 的周期再到 HAProxy 統(tǒng)計(jì)頁上看該節(jié)點(diǎn)應(yīng)該已經(jīng)變成紅色 DOWN 狀態(tài)。此時繼續(xù)發(fā)起請求流量只分布在剩余健康節(jié)點(diǎn)上。重啟這臺后端服務(wù)后等待 rise 2 × inter 3s 周期節(jié)點(diǎn)恢復(fù) UP流量自動重新分布。這個驗(yàn)證過程建議每次變更配置后都做一遍防止配置在“看起來正?!钡臓顟B(tài)下對故障毫無反應(yīng)。4. 用 listen 段實(shí)現(xiàn)一個最簡單的 TCP 轉(zhuǎn)發(fā)前面講的都是 HTTP 層面的負(fù)載均衡但 HAProxy 最初的優(yōu)勢其實(shí)在于 TCP 層的四層轉(zhuǎn)發(fā)能力。它的性能天花板很高很多團(tuán)隊(duì)用它來轉(zhuǎn)發(fā) MySQL、Redis、各種自研 TCP 協(xié)議流量效果同樣出色。4.1 listen 段寫法和適用場景當(dāng)場景非常簡單——一個端口進(jìn)、一個端口出、不需要復(fù)雜的路由判斷——listen 段是最低成本的方案。看一個經(jīng)典例子把本機(jī)的 3306 端口流量轉(zhuǎn)發(fā)到兩臺數(shù)據(jù)庫服務(wù)器上實(shí)現(xiàn) MySQL 的讀寫分離或高可用入口這里以讀流量均衡為例listen mysql_front bind 0.0.0.0:3306 mode tcp balance roundrobin option tcplog server db1 10.0.0.21:3306 weight 100 check inter 5s fall 3 rise 2 server db2 10.0.0.22:3306 weight 100 check inter 5s fall 3 rise 2兩行的體量就完成了一個四層 TCP 負(fù)載均衡。mode tcp告訴 HAProxy 不要解析上層協(xié)議直接把 TCP 流量按字節(jié)流透傳開銷極小。這里option tcplog替代了 HTTP 場景的 httplog記錄 TCP 層的連接信息。listen 段的寫法在結(jié)構(gòu)上的角色是 frontend 和 backend 的合體bind 定義入口server 定義目標(biāo)池平衡策略直接寫在同一個塊里。適用場景就是入口和目標(biāo)都唯一的簡單轉(zhuǎn)發(fā)如果你的場景里有多個不同入口要轉(zhuǎn)到不同目標(biāo)還是用 frontend/backend 分離結(jié)構(gòu)更清晰。4.2 tcp 模式下的健康檢查細(xì)節(jié)TCP 模式的健康檢查默認(rèn)是純 TCP 連接探測HAProxy 去嘗試連一下后端的 IP:端口能連上就算健康。對 MySQL 來說這個探測方式夠用——如果能建立 TCP 連接通常意味著 mysqld 進(jìn)程在監(jiān)聽端口但如果 MySQL 服務(wù)還在但業(yè)務(wù)層面已經(jīng)卡死純 TCP 探測就會誤判為健康。進(jìn)階做法是用更精確的業(yè)務(wù)級探測。比如 MySQL 場景可以配置option mysql-check user haproxy_check它會用指定的 MySQL 賬號連接一次后端用真實(shí)的認(rèn)證流程做健康檢查這比 TCP 端口探測可靠得多。類似的還有 Redis 的option redis-check、PostgreSQL 的option pgsql-check。各數(shù)據(jù)庫場景的健康檢查做對了之后故障切換的準(zhǔn)確率會提升一個量級。另外注意 TCP 模式下沒有option forwardfor這類 HTTP 頭操作后端的客戶端 IP 獲取方式不能在 HAProxy 層解決只能在業(yè)務(wù)應(yīng)用層自行記錄對端地址此時對端地址就是 HAProxy 的 IP無法直接還原真實(shí)客戶端 IP。如果你的業(yè)務(wù)必須拿到真實(shí)客戶端 IP建議改用 proxy protocol這個是另一個進(jìn)階話題基礎(chǔ)階段先了解有這個解決路徑即可。5. 常見問題與排查技巧實(shí)錄HAProxy 本身運(yùn)行極穩(wěn)定真正的故障大多來自配置不當(dāng)或周邊環(huán)境。下面這些問題是我實(shí)操中遇到頻率最高的每一個都有具體的現(xiàn)象和排查路徑。5.1 配置正確但啟動失敗端口權(quán)限與語法錯誤“配置文件明明校驗(yàn)過了怎么啟動還是失敗”這是我被問過最多的問題之一。校驗(yàn)通過只能說明語法沒有基本錯誤很多運(yùn)行時問題需要啟動才會暴露。最常見的是非 root 用戶綁定 80 或 443 端口失敗。Linux 約定 1024 以下的端口只有 root 才能綁定HAProxy 默認(rèn)以 haproxy 用戶運(yùn)行如果 bind 了 80 端口又沒有配置特權(quán)啟動會提示Permission denied。解決辦法有幾種最常見的是保持 bind :80 寫法然后讓 HAProxy 以 root 身份啟動后切換到 haproxy 用戶global 里的 user/group 配置會在綁定完端口后才生效也可以加setcap cap_net_bind_serviceep /usr/sbin/haproxy給可執(zhí)行文件單獨(dú)授權(quán)但這在部分系統(tǒng)上需要額外配置。最簡單穩(wěn)妥的方式是讓 HAProxy 先以 root 啟動綁定端口后再切換到低權(quán)限用戶這也是全局配置里推薦的標(biāo)準(zhǔn)姿勢。另一個坑是配置文件里殘留了 Windows 換行符\r\n。把文件從 Windows 機(jī)器上編輯后傳到服務(wù)器上HAProxy 解析到行尾的\r就會報格式錯誤或參數(shù)異常。解決方式是用sed -i s/\r$// /etc/haproxy/haproxy.cfg清洗一遍再校驗(yàn)。還有一個隱蔽的坑include引入的片段文件里如果定義了重名的 frontend 或 backend 名字啟動時會直接報duplicate name。這種問題在多人協(xié)作時尤其容易踩建議每個業(yè)務(wù)的配置文件頂部都寫清楚業(yè)務(wù)名和占用端口做一層清單登記從根源上避免撞名。5.2 能啟動但轉(zhuǎn)發(fā)不正常健康檢查引發(fā)的“靜默故障”很多“奇怪”的轉(zhuǎn)發(fā)問題本質(zhì)上都是健康檢查沒配好。最典型的場景HAProxy 啟動了端口也監(jiān)聽了但請求梭到后端全部 503 或 504。查統(tǒng)計(jì)頁往往能看到節(jié)點(diǎn)狀態(tài)是 DOWN。排查思路分三步。第一步確認(rèn)后端的 /health 接口是否真的返回 200。有的應(yīng)用把健康檢查路徑寫錯了比如后端實(shí)際是/actuator/health配置里卻寫成/health返回 404HAProxy 根據(jù)http-check expect status 200判斷為不健康節(jié)點(diǎn)被摘除流量自然全掛。第二步確認(rèn) HAProxy 到后端的網(wǎng)絡(luò)是否通。用telnet 10.0.0.11 8080手動測試。第三步確認(rèn)健康檢查超時時間是否合理。timeout check如果設(shè)得太短比如 1s而健康檢查接口本身響應(yīng)較慢例如要查數(shù)據(jù)庫狀態(tài)需要 2s那每次探測都超時節(jié)點(diǎn)會反復(fù)抖動在 UP/DOWN 之間流量時通時斷。這種抖動比完全 DOWN 更折磨人因?yàn)樗憩F(xiàn)成偶發(fā)的 502非常難定位。我自己調(diào)試這類問題最常用的伎倆是開日志看。把 rsyslog 配好然后請求幾次出問題的接口去 /var/log/haproxy.log 里看那一行日志的狀態(tài)碼和耗時基本能判斷出問題出在 HAProxy 本身還是后端。日志里如果顯示后端返回 503問題大概率在后端如果顯示連接超時問題大概率在 HAProxy 到后端這條鏈路上。5.3 流量分布不均算法選擇與節(jié)點(diǎn)狀態(tài)排查流量分布不均是另一個高頻問題。你配置了 roundrobin但統(tǒng)計(jì)頁上每個節(jié)點(diǎn)的流量卻有明顯的傾斜甚至全跑在同一臺上。第一件要查的事那臺“偏瘦”的節(jié)點(diǎn)是不是處于 DOWN 或 MAINT 狀態(tài)。MAINT 表示節(jié)點(diǎn)被管理員手動停用或正在維護(hù)它不會接任何流量觀察這個狀態(tài)首先要看是不是有人通過 stats 頁面或 socket 執(zhí)行了 disable。第二件要查的事后端節(jié)點(diǎn)是否真的在 CHECK 狀態(tài)。如果配置里漏寫了check參數(shù)HAProxy 默認(rèn)不會對這個節(jié)點(diǎn)做健康檢查但這反而會讓流量正常發(fā)過去——除非這臺機(jī)器本身都已經(jīng)宕機(jī)了。如果一臺節(jié)點(diǎn)已經(jīng)掛了但沒有配 checkHAProxy 還是會固執(zhí)地把流量往它上面發(fā)表現(xiàn)就是一部分請求超時、一部分成功。第三件事才是回顧算法本身。roundrobin 在連接數(shù)少時表現(xiàn)可能顯得“不均勻”——它按請求輪轉(zhuǎn)但如果客戶端使用了 keep-alive連接超時前會連續(xù)復(fù)用同一個后端連接長時間看統(tǒng)計(jì)頁就發(fā)現(xiàn)某些節(jié)點(diǎn)連接數(shù)特別多。這不是配置錯誤而是連接復(fù)用的正?,F(xiàn)象。如果你希望更均勻可以把模式改成leastconn或者減小timeout http-keep-alive強(qiáng)制更頻繁地建立新連接。5.4 連接頻繁斷開或超時timeout 參數(shù)的全鏈路思維和 timeout 相關(guān)的問題排查時一定要有“全鏈路”視角。一次完整請求經(jīng)過的鏈路是客戶端 ? HAProxy ? 后端。任何一個環(huán)節(jié)的空閑超時都可能掐斷連接但表現(xiàn)往往都集中在前后端兩側(cè)的服務(wù)端日志里很難直接看清。舉例說明某業(yè)務(wù)是前端頁面定期向后端輪詢數(shù)據(jù)間隔 20 秒。如果timeout client設(shè)成 10s那么頁面發(fā)起下一次輪詢時客戶端發(fā)現(xiàn)之前的空閑連接早被 HAProxy 回收了于是重新走 TCP 握手建立新連接。這個現(xiàn)象本身不是錯誤但會讓網(wǎng)絡(luò)連接數(shù)量持續(xù)增高。如果后端的應(yīng)用框架比較陳舊頻繁重連時會積累 TIME_WAIT日積月累可能導(dǎo)致端口耗盡。這類問題的排查思路是到統(tǒng)計(jì)頁看每個節(jié)點(diǎn)的活躍連接數(shù)再結(jié)合實(shí)際業(yè)務(wù)的長連接間隔反推 timeout 是否合理。還有一類和 timeout 相關(guān)的“事故現(xiàn)場”后端程序臨時卡頓導(dǎo)致某臺節(jié)點(diǎn)積壓了大量慢請求這些請求的處理時間超過了timeout serverHAProxy 直接把后端連接斷開客戶端收到 504。后端恢復(fù)后積壓的請求又瞬間全部集中到它身上引發(fā)雪崩。這種場景下合理做法是配合隊(duì)列和時間參數(shù)雙重限制在健康檢查層面提前摘除“亞健康”節(jié)點(diǎn)而不是單純調(diào)大 timeout 去硬扛。不過這個話題屬于高可用調(diào)優(yōu)范疇基礎(chǔ)階段先把每個 timeout 的語義理解到位遇到問題時能做到準(zhǔn)確定位就算達(dá)標(biāo)了。寫在最后的一點(diǎn)經(jīng)驗(yàn)這套配置跑熟之后HAProxy 在項(xiàng)目里基本就是個“透明但可靠”的存在了。不要急著上那些花哨功能先把負(fù)載均衡、健康檢查、日志、監(jiān)控頁這四個基礎(chǔ)能力用扎實(shí)日常運(yùn)維就會輕松一大截。我個人在實(shí)際操作中養(yǎng)成的習(xí)慣是每次改完配置先執(zhí)行一遍haproxy -c -f校驗(yàn)然后立刻看日志確認(rèn)新配置對流量沒有異常影響。這個習(xí)慣幫我擋掉了不少線上事故建議你也試試。下一篇文章可以聊 ACL 規(guī)則和高級路由到時候見。