原文链接:http://www.dubby.cn/detail.html?id=9009

网关(Gateway)

首先需要考虑,为什么会有网关这个东西呢?他是个什么东西?

经过之前的学习,我们脑海中复现的整个Spring Cloud大概是有服务注册中心(Eureka Server),服务(Eureka Client,Ribbon,Feign,Hystrix等),其中服务之间互相调用,呈网状结构,大致如下图所示:

我们先考虑一下负载均衡这一层,一般这一层都会是一些硬件负载均衡或者软件负载均衡组成,常见的有F5、Nginx(或者Openresty,支持Lua扩展)等。这有一个缺点,就是路由需要手动配置,或者是写个扩展,提供一个管理后台来配置路由关系,其实本质是一样的。这个到后期往往比较难以维护(并不是不可维护,其实这种方式我所知道的很多公司都是这么做的)。

再考虑一个问题,如果你的系统需要用户登录才可以访问某些服务,那么用户的登录状态解析该由谁来负责呢?你的请求希望加密,这种逻辑该由谁来做呢?我们可以在每个服务里做,用户登录态解析,做请求加密。不过,缺点很明显,不统一以后逻辑改变,或者升级难度太大,需要每个服务来配合(这点笔者深有体会)。

为了解决上述问题,API网关应运而生。这是一个景点的Facade模式,是整个微服务系统的门面。他是怎么解决的这些问题的呢?

  • 和Eureka结合,自动维护服务信息,减少手工配置
  • 对于签名,安全校验等,可以使用Zuul提供的Filter机制实现

快速入门

搭建项目

前提是你已经在本地运行起来eureka-server,demo-service,ribbon-consumer或者feign-consumer。

名字就叫Zuul吧,依赖如下:

    <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.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

在启动类上修饰:

@EnableZuulProxy
@SpringCloudApplication
public class Application {
    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }
}

创建application.properties:

spring.application.name=api-gateway
server.port=5555

zuul.routes.a.path=/a/**
zuul.routes.a.url=ribbon-consumer

zuul.routes.b.path=/b/**
zuul.routes.b.url=feign-consumer

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

然后启动。

面向服务的路由

你访问http:localhost:5555/a/hello的时候,这个URL符合/a/**的规则,所以被转发给ribbon-consumer,这个服务实例的信息会从eureka-server上获取,不需要自己配置服务信息。

通过这种方式,我们极大的减少配置信息,让维护变得简单。

请求过滤

@Component
public class AccessFilter extends ZuulFilter {

    private static final Logger logger = 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() {
        logger.info("AccessFilter run");

        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest httpServletRequest = requestContext.getRequest();

        logger.info(httpServletRequest.getRequestURI());

        Object accessToken = httpServletRequest.getParameter("accessToken");
        if (accessToken == null) {
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(401);
            return null;
        } else {

        }

        logger.info("access token ok");
        return null;
    }
}

简单解释一下上面的代码:

  • filterType:过滤器的类型,pre代表请求在被路由之前执行
  • filterOrder:过滤器的执行顺序,根据这个返回值来依次执行
  • shouldFilter:判断是否需要被过滤
  • run:过滤的具体逻辑,这里通过requestContext.setSendZuulResponse(false);来过滤请求,不对骑进行路由,通过requestContext.setResponseStatusCode(401);设置返回状态码,也可以通过requestContext.setResponseBody(body);来设置内容

这样的话,你需要访问http:localhost:5555/a/hello?token=abc才能正确返回,不然的话,只能得到一个401的错误码。

再谈服务路由

忽略服务

上文提过的,面向服务的路由可以这么配置:

zuul.routes.a.path=/a/**
zuul.routes.a.url=ribbon-consumer

zuul.routes.b.path=/b/**
zuul.routes.b.url=feign-consumer

但是,其实服务的serviceId,Zuul是可以通过eureka-server拿到的,服务信息也是可以拿到的,所以Zuul提供了另一种更为方便的路由规则,也就是path的前缀使用serviceId,假设serviceId为hello-service:

zuul.routes.hello-service.path=/hello-service/**
zuul.routes.hello-service.url=hello-service

对于这样的配置,其实就是Zuul默认的路由规则,也就是说我们根本不需要配置这一条,Zuul也可以帮我们实现。

这样会有个问题,有部分服务不希望对外开放,也就是不希望被路由到,那么我们可以选择忽略他,zuul.ignored-services=*的意思是忽略所有的路由,也就是禁用了Zuul帮你自动路由,也可以配置serviceId,多个用逗号隔开。

服务路径匹配

上面用的一直都是zuul.routes.hello-service.path=/hello-service/**这种形式的路径,那么这个代表什么意思呢?

通配符说明
?匹配任意单个字符
*匹配任意数量的字符
**匹配任意数量的字符,支持多及目录

忽略表达式

还可以设置一个被路由忽略的表达式,也就是不被路由,那就是zuul.ignored-patterns

如果你不希望/hello被路由,你可以这么设置:

zuul.ignored-patterns=/**/hello/**

这个配置是会对所有的路由生效的,所以不要忽略了不该忽略的URL。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐