Zuul是Netflix提供一个网管服务。
Zuul是如何工作
Zuul是一些连续filter的集合。Zuul filter中核心概念:
- Type: 定义过滤器被应用的阶段
- Execution Order:在同一个Type中,过滤器执行的顺序
- Criteria: 过滤器被执行必须满足的条件
- Action:条件满足,过滤器中将要执行的动作
Zuul会动态读取、编译、执行这些filters。
Filter之间不会直接交流,他们直接会通过RequestContext共享状态(线程安全)。
Filters是使用groovy写的,这些filter都是放在指定的目录下。
Zuule 结构主要包含三个部分:1.Filter管理(上传、激活、存储);2.Filter加载(将变更的filter加载进来);3.filter运行时。
Filter类型 | 解释 | 执行顺序 |
---|---|---|
PRE | 在请求被路由到源服务器前要执行的过滤器,例如认证、选路由、请求日志 | 1 |
ROUTING | 处理将请求发送到源服务器的过滤器 | 2 |
POST | 在响应从源服务器返回时被执行的过滤器,例如对响应增加http头、收集统计和度量、将响应以流的方式发送给客户端 | 3 |
ERROR | 上述阶段出现错误要执行的过滤器 | 4 |
正常流程是按照上面顺序执行的,如果发生错信息,直接跳转到ERROR阶段。
Filter执行顺序:
1.pre->route->post->over(正常执行流程)
2.pre->error->post(错误流程)
3.pre->route->error->post(错误流程)
4.pre->route->post->error
路由器过滤器:
1.RibbonRoutingFilter:将url路由到服务
2.SimpleHostRoutingFilter:将url路由到url地址
3.SendForwardFilter:转发(转向zuul自己)
网关获取地址的的来源:
1.eureka服务,zuul从eureka获取的服务;
2.从配置文件中获取
Zuul 2
Zuul2.0就类似一个Netty Server,执行前置filter(入站filter),使用Netty Client代理请求,
执行完后置filter(出站filter)返回response。
Filter
- 入站filter:在路由到源之前执行,例如:身份验证、路由和修饰请求。
- 终点filter:可用于返回静态响应,否则内置的ProxyEndpoint过滤器会将请求路由到源。
- 出站filter:从源获取响应后执行,例如:修饰用户的响应或添加自定义标头。
Filter分为两种:同步和异步。在循环事件中千万不要使用阻塞。如果想要在异步filter中使用阻塞,要在单独的线程池上使用,否则使用同步filter。
Zuul2.0在Zuul1.x的基础上添加了一个是否使用异步执行(是否阻塞)。
Server Modes
Zuul2.0支持的服务模式有:HTTP、HTTP2.0(需要tls)、HTTP(手动TLS)。
HTTP
在ELB HTTP监听器之后执行,该监听器终止TLS并传递XFF头部。
在没有ELB的情况下,以纯文本的形式运行,出于安全原因,需要做如下处理:
channelConfig.set(CommonChannelConfigKeys.allowProxyHeadersWhen, StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);
HTTP 2.0
ELB 不支持HTTP 2.0,如果使用HTTP 2.0,需要使用ELB TCP监听器。
Mutual TLS
ELB也不支持交互TLS,所以,你不得不使用 ELB TCP监听器,在Zuul里终止使用TLS。在该模式下,你将需要一个 TLS cert和一个客户证书的信任的存储。也需要启用代理协议代替XFF头。
Core Features
Service Discovery
Zuul可以和Eureka无缝结合,也可以与静态服务器结合或者使用其他服务发现方式。
与Eureka结合的配置:
### 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自动从Eureka获取服务列表,如果配置静态资源,按照下面的配置:
### 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
服务列表的类名用ConfigurationBasedServerList替换DiscoveryEnabledNIWSServerList。
Load Balancing
Zuul默认使用的基于区域的负载均衡。该算法是对可用实例进行轮询,并跟踪可用性区域以便弹性恢复。如果某个区域失败节点达到一定程度,就会丢弃这个区域。
用户使用自定义的负载均衡器,需要指定NFLoadBalancerClassName属性,重写DefaultClientChannelManager的getLoadBalancerClass方法。自定义的负载均衡器继承DynamicServerListLoadBalancer。
Ribbon允许用户配置负载均衡策略:RoundRobinRule、WeightedResponseTimeRule、AvailabilityFilteringRule。
连接池
Zuul2.0使用NettyClient来使用自己的连接池。Zuul2.0为每一个主机、每个循环时间创建连接池。这样可以减少线程之间上下文切换,确保入站事件和出站事件循环的完整性。
这样就可以使得整个请求在同样的线程内运行。该策略的一个副作用:如果运行多个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
# 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
状态分类 | 定义 |
---|---|
SUCCESS | 成功请求 |
SUCCESS_NOT_FOUND | 成功代理但是状态为404 |
SUCCESS_LOCAL_NOTSET | 成功请求但是没有分类 |
SUCCESS_LOCAL_NO_ROUTE | 没有找到请求的终端点 |
FAILURE_LOCAL | 本地Zuul失败 |
FAILURE_LOCAL_THROTTLED_ORIGIN_SERVER_MAXCONN | 请求超最大连接限制 |
FAILURE_LOCAL_THROTTLED_ORIGIN_CONCURRENCY | 请求超源的并发限制 |
FAILURE_LOCAL_IDLE_TIMEOUT | 请求因为idle超时失败 |
FAILURE_CLIENT_CANCELLED | 客户端取消,请求失败 |
FAILURE_CLIENT_PIPELINE_REJECT | 客户端试图发送管道HTTP请求,请求失败 |
FAILURE_CLIENT_TIMEOUT | 来自客户端的读超时 |
FAILURE_ORIGIN | 源返回失败 |
FAILURE_ORIGIN_READ_TIMEOUT | 请求源超时 |
FAILURE_ORIGIN_CONNECTIVITY | 连不到源 |
FAILURE_ORIGIN_THROTTLED | 源扼杀了请求 |
FAILURE_ORIGIN_NO_SERVERS | 没有有效的源的服务 |
FAILURE_ORIGIN_RESET_CONNECTION | 请求完成前,源复位了连接 |
设置Zuul状态:
StatusCategoryUtils.setStatusCategory(request.getContext(), ZuulStatusCategory.SUCCESS)
获取Zuul状态
StatusCategoryUtils.getStatusCategory(response)
Retries
重试机制是Netflix增强弹性的主要功能之一。下面的逻辑来确定如何重试:
Retry on errors
如果发生读超时,充值连接或者连接错误
Retry on status codes
- 如果状态码为503
- 如果状态码是配置的幂等状态,方法是:GET、HEAD或者OPTIONS
- 下列情况不重试:已经向客户端发送响应;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
调试工具,它是请求过程中,按时间排序的状态集,带nanoseconds时间戳。
可以记录 passport,可以加到头里,或者持久化保存。可以使用 channel或者 session context。
Request Attempt
通常,我们将其添加为每个响应的仅供内部使用的标头,这对于我们和我们的内部合作伙伴而言使跟踪和调试请求变得更加简单。
Origin Concurrency Protection
为了保护源和Zuul,使用并发限制,防止服务中断。
两种方法管理源并发:
##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被正确设置到过滤器的 HttpRequestMessage,也可以这样检索
String clientIp = channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get();
Gzip
Zuul出站GZipResponseFilter,将gzip传出响应。它根据内容类型,主体大小以及请求的Accept-Encoding标头是否包含gzip做出决定。
Push Messaging
Zuul2.0支持消息推送,支持Web Socket和SSE。
Authentication
Zuul推送服务器在入站推送连接时,必须要认证。自定义认证要继承PushAuthHandler,实现doAuth()抽象类。
Client Registration and Lookup
Zuul推送服务注册每个已鉴权的连接或者用户标识。
每个 Zuul推送服务使用PushConnectionRegistry 在内存里维护一个本地的全部已连接的客户端的注册信息。
单节点使用基于内存的,多节点使用一个二级的外部全局数据源。
查找特定的client步骤:1.在全局的外部存储里查找客户端连接的服务器;2.返回的服务器里的本地注册到查找实际的连接。
可以通过集成PushRegistrationHandler类,重现registerClient()。
Zuul推送使用的数据源必须有下面的特征:低读取延迟、TTL或者自动超时记录、分区、副本。例如:Redis、Cassandra、Amazon DynamoDB。
Load balancers vs WebSockets and SSE
推送连接是持久、长期存活的。其他的连接,如果一段事件不活跃,就会断开。
集群负载均衡器:1)HAProxy、Nginx、ALB来支持WebSocket代理;2)Load Balance 运行在4层,而不是7层。
Configuration Properties
Name | 描述 | 默认值 |
---|---|---|
zuul.push.registry.ttl.seconds | 全局注册的超时时间 | 1800s |
zuul.push.reconnect.dither.seconds | 每个客户端的最长连接期的随机窗口。以后重连的间隔 | 180s |
zuul.push.reconnect.dither.seconds | 服务器等待客户端关闭连接的时间,超时在服务器侧强制关闭连接 | 4s |
网友评论