Zuul2.1文档

What is Zuul?

Zuul是从设备和web站点来的所有访问Netflix 流程序的请求的前门。作为一个边缘服务程序,Zuul带来动态路由、监视、弹性和安全能力。它也有能力把请求路由到多个适当的 Amazon Auto Scaling Groups。

Why did we build Zuul?

Netflix API流量的数量和多样性,有时候导致快速产生问题而没有报警。我们需要一个系统,它允许我们为应对这些情况而快速改变行为。
Zuul使用很多不同类型的过滤器,可以快速灵活地把功能应用到我们的边缘服务。这些过滤器帮助我们执行下列功能:
*认证和安全-为每个需要的资源做识别认证,拒绝不安全的请求
*洞察和监控-跟踪有意义的数据,在边缘做统计,以便准确了解产品的运行情况
*动态路由-把请求动态路由到不同的后端集群
*压力测试-逐渐增加集群的流量,来评估性能
*甩负荷-为每种请求分配容量,拒绝超限的请求
*静态响应处理-在边缘直接生成一些响应而不访问内部集群
*多区域弹性-跟 AWS区域做路由,让ELB的使用更多样化,让边缘接近成员

How We Use Zuul At Netflix

Alt

Getting Started 2.0

Maven的例子:

<dependency>
    <groupId>com.netflix.zuul</groupId>
    <artifactId>zuul-core</artifactId>
    <version>2.1.2</version>
</dependency>

Gradle:

compile "com.netflix.zuul:zuul-core:2.1.2"

How It Works 2.0

Alt

Architectural Overview

Zuul 2.0是一个 Netty服务器,运行 inbound过滤器,然后使用一个Netty 客户端代理这些请求,然后经过 outbound过滤器返回响应。

Filters

Zuul的过滤器是处理业务逻辑的核心。他们有权力做大范围的的做,能在请求-响应的生存期的不同部分内运行。
*输入过滤-在路由前执行,可以做认证、路由、装饰等类似的工作
*端过滤-能返回静态响应,否则内建的 ProxyEndpoint过滤器讲把请求路由到源
*输出过滤-获得响应以后执行,能用来测量、装饰响应或者增加自定义头。
有两种类型的过滤器:同步和异步。因为我们在事件循环上运行,在过滤器里不要阻塞。如果你需要阻塞,就在异步过滤器里做,使用一个单独的线程池;否则,可以使用同步过滤器。

Server Configuration

Server Modes

当前支持的服务器模式是:
*HTTP
*HTTP/2 (requires TLS)
*HTTP - Mutual TLS
你可以在参考 sample appliction看该怎么配置。通过修改 SERVER_TYPE变量,可以在不同模式运行。

HTTP

该模式打算运行在ELB HTTP监听器后面,它结束TLS,传递 XFF头。
如果你想运行 plaintext模式,没有任何 ELB面向你,出于安全考虑,你可能想去掉代理头。可以这样设置:

channelConfig.set(CommonChannelConfigKeys.allowProxyHeadersWhen, StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);

HTTP/2

ELB不支持 HTTP/2。所以,如果你想使用 HTTP/2,你可以使用 ELB TCP监听器,在Zuul里终止该协议。 HTTP/2配置需要一个 SSL cert,启用代理协议代替XFF头。
如果你终止 HTTP/2,使用ALB,可以使用上面的配置。

Mutual TLS

ELB也不支持交互TLS,所以,你不得不使用 ELB TCP监听器,在Zuul里终止使用TLS。在该模式下,你将需要一个 TLS cert和一个客户证书的信任的存储。也需要启用代理协议代替XFF头。

Filters

过滤器是 Zuul功能的核心。他们负责程序的业务逻辑,执行各种任务。
*Type: 过滤器被应用前,常要被定义,可以是任何字符串
*Async: 过滤器是同步的还是异步的,通常意味着你需要使用外部线程或者内部的
*Execution Order: 在Type中应用,如果有多个过滤器时的执行顺序
*Criteria: 过滤器的执行条件
*Action: 条件得到满足时,执行的动作
Zuul提供的框架可以动态读、编译和运行过滤器。过滤器之间不直接通信-通过每个请求里的RequestContext共享状态。
过滤器使用 Groovy语言写的,所以,Zuul支持任何基于JVM的语言。每一个过滤器的源码写到Zuul服务的特定目录下,周期性的轮询,看是否有变化。从磁盘读入修改后的过滤器,在运行的服务里动态编译,处理随后的每个请求时,都会被Zuul调

Incoming

输入过滤器在请求被代理前执行。大部分业务逻辑一般在这时候执行。例如:认证、动态路由、限速、 DDoS保护、指标。

Endpoint

端过滤器基于输入过滤器的执行情况处理请求。 Zuul的内建的 ProxyEndpoint过滤器负责代理请求访问后端服务,所以,这类过滤器一般用来返回静态内容。例如,健康检查响应,静态错误响应、404响应等。

Outgoing

输出过滤器处理接收后端返回后的动作。一般用来完善响应、增加指标。例如:保存统计信息、增加或者删除标准头,给实时流系统发数据、压缩。

Async

过滤器可以同步或者异步执行。如果过滤器不做很多工作,不阻塞,可以通过扩展 HttpInboundSyncFilter或者 HttpOutboundSyncFilter安全地使用同步过滤器。
如果需要从其他服务或者缓存取数据,或者其他比较重的计算。这时候,你不要阻塞Netty线程,应该使用异步过滤器,它返回一个 Observable包装的响应。此时,你应该扩展 HttpInboundFilter或者 HttpOutboundFilter。

Extracting Body Content

缺省地,Zuul不缓存body 的内容,在 body被接收之前,就把接收到的头发给源了。过滤器依赖头数据时,这样做是高效而理想的。如果你想在 inbound或者 outbound过滤器了提取请求或者响应的body,需要明确地告诉Zull缓存body。你可以在过滤器里重写needsBodyBuffered()方法:

    @Override
    boolean needsBodyBuffered(HttpResponseMessage input) {
        return true
    }

Useful Filters

sample app里有一些有用的过滤器。

Sample Filters

*DebugRequest-查找一个query param,给请求增加一个额外的调试日志
*Healthcheck-返回200的简单的静态端点过滤器,如果启动正确
*ZuulResponseFilter-增加路由细节、请求执行、状态和错误原因的信息头

Core Filters

*GzipResponseFilter-打开gzip响应输出
*SurgicalDebugFilter-调试的时候,可以把特定的请求路由到其他机器

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自动选择服务列表,让Ribbon客户端使用特定的VIP。
想让Zuul使用静态服务列表或者其他发现提供者,可以配置 listOfServers属性:

### 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缺省使用 Ribbon的 ZoneAwareLoadBalancer做负载均衡。采用轮询有效实例的算法,跟踪有效zone的成功。负载均衡器持续统计每个 zone,如果失败率超过配置的阈值,将去掉该zone。
如果想自定义负载均衡,可以设置 Ribbon客户端名称空间的 NFLoadBalancerClassName属性,或者重写 DefaultClientChannelManager的getLoadBalancerClass()方法。注意,你的类应该扩展 DynamicServerListLoadBalancer。
Ribbon也允许你配置负载均衡规则。例如,RoundRobinRule 可以设置为 WeightedResponseTimeRule、 AvailabilityFilteringRule或者你自己的规则。
你可以在here找到细节。

Connection Pooling

Zuul不使用 Ribbon建立流出的连接,而使用Netty客户端创建了自己的连接池。 Zuul为每个主机,每个事件循环建立连接池。这样做,可以减少线程间的上下文切换,确保inbound事件循环和outbound事件循环的完整性。结果是整个请求在同样的线程内运行,而不管哪个事件循环运行它。
该策略的一个副作用是,如果运行很多Zuul实例,每个实例有很多事件循环,到每个后端服务的连接会很多。配置连接池时,要注意这一点。
连接池的一些有用的设置,和他们的默认值是:

**Ribbon Client Config Properties**
<originName>.ribbon.ConnectionTimeout                // default: 500 (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

连接池输出很多指标,如果想采集,看一看 Spectator注册。

Status Categories

HTTP状态是通用的,不提供很多粒度。为了更具体的失败模式,我们增加了一个可能失败的枚举。

StatusCategoryDefinition
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请求超源的并发限制
FAILURE_LOCAL_IDLE_TIMEOUT请求因为idle超时失败
FAILURE_CLIENT_CANCELLED客户端取消,请求失败
FAILURE_CLIENT_PIPELINE_REJECT客户端试图发送管道HTTP请求,请求失败
FAILURE_CLIENT_TIMEOUT来自客户端的读超时(例如POST body被截断),请求失败
FAILURE_ORIGIN源返回失败(例如状态500)
FAILURE_ORIGIN_READ_TIMEOUT请求源超时
FAILURE_ORIGIN_CONNECTIVITY连不到源
FAILURE_ORIGIN_THROTTLED源扼杀了请求(例如状态503)
FAILURE_ORIGIN_NO_SERVERS没有有效的源的服务
FAILURE_ORIGIN_RESET_CONNECTION请求完成前,源复位了连接

可以使用 StatusCategoryUtils类获取或者设置状态:

// set
StatusCategoryUtils.setStatusCategory(request.getContext(), ZuulStatusCategory.SUCCESS)
// get
StatusCategoryUtils.getStatusCategory(response)

Retries

一个关键特性是重试。Zuul的重试是可扩展的。当重试一个请求的时候,使用下面的逻辑做决定:

Retry on errors

*如果发生读超时错误,就复位连接或者连接错误

Retry on status codes

*如果状态码是503
*如果状态码是配置的幂等状态(见下面),并且method是下列之一:GET、HEAD或者OPTIONS
在一个短暂的状态时不重试,特别是:
*已经开始向客户发送响应
*body不完整(只缓存了部分或者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 Passport

这是我们的一个调试的好工具。它是请求过程中,按时间排序的状态集,带nanoseconds时间戳。

可以记录 passport,可以加到头里,或者持久化保存。可以使用 channel或者 session context。

// from channel
CurrentPassport passport = CurrentPassport.fromChannel(channel);
// from context
CurrentPassport passport = CurrentPassport.fromSessionContext(context);

Request Attempts

另一个有用的调试特性是跟踪请求尝试。一般把它加到每个响应的内部的头里。

Example of successful request

[{"status":200,"duration":192,"attempt":1,"region":"us-east-1","asg":"simulator-v154","instanceId":"i-061db2c67b2b3820c","vip":"simulator.netflix.net:7001"}]

Example of failed request

[{"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过滤器里,从session 上下文获得请求尝试:

// from context
RequestAttempts attempts = RequestAttempts.getFromSessionContext(context);

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

如果超限,Zuul向客户端返回503。

HTTP/2

Zuul能在 HTTP/2模式下运行。此时,它需要一个SSL证书,如果Zuul在ELB的后面,需要使用 TCP监听器。
相关属性:

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能运行在交互TLS模式下。此时,需要SSL证书,也要保存发来的证书。像 HTTP/2那样,运行在ELB的TCP监听器后面。

Proxy Protocol

使用TCP监听器时,代理协议是一个重要特性,使用下面的配置打开:

// 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处理响应。
根据内容类型、body大小和请求头Accept-Encoding是否包含gzip 做决定。

Push Messaging

Zulu 2.0支持推送消息-从服务器给客户发消息。支持两种协议, WebSockets和SSE。
sample app演示了两种推送。

Authentication

Zuul推送服务必须对每个接入的连接做鉴权。可以自定义鉴权。通过扩展抽象类 PushAuthHandler,实现它的方法doAuth() 。请参考SamplePushAuthHandler

Client Registration and Lookup

鉴权完成以后, Zuul推送服务注册每个已鉴权的连接或者用户标识,这样可以知道消息推给了谁。实现 PushUserAuth接口,如果鉴权成功,doAuth()返回它的一个实例。参考例子SamplePushUserAuth
每个 Zuul推送服务使用PushConnectionRegistry 在内存里维护一个本地的全部已连接的客户端的注册信息。对于单实例的推送,在本地内存注册已经足够了。如果是多节点的推送集群,需要一个二级的外部全局数据源。此时,查找一个客户端需要两步。首先在全局的外部存储里查找客户端连接的推送服务,然后在返回的服务里的本地注册里查找实际的连接。
可以通过扩展 PushRegistrationHandler,重载registerClient()方法,来整合外部的全局注册。 Zuul推送允许使用任何数据源做全局注册,数据源最好支持下列属性
*Low read latency-低读延迟
*TTL or automatic record expiry of some sort-TTL或其他的自动超时机制
*Sharding-分区
*Replication-复制
有这些特性意味着你的推送集群能水平扩展到数百万的连接。 Redis、 Cassandra和Amazon DynamoDB都是比较好的选择。
例子 SampleWebSocketPushRegistrationHandlerSampleSSEPushRegistrationHandler显示了怎么在推送服务里整合 WebSocket和 SSE连接。

Accepting new Push connection

SampleWebSocketPushChannelInitializerSampleSSEPushChannelInitializer演示了如何设置 Netty管道来接收 WebSocket和 SSE连接。这些类基于使用的协议为每个连接设置鉴权和注册处理器。

Load balancers vs WebSockets and SSE

推送连接和普通的请求/响应类型的HTTP连接是不同的,他们是持久、长期存活的。一旦建立了连接,甚至在没有待处理的请求时,仍然保持连接。而一般的负载均衡器在一段时间不活跃后,会断开连接。Amazon Elastic Load Balancers (ELB) 和旧版本的 HAProxy、 Nginx都是这样的。你的推送集群如何在负载均衡器后面工作,你有两个选择:
*使用最新版的支持WebSocket 代理的负载均衡器,比如 HAProxy、 Nginx或者Application Load Balancer (ALB)代替 ELB,或者
*让负载均衡器运行在4层,而不是7层。大多数负载均衡器-包括ELBs-支持TCP模式。此时,他们只代理TCP包,而不解析/解释应用协议。
你可能也需要增加负载均衡器的空闲超时值,缺省值通常是秒级的,不足以支持长期存活的经常空闲的推送连接。

Configuration options

NameDescriptionDefault value
zuul.push.registry.ttl.seconds全局注册的超时时间1800 seconds
zuul.push.reconnect.dither.seconds每个客户端的最长连接期的随机窗口。以后重连的间隔180 seconds
zuul.push.client.close.grace.period服务器等待客户端关闭连接的时间,超时在服务器侧强制关闭连接4 seconds

如果使用 Netflix OSS Archaius,可以在运行期修改上述配置,而不用重启服务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值