协程、Lua 队列、带宽打满
1、问题现象
在生产现场,早上 6 点,网关出现与所有节点连接断连的异常,具体表现为:
- 网关所有进程与南北向、东西向的节点全部断开连接;
- 日志显示与所有的节点都心跳超时;
- 6 点异常必现
受影响的节点包括:
- 客户端
- 后端微服务
- ZooKeeper
- Redis(哨兵)
- RabbitMQ
2、初步分析与验证思路
现场确认
与现场确认这两个时间段是否有特殊操作,答复:无。日志分析
- 获取并分析
error.log、access.log以及相关组件日志。 - 发现 9 个进程中,8 个 worker 与客户端、后端服务节点全部心跳超时(HTTP 及私有协议均受影响)。
manage进程与 ZooKeeper、Redis Sentinel、RabbitMQ 也出现连接拒绝或重连。- 后端微服务日志显示与网关心跳超时, 但是和zookeeper不超时
- 获取并分析
关键现象
网关 ↔ zookeeper 异常
网关 ↔ 微服务 异常
微服务 ↔ zookeeper未见明显异常
三者对比,于是重点就在于“网关或网络”可能存在问题。
3、前置假设
基于现象,列出网关出问题的可能性:
正向理由(怀疑网关)
- 两台网关与所有组件心跳超时,而组件之间的交互正常。
- 现场曾更换过机器,且网关独立部署,依然存在问题,所以和机器本身没关系,原因指向网关应用。
反向理由(怀疑环境/网络)
- 该系统在多家基金上线运行多年,历史上无类似问题。
- 网关进程运行正常,持续记录日志,且无异常运行日志,无core生成
- 日志显示在某一段时间内,与所有节点的连接都心跳超时,日志信息清晰明了,且其余时间段无任何异常
可能原因假设
- 网关 Bug 导致假死,无法及时处理心跳。
- 网络质量差,流量高时丢包抖动严重。
4、抓包分析
分为2步,抓包+流量监控
在网关、zookeeper、后端服务三端进行同步抓包。
结果:
- 三端均能看到心跳包发出,但在网关侧存在大量延迟与重传,且超时集中在 网关 ↔ 各组件 的交互方向。
- 同时查看网络监控,发现异常时段网关机器的入口带宽飙升至接近万兆上限。
5、问题定位
高带宽来源
网络报文分析显示,异常时段的高流量主要是网关与 Redis 的数据交互。

数据类型为某类公共数据,单份数据体积约 20 MB。
在 6:00~7:00,会进行集中登录,单小时登录量可达 2000+。
登录过程会写入redis:
- 某类数据(0.5M,每次登录一份)
- 公共数据(每个进程总共一份)
公共数据采用的是本地cache、redis、权限数据接口的三级缓存。照理来说,公共数据,如果本地cache、redis数据不存在,会调用接口会去获取一份数据,然后写到redis,后续其他进程就可以从redis获取到数据,写入cache,最后就可以直接从cache获取,达到三级缓存的效果。
但是看日志,以及端口,确认nginx从redis获取了32次该类数据,照理8个进程最多获取8次,明显有异常。
代码分析
首先cache整个队列只设置了100,即存储100个key-value,lru的机制。所以怀疑是不是队列被持续打满,导致不停的从redis获取数据。对集中登录的场景进行复现,8个进程的效果并不明显,切换为32个进程,马上可以复现全部超时的场景。
在高并发时,会出现一笔请求从redis获取公共数据,发起网络io调用时,下一笔请求又来临,在cache查询不到公共数据(此时上一笔请求还没有从redis获取到数据,写到本进程的cache),又去redis获取数据,导致重复从redis获取数据。
同时,每笔请求都会进行权限校验,即会从cache get公共数据,应当一直保持为热点数据,就算队列只有100,也应该不会替换。
那么此时,底层技术问题就分为2个部分:
- openresty实现的协程-cosocket
- cache的lru机制
原理分析
实际上,公共数据是预加载的,所以redis的数据是一直存在,否则如果redis的数据也不存在,那么直接会打穿缓存到权限数据接口。而对于打满带宽,实际还是打穿了cache,到了redis。
缓存机制
- 网关缓存结构为:LRU 本地缓存(每进程) + Redis 缓存。
- 本地 LRU 容量默认 100(key-value 对)。
- 公共数据由于登录阶段几乎不被访问,LRU 认为它是“非热点”数据,容易被淘汰。
- 大量并发登录写入其他数据,触发 LRU 淘汰,将公共数据移除。
- 当随后的鉴权请求需要公共数据时,本地缓存命中失败 → Redis 拉取。
- 协程机制 → 出现并发多次拉取相同大数据的情况。
最终后果
- Redis 出口带宽被大量大包传输占满(接近万兆上限)。
- Redis 返回包堵在链路上,阻塞了:
- 网关 ↔ Redis 的正常交互。
- 微服务响应网关的心跳包(因为走相同带宽出口)。
- 心跳包超时 → 网关判定连接断开 → 南北向、东西向链路全部中断。
6、优化
协程的问题无法解决,也不可能加锁来保证时序,影响网关的高性能,因此重点还在于保证数据不被淘汰。那么对缓存进行优化,将cache替换为共享内存,多个进程共享一份,渐少拉取次数,同时共享内存也是LRU的机制,但不是队列,而是内存单位M,最终缓存调整为
- Worker 使用lua_shared_dict 共享缓存,减少跨 Worker 重复拉取
- 共享内存可以减少公共数据被淘汰的概率,预留足够大小,几乎不会被淘汰