千万级手机号如何脱敏并保证唯一性,同时不降低压测吞吐量
解读
面试官把“数据脱敏”“唯一性”“高并发压测”三件事捆在一起问,核心想验证三点:
- 你是否理解国内对手机号作为个人敏感信息的合规要求(《个人信息保护法》《数据安全法》)。
- 你能否在“脱敏后不可逆”与“仍需唯一”之间做工程权衡,而不是简单 MD5 一把梭。
- 你能否给出在 10w+ TPS 压测场景下,CPU、内存、锁竞争都不成为新瓶颈的落地方案。
如果回答只停留在“replace 中间四位”或者“UUID 替代”,会被直接判定为缺乏性能视角。
知识点
- 国内合规口径:手机号属于“直接标识符”,脱敏后不得逆向还原,且同一系统内不同轮次测试不得出现“彩虹表”撞库风险。
- 唯一性约束:压测脚本常把手机号当账户或登录名,脱敏后仍需 1:1 映射,否则会出现“一用户多账号”或“多用户一账号”,导致事务失败率飙升,压测结果失真。
- 性能指标:单台发压机 8 核 16 G 场景下,目标 10w TPS,脱敏环节耗时必须 <0.05 ms/条,CPU 增幅 <3%,内存零拷贝,无锁。
- 常用算法对比:
- 对称加密(AES)→ 可逆,不合规。
- 普通哈希(MD5/SHA256)→ 定长但无盐会撞库,有盐则丧失唯一性。
- 格式保留加密(FPE)→ 国内无官方算法备案,合规风险高。
- 分段加盐哈希 + 位运算 → 不可逆、定长、可保持唯一,可控性能。
- 零拷贝流水线:利用 JMH/ASM 字节码注入,把脱敏函数编译成 64 位寄存器级指令,避免 new String。
- 分片并行:提前在离线阶段把 1 亿手机号脱敏成 1000 个分片文件,压测时按线程绑片,通过 mmap 顺序读,消除运行时计算。
- 内存对齐:把 11 位手机号存成 64 bit long(去掉 1 前缀,剩 10 位),利用 CPU L1 缓存 64 B 对齐,一次批量处理 8 条,SIMD 加速。
答案
线上真实手机号落库前已做 AES 加密,压测前从备份库解密导出到离线内网堡垒机,全程走脱敏流水线,步骤如下:
-
预计算阶段(离线,零压力)
a. 构建 64 bit 盐表:随机生成 1024 个 64 bit 盐,写入只读数组,长度 2^10,可完全放进 L2 缓存。
b. 对原始手机号去掉首位“1”,得到 10 位数字,转成 long 型,记为 M。
c. 取 M 低 10 bit 做盐表下标,拿到盐 S,做 64 bit 乘法哈希:H = M * S >>> 32,得到 32 bit 正整数。
d. 把 H 格式化为定长 8 位十六进制(保证 0-9,a-f 仍符合手机号字符集,方便日志排查)。
e. 拼接固定前缀“9”+原号段前三位(如 35 个号段各建一张映射表),生成 11 位脱敏号:9ABCXX,其中 ABC 为原号段, 为步骤 d 的 8 位十六进制,XX 为原手机号末两位,既保留号段分布特征,又不可逆。
f. 把原始手机号与脱敏号写入 RocksDB(key=原始,value=脱敏),RocksDB 按 key 做 hash 分片 1000 个 sst 文件,总大小约 2 GB。 -
压测运行时(无计算,只查表)
a. 发压机启动时把对应分片的 RocksDB 通过 mmap 只读方式加载到内存,占用 2 GB/台,可复用 page cache。
b. 脚本拿到原始手机号后,直接用 long 型做分片路由(hash & 1023),走内存指针查表,耗时 20~30 ns,换算 0.000 02 ms,远低于 0.05 ms 预算。
c. 因为脱敏号提前生成,运行时无锁、无对象分配、无正则,CPU 占用增加 <2%,对 10w TPS 无影响。 -
唯一性与一致性保障
a. 乘法哈希在 10 位数字空间内冲突概率 <10^-8,再叠加号段与末两位,冲突率降到 10^-12,千万级数据实测 0 冲突。
b. 每次全量导数后,用 64 bit MurmurHash 对 RocksDB 所有 value 做滚动校验和,保证多轮测试之间同号同脱敏,满足审计追踪。 -
合规与可审计
a. 盐表与算法脚本走 Git 仓库,权限分级,审计日志留痕。
b. 脱敏后数据无法回推原始手机号,符合《个人信息保护法》第51条“去标识化”要求。
拓展思考
- 如果压测模型要求“每轮生成新账号”,可放弃 1:1 映射,改用分段雪花算法:高位为号段,中位为时间戳,低位为发压机编号,直接生成 11 位假号,跳过映射表,实现 0 查表开销,但需接受号段分布与真实生产不一致,需在报告里注明“数据形态差异对缓存命中率影响 <1%”。
- 当数据规模上升到 10 亿,需引入 RoaringBitmap 对已用脱敏号做冲突检测,内存从 2 GB 降到 300 MB,检测耗时仍 <0.01 ms。
- 若公司要求“同城双活压测”,可把 RocksDB 做成只读副本,通过 Rsync 每小时同步一次,避免跨城重复计算,同时保证两地脱敏结果一致。