第6章 微服务的大门谁来守

截止目前,我们已经构建了Eureka注册中心,customer和order微服务,并且创建了配置中心,让微服务可以远程获取配置中心的配置项,同时,我们还研究了微服务之间是如何不通过域名来进行服务间调用。本章我们要通过外部来调用微服务,在本章,我们要为我们的微服务系统树起一道大门,使用Spring Cloud Netflix的Zuul构建一道网关。

服务网关最基本的功能是用来作为服务客户端和被调用服务之间的中介,为微服务系统添加网关,可以让服务的客户端请求只直接与网关服务进行交互对话,至于具体的服务接口则由网关根据配置规则进行自动选择。有了网关服务,所有的客户端请求都应该流经网关服务。

6.1 使用Spring Cloud Zuul构建基础网关

首先,我们创建一个Spring Boot的工程,在pom文件中包含如下内容:

注意:这里的spring-cloud.version是Greenwich.RELEASE。Finchley.SR2的zuul有Bug,可以自行修改测试下。

<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <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>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

</dependencies>
<dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>

</dependencyManagement>

在application.properties中添加下面的配置项:

Eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
spring.cloud.config.fail-fast=true
spring.application.name=gateway
server.port=8003
spring.cloud.config.uri=http://localhost:8888
spring.cloud.config.name=gateway
spring.cloud.config.profile=dev
management.endpoints.web.exposure.include=*
spring.main.allow-bean-definition-overriding=true

最后,在启动类添加@EnableZuulProxy(另一个@EnableZuulServer注解只会创建一个Zuul服务器,不会加载Zuul的任何过滤器,本系列不涉及。),启动项目。

启动之前我们创建的customer,order,eureka-server。打开Eureka的注册页面。可以看到如图6.1所示的信息。

1336be7bed6b33ea030b6449e4067527.png

图6.1 加入了网关后的Eureka注册页面

网关作为系统中的一个微服务存在。同时,它又可以独立于其他微服务存在,作为其他微服务的前置来进行服务访问,将Zuul作为一个微服务注册道Eureka Server上,开发人员可以通过Eureka Server动态地进行服务实例的新增和删除,在这个过程中,不需要对Zuul进行任何修改以及配置,Zuul在不断与Eureka Server进行通讯的过程中,会获取到最新的实例信息,并在Zuul中进行缓存。

我们之前在order中提供了接口/users/{userId},这个接口可以通过RIbbon进行服务间的访问,并且返回用户信息。直接通过order微服务访问的话,完整地址是:http://localhost:8002/users/{userId}。现在我们要通过网关访问这个接口,地址应该是http://localhost:8003/order/users/{userId}。浏览器中访问,可以看到如图6.2所示的结果。

6f469717c5693880abd11169e4733b7c.png

图6.2 通过网关访问order微服务接口

6.2 Zuul的一些常用配置

6.2.1 自定义请求路径

当浏览器中输入http://localhost:8003/order/users/100001时,Zuul能够通过微服务在Eureka中注册的instanceId访问到对应的微服务。但有时候,我们会要求对这个服务名进行隐藏,比如用o代替order这个instanceId。这时候,只需要在配置文件中添加如下配置:

zuul.routes.order=/o/**

重启网关,浏览器中输入http://localhost:8003/o/users/10000,请求会被路由到order微服务的对应接口,可以看到如图6.3所示的结果。

8bcaa99a3d9fc378e566f936d2ae6749.png

图6.3 自定义请求路径访问

注意:这个时候,http://localhost:8003/order/users/100001
也是可以正常访问的。同时,routes端点可以用来查看当前Zuul的路由规则,这个端点是Zuul独有的。

同时,访问routes端点可以看到网关当前的映射关系,如图6.4所示。

681c593547ef18b75ea8d04b397fa30c.png

图6.4 Zuul的routes端点

网关作为多个微服务的前置服务,不但要对接收到的请求进行拦截、转发,同时,还可以对某些特定的服务进行忽略,这个配置需要使用配置项zuul.ignored-services。我们在网关的配置文件中添加配置项:

zuul.ignored-services=order

重启服务,查看网关服务的 routes 断点,可以看到图6.5的结果。

e1fb6119def6a4aad5ebe1a9379a5058.png

图6.5 过滤掉 order 服务的默认路由配置

此时,http://localhost:8003/order/users/100001不能正常访问,但是http://localhost:8003/o/users/100001可以正常获取结果。

zuul.ignored-services可以直接配置成*,然后通过zuul.routes.xxx=yyy来配置生效的动态路由,这种方式类似于我们平常开发中的『白名单』。

最后,再介绍一个比较有用的配置项:zuul.prefix,这个配置主要用来为服务调用添加前缀。比如我们想通过
http://localhost:8003/api/o/users/100001来访问服务,那么就可以在网关的配置文件中添加配置项:

zuul.prefix=/api

可以看到网关的routes 端点变成了图6.6的样子。

ca78f7a859d19d98ec9108b6d930f9fe.png

图6.6 为所有的网关调用添加接口前缀

6.2.2 配置静态 URL 路由

还有一种比较常见的需求,在某些场景下,我们可能需要将某个请求路由到一个非本系统的固定地址上去,这个地址不注册在本系统的
Eureka Server 上,所以不能通过前面讲到的方式去配置。

比如我们要在请求 http://localhost:8003/api/baidu/xxx时路由到地址
https://www.baidu.com/s?wd=xxx。就可以使用本节要讲到的静态 URL
路由配置。在网关的配置文件中添加如下配置:

zuul.routes.user.path=/baidu/**
zuul.routes.user.url=http://localhost:8002/users/

重启网关,配置生效后,查看网关routes端点,可以看到如图6.7所示结果:

769011d1afb441042e29f772867c75cd.png

图6.7 配置静态URL 路由后查看 routes 端点

在浏览器地址栏输入地址http://localhost:8003/api/user/123,会自动跳转到http://localhost:8002/users/123。通过这样的配置,就可以让某类请求直接路由到对应的外部请求接口上。

6.2.3 服务超时

Zuul 使用了 Hystrix 和
Ribbon来防止长时间运行的服务调用影响到网关的整体性能。默认情况,对于任何一个需要运行超过1s的请求来说,会触发 Hystrix 超时,调用将被禁止并且返回 HTTP 500的错误。当然,在某些时候,这个1s 有点短,我们就可以通过
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来自定义Hystrix
的的超时时间。

比如hystrix.command.default.execution.isolation.thread.timeoutInMillisecond=3000,就将
Hystrix 的默认超时时间改成了3s。这里改的是 Hystrix的全局默认超时时间,某些时候,我们只需要对某些特定的服务进行超时时间自定义,只需要使用服务在 Eureka 中注册的instanceId替换上面配置项中的 default即可。比如hystrix.command.order.execution.isolation.thread.timeoutInMillisecond=3000便是将
order 服务的 Hystrix 超时时间修改成了3s,其他服务还是默认的1s。

上面的超时时间是 Hystrix 的默认熔断时间(熔断这个概念我们会在后面专门讲述),Ribbon的调用同样存在超时可能。Ribbon 的调用超时默认为5s(对于微服务来说,5s 其实已经很长了。所以这个配置项不建议大家修改。)。可以使用 serviceId.ribbon.ReadTimeout=10000来配置网关的 Ribbon 超时时间为10s。

6.3 过滤器

网关作为所有的服务的入口必经服务,在提供路由基本路由功能的前提下,更重要的一个功能是可以很方便地编写针对所有流经网关服务的自定义逻辑,这个时候,才是网关发挥真正威力的时候。一般来说,这种自定义逻辑类似于一个“横切面”,可以方便地对安全性、日志进行控制,甚至于对所有服务进行跟踪。

在Zuul中一般使用过滤器实现这种自定义逻辑。

Zuul支持以下四种类型的过滤器。

  • pre过滤器:在Zuul将请求实际发送到目的微服务之前调用。前置过滤器多用来进行安全控制。
  • post过滤器:在目标微服务被调用后还没将响应返回客户端时调用。后置过滤器一般用来处理返回数据,可以对数据进行一些特定修改。
  • route过滤器:在目标微服务被调用前调用。一般用来实现自定义的路由策略,可以用来实现动态路由,实现灰度路由等功能。
  • error过滤器:在处理请求发送错误时被调用。

我们常用主要是pre、post和route三种过滤器。

通过下面一段代码可以看到如何定义不同类型的过滤器:

package cn.com.hanbinit.gateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

@Component
public class CustomFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        System.out.println("preFilter...");
        return null;
    }
}

在Zuul中创建filter,一定要先继承ZuulFilter类,并实现上面代码中的四个方法。不同的类型主要取决于filterType的返回值。可以通过查看com.netflix.zuul.ZuulFilter中的filterType方法注释来了解具体的支持类型。

to classify a filter by type. Standard types in Zuul are “pre” for pre-routing
filtering, “route” for routing to an origin, “post” for post-routing filters, “error” for error handling. We also support a “static” type for static responses see StaticResponseFilter.Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
@return A String representing that type.

通过注释可以看到,这里通过返回pre,route,post,以及和static来分别指定不同的拦截器类型。

shouldFilter方法根据返回值来确认当前的拦截器是否生效,这里可以通过外部的配置项来指定,也可以加入一些特殊的业务逻辑来判断是否要启用当前拦截器。

filterOrder返回int数字,标识当前拦截器的优先级,数字越小,级别越高。

最后的run方法中要实现的便是拦截器的自定义逻辑了。开动大脑,这里可以干的事情太多太多。

6.4 小结

本节简单介绍了Spring Cloud Zuul,作为微服务系统的网关,Zuul通过一些列的拦截器可以完成很多的工作。新版本的Spring Cloud中推荐使用的Spring Cloud Gateway 后面再单独聊。

发表评论

电子邮件地址不会被公开。 必填项已用*标注