Spring Cloud微服务中网关服务是如何实现的?(Zuul篇)

Java框架

浏览数:67

2019-7-23

导读

我们知道在基于Spring Cloud的微服务体系中,各个微服务除了在内部提供服务外,有些服务接口还需要直接提供给客户端,如Andirod、IOS、H5等等。

而一个很尴尬的境地是,如果直接将提供外部接口的微服务暴露给公网,那么意味着为了增强这个微服务的安全性,需要做很多额外的安全性措施,如报文数字签名、加密等;而大部分场景下,微服务本身又是提供给内部其他微服务调用的,即便所有的微服务都会不同程度地直接面向App客户端提供公网服务,那么为了这确保这些微服务的安全性,涉及的微服务也都需要实现接口的安全性,这样不仅会造成重复开发,也会增加微服务体系的复杂性。所以,我们通常会在微服务体系的外部边界架设一个网关服务,俗称Gateway

Gateway这个服务本身不做任何具体的业务逻辑,业务逻辑还是由相应地微服务去实现。因为在微服务开发中,为了简化内部服务之间调用的繁琐程度,大部分实践是不会在微服务内部设防,如微服务本身是不会做报文层面的签名、加密。而在面向外部提供服务时,则由Gateway服务进行统一的安全认证,认证通过后才会把请求路由到具体的微服务

在这种模式下,微服务之间的调用因为都在内部网络,而不直接向外暴露服务接口及IP,所以微服务的安全问题都依赖于内部网络安全。(PS:如果网络被攻破,那么微服务就会直接暴露在攻击者面前,此时调用微服务的接口是可以直接影响业务的,如进行盗刷等,这个问题我们在下一篇文章中讨论!)

当然Gateway除了支持做简单的安全认证,如会话认证外,还具有服务限流,接口监控数据统一上报等其他功能和用途。在基于Spring Cloud的微服务架构体系中,目前提供了两套方案供我们实现Gateway,分别是Netflix的Zuul以及Spring Cloud自身提供的 Spring Cloud Gateway(构建在Spring 5以及Spring Boot 2.0基础之上)。本篇文章的主要内容是基于Zuul来介绍Api Gateway的搭建。

GateWay的位置

在我们具体分析基于Zuul的Gateway实现原理之前,我们先从整体架构上来了解下Gateway在整体微服务体系中所处的位置吧,这样会有利于我们更深刻的理解Gateway在微服务架构体系中的作用。如下图所示:

Gateway是一个处于内部微服务与外部公网请求之间的衔接性服务,它需要通过域名接收到用户客户端通过公网发出的服务请求,然后再路由至内部对应的微服务。因此Gateway本身既处于服务注册中心的管理之下,如注册到Consul,通过Consul来获取其他微服务的地址列表,并进行请求路由转发;又需要在被外部访问的过程中,被诸如Nginx这样的反向代理服务器进行服务代理,从而实现Gateway的负载均衡及高可用。

这里的一个技术细节是,Gateway大部分情况下是通过容器动态进行部署的,这一点与其他Spring Cloud微服务一样是扁平的,但是又因为网关服务的特殊性,其IP端口需要被Nginx识别从而进行反向代理及负载均衡。这里的问题是Nginx如何能够从茫茫的微服务中识别到那些是需要被外部访问,从而进行反向代理的呢?

毕竟如果将Gateway直接部署到固定的IP及端口机器的话,会失去一定的弹性,从架构层面来说会有些蹩脚。作者之前所在的公司是通过将Gateway服务打tag,如需要被外部访问的Gateway服务将其tag标签打成api,这样就可以借助一些工具如consul-template的方式,就可以做一些自动识别的流程来自动进行反向代理了!如果你有更好的实现方式,也欢迎给我留言哦!

到这里,相信你应该对Gateway在微服务架构体系中的位置有一个足够清晰地认识和了解了,下面我们就将重点介绍Zuul的功能与实现原理。

Zuul简介

Zuul是Neflix开源的Api Gateway服务器,它本质上是一个Servlet应用,其核心是通过一系列filters的实现来为整个微服务体系提供路由、安全、监控等边界服务。Zuul目前分为两个大版本Zuul1和Zuul2,它们的区别在于Zuul1的IO模型还是BIO的方式,而Zuul2则是使用NIO对Zuul1进行了重构,所以性能上要优于Zuul1。

正因为Zuul1的IO采用的是BIO,所以在Spring Cloud基于Spring Boot2.0的版本中才自己推出了基于NIO模型的Spring Cloud Gateway来取代Zuul,此时虽然Zuul2已经发布,但是因为发布的时间太晚,所以Spring Cloud对其已经不再默认集成了!

因此如果要升级Zuul的版本至Zuul2的话,你需要将Spring Cloud对应的版本升级到基于Spring Boot2.0的版本,并单独引入Zuul2的版本依赖。而在这种情况下,直接选用Spring Cloud Gateway作为网关服务可能会是一个更合适的选择。以下为基于Spring boot2.0的Spring Cloud版本依赖:

<parent>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-parent</artifactId>
     <version>Finchley.SR3</version>
     <relativePath />
</parent>

而考虑到历史项目原因,目前不少基于Spring Cloud的项目还是构建在基于Spring boot1.0的版本之上,所以在本篇文章中对Zuul的分析还是基于Zuul1的版本,这一点请大家知悉!

Zuul原理

在前面我们提到过Zuul本质上是一个Servlet应用,其核心就是通过定义一系列的filters来对用户的请求进行过滤处理,从而实现一些定制化的边缘服务功能如安全认证,打点监控等。类似于Servlet的Filter,只不过Zuul是提供了一个框架,来实现定义不同类型的filter,并对这些filter进行加载、编译、运行。下面我们来看一下Zuul的核心类图:

因为Zuul本身就是一个Servlet应用,而其要做的事情就是拦截所有用户请求来进行过滤,在Zuul框架中ZuulServlet就是这样一个类型,它类似于SpringMvc的DispatcherServlet,所有最终需要路由到微服务的请求,都会由它进行处理。

ZuulServlet中有3个核心的方法,它们分别是:preRoute(),route(), postRoute()。Zuul对所有request的处理逻辑都在这三个方法里面,而这些方法分别对应了Zuul中定义的几种标准过滤器类型:

  • PRE:这种过滤器会在请求被路由之前调用。我们可以利用这种过滤器类型实现身份认证等前置逻辑;

  • ROUTE:这种过滤器将请求路由到微服务,用于构建发送给微服务的请求。这种类型的过滤器Zuul已经帮我们实现,用于实现Gateway到内部微服务调用的路由、负载均衡、限流等功能;

  • POST:这种过滤器在路由到微服务以后执行。可以用来为响应添加标准的Http Header、收集统计信息和指标,并将响应从微服务发送给客户端;

  • ERROR:在请求处理阶段发生错误时执行该过滤器;

从代码层面看,以上方法的实际执行则是由具体实现了ZuulFilter接口的过滤器来实现的,这些过滤器会返回自身定义的类型如”pre”,这样通过Zuul框架相应的设计就会最终调用到过滤器的对应方法了。(PS:篇幅有限,更细节的实现,需要大家去阅读源码)

Zuul实践示例

使用Zuul搭建一个Gateway非常简单,只需要基于Spring Boot项目,在主类加上注解@EnableZuulProxy即可。如:

@EnableZuulProxy
@SpringBootApplication
@EnableAutoConfiguration(exclude = {RedisAutoConfiguration.class,
        RedisRepositoriesAutoConfiguration.class, DataSourceAutoConfiguration.class,
        MongoAutoConfiguration.class, MongoRepositoriesAutoConfiguration.class})
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

而在具体定制相应的Filter时只需要实现ZuulFilter接口,确定滤器类型后实现run方法就可以了。

public class LoginFilter extends ZuulFilter {
    @Autowired
    private GatewayLoginFilterConfig loginFilterConfig;

    @Override
    public String filterType() {
        return "pre";   //pre在请求真实url之前做处理
    }
    @Override
    public int filterOrder() {
        return 0;
    }
    @Override
    public boolean shouldFilter() {
        return true;
    }
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest req = ctx.getRequest();
        ...   
        return null;
    }

以上就是本文的基本内容了,希望能够让您能有所收获,欢迎转发+点赞!

参考资料:

https://github.com/Netflix/zuul

https://github.com/Netflix/zuul/wiki

http://www.sohu.com/a/221110905_467759

https://www.jianshu.com/p/af1554553b5c

 

作者:无敌的码农