K8S环境下业务黑名单技术选型:Redis、网关与Service Mesh方案对比

发布时间:2026/10/8 10:33:39
K8S环境下业务黑名单技术选型:Redis、网关与Service Mesh方案对比 做业务的人对“黑名单”这三个字应该都不陌生。大促期间风控要快速拉黑一批恶意账号客服要封禁被投诉的违规用户运营要限制某个设备ID参与活动甚至某个接口突然被刷需要立刻切断某个来源的调用。这些需求听起来都很简单但一旦业务跑在 K8S 集群里事情就不再是“改个配置重启一下”能解决的了。我自己在这上面是踩过大坑的。早期我们的服务部署在虚拟机黑名单写进应用配置文件每次更新靠运维手动分发虽然麻烦但勉强能用。后来服务整体容器化并迁移到 K8S问题立刻暴露副本数一多IP 是漂移的发布是滚动的本地文件配置根本没法做到多实例同步。更麻烦的是业务拆成微服务之后同一个黑名单判断可能散落在五六个服务里你在这个服务加了限制流量从另一个服务绕过去禁令就形同虚设。这篇文章就围绕“业务禁令黑名单”在 K8S 环境下的技术选型来展开。我不会只给一个标准答案而是把我在实际项目中对比过的几条路线、每个方案的适用边界、以及最终选型的判断依据都摊开来讲包括完整的落地思路和排错经验。对于正准备做类似功能或者正在为“黑名单到底该写在哪一层”纠结的朋友这篇内容应该能帮你省下不少试错的时间。1. 先搞清楚业务禁令到底要拦什么很多人在技术选型里纠结半天最后发现是需求定义没想清楚。业务禁令看起来都是“不允许某些对象执行某些操作”但拦的位置不同方案天差地别。在 K8S 环境下这个问题被放得更大因为入口流量、服务间调用、业务内部逻辑这三条路径是完全独立的。1.1 从一次“大促风控”风波说起去年我们做一次大型促销活动运营在活动开始两小时后发现一批账号在批量刷优惠券。风控组那边给出了一个大约三千个账号 ID 的名单要求在十分钟内让这些账号无法领取任何优惠券。放在以前单体应用时代改个配置重启就行。但当时我们的领券服务在 K8S 里有二十个副本滚动发布一轮要七八分钟而且风控后面还在陆续补充新的账号。要知道在 K8S 环境里多副本部署意味着任何一个本地方案都会失效——你改了第一个 Pod 里的文件其他十九个 Pod 还是旧的等滚动发布完成第一批被封的账号在缓存里已经领过一轮了。这个场景让我意识到K8S 下的黑名单方案必须具有天然的分布式属性。1.2 黑名单的三个拦截层级我把业务禁令的拦截位置拆成三层第一层是入口网关层拦截维度主要是 IP、User-Agent、设备指纹、JWT 中的用户标识。这一层拦的是“请求还没到业务服务”之前。优点是覆盖面广一个规则能管住所有下游服务缺点是拿不到业务上下文做不了精细判断比如“这个用户是黑名单账号但他手里有未核销的券需要允许看详情页”。第二层是服务间调用层也就是微服务 A 调用微服务 B 时在调用链路上做拦截。这一层能识别调用来源、目标接口、携带的用户信息可以做比较精细的控制。在 K8S 里这一层通常由 Service Mesh 或者服务框架的过滤器实现。第三层是业务逻辑层拦截时机是在代码里执行具体的业务规则比如下订单前判断用户是否在白名单里领券前判断设备是否命中风控名单。这一层最灵活但侵入性最强每个需要做判断的业务方都得单独接入黑名单逻辑容易在各服务间复制粘贴后期维护相当痛苦。选型的第一步就是明确你的禁令到底要覆盖哪一层。只拦入口还是内网全链路都要拦这两个方向对应的技术方案完全不同。2. 主流方案盘点从硬编码到全链路治理在 K8S 环境里做黑名单我梳理下来大致有五条路线应用内本地缓存、Redis 分布式标记、网关层拦截、Service Mesh 流量治理、K8S 原生 NetworkPolicy。它们不是替代关系而是适用场景的差异。2.1 应用内硬编码与本地缓存方案这是最朴素的做法。把黑名单写死在代码里或者启动时从配置中心拉取一次加载到本地内存然后业务逻辑里判断。优点是实现简单查询极快不依赖外部组件。但缺点在 K8S 环境下非常致命多副本之间数据不一致滚动发布期间新旧 Pod 的名单不同更新需要走完整发版流程在需要秒级生效的风控场景里根本不可接受。我见过有团队用 Apollo 或 Nacos 做配置中心配置改了能推送但业务服务需要监听配置变更并重建本地缓存稍微有点并发就会遇到新旧版本不一致。这个方案适合那些“禁用一个功能开关”而不是“禁用一个可变名单”的场景比如临时下线某个接口但它的灵活性完全不够所以不建议作为主方案。2.2 Redis 分布式标记业务侧的“缓存版禁令”这个方案在现在的互联网公司里最普遍。业务服务在判断逻辑处调用 Redis查询某个用户 ID 是否在某个黑名单 Key 里或者查某个设备 ID 是否命中限购标记。榜单存在 Redis 的 Set、Hash、或者 String 类型里配合 TTL 实现自动解禁。这个方案的灵魂在于“分布式”和“热更新”。所有 Pod 共享同一个 Redis数据天然一致写入黑名单只是操作 Redis毫秒级全局生效TTL 到期自动清理不需要额外任务去解禁。我甚至可以开一个管理后台运营点一下“封禁”Redis 里这个 Key 就写入一个账号业务侧下一次请求立刻就能感知。但它的短板也很明显需要业务代码显式接入如果黑名单判断分散在多个服务每个服务都要做一次一样的查询而且如果业务方偷懒直接用本地缓存套 Redis就会遇到我们后面会讲的缓存一致性问题。2.3 网关层拦截Ingress、API 网关与 Lua 脚本网关层方案在 K8S 环境里非常自然。请求先经过 Ingress Controller 再进入 Service所以在 Ingress 这一层做黑名单判断理论上能拦截掉所有外部流量。常见的实现包括用 Nginx Ingress 的 lua_shared_dict 配合 Lua 脚本在 access 阶段做 IP 或 Header 匹配。使用 APISIX、Kong 这类云原生网关通过插件机制做限流和黑白名单规则存在 etcd 或 Redis 里。用 Envoy Gateway 或者 Istio Ingress Gateway配合 EnvoyFilter 或 Lua Filter 做拦截。网关方案的最大好处是业务无侵入。我们可以做到业务服务完全不知道黑名单的存在流量在网关就被拦掉了。比较典型的是全局 IP 黑名单、恶意 UA 拦截、以及按 JWT Claim 里的用户等级做粗粒度限制。但网关方案的粒度受限于网关能解析的内容。比如网关能看到 Authorization 头里的 JWT但看不到数据库里的用户优惠券状态它能拦截掉恶意 IP 的请求但没法判断一个“已登录但设备指纹异常”的请求是否应该被放行。另外网关层的规则更新依赖网关自身的配置管理在 K8S 里通常表现为 ConfigMap 更新 Ingress Controller Reload这个过程中可能存在几秒到几十秒的延迟对大促秒级封禁来说并不理想。2.4 Service Mesh 层拦截Istio EnvoyFilter如果你的集群已经上了 Service MeshIstio 是目前最主流的那么 EnvoyFilter 就是另一个值得考虑的方案。EnvoyFilter 允许你在 Envoy 的过滤器链上插入自定义 Lua 或 WASM 过滤器拦截条件基于流量属性source workload、destination service、URL path、Header、JWT payload 等。这个方案能做到全链路统一拦截。不管调用方是网关、其他服务还是定时任务只要流量经过 Sidecar Proxy就会被统一规则约束。比如我们要“禁止用户 10086 调用下单接口”可以在 EnvoyFilter 里写一条规则对所有流向下单服务的请求检查 Header 里的用户 ID命中就返回 403业务 Pod 全程无感知。但 Service Mesh 的复杂度是真实存在的。EnvoyFilter 的匹配语法有点陡峭排障时需要看 Envoy 的访问日志和动态配置运维成本比前面几个方案高一个量级。而且 WASM 过滤器的开发和调试说实话没有 Redis 方案那么直观。如果你的团队已经熟练运维 Istio这个方案很优雅如果只是为了黑名单去引入 Service Mesh我劝你慎重。2.5 K8S 原生方案NetworkPolicy 与准入控制最后提一下 K8S 原生的 NetworkPolicy很多人一看到“禁令”二字就想用它。NetworkPolicy 负责的是网络层的允许/拒绝比如“禁止 Pod A 访问 Pod B 的 8080 端口”。它做物理隔离没问题但做业务黑名单其实非常不顺手。原因有三点第一业务黑名单的维度是用户 ID、设备 ID、订单号而 NetworkPolicy 的维度是 IP 和端口需要把用户维度翻译成 IP 维度而 Pod IP 又是动态变化的运维成本极高第二NetworkPolicy 在网络层直接丢包业务拿不到任何业务语义的错误码排查问题很被动第三NetworkPolicy 的更新依赖 CNI 插件的实现方式策略生效时间不可控。所以我基本不会把 NetworkPolicy 划入业务禁令的候选方案它更适合做租户隔离和基础安全防护而不是业务规则。2.6 五个方案横向对比我日常选型时会用一张速查表评估方案。方案生效时效业务侵入性覆盖层级运维成本适用场景应用内硬编码/本地缓存分钟级~小时级高业务逻辑层低永久性功能开关名单极少变更Redis 分布式标记毫秒级中业务逻辑层/服务调用层低风控封禁、限购、账号封禁需要热更新网关层拦截秒级~分钟级低入口流量层中恶意 IP/UA 拦截、全局黑名单、限流Service Mesh秒级极低全链路流量层高全链路统一禁令团队有 Mesh 运维能力NetworkPolicy依赖 CNI分钟级无网络层中租户隔离、基础安全不适合业务规则这个表格基本回答了我每次技术评审都会被问到的几个问题。按经验来看Redis 方案适合 70% 的业务场景网关方案适合入口治理Service Mesh 适合“我就是要一刀切”的全链路管控。下面我会重点讲我最终选型的组合方案以及落地过程中的关键细节。3. 选型判断四个决策指标与三个推荐组合选型不能只看网上博客的推荐要把你自己的场景参数带进去算一遍。我习惯用四个指标来卡方案分别是生效时效、作用域、业务侵入性和运维成本。3.1 四个决策指标的判断方法生效时效指从“更新黑名单”到“拦截生效”的延迟。要明确的是K8S 滚动发布带来的延迟问题经常被人低估。比如 ConfigMap 更新后Kubelet 默认刷新周期是 60 秒而 Pod 内挂载的文件还要更久才能看到变化这一下就是几分钟。如果业务要求“分钟级封禁”那就要放弃一切依赖配置文件的方案。作用域指禁令覆盖的范围。是只拦入口流量还是要把服务 A 调服务 B 也给拦住在 K8S 里如果两个服务在同一个 namespace 内可以直接 Service 名通信但服务间调用不一定过网关所以网关方案覆盖不到服务间调用。如果你的场景是全链路禁止就得考虑 Service Mesh 或者在业务代码里统一拦截。业务侵入性指为了接入黑名单方案业务方要改多少代码。Redis 方案要引入 SDK网关方案业务无感Service Mesh 方案业务也基本无感。侵入性越高落地阻力越大推广越难。运维成本包括组件部署、告警、排障和升级维护的投入。Redis 方案通常是复用已有 Redis运维成本最低Service Mesh 需要维护 EnvoyFilter 和环境基线成本最高。3.2 推荐组合一大促风控型禁令选 Redis 双层标记对时效要求极高的补丁型禁令我会首选 Redis。具体做法是定义一个黑名单 HashKey 为业务场景名Field 为用户 ID 或设备 IDValue 为禁令类型和生效时间。业务侧在关键入口调用一个封装好的访问控制组件组件先查 Redis再决定是否放行。这套方案的生效时效几乎为零因为 Redis 操作本身就是业务请求链路的一部分。名单管理端写一套运营后台操作 Redis 即可。对于 K8S 环境Redis 是无状态依赖和 Pod 的调度、滚动发布没有任何耦合这是它最大的优势。3.3 推荐组合二入口级统一拦截选网关 Lua 脚本如果黑名单对象是 IP、请求特征这些入口属性不涉及具体业务数据那就放到网关层拦截。Nginx Ingress 或 APISIX 的 Lua 脚本能做到在 access 阶段直接 return 403业务 Pod 连日志都不用打。我这边有一个典型场景某个第三方服务商的服务器在疯狂刷我们首页接口对方的出口 IP 段我们全知道这时候在网关配一个 IP 黑名单用 lua_shared_dict 缓存名单列表配合 nginx 定时拉取配置几十秒内全局生效。3.4 推荐组合三全链路统一禁令选 Service Mesh如果你所在的部门已经掌握了 K8S 和 Istio且有“某个用户在所有服务里都不能再操作”这类需求那就别在业务代码里到处埋点直接用 EnvoyFilter。让流量带用户标识比如通过 HeaderX-User-IdEnvoy 在进入业务 Pod 前检查这个 Header 是否命中全局黑名单命中直接返回 403。这个方案的好处是响应链路极短规则代理本地完成不经过业务逻辑坏处是排障链路长你需要看 Envoy 日志才能确定请求是被哪个 Filter 拦截的。我建议在 Filter 里写清楚一个自定义 Header 标记拦截原因方便后续排查。3.5 我不推荐的组合本地文件配置方案在今天的 K8S 环境里基本可以淘汰了。除非你的集群规模很小、服务只有单副本或者黑名单一年才改两三次否则本地文件方案会因为多副本数据不一致而埋下巨大的安全隐患。还有一点要提醒不要把黑名单强耦合在业务数据库里。有人喜欢在 MySQL 里建一张黑名单表应用每次查询。数据一致性有了但每次请求多一次数据库 IOC延迟上去不说数据库压力也会白白增加。在高频热路径上缓存是必需品。4. 实操记录Redis Lua 热更新黑名单的落地细节前面讲了这么多选型依据这块我用一个我实际迭代过的方案作为例子面向业务逻辑层的 Redis 黑名单组件配合 K8S ConfigMap 做场景配置。这套方案适合大多数基于微服务的中小型业务团队。4.1 数据结构设计为什么用 Hash 而不是 String黑名单的存储结构我先后试过 String、Set 和 Hash最终固定为 Hash。原因有三个第一Hash 支持按业务场景隔离。一个 Redis Key 对应一个场景Field 是黑名单对象标识互不干扰。比如说biz:ban:coupon存领券禁令名单biz:ban:login存登录禁令名单。第二Hash 可以单个 Field 设置 TTL虽然并不是原生支持但可以通过 Redis 7 的HEXPIRE命令实现或者在 Value 里存过期时间通过 Lua 脚本统一判断。这样同一个场景里不同用户的封禁时长可以不同到期自动失效。第三Hash 的查询是 O(1)在超大规模黑名单场景下性能有保障。如果黑名单数量到了百万级Set 和 String 也能查但 Hash 的粒度管理能力更强。我使用的 Key 结构如下# 领券业务黑名单key biz:ban:coupon HSET biz:ban:coupon 10086 user HSET biz:ban:coupon 10010 user写入时附带封禁时间信息可以用一个辅助 String Key# 记录封禁到期时间戳 SET biz:ban:coupon:expire:10086 1735689600这样读取时先查 Hash 是否存在再比对到期时间戳。当然如果你们团队用的 Redis 版本支持直接上 HEXPIRE 更省事。4.2 用 Lua 脚本保证封禁查询和到期判断的原子性在判断请求是否命中黑名单时要用 Lua 脚本把“是否存在 是否过期”两步操作合成一步执行避免并发问题。-- KEYS[1]: blacklist hash key -- KEYS[2]: expire prefix key -- ARGV[1]: target field (user/device id) -- ARGV[2]: current timestamp local banned redis.call(HEXISTS, KEYS[1], ARGV[1]) if banned 0 then return 0 end local expireAt redis.call(GET, KEYS[2] .. ARGV[1]) if expireAt and tonumber(expireAt) tonumber(ARGV[2]) then return 0 end return 1业务侧封装一个统一的 SDK调用这个 Lua 脚本返回 1 则直接拒绝并抛出业务错误码。封装好之后业务方只需一行代码接入if (banClient.isBanned(coupon, userId)) { throw new BizException(403, 你已被限制参与本活动); }这整个查询过程在 Redis 上通常 1 毫秒内返回对业务延迟基本无感。4.3 写入黑名单的管理通道黑名单的写入不能走业务应用直连 Redis SET我建议走一个独立的管理接口或者定时任务。管理接口负责参数校验、风控审批记录和 Redis 写入。这样后续要追溯“谁在什么时间封禁了谁”有据可查。public void banUser(String scene, String userId, Duration duration) { String hashKey biz:ban: scene; String expireKey biz:ban:expire: scene : userId; long expireAt System.currentTimeMillis() / 1000 duration.getSeconds(); jedis.hset(hashKey, userId, 1); jedis.set(expireKey, String.valueOf(expireAt)); jedis.expire(hashKey, duration.getSeconds() 86400); // 保险过期 }同时在管理后台里保留操作日志关键黑名单操作需要二次确认。这些虽然看起来和 K8S 没啥关系但线上环境的安全审计往往比技术方案本身更重要。4.4 K8S 部署中的几个雷区这套方案整体不依赖 K8S 内部状态但在部署时仍有两个问题要注意。第一个是 Redis 的地址配置。不要把 Redis 连接地址写成某个具体 Pod 的 IP因为 K8S 里的 Pod IP 是不固定的。应该使用 K8S Service 的 DNS 名称比如redis-headless.default.svc.cluster.local:6379并且开启 Sentinel 或 Cluster 模式时让客户端从服务发现中获取节点列表。这里经常能见到有人把 Redis 地址配置在 ConfigMap 里然后滚动发布时配错了 namespace导致服务连不上 Redis。建议统一使用 ConfigMap 管理并做好环境隔离。第二个是黑名单管理服务和业务服务最好放在不同的 namespace 下通过 RBAC 控制权限。这样即使用户误操作删掉了业务 namespace黑名单管理通道依然可用。另外如果你的集群规模很大Redis 黑名单最好独立部署一套或者使用独立的 Redis 逻辑库避免和大促交易缓存互相影响导致 Redis 阻塞进而拖垮黑名单判断。这里就涉及到 K8S 资源调度的问题了黑名单 Redis 的 Pod 要设置足够的 CPU 和内存 Limit并配置独立的 PodDisruptionBudget。5. 常见问题与排查技巧实录任何方案上线后都会遇到问题。下面这几个坑是我在这些年的 K8S 黑名单项目中真实踩过的分享出来至少能让你在排查时少走弯路。5.1 黑名单不生效的典型原因最经典的案例是“明明把用户加进黑名单了但他还是能正常访问”。排查顺序我建议这么走第一确认 Redis 里数据写进去了。用HGETALL biz:ban:coupon看一眼如果数据在再看看过期时间戳是否已过。我们的封禁组件里曾经有个 bug过期时间写的是System.currentTimeMillis()而不是秒导致封禁瞬间就失效了。第二确认业务代码里真的调用了黑名单判断。很多时候加了黑名单组件但业务链路的某个分支漏掉了比如领券时判断了但兑换时没判断用户绕道兑换入口完成了操作。第三如果是多副本部署检查本地缓存。如果业务开发图省事在应用里加了一层 Caffeine 本地缓存来缓存黑名单结果又没有设置合理的过期时间就会出现“有副本还是旧数据”的经典问题。在 K8S 环境下这种情况特别隐蔽因为你滚动更新后部分实例会恢复但没更新的实例依旧放行。5.2 误杀用户后的“急救”流程误杀在风控场景里太常见了。一个正常用户被错误封禁这时最重要的不是分析原因而是先放行。所以黑名单方案的优先级设计一定要有“白名单高于黑名单”的概念。用户在操作时如果命中白名单就直接跳过黑名单判断。这个操作要独立于黑名单 Redis可以用单独的 Key 前缀存储。# 白名单 key SET biz:whitelist:coupon:user:10010 1急救流程固定成三步确认用户 ID写入对应场景白名单再通知业务侧观察日志。等确认是误杀后再清理黑名单里对应的 Field。白名单的设计虽然看起来笨但它在关键时刻是救命的。5.3 黑名单规模过大时的性能策略有人会遇到黑名单数量持续增长Redis Hash 越来越大内存压力增加。这时候有两个调整方向。第一个是拆分场景不要所有禁令都堆在一个 Hash 里。比如“领券禁令”和“下单禁令”是完全不同的业务场景分开管理可以避免单个 Key 过大和热 Key 问题。第二个是引入布隆过滤器做前置过滤。如果黑名单集合非常大比如上千万级设备 ID 需要在毫秒级内判断可以在 Redis 前加一层本地布隆过滤器只有命中布隆过滤器时才回源 Redis 精确判断。不过这个方案会带来误判率而且布隆过滤器不支持删除需要定期重建。所以这个方案我一般只在集合达到千万级以上时才建议使用。5.4 和 K8S 滚动发布相关的黑名单隐患滚动发布时如果旧 Pod 还在处理存量请求而新 Pod 已经加载了新的黑名单配置同一个用户可能在新 Pod 被拦截在旧 Pod 放行造成业务状态不一致。这个问题在网关层方案里更明显因为 Nginx Ingress 的配置 Reload 会短暂中断现有连接。经验做法是发布期间黑名单管理后台禁止大批量操作如果必须操作优先使用 Redis 方案而不是 ConfigMap 方案因为 Redis 不依赖 Pod 生命周期不会受到滚动发布的影响。另外如果你用 ConfigMap 挂载黑名单配置到 Pod 中要清楚 Kubelet 的默认同步周期。你改了 ConfigMapPod 里的文件不会立刻变化。有些团队对这个时间差拿不准就很容易出现“我明明改了配置怎么没生效”的乌龙。这种场景下更合理的做法是把 ConfigMap 当作“初始值”运行时的黑名单变化始终走 Redis。6. 这套选型思路还能怎么扩展黑名单这件事本质上是一类“状态管理”问题在分布式环境里快速判断某个对象是否处于某种特殊状态。想通这一点很多场景都能复用。比如灰度发布。在一个 K8S 集群里同时运行新旧两个版本的服务你可以用同样的 Redis 标记方案按用户 ID 灰度切流实现“白名单先体验、逐步放量”的发布流程。这和黑名单的机制区别不过是判断逻辑反过来而已。再比如限流。以用户维度做限流时“这个用户已经超过限制次数”本质上就是一个禁令判断。用 Redis 计数器配合 Lua 脚本既可以统计次数又可以在超过阈值时直接拦截。这和黑名单方案共用一套基础设施。还可以扩展为功能开关。某个功能出问题了想临时对部分用户关闭不需要紧急发版直接通过 Redis 写入该场景的黑名单把用户挡在功能入口前。等修复后再删除对应 Key用户无感恢复。其实你仔细看各大厂提到的“全链路灰度”“全链路压测”底层都会有类似的控制平面和数据平面分离的思想K8S 只是让这些控制手段更容易规模化落地而已。从我个人的实际体验来看做这类技术选型最重要的不是纠结某个组件有多先进而是想清楚你的禁令要在哪一层生效、有效期多长、更新多频繁。把这三个问题想透了选型基本就定了一大半。最后再分享一个小技巧。无论你选择了哪个方案一定记得给“黑名单判断”单独增加监控指标。我们封装的黑名单组件里每次判断都会记录命中数、放行数、Redis 耗时三个指标。有了这些指标后续做风控效果分析、判断是否有误杀、评估 Redis 容量压力全都有了数据支撑。没有监控的黑名单就是一颗定时炸弹这句话我在无数个复盘会上讲过。