【老码小张-聊架构】从『鲸鱼』到『火箭』:推特10年架构演进,你的系统也能借鉴!

120 阅读10分钟

嘿,各位技术路上的小伙伴们,我是老码小张。

今天想跟大家聊个有意思的话题。咱们平时做开发,有没有遇到过这样的场景:刚开始做的那个小项目、那个内部系统,用户量不大时跑得飞快,功能迭代也嗖嗖的。可随着用户量蹭蹭往上涨,请求一多,系统就开始颤抖,响应变慢,时不时还来个“哎呀,服务器开小差了”?这时候,你是不是也开始琢磨:那些日活过亿的互联网大厂,像推特(现在叫 X 了,但咱们还是习惯叫推特哈)这种,是咋撑住那么恐怖的流量的?

别急,今天咱们就拿推特开刀,看看它从 2012 年那个时不时给大家看“失败鲸鱼”(Fail Whale)的年代,到 2022 年相对稳定高效的这十年间,后端架构都经历了哪些翻天覆地的变化。这不只是个“别人家的故事”,里面藏着的经验教训,对咱们自己设计和优化系统,绝对有实打实的参考价值。看完这篇,下次再遇到性能瓶颈,你可能就有新的思路了!

image.png

一、 回到过去:2012 年的推特与“甜蜜”的单体时代

想象一下 2012 年,那时候的推特,虽然已经挺火了,但技术栈跟现在比起来,那可是相当“古典”。

  • 核心架构: 主力是 Ruby on Rails (RoR) 的单体应用,江湖人称“Monorail”。啥叫单体?简单说,就是所有功能(发推、看推、用户关系、时间线……)都打包在一个巨大的代码库里,部署也是整个一起上。
  • 数据存储: 主要靠 MySQL。关系型数据库嘛,大家懂的,事务性好,开发方便。为了分担压力,他们搞了数据库分片(Sharding),用了一个叫 Gizzard 的框架来管理这些分片。
  • 缓存: Memcached 是当时的主力缓存,用来缓解数据库的读取压力。毕竟社交应用,读多写少是常态。
  • 关系图谱: 用户的关注、粉丝关系,这种图结构数据,MySQL 处理起来比较吃力。所以他们捣鼓出了一个专门的图数据库服务 FlockDB

那时候为啥这么搞?

你想啊,早期业务快速迭代,RoR 开发效率高,单体架构简单直接,改个 Bug、上个小功能,整个团队对着一个代码库使劲儿就行,沟通成本低。MySQL 也是大家最熟悉的伙伴。

但是,痛点也来了!

随着用户量爆炸式增长,这个“大胖单体”开始不堪重负:

  1. 牵一发动全身: 改一个小地方,整个应用都得重新测试、部署,风险大,效率低。
  2. 性能瓶颈集中: 某个功能模块(比如时间线生成)压力大,很容易拖垮整个应用,"Fail Whale" 频频出现。
  3. 技术栈单一: 想用个新语言、新技术?在单体里搞,整合难度大。
  4. 数据库扛不住: 即便分片,MySQL 在超大规模的写入和关系查询下也显得吃力。

那时候的推特工程师,估计每天都在“救火”和“优化”之间反复横跳。

二、 进化之路:拆!走向微服务与定制化存储

面对这些痛点,推特开始了漫长的架构“进化史”。核心思想就一个字:

1. 告别单体,拥抱微服务

这是最大、最核心的变化。推特逐步把那个巨大的 RoR 单体拆分成一个个更小、更专注的服务单元,也就是微服务。比如,专门负责发推的服务、负责用户信息的服务、负责时间线生成的服务、负责关系管理的服务等等。

为啥要拆?

  • 独立部署、独立扩展: 每个服务可以单独开发、测试、部署和扩容。时间线服务压力大?单独给它加机器!不影响其他服务。
  • 技术选型灵活: 不同的服务可以用最适合它的技术栈。比如计算密集型的服务可以用性能更好的语言。推特后来大量转向了 Scala 和 JVM,就是看中了它们在并发处理和性能上的优势。
  • 团队解耦: 每个小团队负责自己的服务,权责清晰,开发效率也能提升。

听起来很美? 是的,但微服务也带来了新的挑战:服务间的通信、分布式事务、服务发现、监控复杂度都直线上升。这个咱们后面细说。

2. 数据库革命:从 MySQL 到 Manhattan

MySQL 虽好,但在推特这种规模下,读写性能、扩展性、运维成本都成了大问题。于是,推特憋了个大招,自研了一个分布式、高可用的 NoSQL 键值存储系统——Manhattan

你可以把它想象成一个超级增强版的 Redis 或 Memcached,但它提供了更强的数据持久化、跨数据中心复制和高可用性保证。推特的核心数据,比如推文内容、用户信息、社交关系等,大量迁移到了 Manhattan 上。

graph TD
    subgraph "2012 年架构 (简化)"
        A[用户] --> B(RoR 单体应用 Monorail);
        B --> C{Memcached};
        B --> D(MySQL 主库/分片 Gizzard);
        B --> E(FlockDB 关系图);
        style B fill:#D1C4E9,stroke:#333,stroke-width:2px
        style D fill:#BBDEFB,stroke:#333,stroke-width:2px
    end

    subgraph "2022 年架构 (简化)"
        F[用户] --> G(API 网关);
        G --> H{服务路由/编排};
        subgraph 微服务集群-Scala/JVM 为主
            I[发推服务];
            J[用户服务];
            K[时间线服务];
            L[关系服务];
            M[...]
        end
        H --> I; H --> J; H --> K; H --> L; H --> M;
        subgraph 核心存储与缓存
            N((Manhattan 分布式存储));
            O((Redis/Pelikan 缓存集群));
            P((其他专用存储/图库));
        end
        I --> N; J --> N; K --> N; L --> P;
        I --> O; J --> O; K --> O; L --> O;
        subgraph 消息与基础设施
             Q{Kafka 消息队列};
             R[容器平台 K8s/Mesos];
             S[监控与告警];
        end
        I --> Q; J --> Q; K --> Q; L --> Q;
        微服务集群 --> R;
        核心存储与缓存 --> R;
        R --> S;

        style G fill:#C8E6C9,stroke:#333,stroke-width:2px
        style N fill:#FFCCBC,stroke:#333,stroke-width:2px
        style O fill:#FFF9C4,stroke:#333,stroke-width:2px
        style Q fill:#CFD8DC,stroke:#333,stroke-width:2px
    end

    linkStyle 0 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 1 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 2 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 3 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 4 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 5 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 6 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 7 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 8 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 9 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 10 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 11 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 12 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 13 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 14 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 15 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 16 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 17 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 18 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 19 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 20 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 21 stroke-width:1px,fill:none,stroke:grey;
    linkStyle 22 stroke-width:1px,fill:none,stroke:grey;

注意:上面的 Mermaid 图简化了很多细节,主要是为了展示核心变化。实际架构要复杂得多。

3. 缓存升级与消息队列引入

  • 缓存: Memcached 依然在用,但 Redis 也被大量引入,功能更丰富。推特内部还搞了个 Pelikan,对缓存做了更统一和优化的管理。缓存策略也更复杂,多级缓存、热点数据预加载等等,都是为了极致的读取性能。
  • 消息队列: 微服务之间直接调用太耦合,容易出问题。推特引入了 Kafka 这样的分布式消息队列。比如你发了一条推文,发推服务可能就是把这条推文扔进 Kafka,然后下游的时间线服务、通知服务、搜索索引服务等自己去 Kafka 里订阅消费这条消息,实现了解耦和异步处理。这对于提高系统的弹性和吞吐量至关重要。

4. 基础设施的现代化

底层的基础设施也鸟枪换炮了。容器化(Docker)容器编排(Mesos,后来也拥抱 Kubernetes) 成为标配,让服务的部署、扩缩容、资源利用率都大大提升。强大的监控、日志和追踪系统也必不可少,否则在成千上万的微服务实例中排查问题,简直是大海捞针。

三、 十年变迁:一张表看懂关键差异

特性2012 年代2022 年代核心变化与原因
架构风格单体应用 (Monolith - RoR)微服务 (Microservices)解决单体扩展性、部署、团队协作瓶颈
核心语言Ruby (on Rails)Scala (运行在 JVM 上) 为主, 也有 Java, Python 等追求更高性能、并发能力和类型安全,JVM 生态成熟
核心数据库MySQL (通过 Gizzard 分片)自研 NoSQL 键值存储 ManhattanMySQL 难以满足超大规模读写和扩展需求,Manhattan 提供更高性能、可用性和可扩展性
缓存MemcachedRedis, Memcached, 自研 Pelikan (统一缓存框架)需求更多样,Redis 功能更丰富,Pelikan 统一管理优化
服务间通信方法调用 (单体内)RPC (如 Finagle), REST API, 消息队列 (Kafka)微服务架构需要网络通信,Kafka 实现解耦和异步
部署方式手动/脚本部署整个应用容器化 (Docker) + 容器编排 (Mesos/Kubernetes)提高部署效率、资源利用率和环境一致性
主要挑战性能扩展 ("Fail Whale"), 单体维护复杂性微服务治理复杂度, 分布式系统一致性, 运维成本, 数据最终一致性架构演进解决了老问题,但也带来了新挑战

四、 对咱们的启发:从小处着手,持续进化

看了推特这十年的折腾史,咱们能学到点啥?尤其是对咱们初级开发者或者正在维护中小规模系统的同学来说:

  1. 别一开始就想搞个“大新闻”: 早期业务不确定、用户量不大时,单体架构往往是最高效的选择。快速验证想法、快速迭代比追求“完美架构”更重要。别上来就微服务、K8s 全家桶,容易把自己绕进去。

  2. 演进式设计,小步快跑: 系统是慢慢“长”大的。遇到瓶颈,分析瓶颈,针对性解决。是数据库慢?加缓存、读写分离、分库分表。是某个业务逻辑复杂且耗时?考虑把它拆成单独服务异步处理。推特的演进也不是一蹴而就的。

  3. 缓存是把“瑞士军刀”: 对于读多写少的应用,缓存往往是提升性能最立竿见影的手段。本地缓存、分布式缓存(Redis/Memcached),该用就用。

  4. 异步处理是个好帮手: 不是所有操作都需要立刻同步返回结果。像发邮件、生成报表、更新用户积分这类操作,完全可以扔给消息队列(如 RabbitMQ, Kafka, 甚至 Redis 的 list 结构有时也能凑合)去后台慢慢做,让主流程更快响应用户。

    # 概念性示例:用 Python 的 Celery (一个流行的分布式任务队列) 实现异步任务
    # tasks.py
    from celery import Celery
    
    # 假设你的 RabbitMQ 或 Redis 运行在本地
    app = Celery('tasks', broker='redis://localhost:6379/0')
    
    @app.task
    def send_welcome_email(user_id):
        # 这里是实际发送邮件的逻辑...
        print(f"正在给用户 {user_id} 发送欢迎邮件...")
        # 模拟耗时
        import time
        time.sleep(3)
        print(f"用户 {user_id} 欢迎邮件发送完毕!")
        return True
    
    # 在你的 Web 应用代码中调用
    # views.py (假设是 Flask 或 Django)
    # from .tasks import send_welcome_email
    
    # def register_user(username, email):
    #    # ... 创建用户的逻辑 ...
    #    user_id = create_user_in_db(username, email)
    #
    #    # 把发送邮件的任务扔给 Celery 去异步执行,立即返回响应给用户
    #    send_welcome_email.delay(user_id)
    #
    #    return "注册成功,欢迎邮件稍后送达!"
    
    # 你需要单独运行 Celery worker 来处理这些任务:
    # celery -A tasks worker --loglevel=info
    

    上面是伪代码和注释,演示了如何将耗时的邮件发送变成异步任务。

  5. 数据库扩展,量力而行: MySQL 优化手段很多,读写分离、索引优化、适当反范式能解决不少问题。真到了需要分库分表或者考虑 NoSQL 的时候,再动手也不迟,但要提前有这个意识。

  6. 微服务是“甜点”,不是“主食”: 只有当单体真的遇到难以解决的扩展性、团队协作或技术异构问题时,再谨慎地考虑微服务化。可以先从最痛的、最独立的模块开始拆分。

推特的架构演进是一个关于规模、性能、可用性和效率的持续斗争故事。它告诉我们,没有一成不变的“最佳架构”,只有最适合当前业务阶段和技术能力的架构。

希望今天聊的这些,能给你带来一些思考和启发。技术之路,道阻且长,但不断学习、实践、总结,咱们就能让自己的系统越来越稳健、越来越强大!


我是老码小张,一个爱琢磨技术原理、在实践中不断踩坑和成长的技术人。欢迎大家在评论区交流你的看法,或者你遇到的系统架构问题,咱们一起探讨,共同进步!下次再聊!