导航
- [避坑指南:如何在 K8S 环境下优雅地实现"业务禁令"黑名单?](#避坑指南:如何在 K8S 环境下优雅地实现“业务禁令”黑名单?)
-
- [1. 业务背景:合规是第一生产力](#1. 业务背景:合规是第一生产力)
- [2. 技术选型:推还是拉?](#2. 技术选型:推还是拉?)
-
- [方案 A:直接调用 API 通知 (弃用)](#方案 A:直接调用 API 通知 (弃用))
- [方案 B:消息队列 Pub/Sub (胜出)](#方案 B:消息队列 Pub/Sub (胜出))
- [3. 最终方案:内存 + Redis Pub/Sub 的"推拉结合"](#3. 最终方案:内存 + Redis Pub/Sub 的“推拉结合”)
- [4. 深度复盘:那些藏在细节里的"魔鬼"](#4. 深度复盘:那些藏在细节里的“魔鬼”)
- [5. 总结与反思:没有最好的架构,只有最适合现状的选择](#5. 总结与反思:没有最好的架构,只有最适合现状的选择)
避坑指南:如何在 K8S 环境下优雅地实现"业务禁令"黑名单?
在分布式系统中,看似简单的"黑名单"需求,往往是检验底层架构功底的试金石。最近我们复盘了一个业务功能封禁(禁令)的技术选型过程。面对**"数据库压力大"和"网关响应要求极快"**这两个硬指标,我们是如何在 K8S 环境下做抉择的?
1. 业务背景:合规是第一生产力
为了满足产品合规性,我们需要在用户访问业务功能时,实时拦截被管理员拉黑的用户。
架构现状:
- 多级网关: 业务网关(负责拦截) → \rightarrow → 数据网关(提供 API) → \rightarrow → 数据库。
- 部署环境: K8S 集群,多个 Pod 副本并行。
- 核心痛点: 数据库曾被查爆过,领导严禁任何高频查库行为;网关作为流量入口,RT(响应时间)必须极低。
2. 技术选型:推还是拉?
为了减少数据库压力,"缓存"是唯一的出路。我们将缓存位置定在了共享内存(Local Cache),这样可以彻底消灭网络 IO 带来的延迟。
剩下的核心问题是:当管理员更新黑名单后,如何让多个独立运行的 Pod 准实时感知并更新本地内存?
我们对比了两套主流方案:
方案 A:直接调用 API 通知 (弃用)
- 原理: 管理员端修改后,遍历所有网关实例的 IP 并发送通知。
- 否决原因: 在 K8S 环境下,Pod 的 IP 是动态的,且通常隐藏在 Service 之后,没有固定入口。维护一个动态的"实例列表"不仅麻烦,且在扩缩容时极易出错。
方案 B:消息队列 Pub/Sub (胜出)
通过中间件广播,让每个 Pod 监听同一个"频道"。在这个领域,我们深度对比了 NSQ 和 Redis Pub/Sub。
| 特性 | Redis Pub/Sub | NSQ (Channel 模式) |
|---|---|---|
| 存储模型 | 不落地(Fire-and-Forget),电台直播模式 | 持久化,没收到的消息会攒着 |
| 消息可见性 | 只有当前在线的订阅者能收到 | 订阅者离线,上线后会重传 |
| 运维成本 | 极低,复用已有 Redis 即可 | 略高,需维护 nsqd 和 nsqlookupd |
| 我们的场景适配度 | 高(我们需要纯广播,且不在乎离线消息) | 低(需要为每个 Pod 维护唯一 Channel,运维压力大) |
3. 最终方案:内存 + Redis Pub/Sub 的"推拉结合"
我们最终选择了 "本地内存 + Redis 订阅推送 + 定时全量同步" 的组合拳。
核心链路设计
- 初始化: Pod 启动时,在
Readiness Check阶段通过DataGateway加载全量黑名单至内存。加载成功后,Pod 才标记为 Ready,确保功能无空窗期。 - 实时更新(推): 管理员修改状态 → \rightarrow → 发送 Redis Pub 消息 → \rightarrow → 网关 Pod 监听到消息立即更新内存。
- 最终一致性兜底(拉): 每个 Pod 开启一个随机扰动的定时任务,每 5-10 分钟拉取一次全量快照。
为什么需要定时拉取? > 因为 Redis Pub/Sub 是"发后既忘"的。如果发生瞬时网络抖动导致订阅中断,Pod 会错过更新。定时拉取是解决"最终一致性"的终极防线。
4. 深度复盘:那些藏在细节里的"魔鬼"
在实施过程中,我们特别关注了以下三个细节,这也是该方案稳健运行的关键:
- 防范"惊群效应": 如果 100 个 Pod 同时定时同步,数据网关会瞬间崩溃。我们通过在定时器中加入 Jitter(随机抖动时间),让流量在时间轴上均匀分布。
- 版本号校验: 每一条黑名单数据都带有
updated_at时间戳。只有接收到的新数据时间戳大于本地旧数据时,才会触发内存更新,防止旧数据覆盖新数据。 - DataGateway 的保护: 为了进一步减轻数据库压力,DataGateway 内部使用了
SingleFlight技术。即使多个请求同时到来,也只会有一个请求真正去查询数据库,其余请求共享结果。
5. 总结与反思:没有最好的架构,只有最适合现状的选择
在这次复盘中,我们之所以最终敲定 "本地内存 + Redis Pub/Sub" ,核心逻辑在于对公司现有基础设施利用率 的最大化,以及对运维复杂度的极度克制。
特别声明: > 技术选型必须结合公司当前的技术栈储备、运维能力以及业务容忍度进行综合考量。在不同的公司背景下,取舍点会有本质区别:
- 如果公司已有成熟的 NSQ 运维体系,或许会选择 Channel 模式来追求更高的可靠性;
- 如果业务对实时性要求达到毫秒级,且黑名单量级巨大,或许会引入更重的中间件。
以上方案,是我基于我们公司目前"Redis 资源成熟、DB 压力敏感、K8S 动态扩缩容频繁"的现状做出的最优权衡。
技术选型不是在实验室里追求单项指标的满分,而是在复杂的现实约束中,找到那个性能、稳定性和开发成本的黄金平衡点。