在微服务架构中,不同的微服务可以有不同的网络地址,各个微服务之间通过互相调用完成用户请求,客户端可能通过调用N个微服务的接口完成一个用户请求。因此,在客户端和服务端之间增加一个API网关成为多数微服务架构的必然选择。
在个推的微服务实践中,API网关也起着至关重要的作用。一方面, API网关是个推微服务体系对外的唯一入口 ;另一方面, API网关中实现了很多后端服务的共性需求,避免了重复建设 。
个推微服务主要是基于Docker和Kubernetes进行实践的。 在整个微服务架构中,最底层的是个推私有部署的Kubernetes集群,在集群之上,部署了应用服务。
个推的应用服务体系共分为三层,最上一层是网关层,接着是业务层,最下面是基础层服务。 在部署应用服务时,我们使用了Kubernetes的命名空间对不同产品线的产品进行隔离。除了应用服务外, Kubernetes集群上还部署了Consul来实现配置的管理、Kube-DNS实现服务注册与发现,以及一些辅助系统来进行应用和集群的管理。
下图是个推微服务体系的架构图。
1. 要支持配置多个产品,为不同的产品提供不同的端口;
2. 动态路由;
3. URI的重写;
4. 服务的注册与发现;
5. 负载均衡;
6. 安全相关的需求,如session校验等;
7. 流量控制;
8. 链路追踪;
9. A/B Testing。
在对市面上已有的网关产品进行调研后,我们的技术团队发现,它们并不太适合应用于个推的微服务体系。第一,个推配置的管理都是基于Consul实现的,而大部分网关产品都需要基于一些DB存储,来进行配置的管理;第二,大部分的网关产品提供的功能比较通用,也比较完善,这同时也降低了配置的复杂度以及灵活性;第三,大部分的网关产品很难直接融入到个推的微服务架构体系中。
最终, 个推选择使用了OperResty和Lua进行自研网关 ,在自研的过程中,我们也借鉴了其他网关产品的一些设计,如Kong和Orange的插件机制等。
个推的API网关的插件设计如下图所示。
OpenResty对请求的处理分为多个阶段。 个推API网关的插件主要是在Set、Rewrite、Access、Header_filter、Body_filter、Log这六个阶段做相应的处理 ,其中,每一个插件都可以在一个或多个阶段起到相应的作用。在一个请求到达API网关之后,网关会根据配置为该请求选择插件,然后根据每个插件的规则,进一步过滤出匹配规则的插件,最后对插件进行实例化,对流量进行相应的处理。
我们可以通过举例来理解这个过程,如上图所示,
localhost:8080/api/demo/test/hello这个请求到达网关后,网关会根据host和端口确定产品信息,并提取出URI(/api/demo/test/hello),然后根据产品的具体配置,筛选出需要使用的插件——Rewrite_URI、Dyups和Auth,接下来根据每个插件的规则配置进行过滤,过滤后,只有Rewrite_URI和Dyups两个插件被选中。之后实例化这两个插件,在各个阶段对请求进行处理。请求被转发到后端服务时,URI就被rewrite为“/demo/test/hello”,upstream也被设置为“prod1-svc1”。请求由后端服务处理之后,响应会经网关返回给客户端,这就是整个插件的设计和工作的流程。为了优化性能,我们将插件的实例化延缓到了请求真正开始处理时,在此之前,网关会通过产品配置和规则,过滤掉不需要执行的插件。从图中也可以看出,每个插件的规则配置都很简单,并且没有统一的格式,这也确保了插件配置的简单灵活。
网关的配置均为热更新,通过Consul和Consul-Template来实现,配置在Consul上进行更新后,Consul-Template会将其实时地拉取下来,然后通过以下两种方式进行更新。
(1)通过调用Update API,将配置更新到shared-dict中。
(2)更新配置文件,利用Reload OpenResty实现配置文件的更新。
1.动态路由
动态路由主要涉及到三个方面:服务注册、服务发现和请求转发。
如下图所示,服务的注册和发现是基于Kubernetes的Service和Kube-DNS实现的,在Consul中,会维持一个服务的映射表,应用的每一个微服务都对应Kubernetes上的一个Service,每创建一个Service都会在Consul上的服务映射表中添加一项(会被实时更新到网关的共享内存中)。网关每收到一个请求都会从服务映射表中查询到具体的后端服务(即Kubernetes中的Service名),并进行动态路由。Kube-DNS可以将Service的域名解析成Kubernetes内部的ClusterIP,而Service代理了多个Pod,会将流量均衡地转发到不同的Pod上。
2.流量控制
流量控制主要是通过一个名为“Counter”的后端服务和网关中的流控插件实现的。 Counter负责存储请求的访问次数和限值,并且支持按时间维度进行计数。 流控插件负责拦截流量,调用Counter的接口进行超限查询,如果Counter返回请求超限,网关就会直接拒绝访问,实现限次的功能,再结合时间维度就可以实现限频的需求。同时流控插件通过输出日志信息到fluent-bit,由fluent-bit聚合计次来更新Counter中的计数。
3.链路追踪
整个微服务体系的链路追踪是基于分布式的链路追踪系统Zipkin来实现的。通过在网关安装Zipkin插件和在后端服务中引入Zipkin中间件,实现最终的链路追踪功能。具体架构如下图所示。
4. A/B测试
在A/B测试的实现中,有以下几个关键点:
(1)所有的策略信息都配置在Consul上,并通过Consul-Template实时生效到各个微服务的内存中;
(2)每条策略均有指明,调用一个微服务时应调用A还是B(默认为A);
(3)网关中实现A/B插件,在请求到达网关时,通过A/B插件配置的规则,即可确定请求适用的A/B策略;
(4)网关会将请求适用的A/B策略通过URL参数传递下去;
(5)每个微服务通过传递下来的策略,选择正确的服务进行访问。
下图给出了两种场景下的调用链路。
以上就是个推微服务网关的设计和主要功能的实现。之后,个推的技术团队会不断提升API网关的弹性设计,使其能够在故障出现时,缩小故障的影响范围;同时,我们也会继续将网关与DevOps平台做进一步地结合,以确保网关在迭代更新时,能够有更多的自动化测试来保证质量,实现更快速地部署。