1.Zull的工作原理
-
Zuul的底层是通过各种Filter来实现的,Zuul中的Filter按照执行顺序分为了以下几种,当各种Filter出现了异常,请求会跳转到ErrorFilter,然后再经过PostFilter,最后返回结果,如果是PostFilter出现异常,那么也会走ErrorFilter,然后直接响应
-
PreFilters:前置Filter,负责鉴权、解包、解码、验签等,customFilters自定义Filter,一般自定义的都是前置Filter,当然也可以自定义其他Filter
-
RoutingFilters:路由Filter,负责分发请求
-
PostFilters:后置Filter,一般负责在数据返回页面之前对数据进行处理,或者是设置返回值内容
-
ErrorFilters:异常Filter,发生异常时进入异常Filter
-
-
正常流程:
-
请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器,而后返回响应
-
-
异常流程:
-
整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户
-
如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回
-
如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了
-
2.ZuulServlet&ZuulFilter
2.1.ZuulServlet
-
ZuulServlet:Zull其根本就一个Servlet,我们可以通过ZuulServlet查看到,Zull原理流程就是在Zull此Servlet中进行控制
2.2.ZuulFilter
-
Zuul提供了一个抽象类ZuulFilter,所有其他Filter都是通过继承ZuulFilter实现自己的过滤器功能
-
filterType :是用来指定filter的类型的,例如返回pre那么就是前置filter
-
filterOrder :是filter的执行顺序,越小越先执行
-
shouldFilter :是其父接口IZuulFilter的方法,用来决定run方法是否要被执行
-
run :是其父接口IZuulFilter的方法,该方法是Filter的核心业务方法
-
3.自定义Filter
-
定义一个自定义的前置Filter,用于在Zull网关层做登录拦截
-
如果请求头中包含Token,那么放行
-
如果请求头中不包含Token,那么拦截,并返回提示信息
-
-
第一步:继承ZuulFilter,实现自定义拦截器
package cn.itsource.filter; import com.alibaba.fastjson.JSON; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; /** * @BelongsProject: springcloudnetflix-parent * @BelongsPackage: cn.itsource.filter * @Author: Director * @CreateTime: 2022-07-29 11:39 * @Description: 使用自定义的前置Filter,做登录校验 * @Version: 1.0 */ @Component // 必须交给spring管理,否则不能生效 public class LoginCheckFilter extends ZuulFilter { // filter类型,可以通过父类查看返回值信息示例,例如:pre、post @Override public String filterType() { return "pre"; } // filter的执行顺序,数字越小,执行时机越高 @Override public int filterOrder() { return 0; } // 是否执行run方法,true:执行/false:不执行 @Override public boolean shouldFilter() { // 1.获取请求地址 HttpServletRequest request = RequestContext.getCurrentContext().getRequest(); String uri = request.getRequestURI(); // 2.判断请求路径是否以login/register结尾,包含就不执行登录业务校验,所以返回false if (uri.endsWith("login") || uri.endsWith("register")){ return false; } // 3.如果不包含那么说明用户访问的资源是受保护的资源,那么需要执行登录校验 return true; } // 此自定义filter的核心业务方法执行地 @Override public Object run() throws ZuulException { // 1.获取到请求头,响应头 HttpServletRequest request = RequestContext.getCurrentContext().getRequest(); HttpServletResponse response = RequestContext.getCurrentContext().getResponse(); response.setContentType("application/json;charset=utf-8"); // 2.判断是否包含token,如果不包含,那么需要阻止执行后面的filter,并且还要返回错误信息,提示用户登录 String token = request.getHeader("token"); // 2.1.判断token为空,那么需要提示用户,并且阻止filter执行 if (!StringUtils.hasLength(token)){ try { // 1.设置响应内容 PrintWriter writer = response.getWriter(); HashMap<String, Object> map = new HashMap<>(); map.put("success", false); map.put("message", "登录校验失败,请重新登录!"); String data = JSON.toJSONString(map); writer.write(data); // 2.阻止后续filter执行 RequestContext.getCurrentContext().setSendZuulResponse(false); } catch (IOException e) { e.printStackTrace(); } } // 如果要放行,这里返回null即可,不用管 return null; } }
-
第二步:通过访问网关查看登录校验是否成功
-
第三步:通过postmain传入token,查看登录校验是否成功
2、Zuul的熔断器配置
-
Zuul作为服务网关面向的是客户端/客户端,当服务调用链路出现异常,我们不希望直接把异常信息抛给客户端,而是希望触发降级,返回友好的提示信息,所以我们需要去配置zuul的熔断机制
-
在Zuul中要实现熔断功能需要实现ZuulFallbackProvider接口,该接口提供了两个方法
-
getRoute():用来指定熔断功能应用于哪些路由的服务
-
fallbackResponse():熔断功能时执行的方法,用来返回托底数据
-
public interface FallbackProvider { /** * The route this fallback will be used for. * @return The route the fallback will be used for. */ public String getRoute(); /** * Provides a fallback response based on the cause of the failed execution. * * @param route The route the fallback is for * @param cause cause of the main method failure, may be <code>null</code> * @return the fallback response */ ClientHttpResponse fallbackResponse(String route, Throwable cause); }
-
第一步:在网关层面对pay-server服务进行降级,服务调用失败触发熔断,返回提示信息
import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; import org.springframework.stereotype.Component; @Component public class PayServerFallback implements FallbackProvider { // 得到日志对象 private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class); // 指定要处理的服务。 @Override public String getRoute() { return "pay-server"; //"*"代表所有服务都有作用 } /** * @param route :服务的路由 * @param cause : 异常 * @return ClientHttpResponse:熔断后的换回值 */ @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { 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("抱歉,服务不可用".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
3、Zuul的参数配置
1.超时配置
-
Zuul集成了Hystrix,如果服务的调用链过长,或者Ribbon调用时间过长,可能会触发Hystrix的熔断机制,导致请求拿不到正常的结果,我们通常会对Ribbon和Hystrix的超时时间配置,如下配置对所有消费者微服务都有用
zuul: retryable: true #是否开启重试功能 ribbon: MaxAutoRetries: 1 #对当前服务的重试次数 MaxAutoRetriesNextServer: 1 #切换相同Server的次数 OkToRetryOnAllOperations: false # 对所有的操作请求都进行重试,如post就不能重试,如果没做幂等处理,重试多次post会造成数据的多次添加或修改 ConnectTimeout: 3000 #请求连接的超时时间 ReadTimeout: 3000 #请求处理的超时时间 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 9000 #如果配置ribbon的重试,hystrix的超时时间要大于ribbon的超时时间
2.Zuul的饥饿加载
-
在学习Ribbon的章节我们为了让服务加快第一次调用,我们可以通过设置Ribbon的饥饿加载,Zuul底层通过Ribbon实现负载均衡器,所以也需要指定饥饿加载
-
需要注意的是,Zuul是通过读取路由配置来实现饥饿加载的,所以如果要让eager-load.enabled: true起作用,我们一般不会使用默认的路由方式,而是单独配置路由规则
zuul: ribbon: eager-load.enabled: true # 饥饿加载