zuul简介
Zuul 是Spring Cloud 子项目Spring Cloud Netflix的一个组件,它是Netflix对ApiGateway实现的一份答卷,应用非常广泛。常见的功能如下
- 身份验证
- 压力测试
- Canary Testing
- 动态路由
- 安全控制
- ...............
zuul实例
OK,既然标题提到zuul动态路由,此文章主要介绍下zuul动态路由,那么我们写个小demo看看zuul
- 1、首先创建对应的pom文件
-
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
- 2、其次对应的主应用
@EnableZuulProxy
注解开启Zuul -
@EnableZuulProxy @EnableEurekaClient @SpringCloudApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } @Bean public PreFilter preFilter() { return new PreFilter(); } @Bean public RoutingFilter routingFilter() { return new RoutingFilter(); } @Bean public PostFilter postFilter() { return new PostFilter(); } @Bean public ErrorFilter errorFilter() { return new ErrorFilter(); } }
3、application.properties
中配置Zuul应用的基础信息,如:应用名、服务端口等。-
spring.application.name=api-gateway server.port=9091
- 到此简单的zuul就可以跑了,但是若需要用到微服务中那么还需如下配置
路由服务
#反响代理配置
#这里的配置类似nginx的反响代理
#当stripPrefix=true的时候 (http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/user/list)
#当stripPrefix=false的时候(http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/api/user/list)
zuul.routes.api.path=/api/**
#代理前缀默认会从请求路径去除
zuul.routes.api.stripPrefix=false
zuul的其他配置
#提高超时配置(有的同学碰到第一次代理时都是timeout,需要配置如下配置)
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds:60000
ribbon.ConnectTimeout: 3000
ribbon.ReadTimeout: 60000
服务过滤
在服务网关中定义过滤器只需要继承zuulfltier抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。
比如下面的例子,定义了一个Zuul过滤器,实现了在请求被路由之前检查请求中是否有accessToken
参数,若有就进行路由,若没有就拒绝访问,返回401 Unauthorized
错误。
public class AccessFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(AccessFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
Object accessToken = request.getParameter("accessToken");
if(accessToken == null) {
log.warn("access token is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
log.info("access token ok");
return null;
}
}
自定义过滤器的实现,需要继承ZuulFilter
,需要重写实现下面四个方法:
filterType
:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:pre
:可以在请求被路由之前调用routing
:在路由请求时候被调用post
:在routing和error过滤器之后被调用error
:处理请求时发生错误时被调用
filterOrder
:通过int值来定义过滤器的执行顺序shouldFilter
:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效。run
:过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)
令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)
设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)
对返回body内容进行编辑等。
在实现了自定义过滤器之后,还需要实例化该过滤器才能生效,还需要在应用主类中增加四个过滤器的实例化。
为什么服务网关是微服务架构的重要部分?
- 不仅仅实现了路由功能来屏蔽诸多服务细节,更实现了服务级别、均衡负载的路由。
- 实现了接口权限校验与微服务业务逻辑的解耦。通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。
- 实现了断路器,不会因为具体微服务的故障而导致服务网关的阻塞,依然可以对外服务。
问题总结
1、第一次代理请求是连接超时(timeout) 需要配置 进行如下配置
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds:60000
ribbon.ConnectTimeout: 3000
ribbon.ReadTimeout: 60000
2、com.netflix.discovery.TimedSupervisorTask(线程池拒绝策略异常),相信很多人都碰到了这个问题,度娘了有的说不影响,确实是不影响,但是程序员都有强迫症。
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@427829d8 rejected from java.util.concurrent.ThreadPoolExecutor@5f0345ff[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048) ~[na:1.7.0_79]
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821) [na:1.7.0_79]
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372) [na:1.7.0_79]
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:110) ~[na:1.7.0_79]
at com.netflix.discovery.TimedSupervisorTask.run(TimedSupervisorTask.java:62) ~[eureka-client-1.4.11.jar:1.4.11]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_79]
at java.util.concurrent.FutureTask.run(FutureTask.java:262) [na:1.7.0_79]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178) [na:1.7.0_79]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292) [na:1.7.0_79]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_79]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_79]
at java.lang.Thread.run(Thread.java:745) [na:1.7.0_79]
ok废话不多说,直接上解决方案,将对应的eurake版本升级,此问题应该是eurake自带的bug
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version> // 注意关键点在这
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
此问题的参考文档 https://github.com/Netflix/eureka/issues/907