@coldxiangyu
2017-06-13T09:23:32.000000Z
字数 4329
阅读 2841
WEB框架
前面我们已经介绍了使用Spring Cloud Netflix中的Eureka注册中心进行服务注册与发现,服务间通过Ribbon,Feign进服务消费以及负载均衡,Hystrix断路器,Spring Cloud Config进行统一配置管理。
在实际应用过程中,我们内部微服务往往是不对外暴露的,需要提供专门的对外服务。这时候难以做到已有服务的复用,也没有一个统一的访问权限控制,因此整个服务需要一个大门,也就是我们这篇文章要讲的服务网关Zuul。
Zuul是整个微服务架构重要的组成部分,它负责对外提供统一的REST API,可以对路由策略进行调整,实现负载均衡,还具备权限控制等功能。将一些复杂的非业务逻辑前移,使得服务集群本身具备高可用。
看完这些,你会不会把Zuul联系到Nginx?实际上,Zuul就是Netflix版的Nginx,不过实现方式不同。而Zuul本身是基于Servlet的过滤器的集合,再加上与Spring boot整合之后,处理请求的速度可能没有nginx这种简单设计的来得快,但确实可以作为一款不错的nginx替代品。
下面我们来看一下Zuul的基本用法:
新建模块zuul-gateway,pom配置如下:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.3.5.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>1.8</java.version></properties><dependencies><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></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Brixton.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
应用主类使用@EnableZuulProxy注解开启Zuul:
@EnableZuulProxy@SpringCloudApplicationpublic class Application {public static void main(String[] args) {new SpringApplicationBuilder(Application.class).web(true).run(args);}}
其中,@SpringCloudApplication注解整合了@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker,相当于三个注解的集合。
配置application.properties中配置Zuul应用的基础信息,如:应用名、服务端口等。
spring.application.name=api-gatewayserver.port=5555
只是配置这些还远远不够,我们还要在这里配置路由策略,路由的映射有两种配置方式:
一种是通过跳转url:
# routes to urlzuul.routes.api-a-url.path=/api-a-url/**zuul.routes.api-a-url.url=http://localhost:2222/
这种方式是将所有的/api-a-url/**的请求跳转到http://localhost:2222/,然而这种配置方式不够友好,因为你需要知道服务的地址才能进行配置。
我们推荐通过serviceId进行映射的方式:
zuul.routes.api-a.path=/api-a/**zuul.routes.api-a.serviceId=service-Azuul.routes.api-b.path=/api-b/**zuul.routes.api-b.serviceId=service-Beureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
我们把zuul注册到Eureka Server上去发现服务,然后只需要配置需要跳转的serviceId,具体要跳转的url无需关心。
接下来验证一下我们Zuul配置的路由策略是否生效:
首先启动我们最早搭建的Eureka注册中心,还有compute-service服务,我们将compute-service服务分别以service-A、service-B的服务名称在2222和3333端口启动,然后启动我们的Zuul服务。
打开Eureka注册中心:
可以看到service-A、service-B以及我们的Zuul均已注册成功。
我们首先验证url的配置路由,访问http://localhost:5555/api-a-url/add?a=1&b=2
service-A后台输出:
再验证通过serviceId映射配置,访问http://localhost:5555/api-b/add?a=1&b=2
service-B后台输出:

除此之外,Zuul还有着强大的过滤功能,比如外部访问安全控制。
我们来实现一个Zuul过滤功能,外部必须通过有效的accessToken才能进行服务调用:
首先要继承ZuulFilter,并重写它的四个抽象方法。
public class AccessFilter extends ZuulFilter {private static Logger log = LoggerFactory.getLogger(AccessFilter.class);@Overridepublic String filterType() {return "pre";}@Overridepublic int filterOrder() {return 0;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic 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;}}
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内容进行编辑等。
之后我们需要在启动类中添加@bean,对定义的过滤器进行实例化。
@EnableZuulProxy@SpringCloudApplicationpublic class Application {public static void main(String[] args) {new SpringApplicationBuilder(Application.class).web(true).run(args);}@Beanpublic AccessFilter accessFilter() {return new AccessFilter();}}
重启Zuul服务,再次访问:http://localhost:5555/api-a/add?a=1&b=2,无返回结果:
后台服务日志:

关于Zuul过滤器的功能还有很多,本文暂到此,在后续的实际应用中再单独研究。
本文源码已上传github:https://github.com/coldxiangyu/spring-cloud-demo/tree/master/zuul-gateway