Skip to content

10 亿量级 QQ 账号注册时,如何快速校验“账号不重复”?

更新: 8/8/2026 字数: 0 字 时长: 0 分钟

先声明边界:下面讨论的是公开的海量数据唯一性校验通用技术原理,不是腾讯或 QQ 的内部实现细节。我们把“10 亿量级 QQ 账号注册”当作一个典型超大规模账号系统问题,推导业内常见的高性能方案。

想象你是一个前端开发者,正在写注册页:

从前端视角看,这只是一次接口请求。但后端真正面对的问题是:

在已经存在 10 亿个账号的情况下,如何用极低延迟判断“这个账号有没有被注册过”,并且在高并发下保证不会重复写入?

这不是简单地 SELECT * FROM user WHERE account = ? 就能优雅解决的问题。数据量、并发、存储成本、误判风险、最终一致性,都会影响方案设计。

10亿量级账号注册唯一性校验

一、核心瓶颈:不是“能不能查”,而是“能不能又快又稳地查”

如果只有 10 万账号,数据库建个唯一索引就够了。注册时查一下,插入时靠唯一索引兜底,问题不大。

但到 10 亿量级,问题会放大:

  1. 查询频率高

    • 注册、登录、找回、风控、改绑都可能查账号。
    • 热点时间可能有大量并发请求。
  2. 索引体积大

    • 10 亿条记录的索引不是小文件。
    • 索引可能跨内存、磁盘、多个分片。
  3. 数据库压力大

    • 如果每次前端输入都请求后端查重,数据库会被打爆。
    • 即使最终能查到,也可能延迟高、成本高。
  4. 必须绝对避免重复注册

    • “快速判断”可以有优化空间;
    • 但最终写入时,唯一性必须强保证。

所以这个问题通常要拆成两层:

前者追求快,后者追求准。

二、常见方案对比

海量唯一性校验方案对比

方案 1:数据库唯一索引

最基础也最可靠的方案是:

sql
CREATE UNIQUE INDEX uniq_account ON user(account);

注册时:

优点

  • 准确;
  • 逻辑简单;
  • 能从根上防止重复写入;
  • 是最终一致性的权威兜底。

缺点

  • 10 亿量级下,单库扛不住;
  • 需要分库分表;
  • 高并发写入时索引维护成本高;
  • 不适合承担所有“预检查”流量。

适用场景

数据库唯一索引适合做:

最终确认层,而不是唯一的前置查询层。

也就是说,最终写入一定要靠它兜底,但不能所有判断都只压到数据库上。

三、方案 2:Redis Set / 缓存

Redis 很快,所以很多人第一反应是:

text
把所有已注册账号放进 Redis Set
注册前 SISMEMBER 查一下

理论上查询很快。

优点

  • 查询延迟低;
  • 接口响应快;
  • 适合做热点缓存;
  • 前后端交互体验好。

缺点

  • 内存成本很高;
  • 10 亿账号如果是字符串,空间消耗巨大;
  • Redis 集群维护成本高;
  • 数据同步和一致性需要额外设计。

如果账号是纯数字,可以压缩;但如果是字符串账号、邮箱、手机号、复杂 ID,Redis Set 会更占空间。

适用场景

Redis 更适合:

  • 缓存最近注册账号;
  • 缓存热点账号;
  • 做短期去重;
  • 缓解数据库压力。

但把 10 亿账号完整塞进 Redis Set,通常不是空间最优方案。

四、方案 3:位图 Bitmap

如果账号是数字,并且范围可控,位图非常强。

位图的思想很简单:

一个账号对应一个二进制位。这个位是 1,表示已注册;是 0,表示未注册。

例如账号 10086 注册了,就把第 10086 位设置为 1。

空间有多省?

10 亿个位需要:

text
10亿 bit ≈ 125 MB

这非常夸张地省空间。相比存 10 亿个字符串,位图优势巨大。

优点

  • 极省内存;
  • 查询极快;
  • 判断结果准确;
  • 很适合连续数字 ID。

缺点

  • 适合数字账号,不适合任意字符串;
  • 如果账号范围很稀疏,会浪费空间;
  • 如果账号不是可直接映射的整数,需要额外转换;
  • 删除、回收、冻结等状态管理要另行设计。

适用场景

如果账号体系类似:

text
10000 到 9999999999 的数字 ID

并且可以把账号直接映射到位图下标,那么 Bitmap 是非常优秀的方案。

对于“QQ 号”这种数字型账号,从通用技术推导上看,Bitmap 是非常值得考虑的前置校验方案之一

五、方案 4:布隆过滤器 Bloom Filter

布隆过滤器是海量数据判断“是否存在”的经典方案。

它的特点是:

text
如果它说“不存在”,那基本可以相信一定不存在;
如果它说“存在”,可能真的存在,也可能误判。

生活化理解:

布隆过滤器像小区门口的保安名单。
如果名单规则判断你“不在名单里”,你肯定不是老住户;
如果判断你“可能在名单里”,还需要去物业系统查身份证确认。

它为什么省空间?

布隆过滤器不会完整保存账号本身,而是通过多个哈希函数,把账号映射到一组 bit 位上。

所以它比 Redis Set 省很多空间,适合判断:

text
这个账号有没有可能已经存在?

优点

  • 空间效率高;
  • 查询非常快;
  • 适合海量数据;
  • 对字符串、数字账号都适用;
  • 非常适合挡掉大量“不存在”的请求。

缺点

  • 有误判;
  • 不能单独作为最终判断;
  • 删除元素比较麻烦,通常需要计数布隆过滤器等变体;
  • 需要设计误判率、哈希函数数量和容量。

适用场景

布隆过滤器适合做:

注册前的第一道快速过滤。

比如:

或者:

六、10 亿量级下,性能最优方案怎么推导?

这个问题没有一个脱离业务条件的“唯一最优答案”。但从通用架构角度,可以推导出一个高性能组合:

如果账号是纯数字且范围可控:优先考虑 Bitmap

例如账号可以映射到一个整数区间:

text
accountId → bitmap index

那么 Bitmap 查询非常快,空间也极低。

注册流程可以是:

这里要注意:

Bitmap 不能替代数据库唯一索引,因为高并发下可能两个请求同时看到 bit = 0。

所以最终仍要靠数据库唯一索引保证绝对不重复。

如果账号是字符串或范围不可控:优先考虑 Bloom Filter

比如账号可能是:

text
abc123
user_2026
email@example.com

这类账号无法自然映射到连续位图下标,用布隆过滤器更合适。

流程是:

这套方案的关键点是:

七、为什么不能只靠前端判断?

前端可以做很多体验优化,比如:

  • 输入格式校验;
  • 长度校验;
  • 非法字符提示;
  • 防抖请求;
  • “账号是否可用”的即时提示。

但前端不能承担最终唯一性判断。

原因很简单:

  1. 前端状态不可信;
  2. 用户可以绕过页面直接调接口;
  3. 多个用户可能同时注册同一个账号;
  4. 前端看到“可用”到真正提交之间,账号可能已被别人抢注。

所以前后端协作应是:

text
前端:提升体验,减少无效请求
后端:保证正确性和一致性

一个比较合理的前端策略是:

注意文案最好不要写得过于绝对。比如“当前可用”比“永久可用”更准确。

八、相关知识拓展:这些技术还能用在哪里?

1. 黑名单校验

比如判断某个 IP、手机号、设备 ID 是否在黑名单中。

  • 数据量大;
  • 查询频率高;
  • 大多数请求不在黑名单里。

这很适合用布隆过滤器前置拦截。

2. 爬虫 URL 去重

搜索引擎或爬虫系统会遇到海量 URL,需要判断:

text
这个 URL 爬过没有?

如果每次都查数据库,成本很高。布隆过滤器可以快速过滤“没见过的 URL”,大幅降低存储压力。

3. 推荐系统去重

比如:

text
某篇文章是否已经推荐给这个用户?
某个商品是否已经曝光过?
某条消息是否已经推送过?

这类场景可以用 Bitmap 记录用户行为。

例如用户 ID 和内容 ID 都是数字时,可以用位图或 Roaring Bitmap 做集合运算。

4. 前端可复用的思路

虽然前端不直接处理 10 亿数据,但可以借鉴这些思想。

防抖:减少无效请求

用户输入账号时,不要每敲一个字符就请求后端。

text
输入停止 300ms 后再请求

这和后端前置过滤思想类似:先减少不必要的压力。

本地轻量校验

前端先判断:

  • 长度是否合法;
  • 是否包含非法字符;
  • 是否符合账号规则。

明显不合法的请求,不必发给后端。

状态提示要考虑并发

前端显示“账号可用”时,要理解这只是“查询时可用”。

最终注册仍可能失败,因为别人可能抢先提交。

所以交互上可以写:

text
当前账号可用,请尽快完成注册

而不是:

text
该账号一定可以注册

九、结论:最优方案不是单点,而是分层组合

10 亿量级账号唯一性校验,真正高性能的思路不是“找一个万能工具”,而是分层:

如果账号是连续或可映射的数字 ID,从通用技术推导看,Bitmap + 分库分表唯一索引兜底通常是空间和查询性能都很优秀的方案。

如果账号是字符串或范围不可控 ID,则更适合:

最终要记住一句话:

海量唯一性校验的核心不是“查一次数据库”,而是用便宜的结构挡住大多数请求,再用权威存储保证最终正确。