我们只用绿色的食品原料

博鱼体育ap加工厂,只为您的健康着想

前端开发之动态管理Nginx集群的方法

2022-01-13 23:12上一篇 |下一篇

  开源版 Nginx 最为人诟病的就是不具备动态配置、远程 API 及集群管理的能力,而 APISIX 作为 CNCF 毕业的开源七层网关,基于 etcd、Lua 实现了对 Nginx 集群的动态管理。

  微服务架构使得上游服务种类多、数量大,这导致路由规则、上游 Server 的变更极为频率。而 Nginx 的路由匹配是基于

  实现的,一旦 server_name、location 变动,不执行 reload 就无法实现配置的动态变更;

  Nginx 将自己定位于 ADC 边缘负载均衡,因此它对上游并不支持 HTTP2 协议。这增大了 OpenResty 生态实现 etcd gRPC 接口的难度,因此通过 watch 机制接收配置变更必然效率低下;

  多进程架构增大了 Worker 进程间的数据同步难度,必须选择 1 个低成本的实现机制,保证每个 Nginx 节点、Worker 进程都持有最新的配置;

  管理集群必须依赖中心化的配置,etcd 就是这样一个数据库。APISIX 没有选择关系型数据库作为配置中心,是因为 etcd 具有以下 2 个优点:

  APISIX 并没有启动 Nginx 以外的进程与 etcd 通讯。它实际上是通过 ngx.timer.at 这个定时器实现了 watch 机制。为了方便对 OpenResty 不太了解的同学,我们先来看看 Nginx 中的定时器是如何实现的,它是 watch 机制实现的基础。

  ngx_event_expire_timers 函数会调用所有超时事件的 handler 方法。事实上,定时器是由红黑树(一种平衡有序二叉树)实现的,其中 key 是每个事件的绝对过期时间。这样,只要将最小节点与当前时间做比较,就能快速找到过期事件。

  Nginx 框架为 C 模块开发提供了许多钩子,而 OpenResty 将部分钩子以 Lua 语言形式暴露了出来,如下图所示:

  可见,ngx_tpl.lua 模板中仅部分数据可由 yaml 配置中替换,其中 conf/config-default.yaml 是官方提供的默认配置,而 conf/config.yaml 则是由用户自行覆盖的自定义配置。如果你觉得仅替换模板数据还不够,大可以直接修改 ngx_tpl 模板。

  sync_data 函数将通过 etcd 的 watch 机制获取更新,它的实现机制我们接下来会详细分析。

  watchcancel 函数又是做什么的呢?这其实是 OpenResty 生态的缺憾导致的。etcd v3 已经支持高效的 gRPC 协议(底层为 HTTP2 协议)。你可能听说过,HTTP2 不但具备多路复用的能力,还支持服务器直接推送消息,从 HTTP3 协议对照理解 HTTP2:

  然而,**Lua 生态目前并不支持 HTTP2 协议!**所以 lua-resty-etcd 库实际是通过低效的 HTTP/1.1 协议与 etcd 通讯的,因此接收/watch 通知也是通过带有超时的/v3/watch 请求完成的。这个现象其实是由 2 个原因造成的:

  我们当然可以直接通过 gRPC 接口修改 etcd 中相应 key 的内容,再基于上述的 watch 机制使得 Nginx 集群自动更新配置。然而,这样做的风险很大,因为配置请求没有经过校验,进面导致配置数据与 Nginx 集群不匹配!

  APISIX 提供了这么一种机制:访问任意 1 个 Nginx 节点,通过其 Worker 进程中的 Lua 代码校验请求成功后,再由/v3/dv/put 接口写入 etcd 中。下面我们来看看 APISIX 是怎么实现的。

  admin 接口能够处理的 API ,其中,当 method 方法与 URI 不同时,dispatch 会执行不同的处理函数,其依据如下:

  最终新配置被写入 etcd 中。可见,Nginx 会校验数据再写入 etcd,这样其他 Worker 进程、Nginx 节点都将通过 watch 机制接收到正确的配置。上述流程你可以通过 error.log 中的日志验证:

  3.在上述 2 个过程中,如果含有正则表达式,则基于数组顺序(在f 中出现的次序)依次匹配。

  而在 http_access_phase 函数中,将会基于 1 个用 C 语言实现的基数前缀树匹配 Method、域名和 URI(仅支持通配符,不支持正则表达式),这个库就是lua-resty-radixtree。每当路由规则发生变化,Lua 代码就会重建这棵基数树:

  这样,路由变化后就可以不 reload 而使其生效。Plugin 启用、参数及顺序调整的规则与此类似。

  最后再提下 Script,它与 Plugin 是互斥的。之前的动态调整改的只是配置,事实上 Lua JIT 的及时编译还提供了另外一个杀手锏 loadstring,它可以将字符串转换为 Lua 代码。因此,在 etcd 中存储 Lua 代码并设置为 Script 后,就可以将其传送到 Nginx 上处理请求了。

  Nginx 集群的管理必须依赖中心化配置组件,而高可靠又具备 watch 推送机制的 etcd 无疑是最合适的选择!虽然当下 Resty 生态没有 gRPC 客户端,但每个 Worker 进程直接通过 HTTP/1.1 协议同步 etcd 配置仍不失为一个好的方案。

  动态修改 Nginx 配置的关键在于 2 点:Lua 语言的灵活度远高于 nginx.conf 语法,而且 Lua 代码可以通过 loadstring 从外部数据中导入!当然,为了保障路由匹配的执行效率,APISIX 通过 C 语言实现了前缀基数树,基于 Host、Method、URI 进行请求匹配,在保障动态性的基础上提升了性能。