08-微服务网关【API网关】之Zuul
Zuul 网关
介绍
Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
- 动态路由:动态将请求路由到不同后端集群
- 压力测试:逐渐增加指向集群的流量,以了解性能
- 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
- 静态响应处理:边缘位置进行响应,避免转发到内部集群
- 身份认证和安全:识别每一个资源的验证要求,并拒绝那些不符的请求。Spring Cloud对Zuul进行了整合和增强
搭建Zuul
引入坐标
<!--zuul网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
配置启动类,开启网关服务器功能
@SpringBootApplication
@EnableZuulProxy //开启Zuul的网关功能
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class, args);
}
}
yml配置
server:
port: 8080 #端口
spring:
application:
name: api-gateway-server #服务名称
路由yml配置
基础路由yml配置
zuul:
routes:
product-service: # 这里是路由id,随意写
path: /product-service/** # 这里是映射路径
url: http://127.0.0.1:9001 # 映射路径对应的实际url地址
参数解释:
- product-service:配置路由id,可以随意取名
- url:映射路径对应的实际url地址
- path:配置映射路径,这里将所有请求前缀为/product-service/的请求,转发到http://127.0.0.1:9001处理
配置好Zuul路由之后启动服务,在浏览器中输入 http://localhost:8080/product-service/product/1 ,即可访问到订单微服务。
面向服务的路由配置
简述
微服务一般是由几十、上百个服务组成,对于一个URL请求,最终会确认一个服务实例进行处理。如果对每个服务实例手动指定一个唯一访问地址,然后根据URL去手动实现请求匹配,这样做显然就不合理。
Zuul支持与Eureka整合开发,根据ServiceID自动的从注册中心中获取服务地址并转发请求,这样做的好处不仅可以通过单个端点来访问应用的所有服务,而且在添加或移除服务实例的时候不用修改Zuul的路由配置。
(1)引入Eureka坐标
<!--引入EurekaClient-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
(2)启动类开启Zuul
@SpringBootApplication
@EnableZuulProxy //开启Zuul的网关功能
@EnableDiscoveryClient //Eureka的服务发现,可省略
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class, args);
}
}
(3)在Zuul网关服务中配置Eureka的注册中心相关信息
#配置Eureka
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
prefer-ip-address: true #使用ip地址注册
(4)修改路由中的映射配置
#路由配置
zuul:
routes:
product-service: # 这里是路由id,随意写
path: /product-service/** # 这里是映射路径
# url: http://127.0.0.1:9001 # 映射路径对应的实际url地址,下面的是根据Eureka获取所有的链接
serviceId: service-product #配置转发的微服务名称
因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而是通过服务名称来访问,并且Zuul已经集成了Ribbon的负载均衡功能。
- serviceId: 指定需要转发的微服务实例名称
依次启动Eureka,商品微服务,API网关服务,在浏览器上通过访问 http://localhost:8080/product-service/product/1 查看最终效果。
简化路由的配置
-
zuul.routes.route.path=/xxx/**: 来指定映射路径。route是自定义的路由名
-
zuul.routes.route.serviceId=service-provider:来指定服务名。
而大多数情况下,我们的route路由名称往往和服务名会写成一样的。
因此Zuul就提供了一种简化的配置语法:zuul.routes.serviceId=path
比方说上面我们关于service-provider的配置可以简化为一条yml配置:
zuul:
routes:
service-product: # 这里是路由id,随意写
# path: /product-service/** # 这里是映射路径
# url: http://127.0.0.1:9001 # 映射路径对应的实际url地址,下面的是根据Eureka获取所有的链接
serviceId: service-product #配置转发的微服务名称
# ================ 改为以下方式,达到简化目的 ===================
zuul:
routes:
#如果路由id和对应的微服务的serviceId一致的话
service-product: /product-service/**
一致体现在,若不写id的话,默认使用的是微服务名称作为路径,因此 默认的id等于serviceId的值
因为路由id和对应的微服务的serviceId一致,则省略id,直接配置 【服务名称:映射路径】
默认的路由规则
在使用Zuul的过程中,上面讲述的规则已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。因此Zuul就指定了默认的路由规则:
- 默认情况下,一切服务的映射路径就是服务名本身
- 例如服务名为: service-product ,则默认的映射路径就是: /service-product/**
#zuu1中的默认路由配置
#如果当前的微服务名称 service-product、则默认的请求映射路径/service-product/**
如下:
- http://localhost:9002/order/1 本身的访问地址
- http://localhost:8080/service-order/order/1 使用默认微服务id进行访问
Zuul加入后的系统架构
全部的yml配置总结
server:
port: 8080 #端口
spring:
application:
name: api-zuul-server #服务名称
#配置Eureka
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
prefer-ip-address: true #使用ip地址注册
#路由配置方式1(基础)
zuul:
routes:
product-service: # 这里是路由id,随意写
path: /product-service/** # 这里是映射路径
url: http://127.0.0.1:9001 # 映射路径对应的实际url地址
#这里将所有请求前缀为/product-service/的请求,转发到http://127.0.0.1:9001处理
#在浏览器中输入 http://localhost:8080/product-service/product/1
#路由配置方式2(面向服务,需要搭配Eureka)
#zuul:
# routes:
# product-service: # 这里是路由id,随意写
# path: /product-service/** # 这里是映射路径
# serviceId: service-product #配置转发的微服务名称,浏览器访问:http://localhost:8080/product-service/product/1
#路由配置方式3(简化路由)
#zuul:
# routes:
# #以商品微服务为例
# #service-product: #路由id,随便写
# #path: /product-service/** #映射路径
# #serviceId: service-product #配置转发的微服务的服务名称
# service-product: /product-service/** #如果路由id和对应的微服务的serviceId的值一致的话,可简写配置。浏览器访问:http://localhost:8080/product-service/product/1
#路由配置方式4(默认路由)
## 默认情况下,一切服务的映射路径就是服务名本身,
## 如 service-product ,则默认的请求映射路径 /service-product/** ,浏览器访问:http://localhost:8080/service-product/product/1
## 如 service-order ,则默认的请求映射路径 /service-order/** ,浏览器访问:http://localhost:8080/service-order/order/buy/1
过滤器
Zuul 过滤器简介
Zuul 中的过滤器跟我们之前使用的 javax.servlet.Filter 不一样,javax.servlet.Filter 只有一种类型,可以通过配置 urlPatterns 来拦截对应的请求。而 Zuul 中的过滤器总共有 4 种类型,且每种类型都有对应的使用场景。
- PRE:在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- ROUTING:将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
- POST:在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTPHeader、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发生错误时执行该过滤器。Zuul提供了自定义过滤器的功能实现起来也十分简单,只需要编写一个类去实现zuul提供的接口
/**
* 编写一个类去实现zuul提供的 ZuulFilter 接口
*/
@Component
public class LoginFilter extends ZuulFilter {
@Override
public String filterType() {
return null;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return false;
}
@Override
public Object run() throws ZuulException {
return null;
}
}
- ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法
- shouldFilter :返回一个 Boolean 值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
- run:过滤器的具体业务逻辑。
- filterType :返回字符串,代表过滤器的类型。包含以下4种:
- pre :请求在被路由之前执行
- routing :在路由请求时调用
- post :在routing和errror过滤器之后调用
- error :处理请求时发生错误调用
- filterOrder :通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
生命周期
- 正常流程:
- 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
- 异常流程:
- 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
- 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
- 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的是,请求不会再到达POST过滤器了。
- 不同过滤器的场景:
- 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
- 异常处理:一般会在error类型和post类型过滤器中结合来处理。
- 服务调用时长统计:pre和post结合使用。
自定义一个过滤器
/**
* 自定义的zuul过滤器
* 继承抽象父类
*/
@Component
public class LoginFilter extends ZuulFilter {
/**
* 定义过滤器类型
* pre
* routing
* post
* error
*/
public String filterType() {
return "pre";
}
/**
* 指定过滤器的执行顺序
* 返回值越小,执行顺序越高
*/
public int filterOrder() {
return 1;
}
/**
* 当前过滤器是否生效
* true : 使用此过滤器
* flase : 不使用此过滤器
*/
public boolean shouldFilter() {
return true;
}
/**
* 指定过滤器中的业务逻辑
* 身份认证:
* 1.所有的请求需要携带一个参数 : access-token
* 2.获取request请求
* 3.通过request获取参数access-token
* 4.判断token是否为空
* 4.1 token==null : 身份验证失败
* 4.2 token!=null : 执行后续操作
* 在zuul网关中,通过RequestContext的上下问对象,可以获取对象request对象
*/
public Object run() throws ZuulException {
//System.out.println("执行了过滤器");
//1.获取zuul提供的上下文对象RequestContext
RequestContext ctx = RequestContext.getCurrentContext();
//2.从RequestContext中获取request
HttpServletRequest request = ctx.getRequest();
//3.获取请求参数access-token
String token = request.getParameter("access-token");
//4.判断
if (token == null) {
//4.1 如果token==null ,拦截请求,返回认证失败
ctx.setSendZuulResponse(false); // 拦截请求
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
//4.2 如果token!=null ,继续后续操作
return null;
}
}
访问:http://localhost:8080/product-service/product/1 失败
访问:http://localhost:8080/product-service/product/1?access-token=1234564 成功