1.Zuul简介
zuul是从设备和网站到后端应用程序所有请求的前门,为内部服务提供可配置的对外URL到服务的映射关系,基于JVM的后端路由器。为所有下游服务的网关层,所有前台请求均先达到zuul服务,而后在由zuul路由至下游服务
2.Zuul工作原理
Zuul是通过Servlet实现的,Zuul通过自定义的ZuulServlet来对请求进行控制,Zuul核心是一系列过滤器,可以在Http请求的发起和响应返回期间执行一系列的过滤器,Zuul包括如下4种过滤器:
(1) PRE过滤器: 它是请求具体实现逻辑前过滤,这种类型的过滤器可以做安全验证,如身份验证,参数验证等
(2) ROUTING过滤器: 它用于将请求路由到具体的微服务实例。默认,它使用Http Client进行网络请求
(3) POST过滤器: 它是处理具体逻辑后,返回前端前过滤
(4) ERROR过滤器: 它是在其它过滤器发生错误时执行的
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
// 省略代码
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
分析上述代码:
(1) 最先执行preRoute(),当出现异常时,执行error()和postRoute()
(2) 执行route(),当出现异常时,执行error()和postRoute()
(3)最后执行postRoute(),当出现异常时,执行error(),而不执行postRoute()
Zuul是通过RequestContext对象来共享数据的,每个请求都会创建一个RequestContext对象,每个过滤器包括如下几个特性:
(1) FilterType: 表示过滤器类型
(2) FilterOrder:执行顺序,值越小,越先执行
(3) shouldFilter: 表示是否继续执行run中逻辑,如果为false则表示不执行
(4) run: 执行具体的过滤细节
3.入门案例
服务名 | 端口 | 用途 |
---|---|---|
eureka-ribbon-client | 8764 | ribbon客户端 |
eureka-client | 8762、8763 | 服务提供者 |
eureka-feign-client | 8765 | feign客户端 |
eureka-zuul-client | 5000 | zuul客户端 |
consul | 8500 | 注册中心 |
- 创建eureka-zuul-client服务
- 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
- 书写启动类,添加注解
@EnableZuulProxy
@EnableZuulProxy
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaZuulClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaZuulClientApplication.class, args);
}
}
- 配置application.yml中路由规则
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: eureka-client
ribbonapi:
path: /ribbonapi/**
serviceId: eureka-ribbon-client
feignapi:
path: /feignapi/**
serviceId: eureka-feign-client
-
访问服务
(1) 访问
http://localhost:8766/hiapi/hi?name=lisi
结果如下hi lisi, i am from port:8762
hi lisi, i am from port:8763
(2) 访问
http://localhost8766/ribbonapi/hi?name=lisi
结果与上边相同(3) 访问
http://localhost8766/feignapi/hi?name=lisi
结果与上边相同
4.Zuul其它操作
-
在Zuul上配置熔断器
(1) 在Zuul中实现熔断功能需要实现 ZuulFallbackProvider接口
(2) getRoute()方法:用于确定具体路由服务
(3) fallbackResponse():返回信息处理
@Component
public class MyZuulFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "eureka-client";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 0;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("sorroy,have error".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return httpHeaders;
}
};
}
}
-
自定义ZuulFilter
自定义ZuulFilter需要集成ZuulFilter,重写其filterTyepe()、filterOrder()、shouldFilter()、run()方法,以下自定义ZuulFilter用于判定是否携带token
方法 作用 shouldFilter 表示是否执行run()方法,false:否,true:是 RequetContext 每个请求均会维护一个RequestContext setSendZuulResponse 表示是否执行下一个过滤器,false:否,true:是 代码演示:
@Component
public class MyFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(MyFilter.class);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String token = request.getParameter("token");
if(Objects.isNull(token)){
logger.warn("token不存在");
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(401);
try {
currentContext.getResponse().getWriter().write("token is empty");
} catch (IOException e) {
e.printStackTrace();
}
}
logger.info("OK");
return null;
}
}
5.Zuul常见使用方式
-
每个平台使用独立的Zuul网关层,从而使Zuul集群化,以下截图来自《深入理解Spring Cloud与微服务构建》
-
通过Nginx根据负载均衡策略将请求路由至Zuul集群,以下截图来自《深入理解Spring Cloud与微服务构建》
6.Zuul常见配置
规则 | 解释 | 例子 |
---|---|---|
/** | 匹配任意路径及字符 | /provider/a,/provider/bbb,/provider/aa/bb/cc |
/* | 匹配任意数量字符,只有一层路径 | /provider/a,/provider/bbb,/provider/ccdc |
/? | 匹配单个字符,一层路径 | /provider/a,/provider/c |
- 自定义指定服务访问路径
path:表示请求前缀路径
serviceId:表示服务名
zuul:
routes:
provider-service:
path: /provider/**
serviceId: provider-service
- 简化写法
自动会将服务映射至
provider-service
zuul:
routes:
provider-service: /provider/**
- 另一种写法
path与serviceId均不需要,自动映射,此种情况下,则会自动生成path = /provider-service/**,serviceId = provdier-service
zuul:
routes:
provider-service:
- 直接配置服务ip:port
url:服务ip:port
zuul:
routes:
provider-service:
ptah: /provider/**
url: http://localhost:8000
- forward本地跳转
url: forward:/本地请求路径,当请求/provider路径时,会跳转至zuul服务本地的路径前缀为/provider的接口,如url:
http://localhost:7000/provider/add?a=10&b=20
,则会自动匹配zuul服务中的/provider/add服务
zuul:
routes:
provider-service:
path: /provider/**
url: forward:/provider
- 相同路径,后者覆盖前者
如下配置,则会请求至provider-service-b服务
zuul:
routes:
provider-service-a:
path: /provider/**
serviceId: provider-service-a
provider-service-b:
path: /provider/**
serviceId: provider-service-b
- 使用指定前缀
prefix: /pre,表示请求前缀为pre,但实际请求路径为/provider/,可以通过stripPrefix=false关闭此功能,则启作用的路径为/pre/provider/,一般不配置功能
zuul:
prefix: /pre
routes:
provider-a: /provider/**
- 服务屏蔽及路径屏蔽
ignored-service:屏蔽服务,当请求该服务时,会报404不存在
ignored-patterns:屏蔽路径
zuul:
ignored-service: provider-b
ignored-patterns: /provider-b/**
prefix: /pre
routes:
provider-a: /provider-a/**
- 敏感头信息
在构建系统时,有许多请求头信息不需要传递至下层服务,则可在zuul层进行拦截,而不传递
zuul:
routes:
provider-service:
path: /provider/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
serviceId: provider-service
- 重试机制
在生产环境中,可能存在请求失败,需重试情况,为了用户无感知,可通过zuul设置重试,但要考虑服务幂等性问题
zuul:
retryable: true # 开启重试
ribbon:
MaxAutoRetries: 1 # 同一个服务重试的次数
MaxAutoRetriesNextServer: 1 # 切换相同服务数量
- 自定义请求路由
通过上述路由配置我们知道,当只服务服务,不配置路径及serviceId时,则会默认生成path:/provider-service/**,servcieId: provider-service,但有时候我们想自定义生成的路径,则可采用如下方式,定义返回的格式为 v e r s i o n / {version}/ version/{name}
@Bean
public PatternServiceRouteMapper serviceRouteMapper(){
return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}");
}
7.参考资料
- 《重新定义Springcloud实战》
- 《Springcloud微服务实战》
- 《深入理解Spring Cloud与微服务构建》