上一篇博客介绍了Zuul网关如何与Hystrix、Apollo、CAT等集成,此篇博客将介绍如何在spring-cloud上使用zuul作为网关,以及zuul网关部署的一些建议。
在Spring cloud中引入zuul网关非常简单,首先在pom.xml文件中引入依赖的jar包(Demo地址)。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
接着自定义四种类型的Filter,在spring cloud中Filter不再是groovy的脚本文件,而是java class。但写法上和groovy中无区别。如下图所示,所有的Filter都继承自ZuulFilter。在run()中编写具体的Filter逻辑。
接着在启动类中增加@EnableZuulProxy,并注入编写的Filter class,即在服务启动时就加载好这些Filter的class。
在application.properties中定义路由规则,如下所示,当访问zuul的服务地址时,实际路由会跳转到localhost:9700服务上。
按照Demo上的提示,启动student服务,student服务监听在9700端口上,此时访问8080端口,即zuul网关所在的服务端口,路由会自动跳转到student的9700端口上。结果如下所示
查看zuul网关服务,打印了相关的信息,这些信息是封装到pre/post/route Filter中,说明各种类型的Filter都生效。
上面是简单的zuul网关路由定义,实际中可以根据api中的url信息,将请求路由到不同的服务上。下面是Zuul路由的一些常见配置。
zuul:
routes:
user-route: # 该配置方式中,user-route 只是给路由一个名称,可以任意起名。
service-id: provider-microservice-user
path: /user/** # service-id 对应的路径
zuul:
routes:
user-route: # 该配置方式中,user-route 只是给路由一个名称,可以任意起名。
url: http://localhost:8000/ # 指定的url
path: /user/** # url对应的路径
zuul:
prefix: /api
strip-prefix: false
routes:
microservice-provider-user: /user/**
访问Zuul的/api/xx/1路径,请求会被转发到micro-service-provider-user的/api/1
如果strip-profix:true,那么请求会被转发到micro-service-provider-user的/1
zuul:
ignored-services: microservice-provider-user, microservice-consumer-movie
#忽略上面的两个服务,只代理其他服务
可以看到,在spring cloud中使用zuul网关非常简单,那么为什么还要学习Netflix原生提供的Zuul呢?第一:学习原生提供的Zuul网关更能透彻理解Zuul工作原理,第二:spring cloud封装后,确实对开发人员非常友好,但是自定义的空间就变小了,第三:spring cloud上不支持Filter的动态加载机制,故项目中还需结合实际来选择使用spring cloud还是原生的zuul。
上面介绍了Spring cloud中如何引入zuul,接着介绍在实际项目中zuul的部署。Zuul作为网关是非常关键的服务,实际项目中也会采用多实例集群部署方式,故在zuul网关前面还需加一层负载均衡机制,常用的是f5加nginx的方式来完成zuul多实例的负载均衡等功能。具体如下图所示 :可以根据业务类型部署不同的网关集群,不同的网关集群管理内部的微服务。另外构建过滤器管理站点,管理各种Filter文件,Zuul原生提供的Filter管理页面非常简陋,实际项目中还需开发自定义的管理平台。IT人员可通过管理平台随时发布过滤器。
因为所有请求都会先经过网关,再转发到内部的微服务上,故可以在网关上集成各种能力,例如:授权认证,服务配置管理,服务注册发现,限流降级,链路监控,日志管理,监控告警等。 这样可完成80%左右的服务治理工作。具体如下图所示,项目中可结合实际选择适配的网关工具和各类服务治理工具。
在网关中路由管理是非常重要的一部分,上面的Demo是个简单的例子,路由直接配置在applicaiton.properties文件上,实际项目中当服务很多时需要统一管理路由信息,另外,在管理路由信息时,不能直接配置内部服务的IP地址,因为服务重启后IP地址可能就改变了,需要配置成服务名称才行,那么在管理路由信息方面有哪些方式呢?
方式一:基于服务注册发现的方式来管理路由信息
所有的服务都会自注册到Eureka上,Zuul网关获取服务信息,Zuul的路由配置信息上直接配置服务名称,而非IP地址。另外,Zuul本身的路由配置信息可以由统一的配置管理工具来管理,例如可以引入Apollo来管理路由配置信息。
方式二:基于域名解析的方式来管理路由信息
服务名称与服务域名间映射管理进行统一的管理,Zuul网关上配置路由信息时,只配置服务名称,定期从管理平台拉取映射关系信息,当读取到服务名称后,先查找映射关系表,转换成服务的域名,再发送到DNS服务器进行解析,转换成服务的IP地址,然后将信息发送到对应的服务器上。具体关系如下所示,另外,路由配置信息也需要配置管理平台统一管理
方式三:通过配置平台管理服务名称与服务地址关系信息
以上就是路由管理的实现方式,前面介绍Zuul时都是按照Zuul1.0的特性介绍的,下面再简要介绍下Zuul2.0的功能。Zuul1.0本质上是一个同步Servlet,采用多线程阻塞模型。即每来一个请求,Servlet容器为该请求分配一个线程负责处理这个请求,直到响应返回客户端,这个线程才会被释放返回容器线程池。如果后台服务调用比较耗时,那么这个线程就会被阻塞,阻塞期间线程资源被占用。Servlet容器线程池的大小是有限制的,当前端请求量大,而后台慢服务比较多时,很容易耗尽容器线程池内的线程,造成容器无法接收新的请求,Netflix为此还专门研发了Hystrix熔断组件来解决慢服务耗尽资源问题。相比较zuul1.0,zuul2.0一个亮点特性是支持异步高并发。异步模式的本质是使用队列Queue(或称总线Bus),如下图所示,可以简单理解为前端有一个队列专门负责处理用户请求,后端有个队列专门负责处理后台服务调用,中间有个事件环线程(Event Loop Thread),它同时监听前后两个队列上的事件,有事件就触发回调函数处理事件。
这种模式下需要的线程比较少,基本上每个CPU核上只需要一个事件环处理线程,前端的连接数可以很多,连接来了只需要进队列,不需要启动线程,事件环线程由事件触发,没有多线程阻塞问题,非阻塞模式下可以接受的连接数大大增加。下图是Zuul2.0的架构概览图
和Zuul1.0相比较,Zuul2.0有两点变化。
- 前端用Netty Server代替Servlet,目的是支持前端异步。后端用Netty Client代替Http Client,目的是支持后端异步。
- 过滤器换了一下名字,用Inbound Filters代替Pre-routing Filters,用Endpoint Filter代替Routing Filter,用Outbound Filters代替Post-routing Filters。
Zuul2为了支持异步高并发模式,代码复杂度上比Zuul1高很多。比如:异步模型没有一个明确清晰的请求->处理->响应的执行流程(call flow),它的流程是通过事件触发,请求处理的流程随时可能被切换断开,内部实现要通过一些关联id机制才能把整个执行流再串联起来,这就给开发调试、运维引入了复杂性。总结而言,异步非阻塞模式比较适用于IO密集型(IO bound)场景,这种场景下系统大部分时间在处理IO,CPU计算比较轻,少量事件环线程就能处理。项目中需结合实际情况来选择使用Zuul1.0还Zuul2.0。Zuul1.0开源时间比较久了,有多个大公司落地案例,相对而言比较可靠、稳定。另外,大部分公司达不到Netflix的量级,Netflix需要应对每日千亿级流量,故它们才研发异步模式,一般公司亿级可能都不到。如果选用Zuul1.0可集成Hystrix进行熔断限流,从而避免后端服务响应太慢造成线程池资源耗尽引发的雪崩效应。