Zuul是Netflix提供一個(gè)網(wǎng)管服務(wù)。
Zuul是如何工作
Zuul是一些連續(xù)filter的集合。Zuul filter中核心概念:
- Type: 定義過濾器被應(yīng)用的階段
- Execution Order:在同一個(gè)Type中,過濾器執(zhí)行的順序
- Criteria: 過濾器被執(zhí)行必須滿足的條件
- Action:條件滿足咳燕,過濾器中將要執(zhí)行的動(dòng)作
Zuul會(huì)動(dòng)態(tài)讀取冷离、編譯上遥、執(zhí)行這些filters。
Filter之間不會(huì)直接交流溃肪,他們直接會(huì)通過RequestContext共享狀態(tài)(線程安全)免胃。
Filters是使用groovy寫的,這些filter都是放在指定的目錄下乍惊。
Zuule 結(jié)構(gòu)主要包含三個(gè)部分:1.Filter管理(上傳杜秸、激活、存儲(chǔ));2.Filter加載(將變更的filter加載進(jìn)來)润绎;3.filter運(yùn)行時(shí)撬碟。
Filter類型 | 解釋 | 執(zhí)行順序 |
---|---|---|
PRE | 在請求被路由到源服務(wù)器前要執(zhí)行的過濾器,例如認(rèn)證莉撇、選路由呢蛤、請求日志 | 1 |
ROUTING | 處理將請求發(fā)送到源服務(wù)器的過濾器 | 2 |
POST | 在響應(yīng)從源服務(wù)器返回時(shí)被執(zhí)行的過濾器,例如對(duì)響應(yīng)增加http頭、收集統(tǒng)計(jì)和度量棍郎、將響應(yīng)以流的方式發(fā)送給客戶端 | 3 |
ERROR | 上述階段出現(xiàn)錯(cuò)誤要執(zhí)行的過濾器 | 4 |
正常流程是按照上面順序執(zhí)行的其障,如果發(fā)生錯(cuò)信息,直接跳轉(zhuǎn)到ERROR階段涂佃。
Filter執(zhí)行順序:
1.pre->route->post->over(正常執(zhí)行流程)
2.pre->error->post(錯(cuò)誤流程)
3.pre->route->error->post(錯(cuò)誤流程)
4.pre->route->post->error
路由器過濾器:
1.RibbonRoutingFilter:將url路由到服務(wù)
2.SimpleHostRoutingFilter:將url路由到url地址
3.SendForwardFilter:轉(zhuǎn)發(fā)(轉(zhuǎn)向zuul自己)
網(wǎng)關(guān)獲取地址的的來源:
1.eureka服務(wù),zuul從eureka獲取的服務(wù);
2.從配置文件中獲取
Zuul 2
Zuul2.0就類似一個(gè)Netty Server励翼,執(zhí)行前置filter(入站filter),使用Netty Client代理請求,
執(zhí)行完后置filter(出站filter)返回response辜荠。
Filter
- 入站filter:在路由到源之前執(zhí)行汽抚,例如:身份驗(yàn)證、路由和修飾請求伯病。
- 終點(diǎn)filter:可用于返回靜態(tài)響應(yīng)发侵,否則內(nèi)置的ProxyEndpoint過濾器會(huì)將請求路由到源训桶。
- 出站filter:從源獲取響應(yīng)后執(zhí)行驮履,例如:修飾用戶的響應(yīng)或添加自定義標(biāo)頭苦锨。
Filter分為兩種:同步和異步。在循環(huán)事件中千萬不要使用阻塞药磺。如果想要在異步filter中使用阻塞告组,要在單獨(dú)的線程池上使用,否則使用同步filter与涡。
Zuul2.0在Zuul1.x的基礎(chǔ)上添加了一個(gè)是否使用異步執(zhí)行(是否阻塞)惹谐。
Server Modes
Zuul2.0支持的服務(wù)模式有:HTTP持偏、HTTP2.0(需要tls)、HTTP(手動(dòng)TLS)氨肌。
HTTP
在ELB HTTP監(jiān)聽器之后執(zhí)行鸿秆,該監(jiān)聽器終止TLS并傳遞XFF頭部。
在沒有ELB的情況下怎囚,以純文本的形式運(yùn)行卿叽,出于安全原因,需要做如下處理:
channelConfig.set(CommonChannelConfigKeys.allowProxyHeadersWhen, StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);
HTTP 2.0
ELB 不支持HTTP 2.0,如果使用HTTP 2.0恳守,需要使用ELB TCP監(jiān)聽器考婴。
Mutual TLS
ELB也不支持交互TLS,所以催烘,你不得不使用 ELB TCP監(jiān)聽器沥阱,在Zuul里終止使用TLS。在該模式下伊群,你將需要一個(gè) TLS cert和一個(gè)客戶證書的信任的存儲(chǔ)考杉。也需要啟用代理協(xié)議代替XFF頭。
Core Features
Service Discovery
Zuul可以和Eureka無縫結(jié)合舰始,也可以與靜態(tài)服務(wù)器結(jié)合或者使用其他服務(wù)發(fā)現(xiàn)方式崇棠。
與Eureka結(jié)合的配置:
### Load balancing backends with Eureka
eureka.shouldUseDns=true
eureka.eurekaServer.context=discovery/v2
eureka.eurekaServer.domainName=discovery${environment}.netflix.net
eureka.eurekaServer.gzipContent=true
eureka.serviceUrl.default=http://${region}.${eureka.eurekaServer.domainName}:7001/${eureka.eurekaServer.context}
api.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
api.ribbon.DeploymentContextBasedVipAddresses=api-test.netflix.net:7001
需要指定Eureka的地址,Zuul自動(dòng)從Eureka獲取服務(wù)列表丸卷,如果配置靜態(tài)資源枕稀,按照下面的配置:
### Load balancing backends without Eureka
eureka.shouldFetchRegistry=false
api.ribbon.listOfServers=100.66.23.88:7001,100.65.155.22:7001
api.ribbon.client.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList
api.ribbon.DeploymentContextBasedVipAddresses=api-test.netflix.net:7001
服務(wù)列表的類名用ConfigurationBasedServerList替換DiscoveryEnabledNIWSServerList。
Load Balancing
Zuul默認(rèn)使用的基于區(qū)域的負(fù)載均衡谜嫉。該算法是對(duì)可用實(shí)例進(jìn)行輪詢萎坷,并跟蹤可用性區(qū)域以便彈性恢復(fù)。如果某個(gè)區(qū)域失敗節(jié)點(diǎn)達(dá)到一定程度沐兰,就會(huì)丟棄這個(gè)區(qū)域食铐。
用戶使用自定義的負(fù)載均衡器,需要指定NFLoadBalancerClassName屬性僧鲁,重寫DefaultClientChannelManager的getLoadBalancerClass方法。自定義的負(fù)載均衡器繼承DynamicServerListLoadBalancer象泵。
Ribbon允許用戶配置負(fù)載均衡策略:RoundRobinRule寞秃、WeightedResponseTimeRule、AvailabilityFilteringRule偶惠。
連接池
Zuul2.0使用NettyClient來使用自己的連接池春寿。Zuul2.0為每一個(gè)主機(jī)、每個(gè)循環(huán)時(shí)間創(chuàng)建連接池忽孽。這樣可以減少線程之間上下文切換绑改,確保入站事件和出站事件循環(huán)的完整性谢床。
這樣就可以使得整個(gè)請求在同樣的線程內(nèi)運(yùn)行。該策略的一個(gè)副作用:如果運(yùn)行多個(gè)Zuul實(shí)例厘线,每個(gè)實(shí)例有很多事件循環(huán)识腿,到每個(gè)后端服務(wù)的連接會(huì)很多。
### Ribbon Client Config Properties
<originName>.ribbon.ConnectTimeout // default: 500 (ms)
<originName>.ribbon.ReadTimeout // default: 90000 (ms)
<originName>.ribbon.MaxConnectionsPerHost // default: 50
<originName>.ribbon.ConnIdleEvictTimeMilliSeconds // default: 60000 (ms)
<originName>.ribbon.ReceiveBufferSize // default: 32 * 1024
<originName>.ribbon.SendBufferSize // default: 32 * 1024
<originName>.ribbon.UseIPAddrForServer // default: true
###Zuul Properties
# Max amount of requests any given connection will have before forcing a close
<originName>.netty.client.maxRequestsPerConnection // default: 1000
# Max amount of connection per server, per event loop
<originName>.netty.client.perServerWaterline // default: 4
# Netty configuration connection
<originName>.netty.client.TcpKeepAlive // default: false
<originName>.netty.client.TcpNoDelay // default: false
<originName>.netty.client.WriteBufferHighWaterMark // default: 32 * 1024
<originName>.netty.client.WriteBufferLowWaterMark // default: 8 * 1024
<originName>.netty.client.AutoRead // default: false
Status Categories
狀態(tài)分類 | 定義 |
---|---|
SUCCESS | 成功請求 |
SUCCESS_NOT_FOUND | 成功代理但是狀態(tài)為404 |
SUCCESS_LOCAL_NOTSET | 成功請求但是沒有分類 |
SUCCESS_LOCAL_NO_ROUTE | 沒有找到請求的終端點(diǎn) |
FAILURE_LOCAL | 本地Zuul失敗 |
FAILURE_LOCAL_THROTTLED_ORIGIN_SERVER_MAXCONN | 請求超最大連接限制 |
FAILURE_LOCAL_THROTTLED_ORIGIN_CONCURRENCY | 請求超源的并發(fā)限制 |
FAILURE_LOCAL_IDLE_TIMEOUT | 請求因?yàn)閕dle超時(shí)失敗 |
FAILURE_CLIENT_CANCELLED | 客戶端取消造壮,請求失敗 |
FAILURE_CLIENT_PIPELINE_REJECT | 客戶端試圖發(fā)送管道HTTP請求渡讼,請求失敗 |
FAILURE_CLIENT_TIMEOUT | 來自客戶端的讀超時(shí) |
FAILURE_ORIGIN | 源返回失敗 |
FAILURE_ORIGIN_READ_TIMEOUT | 請求源超時(shí) |
FAILURE_ORIGIN_CONNECTIVITY | 連不到源 |
FAILURE_ORIGIN_THROTTLED | 源扼殺了請求 |
FAILURE_ORIGIN_NO_SERVERS | 沒有有效的源的服務(wù) |
FAILURE_ORIGIN_RESET_CONNECTION | 請求完成前,源復(fù)位了連接 |
設(shè)置Zuul狀態(tài):
StatusCategoryUtils.setStatusCategory(request.getContext(), ZuulStatusCategory.SUCCESS)
獲取Zuul狀態(tài)
StatusCategoryUtils.getStatusCategory(response)
Retries
重試機(jī)制是Netflix增強(qiáng)彈性的主要功能之一耳璧。下面的邏輯來確定如何重試:
Retry on errors
如果發(fā)生讀超時(shí)成箫,充值連接或者連接錯(cuò)誤
Retry on status codes
- 如果狀態(tài)碼為503
- 如果狀態(tài)碼是配置的冪等狀態(tài),方法是:GET、HEAD或者OPTIONS
- 下列情況不重試:已經(jīng)向客戶端發(fā)送響應(yīng);body不完整旨枯。
# Sets a retry limit for both error and status code retries
<originName>.ribbon.MaxAutoRetriesNextServer // default: 0
# This is a comma-delimited list of status codes
zuul.retry.allowed.statuses.idempotent // default: 500
Request Password
調(diào)試工具蹬昌,它是請求過程中,按時(shí)間排序的狀態(tài)集攀隔,帶nanoseconds時(shí)間戳皂贩。
可以記錄 passport,可以加到頭里竞慢,或者持久化保存先紫。可以使用 channel或者 session context筹煮。
Request Attempt
通常遮精,我們將其添加為每個(gè)響應(yīng)的僅供內(nèi)部使用的標(biāo)頭,這對(duì)于我們和我們的內(nèi)部合作伙伴而言使跟蹤和調(diào)試請求變得更加簡單败潦。
Origin Concurrency Protection
為了保護(hù)源和Zuul本冲,使用并發(fā)限制,防止服務(wù)中斷劫扒。
兩種方法管理源并發(fā):
##Overall Origin Concurrency
zuul.origin.<originName>.concurrency.max.requests // default: 200
zuul.origin.<originName>.concurrency.protect.enabled // default: true
##Per Server Concurrency
<originName>.ribbon.MaxConnectionsPerHost // default: 50
HTTP/2
server.http2.max.concurrent.streams // default: 100
server.http2.initialwindowsize // default: 5242880
server.http2.maxheadertablesize // default: 65536
server.http2.maxheaderlistsize // default: 32768
Proxy Protocol
//strip XFF headers since we can no longer trust them
channelConfig.set(CommonChannelConfigKeys.allowProxyHeadersWhen, StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);
// prefer proxy protocol when available
channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);
// enable proxy protocol
channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, true);
//客戶端IP被正確設(shè)置到過濾器的 HttpRequestMessage檬洞,也可以這樣檢索
String clientIp = channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get();
Gzip
Zuul出站GZipResponseFilter,將gzip傳出響應(yīng)沟饥。它根據(jù)內(nèi)容類型添怔,主體大小以及請求的Accept-Encoding標(biāo)頭是否包含gzip做出決定。
Push Messaging
Zuul2.0支持消息推送贤旷,支持Web Socket和SSE广料。
Authentication
Zuul推送服務(wù)器在入站推送連接時(shí),必須要認(rèn)證幼驶。自定義認(rèn)證要繼承PushAuthHandler艾杏,實(shí)現(xiàn)doAuth()抽象類。
Client Registration and Lookup
Zuul推送服務(wù)注冊每個(gè)已鑒權(quán)的連接或者用戶標(biāo)識(shí)盅藻。
每個(gè) Zuul推送服務(wù)使用PushConnectionRegistry 在內(nèi)存里維護(hù)一個(gè)本地的全部已連接的客戶端的注冊信息购桑。
單節(jié)點(diǎn)使用基于內(nèi)存的畅铭,多節(jié)點(diǎn)使用一個(gè)二級(jí)的外部全局?jǐn)?shù)據(jù)源。
查找特定的client步驟:1.在全局的外部存儲(chǔ)里查找客戶端連接的服務(wù)器;2.返回的服務(wù)器里的本地注冊到查找實(shí)際的連接勃蜘。
可以通過集成PushRegistrationHandler類硕噩,重現(xiàn)registerClient()。
Zuul推送使用的數(shù)據(jù)源必須有下面的特征:低讀取延遲元旬、TTL或者自動(dòng)超時(shí)記錄榴徐、分區(qū)、副本匀归。例如:Redis坑资、Cassandra、Amazon DynamoDB穆端。
Load balancers vs WebSockets and SSE
推送連接是持久袱贮、長期存活的。其他的連接体啰,如果一段事件不活躍攒巍,就會(huì)斷開。
集群負(fù)載均衡器:1)HAProxy荒勇、Nginx柒莉、ALB來支持WebSocket代理;2)Load Balance 運(yùn)行在4層沽翔,而不是7層兢孝。
Configuration Properties
Name | 描述 | 默認(rèn)值 |
---|---|---|
zuul.push.registry.ttl.seconds | 全局注冊的超時(shí)時(shí)間 | 1800s |
zuul.push.reconnect.dither.seconds | 每個(gè)客戶端的最長連接期的隨機(jī)窗口。以后重連的間隔 | 180s |
zuul.push.reconnect.dither.seconds | 服務(wù)器等待客戶端關(guān)閉連接的時(shí)間仅偎,超時(shí)在服務(wù)器側(cè)強(qiáng)制關(guān)閉連接 | 4s |