什么是 Zuul ?
zuul 是从设备和网站到后端应用程序所有请求的前门,为内部服务提供可配置的对外 URL 到服务的映射关系. 基于 JVM 的后端路由器.其具备一下功能:
- 认证与鉴权
- 压力控制
- 金丝雀测试
- 动态路由
- 负载削减
- 静态响应处理
- 主动流量管理
其地层是基于 Servlet, 本质组件是一系列的 Filter 所构成的责任链.
示例
创建 Eureka Server 注册中心, Eureka Client (服务提供者工程), Zuul Server 网关中心. 三个工程
Eureka Client 工程:
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
配置文件:
server:
port: 8081
spring:
application:
name: eureka-clientA
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
主程序启动类
@SpringBootApplication
@EnableDiscoveryClient
public class EurelaClientAApplication {
public static void main(String[] args) {
SpringApplication.run(EurelaClientAApplication.class, args);
}
}
测试接口:
@RestController
public class TestController {
@RequestMapping("/add")
public Integer add(Integer a, Integer b) {
return a + b;
}
}
Zuul Server 工程:
依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
配置文件:
server:
port: 8080
spring:
application:
name: zuul-server
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
zuul:
routes:
clienta:
path: /client/** #凡是 /client 开头的 HTTP 请求, 都会被映射到 clientA 这个服务实例上去, 假设 clientA 的服务实例的端口为 8081
# 那么 http://localhost:8080/client/getuser 的请求,在内部会调用映射的地址 http://localhost:8081/getuser 进行服务
# 请求, 获取到结果,然后返回
serviceId: eureka-clientA #服务实例 ID
主程序启动类:
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class, args);
}
}
然后依次启动 Eureka Server, Eureka Client, Zuul Server 三个工程, 然后先直接访问以下接口: http://localhost:8081/add?a=10&b=20 可以看到输出, 说明服务能正常访问:
然后通过 zuul 去访问服务: http://localhost:8080/client/add?a=10&b=20 可以看到以下输出, 说明通过 zuul 能正确访问服务:
当我们调用 http://localhost:8080/client/add?a=10&b=20 地址的时候, 实际上在内部调用的时候调用的是: http://localhost:8081/add?a=10&b=20 , 这是因为我们在 zuul 中配置了路由规则, 当向 zuul 发起请求的时候, 它会去 Eureka 注册中心拉取服务列表, 如果发现有指定的路由规则, 就会按照规则路由到相应的服务接口上去.
zuul 路由配置:
单实例 ServiceId 映射: 上例中我们使用了
zuul:
routes:
clienta:
path: /client/** #凡是 /client 开头的 HTTP 请求, 都会被映射到 clientA 这个服务实例上去, 假设 clientA 的服务实例的端口为 8081
# 那么 http://localhost:8080/client/getuser 的请求,在内部会调用映射的地址 http://localhost:8081/getuser 进行服务
# 请求, 获取到结果,然后返回
serviceId: eureka-clientA #服务实例 ID
其实这个写法可以简化成这样 (第一种写法)
zuul:
routes:
eureka-clientA: /client/**
还可以简化成这样, 映射规则与 ServiceId 都不用写 (第二种写法):
zuul:
routes:
eureka-clientA:
在这种情况下, Zuul 会为 eureka-clientA 添加一个默认的映射规则 /client-clienta/** , 也就是相当于 下面这样写
zuul:
routes:
eureka-clientA:
path: /eureka-clienta/**
serviceId: eureka-clientA
单实例 url 映射
除了路由到服务之外, 还可以路由到具体的地址, 只需要将 serviceId 替换成 url 就可以了.如下所示:
#映射到具体的url
zuul:
routes:
eureka-clientA:
path: /client/**
url: http://localhost:8081 # eureka-clientA 所在的地址
多实例路由
在默认情况下 Zuul 会使用 Eureka 中集成的负载均衡功能, 如果想要使用 Ribbon 的辅助均衡功能, 就需要指定一个 serviceId, 此操作需要禁止 Ribbon 使用 Eureka. 在 E 版之后,新增了负载均衡策略的配置,如下
zuul:
routes:
eureka-clientA:
path: /client/**
serviceId: eureka-clientA #服务实例ID
ribbon:
eureka:
enabled: false #禁止 ribbon 使用eureka
#为服务实例ID 配置 负载均衡策略
eureka-clientA:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList #用于获取服务实例列表的类
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡策略
listOfServers: localhost:8081, localhost:8082, localhost:8083 # 所有可用的服务提供者的服务实例地址,
然后在 eureka-clientA 服务提供者个工程的对外接口中增加一个方法,
@RequestMapping("/gethost")
public String getHostAndPort(HttpServletRequest request) {
return String.valueOf(request.getServerPort()); //获取端口
}
然后依次eureka 注册中心, 并且以不同的端口启动 eureka-clientA 服务的3 个实例, 然后在启动 zuul , 然后在浏览器访问:http://localhost:8080/client/gethost 可以看到
这几个端口随机出现, 说明使用了我们定义好的 随机策略. 至于为什么要 Ribbon 禁用 Eureka , 这个还在研究当中
forward 本地跳转
有时候我们在 zuul 中做一些逻辑处理, 我们希望在访问 /client 接口的时候转到指定的方法上来处理, 就需要用到 zuul 的本地跳转,如:
修改 zuul 配置文件
# zuul 配置本地转发, 当请求/client 开头的接口时, 会被 API网关转到到 API 网关中以 /client 为前缀的请求上
# 假如: 当API 网关接收到 /client/add 之类的请求的时候, 因为符合规则, 所以请求会被 API 网关转发到网关的
# /client/add 上.
zuul:
routes:
eureka-clientA:
path: /client/** #当符合该路由规则的请求到达时, 会被 API网关转到到 API 网关中以 /client 为前缀的请求上
# 个人理解是, 本地转发的时候, 会用 本地转发指定的前缀, 替换掉 路由匹配的前缀, 然后把后面的地址
#在拼接起来, 如, 请求 http://localhost:8080/client/a/b/c/getuser ,因为符合路由规则, 所以会转发到
# zuul 中的 http://localhost:8080/user/a/b/c/getuser.
url: forward:/user
在 zuul 中新增一个对外接口类, 根据上面的描述 接口的地址必须为: /user/add 否则会报 404 错误
@RestController
public class TestControlelr {
@RequestMapping("/user/add")
public String add(Integer a, Integer b) {
return "本地跳转" + (a + b);
}
}
启动服务, 然后访问 http://localhost:8080/client/add?a=10&b=20 可以看到输出, 说明本地跳转成了,
如果将 zuul 网关中的接口地址改成了 /user, 然后在访问接口, 这个时候本地转发就会错误了.
所以要按照规则处理.
如果有这样一种配置:
zuul:
routes:
eureka-clientA:
path: /client/**
serviceId: eureka-clientA
eureka-clientB:
path: /client/**
serviceId: eureka-clientB
路由规则是一样的, 经过测试, 它总是会匹配到配置文件后面的那个服务上去. 这里是 eureka-clientB 服务上.yml解释器在 工作的时候, 如果同一个映射对应多个服务, 按照加载顺序, 最末尾加载的映射会覆盖前面加载的映射.
zuul 还可以配置路由前缀, 服务屏蔽, 路径屏蔽, 敏感头信息(也就是切换敏感信息与下游服务器的交互),配置重定向(比如隐藏真实服务的 IP, 暴露zuul 网关的IP), 重试机制等.
参考<重新定义 Spring Cloud> 一书