欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Cloud入门之Zuul

程序员文章站 2022-06-13 20:54:47
...

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

网关(Gateway)

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

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

Spring Cloud入门之Zuul

我们先考虑一下负载均衡这一层,一般这一层都会是一些硬件负载均衡或者软件负载均衡组成,常见的有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。