1、概述
Zuul 作为路由网关组件、隶属Netfix
SpringCloud微服务架构图:
通过图可以看到、zuul是整个架构对外的大门、pc端和移动端的请求都要通过Zuul这个网关、然后由网站
来实现鉴权、动态路由等操作、
2、工作原理
话不多说、先来个图
Zuul 是通过Servlet 来实现的, Zuul 通过自定义的Zuu!Servlet 来对请求进行控制。
Zuul 的核心是一系列过滤器,可以在Http 请求的发起和响应返回期间执行一系列的过滤器。
Zuul 包括以下4 种过滤器:
口PRE 过滤器: 它是在请求路由到具体的服务之前执行的,这种类型的过滤器可以做安全验证,例如身份验证、参数验证等。
口ROUTING 过滤器: 它用于将请求路由到具体的微服务实例。在默认情况下,它使用Http Client 进行网络请求。
口POST 过滤器:它是在请求己被路由到微服务后执行的。一般情况下,用作收集统计信息、指标,以及将响应传输到客户端。
口ERROR 过滤器:它是在其他过滤器发生错误时执行的。
3、入门案例
依赖pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>cn.itcast.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.itcast.demo</groupId>
<artifactId>zuul-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
</project>
启动类ZuulDemoApp.java
package www.baidus.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @ Author :ShaoWei Sun.
* @ Date :Created in 13:47 2018/12/2
*/
@SpringBootApplication
@EnableZuulProxy //开启Zuul的网关功能
@EnableDiscoveryClient //开启Eureka客客户端发现功能
public class ZuulDemoApp {
public static void main(String[] args) {
SpringApplication.run(ZuulDemoApp.class,args);
}
}
application.yml全局配置
server:
port: 10010 #服务端口
spring:
application:
name: api-gateway #指定服务名
编写路由规则
zuul:
routes:
user-service: # 这里是路由id,随意写
path: /user-service/** # 这里是映射路径
url: http://127.0.0.1:8081 # 映射路径对应的实际url地址
我们将符合path 规则的一切请求,都代理到 url参数指定的地址
本例中,我们将 /user-service/**开头的请求,代理到http://127.0.0.1:8081
启动测试:http://127.0.0.1:10010/user-service/user/1
4、动态路由Eureka注册中心实例列表(面向服务的路由)
上面的实例中、(将 /user-service/**开头的请求,代理到http://127.0.0.1:8081)
我们把路径对应的服务地址写死了!如果同一服务有多个实例的话,这样做显然就不合理了。
我们应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由才对!
在zuul-service中pom.xml添加Eureka客户端依赖、使网关通过Eureka动态路由到(负载小的)服务器上。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
zuul-service开启Eureka客户端发现功能
package www.baidus.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @ Author :ShaoWei Sun.
* @ Date :Created in 13:47 2018/12/2
*/
@SpringBootApplication
@EnableZuulProxy //开启Zuul的网关功能
@EnableDiscoveryClient //开启Eureka客客户端发现功能
public class ZuulDemoApp {
public static void main(String[] args) {
SpringApplication.run(ZuulDemoApp.class,args);
}
}
添加Eureka配置,获取服务信息 、修改映射文件application.yml
eureka:
client:
registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
service-url:
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka,http://127.0.0.1:10088/eureka,
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
修改映射配置,通过服务名称获取
zuul:
routes:
user-service: # 这里是路由id,随意写
path: /user-serverss/** # 这里是映射路径
serviceId: user-service # 指定服务名称
启动测试:http://127.0.0.1:10010/user-serverss/user/1 查看日志
5、简化的路由配置
zuul:
routes:
user-service: /user-service/** # 这里是映射路径
6、默认路由规则
默认情况下,一切服务的映射路径就是服务名本身。
- 例如服务名为:user-service,则默认的映射路径就是:/user-service/**
7、路由前缘
zuul:
prefix: /ap # 添加路由前缀
routes:
user-service: /user-service/** # 这里是映射路径
路径/ap/user-service/user/1
将会被代理到/user-service/user/1
8、过虑器、自定义过虑器
Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。
ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法:
public abstract ZuulFilter implements IZuulFilter{
abstract public String filterType();
abstract public int filterOrder();
boolean shouldFilter();// 来自IZuulFilter
Object run() throws ZuulException;// IZuulFilter
}
说明:
filterType:返回字符串,代表过滤器的类型。包含以下4种:
- pre:请求在被路由之前执行
- routing:在路由请求时调用
- post:在routing和errror过滤器之后调用
- error:处理请求时发生错误调用
filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
run:过滤器的具体业务逻辑。
过滤器执行生命周期:图解
正常流程:
请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,
请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
异常流程:
- 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,
再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
- 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
- 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,
请求不会再到达POST过滤器了。
使用场景:
- 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
- 异常处理:一般会在error类型和post类型过滤器中结合来处理。
- 服务调用时长统计:pre和post结合使用。
自定义过滤器:如果请求参数中没token就拦截不放行。
MyFilter.java
package www.baidus.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @ Author :ShaoWei Sun.
* @ Date :Created in 14:15 2018/12/4
*/
@Component
public class MyFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//获取上下文资源
RequestContext currentContext = RequestContext.getCurrentContext();
//从上下文获取request
HttpServletRequest request = currentContext.getRequest();
//获取参数
String token = request.getParameter("token");
if (StringUtils.isEmpty(token) || "".equals(token.trim())){
// 没有token,登录校验失败,拦截不响应
currentContext.setSendZuulResponse(false);
//返回401状态码
currentContext.setResponseStatusCode(401);
}
return null;
}
}
http://127.0.0.1:10010/ap/user-service/user/2
http://127.0.0.1:10010/ap/user-service/user/2?token=lan
9、负载均衡和熔断
Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,
比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:
ribbon:
ConnectTimeout: 250 # 连接超时时间(ms)
ReadTimeout: 2000 # 通信超时时间(ms)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000