一、前言
在微服务的架构中,服务网关就是一个介于客户端与服务端之间的中间层。在这种情况下,客户端只需要跟服务网关交互,无需调用具体的微服务接口。这样的好处在于,客户端可以降低复杂性;对于需要认证的服务,只需要在服务网关配置即可;同样也方便后期微服务的变更和重构,即微服务接口变更只需在服务网关调整配置即可,无需更改客户端代码。
Zuul是一款由Netflix开发的微服务网关开源软件,可以和其自家开发的Eureka,Ribbon和Hystrix配合使用,Spring Cloud对其进行了封装。
二、介绍
2.1 API 网关使用场景
1>黑白名单: 实现通过 IP 地址控制请求的访问
2>日志:实现访问日志的记录,进而实现日志分析,处理性能指标等
3>协议适配:实现通信协议的校验、适配转换的功能
4>身份认证:对请求进行身份认证
5>计流限流:可以设计限流规则,记录访问流量
6>路由:将请求进行内部(服务)转发
2.2 API 网关的实现
业界常用的 API 网关有很多方式,如:Spring Cloud Zuul、 Nginx、Tyk、Kong。本篇介绍的对象正是 Spring Cloud Zuul。
Zuul 是 Netflix 公司开源的一个 API 网关组件,提供了认证、鉴权、限流、动态路由、监控、弹性、安全、负载均衡、协助单点压测等边缘服务的框架。
Spring Cloud Zuul 是基于 Netflix Zuul 的微服务路由和过滤器的解决方案,也用于实现 API 网关。其中,路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入门的基础。而过滤功能是负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
Spring Cloud Zuul 和 Eureka 进行整合时,Zuul 将自身注册到 Eureka 服务中,同时从 Eureka 中获取其他微服务信息,以便请求可以准确的通过 Zuul 转发到具体微服务上。
三、实战演练
本次测试案例基于之前发表的文章中介绍的案例进行演示,不清楚的读者请先转移至 《Spring Cloud Hystrix服务容错》 进行浏览。
当前的项目列表如下:
项目 | 服务实例 | 实例id | 端口 | 描述 |
---|---|---|---|---|
common-api | 无 | 无 | 无 | 公用的 api,如:实体类 |
eureka-server | Eureka-Server | eureka-server1 | 9001 | 注册中心(Eureka 服务端) |
eureka-server | Eureka-Server | eureka-server2 | 9002 | 注册中心(Eureka 服务端) |
goods-server | Goods-Server | goods-server1 | 8801 | 商品服务(Eureka 客户端) |
goods-server | Goods-Server | goods-server2 | 8802 | 商品服务(Eureka 客户端) |
goods-server | Goods-Server | goods-server3 | 8803 | 商品服务(Eureka 客户端) |
order-server | Order-Web | order-web | 8900 | 订单服务(Eureka 客户端) |
user-server | User-Server | user-server1 | 8788 | 用户服务(Eureka 客户端) |
user-server | User-Server | user-server2 | 8789 | 用户服务(Eureka 客户端) |
gateway-server | GateWay-Server | gateway-server | 9100 | 服务网关(Eureka 客户端) |
创建一个为名 gateway-server 的 Spring Boot 项目。
3.1 添加依赖
1 | <dependency> |
3.2 配置文件
1 | server: |
3.3 启动 Zuul
在启动类上添加 @EnableZuulProxy 注解:
1 | @EnableZuulProxy |
启动上边的所有项目,使用idea的http工具请求订单下单接口
图中,我们首先不经过网关直接访问 order-server 项目请求地址: http://localhost:8900/order/hystrix
之后再修改成访问 gateway-server 项目的请求地址: http://localhost:9100/order-web/order/hystrix
最终,响应结果都一样。
提示
: http://localhost:9100/order-web/order/hystrix 中第一个 order-web 表示的是注册在 Eureka 上的订单服务名称。
3.4 zuul 常用配置
传统配置
传统配置就是手动指定服务的转发地址,如在yml中配置:
1 | zuul: |
通过上面的配置,所有符合/userweb/**规则的访问都将被路由转发到 http://localhost:8765 地址上,即当我们向服务网关访问 http://localhost:9100/userweb/userFeign/user/1 请求的时候,请求将被转发到 http://localhost:8765/userFeign/user/1 服务上.
基于服务名称配置
1 | zuul: |
将订单服务的路由名称改成 orderGateWay。
使用 http 请求 http://localhost:9100/orderGateWay/order/hystrix 下单接口,运行结果:
请求成功。
基于服务名称的路由配置还可以进行简化,格式为zuul.routes.
1 | zuul: |
默认路由配置规则
我们尝试访问 http://localhost:9100/user-web/userFeign/user/1
我们压根没配置这个路由,为什么可以正常响应??其实Zuul配合Eureka后将会成一套默认的配置规则。当我们使用服务名称作为请求的前缀路径时,实际上就会匹配上类似下面的默认路由配置:
1 | zuul: |
如果不想启动这个默认配置,我们可以使用zuul.ignored-services配置来关闭,如在yml中加入如下配置之后,再次访问 http://localhost:9100/user-web/userFeign/user/1 将返回404:
1 | zuul: |
优先级
假如某个请求路径可以和多个路由配置规则相匹配的话,Zuul根据匹配的先后顺序来决定最终使用哪个路由配置。比如:
1 | zuul: |
当我们访问 http://localhost:9100/userFeign/userFeign/user/1 的时候,userFeign和userFeign1的路由配置都可以匹配上,但由于userFeign先于userFeign1配置,所以最终生效的是userFeign的配置。
假如将userFeign和userFeign1的配置顺序调换,再次访问 http://localhost:9100/userFeign/userFeign/user/1 时将抛出异常,原因是不存在服务名为User-Web1的服务:
com.netflix.zuul.exception.ZuulException: Forwarding error
....
Caused by: com.netflix.client.ClientException: Load balancer does not have available server for client: User-Web1
...
禁用路由:
1 | zuul: |
http://localhost:9100/orderGateWay/order/place 无法被正常路由到订单服务,响应返回 404。
本地跳转
Zuul网关除了支持将服务转发到各个微服务上之外,还支持将服务跳转到网关本身的服务上,比如现在yml中有如下一段配置:
1 | zuul: |
我们在gateway-server入口类中加上该REST服务:
1 | @RestController |
当访问 http://localhost:9100/test2/hello 时,Zuul会从本地/test/hello获取服务。
前缀配置:
1 | zuul: |
所有请求中的 path 需要添加 api 前缀。如: http://localhost:9100/orderGateWay/order/hystrix 需要改成 http://localhost:9100/api/orderGateWay/order/hystrix 。
头部过滤:
在使用Zuul网关的时候你可能会遇到Cookie丢失的情况,这是因为默认情况下Zuul会过滤掉HTTP请求头中的一些敏感信息,这些敏感信息通过下面的配置设定:
1 | zuul: |
或
1 | zuul: |
使用Zuul另一个常见问题是重定向的问题,可以通过下面的设置解决:
1 | zuul: |
四、核心过滤器
Spring Cloud Zuul为各个生命周期阶段实现了一批过滤器,这些过滤器的优先级和作用如下表所示:
生命周期 | 优先级 | 过滤器 | 功能描述 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 标记处理Servlet的类型 |
pre | -2 | Servlet30WrapperFilter | 包装HttpServletRequest请求 |
pre | -1 | FormBodyWrapperFilter | 包装请求体 |
route | 1 | DebugFilter | 标记调试标志 |
route | 5 | PreDecorationFilter | 处理请求上下文供后续使用 |
route | 10 | RibbonRoutingFilter | serviceId请求转发 |
route | 100 | SimpleHostRoutingFilter | url请求转发 |
route | 500 | SendForwardFilter | forward请求转发 |
post | 0 | SendErrorFilter | 处理有错误的请求响应 |
post | 1000 | SendResponseFilter | 处理正常的请求响应 |
其中优先级数字越小,优先级越高。
要关闭这些过滤器可以在application.yml中按照格式zuul.
1 | zuul: |
五、Zuul 自定义过滤器
Zuul 的核心技术就是过滤器,该框架提供了 ZuulFilter 接口让开发者可以自定义过滤规则。
我们以身份检验为例,自定义 ZuulFilter 过滤器实现该功能。
5.1 改造user-server
登录接口:
LoginController.java
1 | @RestController |
5.2 创建 ZuulFilter 过滤器
在 gateway-server 项目中,新建一个过滤器,需要继承 ZuulFilter 类:
1 | @Component |
其中,filterType 有 4 种类型:
1>pre: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
2>routing:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务。
3>post:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
4>error:在其他阶段发生错误时执行该过滤器。
5.3 测试过滤器
运行所有项目,测试操作步骤如下:
测试效果图如下:
请求用户服务的登录接口( http://localhost:9100/api/user/user/login ),请求不执行 zuul 过滤方法,并且请求响应返回的 cookie 包含 token
请求订单服务的下单接口( http://localhost:9100/api/orderGateWay/order/hystrix ),但不携带 token,请求需要执行 zuul 过滤方法,请求响应 401 权限不足
请求订单服务的下单接口( http://localhost:9100/api/orderGateWay/order/hystrix ),携带之前登录接口返回的 token,请求需要执行 zuul 过滤方法,校验通过后路由到订单服务执行之后的操作