API网关在北科的实践与应用
一、什么是API网关?
1、缘起
一个架构的出现从来都不是空穴来风,API网关作为服务端与客户端之间的重要桥梁,自然必有其独到之处,想要了解API网关,那我们还是先从头说起。
很久以前,我们还只有一个服务,这个服务有一个专享的域名,有一套自己的完整路由体系,为了防止恶意用户攻击,实现了鉴权、限流、熔断、IP黑白名单、监控等一系列的手段来保护自己,这一切都运行良好。
2、问题
直到某一天,我们的服务越做越大,所有的代码都在一个项目里耦合严重不利于维护,我们需要把一个服务拆分成了多个小的服务,于是乎微服务诞生了。
细心的同学可能会发现一些问题,那些与实际业务逻辑无关的代码(鉴权、限流等),是每一个微服务都需要的基础功能,各自实现一遍似乎在重复造轮子,并且多个微服务诞生了多个域名,我们不可能给每个微服务单独申请一个外网域名,这样增加了客户端的复杂度,毕竟客户端不需要关心服务端有多少个微服务,客户端只需要知道路由其实足够了。
3、解决
讲到这里,终于轮到我们的主角API网关出场了。首先我们需要统一流量的入口,所有的微服务流量需要先经过网关,通过网关再反向路由到各个微服务中。其次,各服务的通用功能收敛到网关层实现,避免各自重复造轮子。此时在客户端眼里,各个微服务会被当作一个整体的服务,屏蔽了服务内部细节,统一对外接口输出。
4、网关能力小结
使用API网关有以下优势:
反向代理 / 负载均衡 / 健康检查
统一入口 / 安全防护 / 流量识别 / 流量监控
身份认证
限流熔断
灰度分流
其他通用能力(例如Passport登录校验等)
名称 | Kong |
Zuul |
Spring Cloud Gateway | Tyk |
语言 |
Lua | Java | Java | Go |
高并发 |
基于Openresty | 基于Netty的异步IO | 基Netty的异步IO | 基于Go |
社区star |
30k |
11.2k |
3.1k |
6.6k |
扩展性 |
官方插件 自研插件 |
需要开发 | 过滤器 | 插件 |
存储 |
Postgres Cassandra Yaml |
内存文件 | 编写代码 Yaml 配置中心 |
Redis |
健康检查 |
支持 |
支持 |
支持 | 支持 |
鉴权 |
Key、OAuth2.0 JWT、HMAC Passport(自研) |
需要开发 | 普通鉴权 Oauth2.0 |
Oauth2.0 JWT HMAC |
限流 |
支持 | 需要开发 | 通过ip、用户、路由限流,通过接口开发扩展 | 支持 |
监控 |
Datadog Prometheus zipkin |
需要开发 | gateway metrics filter | 工具套件 |
生命周期 |
开发插件 | 需要开发 | 需要注册服务到eureka | 开发插件 |
安全 |
机器人探测 防攻击 |
需要开发 | 需要开发 | 插件支持 |
可维护性 |
社区活跃 可开发插件 支持UI面板 |
可开发过滤器 | Spring Cloud系列 Gateway资源较少 |
资料较少 支持UI面板 |
注:数据部分来源于网络;
小结:
公司团队对Nginx有比较丰富的维护经验,更偏向基于Openresty相关生态的开源解决方案。Zuul与Spring Cloud Gateway由Java编写,属于Java生态系统的一部分。对于Tyk而言,社区资料相对较少,虽然与Kong相比功能较为齐全,但Kong站在了巨人Nginx的肩膀上,天生拥有高并发特性,从Star数远超其他网关来看,Kong的受众用户更广,生态也更齐全。另外,在网关的选择之初,我们也关注过APISIX,但当时APISIX开源时间较短,组件不够丰富,因此没有纳入考虑。
当然Kong也有缺点,Kong的集群支持需要Postgres或者Cassandra数据库,引入这两种数据库会增加我们的运维成本,因此我们对Kong做了二次开发,引入了MySQL集群模式。其次,Kong的管理界面只有企业版才能使用,这部分需要额外开发支持。最后一点是Kong采用Lua语言开发,想要阅读源码或者开发新的插件都必须学习Lua语言,幸运的是,Lua语言的学习成本较低。
三、Kong是如何运行的
Kong的网关是架构在了Openresty上,Openresty共有11个执行阶段,详见下图:
Kong将自己的代码嵌入进了这11个执行阶段中,从这份Nginx配置文件中,可以看到Kong如何实现了自己的执行计划:
因此想要了解Kong的话,我们只需要按照这份配置文件的顺序,去梳理Kong的源码逻辑即可。比如,在kong.init阶段,完成了配置文件的加载,路由的初始化等,在kong.init_work阶段,完成了全局事件的加载。而我们的插件,基本都执行在了kong.rewrite与kong.access阶段,在这两个阶段中,我们可以对请求进行鉴权,对非法请求进行拦截,可以进行限流处理,甚至可以灵活修改请求头信息,以实现我们的特定逻辑。在kong.header_filter与kong.body_filter阶段,完成对请求头与请求内容的最后过滤。而在最后的kong.log中,可以用来实现监控功能,由于此时已经响应了业务的请求,因此监控并不占用业务请求时间。
上述的源码是Kong在rewrite的阶段执行的,主要工作就是对已加载的插件进行遍历,然后执行各个插件的rewrite方法,其他阶段的插件执行基本也都是这个逻辑。因此简单来说,Kong插件加载就是在Openresty的各个执行阶段,对符合规则的插件进行遍历,然后执行这个节点插件对应的方法。
四、Kong的特性
1、集群扩展性
得益于Kong的集群模式,我们可以简单的添加更多的机器,来支持更高的并发访问。目前共有四种集群模式,分别是Yaml配置文件、Postgres、Cassandra、MySQL(官方不支持MySQL,此处为二次开发)。
Kong的事件轮询机制,会每间隔1s扫描数据库中的表,来动态的更新自己缓存中的路由信息。当某个服务收到了变更路由请求,会在数据库中的cluster_event表中添加一条变更信息,其余服务轮询到该变更信息时,触发自己的变更动作,最终集群里的所有服务路由达到一致状态。
2、插件化
插件化是Kong的另一个重要特性,因为除了基本的路由功能外,几乎所有的其他类似于鉴权、限流、监控等功能,全部都使用了插件来实现。
Kong的开源插件不能完全满足我们的业务需求,为了适配同城的众多业务场景,我们针对HMAC鉴权插件、Rate-Limiting限流插件、IP黑白名单插件、Request-Transformer、Prometheus等插件都做了二次开发,目前线上运行稳定。
此外,为了简化业务同学的研发成本,我们研发了Passport插件与Auth插件。业务代码不需要在每个接口中重复的调用checkLogin接口校验用户是否已登录,这一切都封装在了网关的插件中。
3、多平台
Kong支持在多种基础环境中运行,可以安全运行在我们线上广泛使用的Centos系统中,并且如果我们后期拥抱Docker甚至于Kubernetes,Kong都可以运行良好。
五、Kong的性能
Avg(ms) | TP90(ms) | TP99(ms) | RPS | |
原服务直接访问 | 1.28ms | 1.49ms | 2.60ms | 78554.00 |
纯Nginx Proxy | 1.49ms | 1.72ms | 2.95ms | 67618.42 |
Kong+Yaml | 1.69ms | 2.17ms | 4.05ms | 59823.12 |
Kong+MySQL | 1.77ms | 2.37ms | 4.30ms | 56816.23 |
注1:原服务是Go语言实现的一个mock服务,收到请求后会返回一个大小1k的response body;
注2:网关服务机器配置为8c/16g;原服务机器配置为4c/8g;
注3:压测过程中,关闭了所有的网关插件,仅测试转发性能;
注4:wrk压测参数为 wrk -t4 -c 100 –latency;
六、Kong在北科架构中的应用场景
从下图可以看出,我们目前有两层负载均衡,分别是腾讯云的四层负载均衡与SLB的七层负载均衡。我们在SLB之下,内网服务之上,引入了网关层,以此来为内网服务提供更便捷更安全的保障。
下图中把网关层单独拿出来放大,可以看出对于网关而言,依托于插件机制是非常灵活的。我们可以根据配置文件或者数据库,给网关增加或者减少某种插件,使得网关可以快速的增加或者减少某种能力。
值得一提的是,我们在PIE3.0(PHP集成开发环境)中也引入了对Kong的支持,也就意味着所有升级了PIE3.0的用户,天生拥有以上能力,并且日后对Kong所做的所有插件支持,都可以无缝与PIE3.0对接。
七、Kong的监控
为了监控网关集群与下游业务的运行状态,我们搭建了基于Prometheus存储的Grafana监控面板,主要提供了关于Nginx共享内存监控、Nginx连接相关监控、RPS监控、带宽监控、访问延迟监控等多个维度的监控信息,详见下图:
八、在Kong上碰到的问题及解决
1、Kong的集群模式不支持MySQL
由于我们北科尚未接入Postgres、Cassandra两种数据库,引入两种数据库无疑会大大增加我们的运维成本。因此,我们针对Kong做了二次开发,新增了对MySQL的适配。目前MySQL版本的Kong集群,在Op科技防火墙模块中运行良好。
2、没有合适的管理平台
Kong企业版有出付费版的管理平台,但是收费较贵。
另外调研了两款开源的管理平台 Kong-Dashboard 与 Konga。其中,Kong-Dashboard对我们当前的版本兼容性较差,直接排除。Konga的兼容较好并且界面风格使用起来也更顺畅,但是Konga的权限粒度较粗,并且Konga里有一些配置是我们永远可能都用不到的,会增加研发同学的使用复杂度。
综上所述,我们计划根据自己的管理平台,简化原生配置,增加更细粒度的比如某个按钮级别的权限粒度,保证网关的稳定性。
3、网关在某业务线上线后,线上经常有如下报错:upstream prematurely closed connection while reading response headerfrom upstream
阅读Nginx源码后发现,此报错发生在Nginx的upstream阶段,当对fd描述符使用recv命令时,发现fd对应的对端连接已经关闭,因此Nginx此时不得不放弃当前连接。
检查Java服务的idleTimeout(空闲连接超时)设置为30s,而这个参数在Nginx里配置为60s。当Java发现连接30s超时后,发起四次挥手关闭连接。关闭连接的过程中如果Nginx同时有请求过来,就会造成这个报错。
修复过程:调整Nginx的参数由60s改为25s,使得Nginx方先关闭连接。修改参数后,后续没有相关的报错了。
4、网关在某业务线上线后,使用IP-Restriction黑名名单插件,无法拿到用户的真实IP
此处对IP-Restriction插件做了二次开发,使其可以从HTTP中先后检查头信息中X-Real-Ip、x_forwarded_for、remote_addr对应的值,此时即可以拿到用户的真实IP。
5、Prometheus插件的性能较差
Avg(ms) | TP90(ms) | TP99(ms) | RPS | |
原服务直接访问 | 1.29ms | 1.51ms | 2.82ms | 78307.02 |
纯Nginx Proxy | 1.53ms | 1.85ms | 3.21ms | 65265.46 |
Kong不含插件 |
1.64ms | 2.03ms | 3.60ms | 60877.41 |
Kong+Prometheus 0.6 | 6.68ms | 8.68ms | 13.00ms | 14979.34 |
Kong+Prometheus 0.9 | 1.76ms | 2.25ms | 4.39ms | 57705.56 |
注1:原服务是Go语言实现的一个mock服务,收到请求后会返回一个大小1k的response body;
注2:网关服务机器配置为8c/16g;原服务机器配置为4c/8g;
注3:压测过程中,关闭了所有的网关插件,仅测试转发性能;
注4:wrk压测参数为 wrk -t4 -c 100 –latency;
九、写在最后
API网关作为一款管控流量入口的中间件,已经在公司的多个业务线落地,在一定程度上满足了绝大多数的现有需求。Kong有着不错的扩展能力,使得我们可以通过自研插件作为补充,解决更多的问题,Kong有着活跃的社区,我们从社区中学到了很多的经验,有一些开源插件完美的适配了我们的业务场景,后续我们也会逐步回馈开源社区,相互促进。