Zuul
Zuul的作用
- 所有服务统一的入口,可以方便做参数校验,安全校验,权限校验。(Nginx目的不是实现业务)
- Zuul可以通过eureka获取每一个服务的信息。(客户端自己记录/Nginx记录都很麻烦)
- 如果服务地址信息改变了,Zuul基本不需要改变。(客户端/Nginx都需要做大量的维护信息)
- 做统一的监控信息。(Nginx目的不是实现业务)
Zuul的基础使用
创建项目导入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
启动类添加注解
@EnableEurekaClient @EnableZuulProxy
编写配置文件
# 指定Eureka服务地址 eureka: client: service-url: defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka #指定服务的名称 spring: application: name: ZUUL server: port: 80
直接测试
Zuul的常用配置
Zuul的监控界面
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
编写配置文件
# 查看zuul的监控界面(开发时,配置为*,代表监控全部服务,上线的时候,不要配置) management: endpoints: web: exposure: include: "*"
通过localhost:端口号/actuator/routes
忽略服务配置# zuul的配置 zuul: # 基于服务名忽略服务,无法查看 ,如果要忽略全部的服务 "*",默认配置的全部路径都会被忽略掉(自定义服务的配置,无法忽略的) ignored-services: eureka # 监控界面依然可以查看,在访问的时候,404 ignored-patterns: /**/search/**
自定义服务配置
# zuul的配置 zuul: # 指定自定义服务(方式一 , key(服务名):value(路径)) #routes: #search: /ss/** #customer: /cc/** # 指定自定义服务(方式二) routes: kehu: # 自定义名称 path: /ccc/** # 映射的路径 serviceId: customer # 服务名称
灰度发布
启动类添加一个配置类
**@Bean public PatternServiceRouteMapper serviceRouteMapper() { return new PatternServiceRouteMapper( "(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }**
准备一个服务,提供两个版本
version: v1 #指定服务的名称 spring: application: name: CUSTOMER-${version}
第二个版本可以直接在idea启动的地方(Run/Debug Configurations)直接复制一个,在VM options中加入(-Dversion=v2 -Dserver.port=11112)
修改Zuul的配置# zuul的配置 zuul: # 基于服务名忽略服务,无法查看 , 如果需要用到-v的方式,一定要忽略掉 # ignored-services: "*"
Zuul的原理(过滤器执行流程)
Zuul的实现原理:
- Zuul就是由多个过滤器组成,并由RoutingFilter的过滤器将请求转发到其他服务上。
客户端请求发送到Zuul服务上,首先通过PreFilter链,如果正常放行,会吧请求再次转发给RoutingFilter,请求转发到一个指定的服务,在指定的服务响应一个结果之后,再次走一个PostFilter的过滤器链,最终再将响应信息交给客户端。
基于Zuul提供的前置,后置,异常过滤器去编写业务逻辑:
- PreFilter:在请求路由到其他服务前,先执行。
- RouteFilter:将请求路由到其他服务。
- PostFilter:在响应之前,执行。
- ErrorFilter:在出现异常时,执行。
自定义过滤器
- 创建POJO类,继承ZuulFilter的抽象类。
- 重写四个方法:
- filterType:指定过滤器的类型,推荐用FilterConstants的常量指定过滤器类型。
- filterOrder:指定过滤器的执行顺序,推荐用FilterConstants进行加减。
- shouldFilter:指定过滤器是否开启。返回true代表开启
- run:编写你要执行的业务逻辑代码。
// 获取request等信息 - RequestContext requestContext = RequestContext.getCurrentContext(); - HttpServletRequest request = requestContext.getRequest();
前置过滤器实现token校验
创建AuthenticationFilter
@Component public class AuthenticationFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return PRE_DECORATION_FILTER_ORDER - 2; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { //.. } }
在run方法中编写具体的业务逻辑代码
@Override public Object run() throws ZuulException { //1. 获取Request对象 RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); //2. 获取token参数 String token = request.getParameter("token"); //3. 对比token if(token == null || !"123".equalsIgnoreCase(token)) { //4. token校验失败,直接响应数据 requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } return null; }
Zuul的降级
创建POJO类,实现接口FallbackProvider
@Component public class ZuulFallBack implements FallbackProvider {}
重写两个方法
@Override public String getRoute() { return "*"; // 代表指定全部出现问题的服务,都走这个降级方法 } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { System.out.println("降级的服务:" + route); cause.printStackTrace(); return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { // 指定具体的HttpStatus return HttpStatus.INTERNAL_SERVER_ERROR; } @Override public int getRawStatusCode() throws IOException { // 返回的状态码 return HttpStatus.INTERNAL_SERVER_ERROR.value(); } @Override public String getStatusText() throws IOException { // 指定错误信息 return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(); } @Override public void close() { } @Override public InputStream getBody() throws IOException { // 给用户响应的信息 String msg = "当前服务:" + route + "出现问题!!!"; return new ByteArrayInputStream(msg.getBytes()); } @Override public HttpHeaders getHeaders() { // 指定响应头信息 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; }
动态路由的实现
- 可以基于Zuul实现动态路径,根据项目的业务动态修改路由的规则
- 实现:
- 是前置过滤器:pre
- 尽量放在前置过滤器的最后。
- 在过滤器中设置RequestContext对象中的信息:
- SERVICE_ID_KEY:找服务对应的项目路径
- SERVICE_ID_KEY:找服务对应的项目路径
- 在执行Route过滤器时,根据路由规则处理。
- 代码实现
@Override public Object run() throws ZuulException { // http://ip:port/xxxx?redisKey=customer ----> customer服务的/sys/customer/table路径 // http://ip:port/xxxx?redisKey=search ----> search服务的/search/customer/get/1路径 //1. 获取请求携带的redisKey参数 RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); String redisKey = request.getParameter("redisKey"); //2. 判断redisKey不为null, if(!StringUtils.isEmpty(redisKey)){ Object serviceId = redisTemplate.opsForHash().get(redisKey, "serviceid"); Object requestURI = redisTemplate.opsForHash().get(redisKey, "requesturi"); context.put(FilterConstants.SERVICE_ID_KEY,serviceId); context.put(FilterConstants.REQUEST_URI_KEY,requestURI); } return null; }
Zuul整合Hystrix
避免其他服务出现问题后,问题抛到Zuul,导致给页面发送错误信息。
根据官网编写指定POJO类:
public class MyFallbackProvider implements FallbackProvider { @Override public String getRoute() { return "*"; // 指定哪些服务,在出现问题后,走这个fallback方法 } @Override public ClientHttpResponse fallbackResponse(String route, Throwable throwable) { // route:哪个服务出现了问题 // throwable: 服务出现的问题是什么 return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; // 响应的错误信息状态 } @Override public int getRawStatusCode() throws IOException { return 200; // 响应的错误状态码 } @Override public String getStatusText() throws IOException { return "OK"; // 文本 } @Override public void close() { // 无需管他 } @Override public InputStream getBody() throws IOException { // 响应体中需要指定的据体数据 return new ByteArrayInputStream("fallback".getBytes()); } @Override public HttpHeaders getHeaders() { // 设置响应头信息 HttpHeaders headers = new HttpHeaders(); >headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }