Clipping 微信公众号

面试官:为什么全中国 10 亿人零点步数一齐清零,微信服务器却没崩?

by Fox爱分享 原文 ↗
Created: 2026-05-28

公众号名称:Fox爱分享

作者名称:Fox爱分享

发布时间:2026-05-28 07:00

在高级系统架构师的面试中,“高并发”往往被具象化为电商秒杀、12306 抢票等场景。但很少有人注意到,每天深夜 23:59 到 00:01 这短短两分钟内,你的微信里正在发生一场全中国规模最大的分布式“数据海啸”:

全国接近 10 亿的活跃用户,每天积攒的微信步数会在零点准时逻辑清零;与此同时,系统需要瞬间完成昨天的步数结算、拉取几十万个微信群的排行榜、生成步数冠军、并承受数亿次“点赞占领封面”的社交并发写冲击。

这种“绝对零点”场景,在架构层面的恐怖之处在于:全网用户在同一秒钟由物理时钟协同触发,没有任何前置的用户行为缓冲,是一场人为蓄意的顶级 DDoS 攻击。

如果让你来设计这个系统,面对 10 亿级的数据清零与群排行结算,如何才能做到让服务器稳如泰山?腾讯的顶尖架构师们究竟用到了哪些秘籍?


一、 饮鸩止渴:如果用常规 MySQL 思路硬刚零点,会发生什么?

面对这种“全网在同一秒钟协同触发”的绝对零点场景,很多缺乏高并发经验的程序员,第一反应通常是:这有什么难的?零点一到,后台跑一个定时任务(Cron Job),执行一条 UPDATE 语句把全表的步数清零不就得了?

如果真这么做,公司第二天就可以直接开除你了。

10 亿行数据的全表更新会引发极其恐怖的行锁与表锁争用,IOPS(每秒输入输出量)瞬间打满,数据库会直接陷入死锁和瘫痪。

那么,面试官的第一个灵魂拷问就来了:既然不能直接更新,那面对 10 亿用户量,微信到底还用不用 MySQL 关系型数据库?

1. 微信对 MySQL 的核心定位:最终无状态的持久化归档

根据微信后台团队在历届 InfoQ 架构师峰会及腾讯内部技术大讲堂(如《从无到有:微信后台系统的演进之路》)披露的信息,微信并没有完全抛弃关系型数据库,但 MySQL 被放到了存储链条的最末端。

在微信的全局架构中,所有的突发流量洪峰(点赞、高频上报步数、拉取排行)100% 在内存缓存层和自研分布式中间件层被消化掉。送到 MySQL 里的数据,不是高频突发的请求,而是经过了消息队列(MQ)削峰、聚合、洗干净后的“冷账本”。

2. 激进的“分库分表 + 用户隔离”

微信内部拥有海量的 MySQL 实例。它通过用户全局逻辑标识(UIN/UID)进行严格的 Hash 路由,把 10 亿用户切得极碎,分流到数十万个标准的 MySQL “小数据表”中。

  • 每一个分表可能只负责 1 万个用户。

  • 零点时,高并发的落地压力被平均分摊到了无数台独立的物理服务器和硬盘上。对单个 MySQL 实例而言,它在零点根本感知不到“10 亿人清零”的宏观洪峰,它只看到自己负责的那 1 万个用户,在按部就班地由后台线程写入一条历史记录而已。


二、 微信官方披露的底层存储底牌:PaxosStore 与自研生态

单纯的 MySQL 显然无法支撑微信步数这种“写多读少、高频覆盖、强社交关联”的业务。根据腾讯官方披露的技术演进,微信运动背后的真正底牌是自研分布式强一致性存储系统

1. 核心底牌:PaxosStore

微信目前全量核心业务(包括朋友圈、三方登录、微信运动等)的底层,运行在腾讯自研的 PaxosStore 上。这是一款基于 Paxos 协议的、具备多副本强一致性的海量分布式 KV(Key-Value)/ Table 存储系统。

  • 抗峰值能力: PaxosStore 的底层完全是基于内存+ WAL(预写日志)的。步数上报时,请求直接在内存中完成原子累加(Atomic Increment)并写盘持久化日志,其并发吞吐量比传统关系型数据库高出几个数量级。

  • UIN 路由: 微信运动的数据在技术特征上属于数值型。PaxosStore 以用户的 UIN(uint32_t 整型)为 Key,实现极速的 $O(1)$ 级读写,直接在前端筑起了一道无法撼动的“防洪堤”。

2. 全球多数据中心(Multi-DC)的自治闭环

微信团队在分享多数据中心架构时提到,为了让 10 亿用户在不同地区都能秒级打开微信,微信采用了 “用户归属地自治” 的存储架构:

  • 你的微信步数上报时,写操作必须回到你归属的数据中心完成(例如你在南方,就写入深圳集群;在北方,则写入上海集群)。

  • 零点的排行榜结算,首先在各个数据中心内部的缓存集群中各自闭环计算,然后再通过腾讯骨干网异步同步(Async Replication)到其他数据中心,从而彻底避免了跨地域网络延迟带来的系统阻塞。


三、 微信核心算法演进:如何实现“10亿人秒级无感清零”?

真正的高并发系统,从来不会在核心业务路径上去做真正的“物理删除”或“同步更新”。微信步数之所以能“秒级清零”,核心秘密在于:数据从未被真正清空,改变的只是时间的指针。

杀手锏一:基于时间窗的版本号滚动(Time-Based Versioning)

架构师在设计数据 Key 时,不会针对每个用户存一个叫 user:steps:12345 且不断修改的变量,而是引入了一个逻辑版本号,通常以当前的日期作为 Key 的一部分。

1. 数据的存储结构

  • 前天(5月15日)的 Key:User:Steps:20260515:UIN_12345$\rightarrow$ 值:12800 步

  • 昨天(5月16日)的 Key:User:Steps:20260516:UIN_12345$\rightarrow$ 值:15400 步

  • 今天(5月17日)的 Key:User:Steps:20260517:UIN_12345$\rightarrow$ 值:0 步(或未初始化)

2. 零点无感切换

当时间从 23:59:59 迈入 00:00:00 的那一刹那,服务器内部的机器时钟发生变化。此时,业务逻辑层在获取当前日期的格式化字符串时,自动从 20260516 变成了 20260517

当你的手机端在零点后第一次上报步数,或者请求步数主页时,路由代码自动去读写 User:Steps:20260517:UIN_12345。因为新一天的 Key 尚未初始化,系统默认返回的就是 0 步。

看明白了吗? 微信根本没有去遍历 10 亿用户把数据改成 0,它只是把写请求的靶子,从“昨天的盘子”平滑移到了“今天的盘子”。这就是所谓的“逻辑清零”,时间复杂度是完美的 O(1)

杀手锏二:延迟双写(Lazy Double-Write)缓冲期

如果仅仅靠时间戳切换,还会面临一个严重的分布式问题:全网时钟不同步。

尽管有 NTP(网络时间协议)进行时钟同步,但分布式集群中各台服务器、以及全球 10 亿条手机客户端的时间,不可能做到绝对零误差的对齐。有些用户的手机可能慢了 2 秒,在 00:00:02 的时候还在上报 23:59:58 的步数。

为了防止这部分因时钟微弱偏差而迟到的数据被彻底丢弃,或者误写到新一天的账本里,微信采用了延迟双写动态时间窗口策略:

在零点前后的一个微小时间窗口内(例如 23:58 到 00:05),接入层网关会采用双写机制

  • 客户端上报的步数流,既会累加到昨天的 Key 中(确保昨天的冠军结算数据绝对准确),也会同步初始化到今天的 Key 中。

  • 过了缓冲期后,昨天的 Key 彻底变成只读的冷数据,静静等待异步归档。

这就是分布式架构中经典的“用空间换取时间一致性”。

通过这短短 7 分钟的双写缓冲窗口,微信不仅完美容忍了全网由于物理时钟漂移(Clock Skew)带来的分布式误差,还极其巧妙地完成了新旧账本的平滑过渡。它最厉害的地方在于,在流量最凶猛的交汇点,全网没有发生任何一次高空坠落式的阻塞等待,而是像丝绸滑过桌面一样,完成了新旧一天的无感交替。

解决了写冲击与时间错峰的难题,那么接下来的考验就是:当零点过后,用户陆陆续续点开手机,那令人期待的“群步数排行榜”又是如何在瞬间完成高并发计算,且不引发 Redis 瘫痪的呢?我们接着往下看。

四、 高并发排行与冷数据归档:如何解决 Redis BigKey 与存储成本?

在解决了“逻辑清零”的写冲击后,微信运动面临的第二个天堑是:如何高效生成排行榜,并处理海量的历史冷数据?

杀手锏三:关系链懒加载与微信群 ZSet 哈希分片

在 Redis 中,排行榜最天然的数据结构是 ZSet(有序集合)。它可以通过 ZADD 动态更新步数,通过 ZREVRANGE 瞬间拉取前几名。但如果直接把全网 10 亿用户放进一个 ZSet,单 Key 元素过多会导致严重的 BigKey 问题,引发单台 Redis 实例的内存分配阻塞和网关暴毙。

微信的解法是:拒绝全局大总榜,化整为零。

1. 朋友圈排行:基于关系链的“动态懒加载(Lazy Load)”

微信运动其实并没有一个“全国大总榜”,你的排行榜里只有你的微信好友。

  • 当用户在零点后第一次打开微信运动时,系统不会去触动任何全局大表,而是拿着该用户的好友列表 UIN(例如 200 人),去内存缓存中执行一次高效的批量获取(MGET),拿到这 200 个用户今天的动态步数。

  • 随后,在应用层内存中完成一次轻量级的排序(Sort),然后渲染给用户。

  • 架构收益: 这个查询压力被均匀分散到了全网用户自发、错峰刷新的动作中,将 $O(N)$ 的全局计算降维成了 $O(1)$ 的局部读操作。

2. 微信群排行:群 ID 哈希分片(Sharding)

对于微信群排行榜,每个群都拥有一个独立的 Group_ID

  • 微信通过 Hash(Group_ID) % Redis_Nodes 算法,将全中国数亿个微信群均匀地打散到成千上万台 Redis 缓存节点上。每一个群对应一个独立的、体积很小的 ZSet。

  • 这样一来,即便是零点有几百万人同时在群里飙步数,流量也被物理隔离在了不同的机器上,任何一台服务器的负载都在安全水位以下。

杀手锏四:历史冷数据的“低谷期静悄悄归档”

那些已经过去的日子(比如昨天、前天)的步数 Key,如果一直堆在昂贵的内存缓存(Redis/PaxosStore 内存层)里,会带来恐怖的硬件成本。

在零点海啸过去、全网流量进入深夜低谷期(凌晨 2:00 - 4:00)时,微信后方的分布式调度系统(基于 Flink 或大数据的批处理组件)开始悄悄发力:

  1. 异步批处理归档: 归档线程以低优先级、限流(Throttling)的方式,将昨天已经“盖棺定论”的步数数据,批量同步到持久化分布式数据库中。

  2. 过期自毁(TTL): 内存中的旧 Key 都会被设置 2~3 天的生存时间(TTL)。一旦数据安全落地到分布式硬盘中,内存中的旧 Key 到期后会被自动回收。

通过这种内存抗高并发现货、持久化留存历史期货的冷热分离架构,既保证了零点的丝滑,又控制了运营成本。


五、 面试满分答案模板:当面试官问“如何设计10亿用户零点清零系统”

如果在面试中遇到了类似的场景设计题(如:微信步数清零、全网会员积分跨年清零、大型游戏排行榜零点结算),你可以直接抛出下面这套高级架构师级别的满分作答模板

面试官提问:

“如果让你来设计微信步数系统,面对 10 亿用户在零点同时清零和排行榜结算,你会如何设计整体的架构?会用 MySQL 吗?”

核心回答框架(分为四步):

第一步:定调存储选型,破除“单体 MySQL 误区”

回答示例:

“首先,面对 10 亿用户和绝对零点的瞬时洪峰,绝对不能让流量直达传统的单体关系型数据库(如 MySQL)。在我的设计中,MySQL 只会作为最末端的、解耦后的持久化归档账本,并且必须通过用户 UIN 进行严格的分库分表(如 1024 个库、每库 1024 张表),确保单表数据量控制在万级,将物理写压力彻底打散。

真正的核心战场在缓存和自研 KV 层。我会参考微信 PaxosStore 的设计,前端采用‘内存 + WAL(预写日志)’的强一致性分布式 KV 存储来支撑高频的步数原子累加上报。”

第二步:引入“逻辑清零”,将 $O(N)$ 降维为 $O(1)$

回答示例:

“其次,针对‘10 亿用户瞬间清零’这一诉求,我在零点那一秒,连一条数据都不会去物理修改或删除。因为任何物理操作都会导致严重的锁表或缓存阻塞。 我会采用‘基于时间窗的版本号滚动(Time-Based Versioning)’策略。将数据 Key 设计为 User:Steps:{Date}:{UIN}。零点一过,全网服务器的机器时钟自动向前跳动一秒,业务层的日期指针从昨天切换到今天。此时客户端上报和查询的请求,会自动路由到新日期的 Key 上。由于新一天的账本尚未初始化,系统读取出的默认值自然就是 0 步。通过改变时间指针,我将 10 亿次清除操作降维转变成了 $O(1)$ 的逻辑偏移,对服务器而言,零点瞬间的工作量是完美的 0。

第三步:容忍分布式缺陷,设计“延迟双写缓冲期”

回答示例:

“考虑到分布式环境下服务器与客户端之间存在 时钟漂移(Clock Skew),为了防止零点前后几秒钟内由于时钟不同步导致的数据丢失,我会设计一个 5分钟的‘延迟双写时间窗口’

在 23:58 到 00:03 之间,客户端上报的步数流会同时累加到昨天和今天的两个 Key 中。这样既能保证昨天冠军结算数据的绝对准确,又能确保今天新账本的平滑过渡。”

第四步:化整为零,解决排行榜 BigKey 与冷数据归档

回答示例:

“在社交排行方面,为了防止 Redis ZSet 出现 BigKey,我拒绝设计全局总榜。

  1. 对于朋友圈排行,利用关系链在应用层进行懒加载(Lazy Load),在用户打开瞬间批量 MGET 好友步数并在内存中排序,将写扩散转化为读扩散,利用用户行为错峰消减压力。

  2. 对于群排行榜,以 Group_ID 作为分片键,将不同群的 ZSet 均匀哈希路由到不同的 Redis 节点。

最后,在凌晨 2:00 到 4:00 的业务低谷期,通过分布式调度任务将昨天的冷数据异步刷入底层的分库分表(或 HBase),内存 Key 结合 TTL 机制自动过期释放,实现整体架构的闭环。”


Fox 老师的技术总结

这个案例的精髓在于“避其锋芒,借力打力”。 在面对极端高并发时,顶尖的架构师从来不去硬碰硬地优化物理删除的速度,而是通过改变数据结构(引入时间版本号),把一个原本需要消耗海量算力的灾难级场景,直接消解于无形。

你在面试中展示出这种“空间换时间”、“逻辑清除代替物理清除”、“分片路由隔离流量”的思维模型,正是大厂期望的高级/资深架构师所具备的技术大局观。


满大街吃灰资料改不了底薪,看透业务本质的架构思维,才能真正决定职业上限

我是Fox。不堆砌无用资料,只带你死磕书本里没有的实战硬坑。

觉得今天这篇硬核拆解对你有启发,拒绝白嫖,顺手点个在看

关注公众号**【Fox 爱分享】**,吃透底层架构,技术面试一路畅通!


cover_image

Original Fox爱分享 Fox爱分享


内容效果不满意?点此反馈

输入关键词开始搜索