1、Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能。
2、Netflix使用Zuul进行以下操作:
3、Zuul的规则引擎允许任何JVM语言编写规则和过滤器,内置支持Java和Groovy。
4、配置属性zuul.max.host.connections已被两个新属性zuul.host.maxTotalConnections和zuul.host.maxPerRouteConnections取代,它们分别默认为200和20。
5、所有路由的默认Hystrix隔离模式(ExecutionIsolationStrategy)是SEMAPHORE。 如果首选此隔离模式,则可以将zuul.ribbonIsolationStrategy更改为THREAD。
直接拿上面的项目进行修改
1、pom 文件加入相关依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> 复制代码
2、启动类加上注解@EnableZuulProxy,开启zuul的功能:
@SpringBootApplication @EnableDiscoveryClient //开启服务注册客户端 @EnableFeignClients //开启feign client @EnableZuulProxy //开启zuul的功能 复制代码
3、修改配置文件application.yml,加入相关配置:
zuul: routes: api: path: /api/** serviceId: spring-cloud-eureka-pro 复制代码
意思就是以/api/ 开头的请求都转发给spring-cloud-eureka-pro服务
重启项目进行访问
继续修改项目,创建MyZuulFilter 类
@Component public class MyZuulFilter extends ZuulFilter { @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); Object token = request.getParameter("token"); if(token == null) { ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("token is empty"); }catch (Exception e){} }else{ if(!"zuul".equals(token.toString())){ ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("token is error"); }catch (Exception e){} } } return null; } } 复制代码
1、filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
2、filterOrder:过滤的顺序
3、shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
4、run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。
重启项目进行访问
当我们的后端服务出现异常的时候,我们不希望将异常抛出给最外层,期望服务可以自动进行一降级。Zuul给我们提供了这样的支持。当某个服务出现异常时,直接返回我们预设的信息。
我们通过自定义的fallback方法,并且将其指定给某个route来实现该route访问出问题的熔断处理。主要继承FallbackProvider接口来实现,FallbackProvider默认有两个方法,一个用来指明熔断拦截哪个服务,一个定制返回内容。
public interface FallbackProvider { String getRoute(); ClientHttpResponse fallbackResponse(String route, Throwable cause); } 复制代码
实现自己的FallbackProvider 方法:
/** * Zuul 目前只支持服务级别的熔断,不支持具体到某个URL进行熔断。 */ @Component public class MyFallbackProvider implements FallbackProvider { //指定要处理的 service。 //如果要为所有路由提供默认回退,可以创建FallbackProvider类型的bean并使getRoute方法返回*或null @Override public String getRoute() { return "*"; } @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 this.getStatusCode().value(); } @Override public String getStatusText() throws IOException { return this.getStatusCode().getReasonPhrase(); } @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_UTF8); return headers; } }; } } 复制代码
当服务出现异常时,返回“服务不可用”,这的逻辑可以自己定义。
重启项目进行测试,先试一下正常情况
将provider停掉,在试一下
可以看到此时已经对provider进行了熔断处理
有时候因为网络或者其它原因,服务可能会出现暂时不可用,这个时候我们希望可以主动对服务进行重试,Zuul也帮我们实现了此功能,需要结合Spring Retry 一起来实现
pom文件添加依赖
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> 复制代码
修改配置文件中,启用Zuul Retry
zuul: routes: api: path: /api/** serviceId: spring-cloud-eureka-pro retryable: true #是否开启重试功能 ribbon: MaxAutoRetries: 2 #对当前服务的重试次数 MaxAutoRetriesNextServer: 1 #切换相同Server实例的次数 ConnectTimeout: 250 #ribbon重试超时时间 ReadTimeout: 1000 #建立连接后的超时时间 复制代码
将第一个provider项目关闭,第二个provider2项目进行修改并重启
@RequestMapping("/hi") public String hi(@RequestParam String name) { System.out.println(name+"发送了请求。。。"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } return "hi " + name + ",i am from port:" + port; } 复制代码
此时客户端的请求即使被路由到provider2,也需要等待10s才能返回结果,在服务请求端已经超时,所以不会等到它返回结果,就进行下一次的重试了,最终结果
一共是三次打印,第一次请求+两次重试