好友/关注/粉丝动态系统的挑战与渐进优化方案
在大型社交平台中,如微博、抖音、小红书等,动态流(Feed)是核心功能之一。随着平台规模和用户数量的不断增加,如何高效、实时地处理动态流中的信息成为架构设计中的关键挑战。本文将从架构设计的角度,分析动态系统的主要难点,并提出渐进的优化方案。
一、问题背景
动态系统的核心问题是Topic的发布与分发。每个Topic代表了用户的一次操作或事件(如发布微博、视频或评论),并且这些Topic都有一个唯一的ID。 ID是时间序列化的(同一个用户的Topic ID是递增的,不要求有one by one的,比如昨天发布的是1001,今天是1020),系统可以利用这些ID进行分页和时间线排序。 动态系统的本质是如何将用户发布的Topic高效地分发给关注该用户的粉丝,并确保系统在高并发和海量数据下保持稳定和低延迟。
最初,系统架构较为简单,但随着平台的成长和用户数量的急剧增加,性能瓶颈逐渐显现。如何解决这些瓶颈成为系统架构设计中的一大挑战。
二、蛮荒时代:简单的写扩散
在初期阶段,用户数量相对较小,系统架构较为简单。采用写扩散模式是常见的做法。具体流程如下:
-
Topic发布: 用户发布一个新的Topic后,系统会将该Topic的ID和发送者ID写入到该用户的post_box中。
-
动态推送: 系统异步遍历该用户的粉丝列表,将Topic推送到每个粉丝的read_box中。这是典型的推模式,动态会推送给所有关注该用户的粉丝。
-
动态查询: 用户登录后,查询自己的read_box,根据Topic ID进行排序,从而保证时间线的准确性。
虽然这种方式在小规模用户下能很好地工作,但当超级用户(拥有大量粉丝)发布Topic时,系统将面临粉丝遍历的性能瓶颈。遍历每个粉丝的操作会导致性能下降,严重时可能导致系统崩溃。
三、青铜时代:引入读写扩散结合
随着平台规模的扩大,系统中出现了大量的超级用户。此时,单纯的写扩散模型已不再适用,需要引入读写扩散结合的优化方案。在这个阶段,系统优化的核心目标是减少读取操作的延迟,并降低粉丝遍历的开销。
1. 超级用户优化
对于粉丝数较多的超级用户,系统无法每次都遍历其粉丝列表。优化方式如下:
- Topic发布时不推送: 当超级用户发布Topic时,系统仅将Topic的ID和发送者ID写入该用户的post_box,而不立即推送到所有粉丝的read_box中。粉丝在查询时,会去查找超级用户的post_box获取新增的Topic。
2. 普通用户的处理
对于粉丝数相对较少的普通用户,系统继续采用简单的写扩散策略。普通用户发布Topic后,系统遍历其粉丝列表并将Topic推送到每个粉丝的read_box中。
3. 优化的读取逻辑
用户登录后,首先查询自己关注的超级用户列表。对于超级用户,系统通过查询其post_box来增量获取Topic。只有对于非超级用户,才依赖于read_box进行全量查询和推送。这种方式能大大减少遍历操作的压力。
四、白银时代:异步、缓存、二级缓存与分布式架构
当系统进入白银时代,用户数量达到数千万时,单纯的读写扩散结合模式仍然面临高并发和低延迟的挑战。此时,需要引入异步处理、缓存机制、二级缓存、分布式架构等高级优化方案,以应对更大的负载和高吞吐量的需求。
1. 异步处理与消息队列
为了提升系统吞吐量,系统引入异步处理机制,将Topic发布与推送操作解耦。具体做法如下:
- 用户发布Topic时,系统将该操作写入消息队列(如Kafka、RabbitMQ)。
- 后台服务从消息队列中读取待推送的Topic,并异步将这些Topic推送到粉丝的read_box中。这样,发布Topic的操作不再阻塞主线程,提高了系统的响应速度。
2. 缓存机制
为了减少数据库访问频率和压力,系统引入了以下缓存机制:
- 热点Topic缓存: 热点Topic存储在内存中(如Redis),以减少对数据库的频繁访问。
- 用户read_box缓存: 用户的read_box内容也可以缓存在内存中,特别是对于访问频繁的用户,可以通过缓存提升查询效率。
- 超级用户post_box缓存: 普通用户在遍历自己关注的超级用户的时候,直接去超级用户post_box是个很heavy的操作,所有超级用户的post_box可以缓存起来, 具体Size可以根据业务需求来约定,这样,用户在进行读扩散操作的时候,可以直接从缓存中读取,从而减少数据库的压力.然后在再异步的去补全用户的read_box
3. 二级缓存策略
在缓存机制的基础上,系统可以引入二级缓存策略:
- 一级缓存: 存储热点数据,使用如Redis来保证高并发下的低延迟。
- 二级缓存: 目前的成熟语言基本都有很好用的memory cache组件了,比如Golang的Go - cache,二级缓存有个需要一起搭配的事半功倍的操作,就是同一个用户ID访问接口 最好是在同一台机器上,对于此用户的二级缓存,在同一个service中,就不会分布在不同的pod中,从而减少二级缓存的的dup.当然,增加了二级缓存,同时也提高了系统的复杂度,缓存不一致问题是一定存在的 如何去解决,需要具体去分析了.
二级缓存策略能显著提高数据存取效率,减少数据库的负载。
4. 分布式架构与横向扩展
随着用户数量和数据量的进一步增长,系统需要支持水平扩展。具体实现方式如下:
- 分布式数据库: 将read_box、post_box等数据进行分片和分布式存储,以提高系统的扩展性,解决单点故障。
- 分布式消息队列: 使用分布式消息队列(如Kafka)来解耦Topic发布和推送,确保系统在高负载下的可靠性和高效性。
5. 批量处理与增量推送
对于超级用户,系统可以采用批量处理和增量推送策略,减少系统压力:
- 批量写入读取: 避免一条一条读写的场景,尽量批量batch读写。
- 增量更新: 粉丝在查询时,只需获取自上次查看后的新增Topic,避免全量数据的传输。
五、黄金时代
目前还没有黄金时代的优化方案,因为我还没干到黄金时代的场景,这个等以后再更新吧
六、聊点其他的
1. 负载均衡与限流
为了确保系统的稳定性和高可用性,系统需要实现负载均衡和限流:
-
负载均衡: 将请求分发到不同的服务器或服务实例,避免单点过载。负载均衡算法可以使用一致性哈希、轮询哈希等,具体选择依据业务场景和缓存一致性需求。 个人建议一致性hash,或者其他hash算法,这样的好处是,可以保证同一个用户访问的请求,会被分发到同一个服务器上,从而保证了缓存的一致性,减少了缓存的不一致问题。二级缓存也不会有很多dup.
-
限流: 为防止突发流量导致系统崩溃,系统需实施限流策略。老生常谈的场景了,限流算法有很多,比如令牌桶,漏桶等等. 推荐使用令牌桶算法,它能平滑地控制流量,避免突发流量造成系统压力过大。
2. 监控与报警
系统需要实施完善的监控与报警机制:
- 实时监控: 监控系统的各项性能指标,如响应时间、吞吐量、错误率等。
- 报警机制: 在系统异常时及时发出报警,以便运维人员进行处理,避免故障蔓延。
3. 补偿机制
当用户反馈动态流中存在数据不一致时,系统可以提供动态偿功能,建议设计自动化方案,自动处理数据重写请求,减少人工干预。