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