Netflix-Zuul
一. 简介
路由是微服务架构中的一个组成部分。
例如【/】可能是你映射到web应用的路径,【/api/user】可能是你映射到user服务的路径,【/api/shop】可能是
映射到shop服务的路径。
Zuul是一款出自于Netflix基于JVM的服务器端的负载均衡器。
Netflix在用Zuul做这些事情:
- 身份验证(Authentication)
- Insights
- 压力测试(Stress Testing)
- 金丝雀测试(Canary Testing)
- 动态路由(Dynamic Routing)
- 服务迁移(Service Migration)
- 减载(Load Shedding)
- 安全(Security)
- 静态响应处理(Static Response handling)
- 流量控制(Active/Active traffic management)
二. 集成Zuul反向代理
-
Spring Cloud 已经建立了Zuul代理,用来适用前端应用程序想要通过代理来访问一个或者多个后端服务。
这个特点对于用户接口代理访问到后端服务非常有用。避免了跨域问题并且可以使认证独立于后端服务。 -
如果要启用Zuul反向代理,可以在Spring Boot应用程序的主类中添加注解@EnableZuulProxy。 这样做
可以使本地的调用请求路由到相应的服务。按照惯例,一个users服务从代理那里接收/users(使用前缀剥离服务)
的请求Zuul代理使用Ribbon通过服务发现来定位具体的服务。所有的请求在hystrix command中执行,所以失败
的请求可以在Hystrix监控中获取到。一旦断路器打开,Zuul代理将不会尝试连接该服务。注:Zuul starter 没有包含服务发现客户端,因此,为了实现基于服务id的路由,最好提供服务发现的客户端
(Eureka是其中之一的选择) -
如果服务要跳过Zuul的反向代理,可在属性【zuul.ignored-services】中设置跳过的服务ID列表。
如果某个服务和需要跳过的服务列表匹配,但是又明确的设置在了路由map中,该服务将不会跳过Zuul的代理。
下述例子就是这样的效果:
application.ymlzuul: ignoredServices: '*' routes: users: /myusers/**
上述例子中,所有的服务都将跳过Zuul的代理,除了users服务。
-
如果要添加或者改变代理路由,可以将配置信息配置成如下所示:
application.ymlzuul: routes: users: /myusers/**
上述配置意味着如果HTTP请求/myusers将会转发到users服务(例:/myusers/101将会转发到users服务的/101)
-
如果想要通过路由获取到更好的粒度控制,则可以独立的指定path和serviceId,配置如下:
application.ymlzuul: routes: users: path: /myusers/** serviceId: users_service
上述配置意味着如果HTTP请求/myusers将会转发到serviceId为users_service的服务中去。
此场景中必须指定ant-style风格的格式。所以/myusers/*只匹配一级路径,/myusers/**可匹配多级路径。 -
后台服务的位置可以通过serviceId(通过服务发现获取)指定,也可以通过url指定,配置如下:
application.ymlzuul: routes: users: path: /myusers/** url: http://example.com/users_service
-
上述所有的这些路由例子都不会以HystrixCommand的方式执行,有多个url的时候也不会使用Ribbon做负载均衡。
如果要实现这些目标,可以指定一个静态服务的list,配置如下:
application.ymlzuul: routes: echo: path: /myusers/** serviceId: myusers-service stripPrefix: true hystrix: command: myusers-service: execution: isolation: thread: timeoutInMilliseconds: ... myusers-service: ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList listOfServers: http://example1.com,http://example2.com ConnectTimeout: 1000 ReadTimeout: 3000 MaxTotalHttpConnections: 500 MaxConnectionsPerHost: 100
另一种方式是指定一个服务路由并且为serviceId配置一个Ribbon客户端(这样做需要在Ribbon中禁止Eureka的支持),配置如下:
application.ymlzuul: routes: users: path: /myusers/** serviceId: users ribbon: eureka: enabled: false users: ribbon: listOfServers: example.com,google.com
-
也可以使用正则表达式在serviceId和如路由之间指定一个规则。从serviceId中提取变量并且将它们注入到路由中,进而使用有规律的表达式来定义groups。例子如下:
ApplicationConfiguration.java@Bean public PatternServiceRouteMapper serviceRouteMapper() { return new PatternServiceRouteMapper( "(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }
上述例子意味着serviceId为myusers-v1的服务将指向地址/v1/myusers/**.任何正则表达式都可被接受,但是任何被命名的groups必须存在于servicePattern和routePattern中。如果servicePattern不能匹配serviceId,将使用默认的操作。上述例子中,myusers的serviceId映射到了/myusers/**的路由中(没有检测到版本号)。这个功能默认是禁用的,只有在服务被发现时才有这个功能。
-
增加一个映射的前缀,可以设置zuul.prefix属性值,例如/api。默认情况下,Zuul在请求转发前会将这个前缀丢弃(可以通过设置zuul.stripPrefix=false来关掉这个功能)。也可以在个别的路由中特定服务中关掉丢弃映射前缀的功能。例如:
application.ymlzuul: routes: users: path: /myusers/** stripPrefix: false
注:zuul.stripPrefix只有在设置了zuul.prefix时才会起作用。它不会对路由中设置的path产生任何影响。
在上述例子中,请求/myusers/101将转发到users服务的/myusers/101路径中。 -
事实上,zuul.routes中的属性会绑定到ZuulProperties的对象中。如果你去看那个对象的属性,里面有一个retryable的标志。如果设置那个标志为true,则Ribbon的客户端会自动重试失败的请求。当你需要修改Ribbon客户端的重试参数配置时你也可以将这个标记设置为true。
默认情况下,X-Forward-Host头部属性会加到转发的请求中。如果要关闭这个属性,可设置zuul.addProxyHeaders=false.
默认情况下,前缀路径在转发时将丢弃,并且后台请求中会增加X-Forward-Prefix头部属性(如上述例子中的/myusers)。 -
如果设置默认的路由路径(/),被@EnableZuulProxy注解的应用程序将类似一个独立的服务器。例如,zuul.route.home:/将会路由home服务的/**路径。
-
如果需要将忽略路径控制到更好的颗粒度,可以指定一些特别的pattern。这些pattern将会在处理路由位置开始时处理,这意味着前缀应该包含在pattenr中以保证匹配。忽略的模式跨越所有服务并取代任何其他路由规范。下面的例子将展示如何创建一个忽略pattern:
application.ymlzuul: ignoredPatterns: /**/admin/** routes: users: /myusers/**
上面的例子意味着请求如/myusers/101将转发到users服务的/101路径中,然而如果请求路径中包含/admin/将不会被转发。
-
如果你需要保证路由的顺序,你需要使用YAML文件。使用properties文件将丢失顺序访问的功能。下面的例子就是这样一个YAML文件:
application.ymlzuul: routes: users: path: /myusers/** legacy: path: /**
如果使用properties文件,legacy的路径有可能变成优先于users的路径处理,最后导致users服务不可达。
三. Zuul Http Client
Zuul默认使用的HTTP client是 Apache HTTP client而不是丢弃的Ribbon的RestClient。如果要使用RestClient或者okhttp3.OkHttpClient,分别设置 ribbon.restclient.enabled=true 或者 ribbon.okhttp.enabled=true即可。如果你想自定义Apache HTTP client或者OK HTTP client,则需要提供ClosableHttpClient或者OkHttpClient的bean类型。
四. Cookies和敏感头部信息
-
在同一个系统的不同服务之间你可以共享头部信息,但是可能你不想将某些敏感头部信息泄露给下游的外部服务器。这种情况下可以指定忽略的头部信息作为路由配置的一部分。Cookies扮演了一个特殊的角色,因为它们能在浏览器中很好的定义语义,并且经常当做处理过的敏感信息使用。如果你的代理的消费者是浏览器,对于用户来说给下游服务使用的Cookies可能是个问题,因为它们都被搞得乱七八糟(所有的下游服务认为Cookies都是来自于一个地方)。
-
如果你充分的设计了你的服务(比如,只有一个下游服务设置了Cookies),你也许可以使他们从后端服务一直流转到调用者。当然,如果你的代理设置了Cookies并且你所有的后端服务都是同一个系统的一部分,可以很自然和简单的共享他们(例如使用Spring Session让他们维持在共享的状态)。除此之外, 对于调用者来说,下游服务队任何cookie的get和set操作似乎都没有多大用处,所以推荐至少将Set-Cookie和Cookie放入属于域的一部分的路由的敏感头部中。即使该路由属于域的一部分,在让cookie流转与服务和代理之前,请仔细考虑那将意味着什么。
每个路由的敏感头部可以通过逗号隔开字符串来设置,下面的例子就是这样一个YAML文件:
application.ymlzuul: routes: users: path: /myusers/** sensitiveHeaders: Cookie,Set-Cookie,Authorization url: https://downstream
这个是sensitive