作者:个推应用平台基础架构高级研发工程师 阿飞
在微服务架构中,不同的微服务可以有不同的网络地址,各个微服务之间通过互相调用完成用户请求,客户端可能通过调用 N 个微服务的接口完成一个用户请求。因此,在客户端和服务端之间增加一个 API 网关成为多数微服务架构的必然选择。
在个推的微服务实践中,API 网关也起着至关重要的作用。一方面,API 网关是个推微服务体系对外的唯一入口;另一方面,API 网关中实现了很多后端服务的共性需求,避免了重复建设。
个推微服务网关的设计与实现
个推微服务主要是基于 Docker 和 Kubernetes 进行实践的。在整个微服务架构中,最底层的是个推私有部署的 Kubernetes 集群,在集群之上,部署了应用服务。
个推的应用服务体系共分为三层,最上一层是网关层,接着是业务层,最下面是基础层服务。在部署应用服务时,我们使用了 Kubernetes 的命名空间对不同产品线的产品进行隔离。除了应用服务外,Kubernetes 集群上还部署了 Consul 来实现配置的管理、Kube-DNS 实现服务注册与发现,以及一些辅助系统来进行应用和集群的管理。
下图是个推微服务体系的架构图。
个推对 API 网关的功能需求主要有以下几方面:
要支持配置多个产品,为不同的产品提供不同的端口;
动态路由;
URI 的重写;
服务的注册与发现;
负载均衡;
安全相关的需求,如 session 校验等;
流量控制;
链路追踪;
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 平台做进一步地结合,以确保网关在迭代更新时,能够有更多的自动化测试来保证质量,实现更快速地部署。