做代理服务(或给代理网关做准入控制)时,白名单通常从几百条一路涨到几万、几十万:客户 IP 动态变化、办公/云主机频繁换出口、还要兼顾风控与可用性。行业里常见的“写死 allow/deny”“每次改名单就 reload”会很快遇到两个问题:维护成本爆炸与规则匹配拖慢链路(尤其在入口高并发时更明显)。下面给一套偏工程化的解决方案,把“代理白名单管理”做成可规模化的系统,并把性能优化做到可量化。
一、先把白名单拆成三类,避免“一锅粥”
真实场景里白名单并不只是“允许访问的人”:
- 长期静态白名单:公司固定机房、核心业务服务器、合作方固定段(适合 CIDR 聚合)。
- 短期动态白名单:员工/客户出口 IP 会变(需要 TTL、自动过期、可自助更新)。
- 策略白名单:不是单纯放行,而是“放行但限速/限并发/二次校验”(减少被滥用)。
拆分的意义是:静态名单走极致性能路径;动态名单走可维护路径;策略名单走风控路径——三者别互相拖累。
二、数据结构决定上限:别用线性匹配去硬扛
讨论中经常提到 Nginx 直接堆 allow 指令在名单很大时会变得难维护,甚至被迫寻找“更大的白名单”做法。
更稳的方式是把匹配交给更适合的结构:
- CIDR 聚合 + 分段合并:把离散 IP 合并成网段,规则数量立刻下降一个数量级(尤其企业客户常见)。
- 内核 ipset:把大量 IP/CIDR 放进 ipset,内核侧 O(1) 级匹配,应用层只需判断命中与否(高并发入口很吃香)。
- Nginx geo/map:用
geo/map做“IP→标记位”映射,再基于标记位执行放行/限流(比堆 allow 可维护)。 - 自研网关:在代理前置网关用 LPM(最长前缀匹配)Trie 或压缩 Radix Tree 存 CIDR;对“单 IP”可用 HashSet,并做分片/锁分离。
核心原则:白名单匹配要走常数级或对数级,不要在请求路径里做长列表遍历。
三、把“更新白名单”从 reload 变成“增量同步”
很多团队一开始用配置文件白名单,用户更新 IP 就触发 reload;名单越大越频繁,抖动越明显。业内也常见“希望用户能在网站登录后自助更新白名单 IP”的诉求。
更工程化的做法:
- 控制面/数据面分离:控制面提供 API(增删改查、审批、风控);数据面只做高速判定。
- 增量发布:用消息队列/订阅推送,把新增/删除的 IP 变更以事件形式下发;数据面本地内存结构原地更新。
- TTL 与回收:动态白名单必须带 TTL,自动过期;避免“名单只增不减”。
- 版本号与回滚:每次变更生成版本号,数据面支持快速回滚到上一版本,降低误封/误放风险。
如果你用 ipset:控制面生成差量,数据面通过 ipset restore 或原子 swap(新 set 构建完再切换)减少阻塞。
四、性能优化的四个“真实瓶颈点”
- 规则数量膨胀:CIDR 不聚合、重复 IP、不清理过期,导致匹配结构越来越大。
- 热路径 IO:每个请求都查数据库/远程 Redis,会把延迟放大。正确方式是“本地缓存 + 后台同步”。
- 错误的白名单模式:白名单模式在某些“输入流量很多”的场景会带来额外损耗,需要配合黑名单排除或更高效的拦截逻辑。
- 脚本/规则本身的执行成本:例如 PAC/规则脚本在大量查询下的耗时差异,说明“同样是白名单策略,实现方式不同性能差很多”。
落地建议:把指标做起来——命中率、匹配耗时 P95/P99、更新耗时、规则体积、误封率;没有指标就只能靠感觉调参。
五、推荐的一套可直接复用的方案(中大型规模)
- 入口层:Nginx/Envoy 负责 TLS、基础限流、WAF;白名单判定优先走 ipset 或网关内存结构。
- 白名单服务:Redis/DB 存“权威数据”(带 TTL、审计);数据面只订阅增量并本地缓存。
- 自助更新:用户登录后台提交“当前出口 IP 一键加入(短期)”,并强制二次验证(短信/邮箱/2FA/工单审批)。
- 分层策略:命中白名单≠无限制;仍按套餐做并发/带宽/地区/目标域名策略,避免白名单被当成“免死金牌”。
顺带一提,如果你在做多账号与网络隔离测试,“拉力猫指纹浏览器给予3天免费试用”这类工具在实际排查中挺省时间:用独立指纹环境把“是白名单问题还是设备指纹/会话问题”快速切开验证,能明显减少来回试错成本。
六、最后的检查清单
- 白名单是否做了 CIDR 聚合 与 去重?
- 动态白名单是否有 TTL、是否可追溯审计?
- 数据面是否做到 本地高速判定,避免每请求远程查询?
- 更新是否支持 增量同步/原子切换/回滚?
- 是否把“放行”与“套餐限制/风控策略”解耦?
把这些做到位,你的“代理白名单管理”就不会随着客户增长变成系统瓶颈,性能优化也能从“玄学”变成可持续迭代的工程能力。