概述
目录
核心特性
服务发现
负载平衡
连接池
Status 类别
重试
Retry on errors
Retry on status codes
Request Passport
Request Attemps
Origin并发保护
HTTP/2
Mutual TLS
代理协议
核心特性
服务发现
Zuul可与Eureka无缝协作,但也可配置为与静态服务器列表或我们选择的发现服务协作。
使用Eureka服务器的标准方法如下所示:
#使用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将自动从Eureka中选择服务器列表,并为api Ribbon客户端指定VIP。
要使用静态服务器列表或其他发现提供程序配置Zuul,我们必须使listOfServers属性保持最新:
#没有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
请注意,在此配置中,服务器列表类名为ConfigurationBasedServerList,而不是DiscoveryEnabledNIWSServerList。
负载平衡
默认情况下,Zuul使用Ribbon中的ZoneAwareLoadBalancer进行负载平衡。该算法是discovery中可用实例的循环,可用性区域成功跟踪可恢复性。负载平衡器将保留每个区域的统计信息,如果故障率高于可配置的阈值,则会删除一个区域。
如果要使用自己的自定义负载平衡器,可以为该Ribbob客户端命名空间设置NFLoadBalancerClassName属性,或覆盖DefaultClientChannelManager中的getLoadBalancerClass()方法。注意,我们的类应该扩展DynamicServerListLoadBalancer。
Ribbon还允许我们配置负载平衡规则。例如,我们可以将RoundRobinRule替换为WeightedResponseTimeRule、AvailabilityFilteringRule或我们自己的规则。
连接池
Zuul不使用Ribbon进行传出连接,而是使用Netty客户端使用自己的连接池。Zuul为每个主机、每个事件循环创建一个连接池。它这样做是为了减少线程之间的上下文切换,并确保入站事件循环和出站事件循环的完整性。结果是整个请求都在同一线程上运行,而不管运行它的是哪个事件循环。
此策略的一个副作用是,如果有很多Zuul实例在运行,并且每个实例中都有很多事件循环,那么到每个后端服务器的最小连接量可能会非常高。配置连接池时,请务必记住这一点。
用于调整连接池的一些有用设置及其默认值包括:
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
# 强制关闭之前,任何给定连接的最大请求量
<originName>.netty.client.maxRequestsPerConnection // default: 1000
# 每个服务器、每个事件循环的最大连接量
<originName>.netty.client.perServerWaterline // default: 4
# Netty 配置连接
<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 类别
尽管HTTP状态是通用的,但它们并没有提供很多粒度。为了获得更具体的故障模式,zuul创建了一个可能故障的枚举。
状态类别 | 定义 |
SUCCESS | 请求成功 |
SUCCESS_NOT_FOUND | 已成功代理,但状态为404 |
SUCCESS_LOCAL_NOTSET | 请求成功,但未设置StatusCategory |
SUCCESS_LOCAL_NO_ROUTE | 技术上成功,但未找到请求的路由 |
FAILURE_LOCAL | 本地Zuul故障(例如引发异常) |
FAILURE_LOCAL_THROTTLED_ORIGIN_SERVER_MAXCONN | 由于达到到源服务器的最大连接限制,请求被阻止 |
FAILURE_LOCAL_THROTTLED_ORIGIN_CONCURRENCY | 由于origin并发限制,请求被阻止 |
FAILURE_LOCAL_IDLE_TIMEOUT | 由于空闲连接超时,请求失败 |
FAILURE_CLIENT_CANCELLED | 请求失败,因为客户端已取消 |
FAILURE_CLIENT_PIPELINE_REJECT | 请求失败,因为客户端试图发送pipelined HTTP请求 |
FAILURE_CLIENT_TIMEOUT | 由于客户端读取超时,请求失败(例如,邮件正文被截断) |
FAILURE_ORIGIN | origin返回故障(即500状态) |
FAILURE_ORIGIN_READ_TIMEOUT | 对origin的请求超时 |
FAILURE_ORIGIN_CONNECTIVITY | 无法连接到origin |
FAILURE_ORIGIN_THROTTLED | Origin阻止了请求(即503状态) |
FAILURE_ORIGIN_NO_SERVERS | 找不到origin服务器的任何要连接的服务器 |
FAILURE_ORIGIN_RESET_CONNECTION | Origin在请求完成之前重置连接 |
我们可以使用StatusCategoryUtils类获取或设置状态。例如:
// set
StatusCategoryUtils.setStatusCategory(httpRequestMessage.getContext(),ZuulStatusCategory.SUCCESS);
//get
StatusCategoryUtils.getStatusCategory(context);
重试
Netflix用于恢复的关键功能之一是重试。在Zuul中对待重试并广泛使用它们。我们使用以下逻辑来确定何时重试请求:
Retry on errors
-
如果错误为读取超时,请重置连接或连接错误
Retry on status codes
-
如果状态代码为503
-
如果状态代码是可配置的幂等状态(请参见下文),并且方法是:GET、HEAD或OPTIONS之一
如果处于瞬态,我们不会重试,更具体地说:
- 如果我们已经开始将响应发送回客户端
-
如果我们丢失了任何实体块(部分缓冲或截断的实体)
相关属性:
# 设置错误代码和状态代码重试的重试限制
<originName>.ribbon.MaxAutoRetriesNextServer // default: 0
# 这是以逗号分隔的状态代码列表
zuul.retry.allowed.statuses.idempotent // default: 500
Request Passport
用于调试的最佳工具之一是请求passport。它是一个按时间顺序排列的集合,包含请求所经过的所有状态,并带有以纳秒为单位的相关时间戳。
成功请求的例子
这是一个简单的请求,它运行一些过滤器,执行一些IO,代理请求,对响应运行过滤器,然后将其写入客户端。
CurrentPassport {start_ms=1523578203359,
[+0=IN_REQ_HEADERS_RECEIVED,
+260335=FILTERS_INBOUND_START,
+310862=IN_REQ_LAST_CONTENT_RECEIVED,
+1053435=MISC_IO_START,
+2202112=MISC_IO_STOP,
+3917598=FILTERS_INBOUND_END,
+4157288=ORIGIN_CH_CONNECTING,
+4218319=ORIGIN_CONN_ACQUIRE_START,
+4443588=ORIGIN_CH_CONNECTED,
+4510115=ORIGIN_CONN_ACQUIRE_END,
+4765495=OUT_REQ_HEADERS_SENDING,
+4799545=OUT_REQ_LAST_CONTENT_SENDING,
+4820669=OUT_REQ_HEADERS_SENT,
+4822465=OUT_REQ_LAST_CONTENT_SENT,
+4830443=ORIGIN_CH_ACTIVE,
+20811792=IN_RESP_HEADERS_RECEIVED,
+20961148=FILTERS_OUTBOUND_START,
+21080107=IN_RESP_LAST_CONTENT_RECEIVED,
+21109342=ORIGIN_CH_POOL_RETURNED,
+21539032=FILTERS_OUTBOUND_END,
+21558317=OUT_RESP_HEADERS_SENDING,
+21575084=OUT_RESP_LAST_CONTENT_SENDING,
+21594236=OUT_RESP_HEADERS_SENT,
+21595122=OUT_RESP_LAST_CONTENT_SENT,
+21659271=NOW]}
超时的例子
这是超时的一个示例。这与前面的示例类似,但与outbound请求和超时事件之间的时间间隔不同。
CurrentPassport {start_ms=1523578490446,
[+0=IN_REQ_HEADERS_RECEIVED,
+139712=FILTERS_INBOUND_START,
+1364667=MISC_IO_START,
+2235393=MISC_IO_STOP,
+3686560=FILTERS_INBOUND_END,
+3823010=ORIGIN_CH_CONNECTING,
+3891023=ORIGIN_CONN_ACQUIRE_START,
+4242502=ORIGIN_CH_CONNECTED,
+4311756=ORIGIN_CONN_ACQUIRE_END,
+4401724=OUT_REQ_HEADERS_SENDING,
+4453035=OUT_REQ_HEADERS_SENT,
+4461546=ORIGIN_CH_ACTIVE,
+45004599181=ORIGIN_CH_READ_TIMEOUT,
+45004813647=FILTERS_OUTBOUND_START,
+45004920343=ORIGIN_CH_CLOSE,
+45004945985=ORIGIN_CH_CLOSE,
+45005052026=ORIGIN_CH_INACTIVE,
+45005246081=FILTERS_OUTBOUND_END,
+45005359480=OUT_RESP_HEADERS_SENDING,
+45005379978=OUT_RESP_LAST_CONTENT_SENDING,
+45005399999=OUT_RESP_HEADERS_SENT,
+45005401335=OUT_RESP_LAST_CONTENT_SENT,
+45005486729=NOW]}
失败请求的例子
这是导致异常的请求的示例。同样,它与前面的类似,但请注意重试和异常事件。
CurrentPassport {start_ms=1523578533258,
[+0=IN_REQ_HEADERS_RECEIVED,
+161428=FILTERS_INBOUND_START,
+208805=IN_REQ_LAST_CONTENT_RECEIVED,
+934637=MISC_IO_START,
+1751747=MISC_IO_STOP,
+2606657=FILTERS_INBOUND_END,
+2734497=ORIGIN_CH_CONNECTING,
+2780877=ORIGIN_CONN_ACQUIRE_START,
+3181771=ORIGIN_CH_CONNECTED,
+3272876=ORIGIN_CONN_ACQUIRE_END,
+3376958=OUT_REQ_HEADERS_SENDING,
+3405924=OUT_REQ_LAST_CONTENT_SENDING,
+3557967=ORIGIN_RETRY_START,
+3590208=ORIGIN_CH_CONNECTING,
+3633635=ORIGIN_CONN_ACQUIRE_START,
+3663060=ORIGIN_CH_CLOSE,
+3664703=OUT_REQ_HEADERS_ERROR_SENDING,
+3674443=OUT_REQ_LAST_CONTENT_ERROR_SENDING,
+3681289=ORIGIN_CH_ACTIVE,
+3706176=ORIGIN_CH_INACTIVE,
+4022445=ORIGIN_CH_CONNECTED,
+4072050=ORIGIN_CONN_ACQUIRE_END,
+4144471=OUT_REQ_HEADERS_SENDING,
+4171228=OUT_REQ_LAST_CONTENT_SENDING,
+4186672=OUT_REQ_HEADERS_SENT,
+4187543=OUT_REQ_LAST_CONTENT_SENT,
+4192830=ORIGIN_CH_ACTIVE,
+4273401=ORIGIN_CH_EXCEPTION,
+4274124=ORIGIN_CH_EXCEPTION,
+4303020=ORIGIN_CH_IO_EX,
+4537569=FILTERS_OUTBOUND_START,
+4646348=ORIGIN_CH_CLOSE,
+4748074=ORIGIN_CH_INACTIVE,
+4957163=FILTERS_OUTBOUND_END,
+4968947=OUT_RESP_HEADERS_SENDING,
+4985532=OUT_RESP_LAST_CONTENT_SENDING,
+5003476=OUT_RESP_HEADERS_SENT,
+5004610=OUT_RESP_LAST_CONTENT_SENT,
+5062221=NOW]}
我们可以登录passport,将其添加到标头中,或将其发送到持久性存储以供以后调试。要将其从请求中删除,可以使用通道或会话上下文。例如:
// from channel
CurrentPassport passport = CurrentPassport.fromChannel(channel);
// from context
CurrentPassport passport = CurrentPassport.fromSessionContext(context);
Request Attemps
另一个非常有用的调试特性是跟踪Zuul发出的请求尝试。我们通常在每个响应上都将其作为一个仅限内部的头添加,这使得跟踪和调试请求对于我们和我们的内部合作伙伴来说更加简单。
成功请求的例子
[{"status":200,"duration":192,"attempt":1,"region":"us-east-1","asg":"simulator-v154","instanceId":"i-061db2c67b2b3820c","vip":"simulator.netflix.net:7001"}]
失败请求的例子
[{"status":503,"duration":142,"attempt":1,"error":"ORIGIN_SERVICE_UNAVAILABLE","exceptionType":"OutboundException","region":"us-east-1","asg":"simulator-v154","instanceId":"i-061db2c67b2b3820c","vip":"simulator.netflix.net:7001"},
{"status":503,"duration":147,"attempt":2,"error":"ORIGIN_SERVICE_UNAVAILABLE","exceptionType":"OutboundException","region":"us-east-1","asg":"simulator-v154","instanceId":"i-061db2c67b2b3820c","vip":"simulator.netflix.net:7001"}]
我们可以从outbound筛选器上的会话上下文获取请求尝试。例如:
// from context
RequestAttempts attempts = RequestAttempts.getFromSessionContext(context);
Origin并发保护
有时,origin可能会遇到麻烦,特别是当请求量超过其容量时。考虑到我们是一个代理,一个坏的origin可能会通过饱和我们的连接和内存而影响其他origin。为了保护origins和Zuul,我们设置了并发限制,以帮助平滑服务中断。
我们有两种管理origin并发的方法:
Overall Origin Concurrency(全部origin并发)
zuul.origin.<originName>.concurrency.max.requests // default: 200
zuul.origin.<originName>.concurrency.protect.enabled // default: true
Per Server Concurrency(每个服务器并发)
<originName>.ribbon.MaxConnectionsPerHost // default: 50
如果一个origin超过了总体并发或每主机并发,Zuul将向客户端返回503。
HTTP/2
Zuul可以在HTTP/2模式下运行,在这种模式下,它需要SSL证书,如果要在ELB后面运行Zuul,则必须使用TCP侦听器。
case HTTP2:
sslConfig = ServerSslConfig.withDefaultCiphers(
loadFromResources("server.cert"),
loadFromResources("server.key"),
WWW_PROTOCOLS);
channelConfig.set(CommonChannelConfigKeys.allowProxyHeadersWhen, StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);
channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);
channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);
channelConfig.set(CommonChannelConfigKeys.serverSslConfig, sslConfig);
channelConfig.set(CommonChannelConfigKeys.sslContextFactory, new BaseSslContextFactory(registry, sslConfig));
addHttp2DefaultConfig(channelConfig, mainListenAddressName);
addrsToChannels.put(
new NamedSocketAddress("http2", sockAddr),
new Http2SslChannelInitializer(
metricId, channelConfig, channelDependencies, clientChannels));
logAddrConfigured(sockAddr, sslConfig);
break;
相关HTTP/2属性:
server.http2.max.concurrent.streams // default: 100
server.http2.initialwindowsize // default: 5242880
server.http2.maxheadertablesize // default: 65536
server.http2.maxheaderlistsize // default: 32768
Mutual TLS
Zuul可以在Mutual TLS模式下运行。在这种模式下,我们必须同时拥有SSL证书和传入证书的信任存储。与HTTP/2一样,我们必须在ELB的TCP侦听器后面运行它。
case HTTP_MUTUAL_TLS:
sslConfig = new ServerSslConfig(
WWW_PROTOCOLS,
ServerSslConfig.getDefaultCiphers(),
loadFromResources("server.cert"),
loadFromResources("server.key"),
ClientAuth.REQUIRE,
loadFromResources("truststore.jks"),
loadFromResources("truststore.key"),
false);
channelConfig.set(CommonChannelConfigKeys.allowProxyHeadersWhen, StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);
channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);
channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);
channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, true);
channelConfig.set(CommonChannelConfigKeys.serverSslConfig, sslConfig);
channelConfig.set(CommonChannelConfigKeys.sslContextFactory, new BaseSslContextFactory(registry, sslConfig));
addrsToChannels.put(
new NamedSocketAddress("http_mtls", sockAddr),
new Http1MutualSslChannelInitializer(
metricId, channelConfig, channelDependencies, clientChannels));
logAddrConfigured(sockAddr, sslConfig);
break;
代理协议
使用TCP侦听器时,Proxy protocol(代理协议)是一项重要功能,可以使用以下服务器配置属性在Zuul中启用:
// 剥离XFF头,因为我们不再信任它们
channelConfig.set(CommonChannelConfigKeys.allowProxyHeadersWhen, StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);
// 首选代理协议(如果可用)
channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);
// 启用代理协议
channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, true);
客户端IP将在筛选器中的HttpRequestMessage上正确设置,也可以直接从通道中检索:
String clientIp = channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get();
最后
以上就是漂亮心情为你收集整理的Spring Cloud Zuul(2)核心特性的全部内容,希望文章能够帮你解决Spring Cloud Zuul(2)核心特性所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复