作者>吴建苗
来源>https://www.infoq.cn/article/JD-Nignx-JEN
Nginx 是优秀的 HTTP 和反向代理服务器,京东各部门都在广泛使用,但普遍都面临着一些问题:
- 配置复杂,专业性强。
- 配置文件无法批量修改且配置变更依赖重启操作。
- 不同应用依赖不同模块、配置项,管理混乱。
- 同一应用的 Nginx 无法批量、快速扩容。
所有问题的根源在于 Nginx 是一个单机系统,虽然模块化、高性能,但在互联网高速发展的今天,像京东这样拥有大规模 Nginx、业务集群的场景下,所有问题都有可能被无限放大,针对这种现状我们设计研发了 JEN(JD EXTENDED NGINX),截止目前 JEN 已覆盖京东金融大部分核心业务,如夺宝吧,卡超市,白条等。
一、整体结构
图 1:JEN 结构图
如上图,运维通过 Web 控制台做相应的配置操作,若是分流、限流等配置,则信息入库等待 Nginx 通过 Restful API 同步规则后开始生效;若是平滑升级、重启等强运维性操作,则 Web 控制台通过控制 Ansible 对 Nginx 进行相应操作。
图 2:Nginx 和 Web 控制台多机房部署图
JEN 特点:
- 支持 Nginx 自动发现,分组管理,状态监控。
- 统一入口,通过抽象配置,简化操作管控 Nginx 集群生命周期,并支持规则批量配置,操作批量执行。
- 扩展了原生 Nginx 的分流、限流功能,支持规则的内存实时同步,无需修改配置文件,更无需重启 Nginx 进程。
1. 基础信息
Web 上所有的展示和操作全部基于对基础信息的计算整合,主要包含两类:
- 分组信息(业务线、应用、机房、Nginx IP)
- Nginx 属性,例如 upstream 信息,server_name,listen_port 等,主要来源 Nginx 读取 Nginx.conf 内容后的信息上报(心跳)
对于分组信息,JEN 支持以下两种方式填充:
- 调用外部服务的 Restful API 导入完整的基础信息。
- 对自动发现的 Nginx 做分组的手工编辑。
图 3:各分组间关系图
如上图,分组包括业务线、应用、机房、Nginx 共四层关系,在大规模集群环境下可以通过这种关系并结合 Nginx 属性,支持对所有操作的批量执行,如批量修改配置文件,批量升级重启等,解放生产力。
2. 规则获取
用户在 Web 控制台配置后,在 Nginx 端我们实现了全异步的模块支持定时向 Web 获取属于当前 Nginx 的规则信息,规则存储内存,即时生效,其中:
a)规则信息每个进程存储一份,避免进程间资源共享导致锁竞争。
b)版本号设计,保证规则和心跳的绝对顺序,不因丢包、延迟等网络因素导致版本错乱,而且在规则未变更时 Nginx 无需频繁解析大量规则信息而消耗 CPU 资源。
3. 安全
JEN 支持三类角色,每种角色支持不同的操作权限(默认是普通用户角色,无写权限),任何角色对 Web 的任何操作都会被记录,并在 Web 提供了入口支持多维度操作日志查询,便于审计
4. 监控
我们实现了更为全面的监控信息采集与展示,包括:
a)扩展了 tengine 的主动探测模块,支持上游服务器的平均、当前延时统计。
b)通过与 Web 的心跳保持支持 Nginx 存活状态监控。
c)支持 TCP 连接信息,in/out 流量,QPS,1xx 到 5xx 回应报文等信息监控。
以上的监控信息支持分组统计(业务线、应用、机房)和大屏展示,便于相关人员(业务,运维)实时监控应用状态。
二、分流
概念:根据请求特征(IP,header 中任意关键字)支持把某些特定请求分流到单个或多个上游服务器中,如下图:
图 4:分流示例图
分流主要适用灰度发布,ab testing 等场景,另外我们也对分流功能做了扩展,支持 Web 控制台一键启停上游服务器,便于当应用服务器需要维护或升级时,用户请求正常访问。
三、限流
京东 618 等大促,货物都提前堆积在购物车,等待零点秒杀,换成工程师的语言来说,就是前一秒的 QPS 很低,但是下一秒 QPS 非常高,流量大意味着机器负载高,若一个应用的一两台机器没有扛住,这样就会导致整个应用集群雪崩。
限流不可盲目,首先需要根据业务特点选择合适的限流算法(漏桶算法、令牌桶算法),其次需要结合历史流量、应用服务能力、营销力度等因素综合评定限流参数,最后决定以何种优雅的方式反馈用户。
Nginx 在实现上通过共享内存共享限流中间信息的方式来达到多进程间的状态统一。在 JEN 设计初衷,原本计划和分流一致,即每个进程存储一份限流规则,限流只在当前进程内限流,但不可避免的会出现如下问题:
- 每个进程“你限你的,我限我的”,信息不一致进而导致限流不准确。
- 类似用户 ID 的限流,在京东这样拥有庞大日活用户的场景下,每个进程需要开辟足够大的内存才能避免限流算法中对于红黑树节点的频繁置换,这样一来 Nginx 占用内存就会随着进程数成倍扩大。
我们的做法:
- 预分配共享内存,Nginx 获取到限流规则时动态适配一块共享内存。
- 规则共享,生效后实时同步至所有进程,规则链保证所有旧版本规则只有在当前流量更新之后才会删除,如下图:
图 5:规则链
我们在限流功能上的几点扩展:
- 支持错误页定制,除了返回 Nginx 静态页,还支持 302 错误页重定向,根据在 Web 控制台的配置可以重定向到任何外部链接,但 302 重定向存在一个问题:用户浏览器的 URL 和内容都发生了变更,意味着用户需要重新输入 URL 重新请求或者是重复之前的操作步骤,用户体验差可能导致用户放弃此次购买行为而转投它家。在逻辑上我们通过 Nginx 的 subrequest 机制支持返回内容发生变更而 URL 保持不变,这样一来每当用户被限流,只需重新刷新页面即可重复之前的操作步骤。
图 6:两种错误页对比
- 通过扩展限流算法支持限流后一段时间不可用,例如按 IP 限流且某个 IP 已经触发限流,则支持该 IP 一段时间内不可访问,无需重新通过算法计算。
- 同步实现了黑名单、白名单功能,通过白名单避免一些复杂场景下的限流“误杀”(例如 nat 网络下按 ip 限流)。
四、运维特性
运维特性主要指 Nginx 的安装、升级、配置文件修改、启停等操作,运维特性与之前介绍内容的最大区别在于需要重启操作,所以结合第三方工具 Ansible 是比较合适的想法(Ansible 相对于 Puppet 等运维工具,其迁移成本相对较小)。
在实际生产中 Ansible 和 Web 为避免单点需要集群部署,我们的方案是:Web 和 Ansible 在同一 PC 上部署,相关数据改用 DB 存储替代 Ansible 本地文件存储,通过这种简单的改造可以方便 Ansible 和 Web 这组“套件”进行扩容。
图 7:自动化运维操作逻辑图
如上图,用户通过 Web 操作控制 Ansible 对 Nginx 进行升级、重启等操作,Web 是 Nginx 操作的统一入口,这是平台化的重要意义所在,可以放弃 SSH,Shell 甚至是监控系统,开始在 JEN 自给自足了。
通过主动拉取或者是用户在页面导入、手工配置,JEN 会为所有 Nginx 存储配置文件,这样不仅原本因为每个应用都依赖不同的配置项而导致管理混乱的局面得到了改善,而且也可以方便的对配置文件做些扩展,例如历史记录追溯,配置比对,配置复用,操作回滚等。
在页面执行相关操作时,Web 会读取 Ansible 的标准输出并在页面实时展示,为了让使用者以相对友好的方式获知进度我们对 Ansible 做了优化:
- 丰富了标准输出的内容,尽量细化到每一个步骤。
- 格式化标准输出,便于 Web 获取和展示。
Nginx 在生产环境大规模部署,倘若因为一些原因导致 Nginx 大规模异常,这是我们不希望看到的,所以在可靠性方面,JEN 也提供了多种机制来保证:
- 三层错误校验,保证只有在完全正确的情况下才会重启和更新进程,中途发生任何错误不影响线上服务 a)在 Web 填充表单时做第一层校验。 b)在目标机器做操作时做第二层检测,例如先执行 Nginx –t 校验。 c)执行完毕做第三层校验,例如端口是否启动,进程数是否一致等。
- 灰度执行 a)单个 Nginx 依次执行,有任何异常立即中断开始人工介入。 b)按百分比支持批量执行,例如某个机房的 Nginx 先升级 10%。
五、总结
以上整理了京东在 Nginx 平台化方面的一些实践,JEN 提供了统一入口管控整个 Nginx 生命周期,并支持规则的批量修改即时生效,我们希望这些实践经验能对所有读者产生帮助。