Stream & Go:为超过 3 亿终端用户提供新闻推送 挑战:新闻推送和活动流 从 Python 切换到 Go 从 Cassandra 切换到 RocksDB 和 Raft 不完全是微服务 Devops、测试和多区域 结束语

2025-06-08

Stream & Go:为超过 3 亿终端用户提供新闻推送

挑战:新闻提要和活动流

从 Python 切换到 Go

从 Cassandra 切换到 RocksDB 和 Raft

并非完全微服务

DevOps、测试和多区域

结束语

GetStream.io

Stream是一个 API,它使开发人员能够构建新闻提要和活动流(尝试 API)。超过 500 家公司使用我们的 API,为超过 3 亿最终用户的提要提供支持。Product Hunt、Under Armour、Powerschool、Bandsintown、Dubsmash、Compass 和 Fabric(Google)等公司都依靠 Stream 来支持他们的新闻提要。除了 API 之外,Stream 的创始人还编写了最广泛使用的用于构建可扩展提要的开源解决方案

以下是 Stream 目前的状况:

  • 服务器数量:180
  • 每月信息流更新:340亿次
  • 平均 API 响应时间:12ms
  • 平均实时响应时间:2ms
  • 区域:4 个(美国东部、欧盟西部、东京和新加坡)
  • 每月 10 亿个 API 请求(约 2 万次/分钟)

鉴于我们的大多数客户都是工程师,我们经常谈论我们的技术栈。以下是我们的总体概述:

  1. Go 是我们的主要编程语言。
  2. 我们的主要数据库使用了基于 RocksDB + Raft 构建的自定义解决方案(最初使用的是 Cassandra,但希望更好地控制性能)。PostgreSQL 存储配置、API 密钥等。
  3. OpenTracing 和 Jaeger 处理跟踪,StatsD 和 Grafana 提供详细监控,我们使用 ELK 堆栈进行集中日志记录。
  4. Python 是我们用于机器学习、DevOps 和我们的网站https://getstream.io(Django、DRF 和 React)的首选语言。
  5. Stream 结合使用了写入时扇出 (fanout-on-write) 和读取时扇出 (fanout-on-read) 两种机制。这使得用户打开动态时读取速度更快,而知名用户发布动态时传播速度也更快。

Tommaso BarguliThierry Schellenbach是近三年前创立 Stream 的开发人员。我们在阿姆斯特丹创立了这家公司,参加了 2015 年纽约 Techstars 大会,并于 2016 年在科罗拉多州博尔德开设了办公室。在如此短的时间内,我们经历了一段疯狂的旅程!现在团队拥有超过 15 名开发人员,以及包括销售和市场营销在内的一系列支持人员,与早期相比,感觉规模庞大得多。

挑战:新闻提要和活动流

关注关系的本质决定了信息流的扩展性。大多数人都会记得 Facebook 的漫长加载时间、Twitter 的失败鲸鱼,以及 Tumblr 一年来的技术债务。信息流难以扩展,因为没有明确的数据分片方法。关注关系将每个人与其他人连接起来。这使得在多台机器上拆分数据变得困难。如果您想了解更多关于这个问题的信息,请查看以下论文:

  • Twitter 的技术在拥有 1.5 亿活跃用户的时候
  • LinkedIn 的排名信息流
  • 雅虎/普林斯顿研究论文

从高层次来看,有 3 种不同的方式来扩展您的 feed:

  1. 写时扇出:基本上是预先计算所有内容。虽然成本高昂,但数据分片很容易。
  2. 读取时扇出:难以扩展,但更便宜,新活动出现得更快。
  3. 上述两种方法的结合:性能更好、延迟更低,但代码复杂性增加。

Stream 结合了写入时扇出和读取时扇出两种功能。这使我们能够有效地支持拥有高度连通图的客户以及拥有稀疏数据集的客户。这一点至关重要,因为我们的客户使用 Stream 的方式各不相同。请看以下来自 Bandsintown、Unsplash 和 Product Hunt 的截图:

产品搜寻

从 Python 切换到 Go

经过多年对现有 feed 技术进行优化,我们决定在 Stream 2.0 上实现更大的飞跃。Stream 的初代版本由 Python 和 Cassandra 驱动,而为了构建 Stream 2.0 的基础架构,我们迁移到了 Go。从 Python 迁移到 Go 的主要原因是性能。Stream 的某些功能(例如聚合、排名和序列化)很难用 Python 来加速。

我们从 2017 年 3 月开始使用 Go,到目前为止体验非常好。Go 极大地提高了我们开发团队的生产力。它不仅提升了我们的开发速度,还使 Stream 的许多组件的开发速度提高了 30 倍。

Go 的性能极大地积极地影响了我们的架构。使用 Python 时,我们经常会纯粹出于性能考虑而将逻辑委托给数据库层。Go 的高性能赋予了我们在架构方面更大的灵活性。这极大地简化了我们的基础架构,并显著降低了延迟。例如,由于相同数量的请求,内存和 CPU 使用率更低,Web 服务器数量减少了 10 倍。

最初,我们在 Go 的包管理上遇到了一些困难。不过,将Dep与VG 包结合使用,有助于创建出更出色的工作流程。

维生素G

如果你从未尝试过 Go,你一定想尝试一下这个在线导览:https://tour.golang.org/welcome/1

Go 语言非常注重性能。内置的 PPROF 工具在查找性能问题方面非常出色。Uber 的 Go-Torch 库非常适合可视化 PPROF 数据,并将在 Go 1.10 中与 PPROF 捆绑在一起。

火焰图

从 Cassandra 切换到 RocksDB 和 Raft

Stream 1.0 利用 Cassandra 来存储数据流。Cassandra 是构建数据流的常用选择。例如,Instagram 最初使用 Redis,但最终为了应对快速增长的使用量而转向了 Cassandra。Cassandra 可以非常高效地处理写入密集型工作负载。

Cassandra 是一款非常棒的工具,它允许你通过添加更多节点来扩展写入容量,尽管它也非常复杂。这种复杂性使得诊断性能波动变得困难。尽管我们拥有多年运行 Cassandra 的经验,但它仍然感觉像个黑匣子。在构建 Stream 2.0 时,我们决定另辟蹊径,构建了 Keevo。Keevo 是我们基于 RocksDB、gRPC 和 Raft 构建的内部键值存储系统。

RocksDB 是一个高性能嵌入式数据库库,由 Facebook 数据工程团队开发和维护。RocksDB 最初是 Google LevelDB 的一个分支,引入了多项针对 SSD 的性能改进。如今,RocksDB 已发展成为一个独立的项目,并正在积极开发中。它采用 C++ 编写,速度很快。您可以看看这个基准测试是如何处理每秒 700 万次查询的。就技术而言,它比 Cassandra 简单得多。这意味着维护开销更低、性能提升,最重要的是,性能更加稳定。值得注意的是,LinkedIn 也在其动态消息中采用了 RocksDB。

我们的基础设施托管在 AWS 上,旨在应对整个可用区发生故障的情况。与 Cassandra 不同,Keevo 集群将节点组织为领导者节点和追随者节点。当领导者节点(主节点)不可用时,同一部署中的其他节点将启动选举并选出新的领导者节点。选举新的领导者节点操作快速,几乎不会影响实时流量。

为此,Keevo 使用 Hashicorp 的 Go 实现实现了 Raft 共识算法。这确保了 Keevo 中存储的每个比特都存储在 3 个不同的服务器上,并且操作始终保持一致。此网站很好地可视化了 Raft 的工作原理:https://raft.github.io/

共识

并非完全微服务

通过利用 Go 和 RocksDB,我们能够实现出色的 feed 性能。平均响应时间约为 12 毫秒。该架构介于单体应用和微服务之间。Stream 运行在以下 7 个服务上:

  1. 流 API
  2. 基沃
  3. 实时和 Firehose
  4. 分析
  5. 个性化与机器学习
  6. 网站和仪表板
  7. 异步工作者

要查看我们所有服务按堆栈划分的情况,请访问https://stackshare.io/stream/stacks

个性化与机器学习

几乎所有提供信息流的大型应用都运用了机器学习和个性化功能。例如,LinkedIn 会根据用户信息流的优先级排列商品。Instagram 的探索信息流会显示用户关注者之外的、可能感兴趣的图片。Etsy 也采用了类似的方法来优化电商转化率。Stream 支持以下 5 种个性化用例:

社会的

用于构建个性化提要的文档

所有这些个性化用例都依赖于信息流与分析和机器学习的结合。在机器学习方面,我们使用 Python 生成模型。每个企业客户使用的模型都不同。通常,我们会使用以下这些优秀的库之一:

  • LightFM——如何实现推荐系统
  • XGBoost
  • Scikit学习
  • Numpy、Pandas 和 Dask
  • Jupyter Notebook(用于开发)
  • Mesa框架

分析

分析数据使用一个基于 Go 的小型服务器进行收集。它会在后台根据需要生成 go-routine 来汇总数据。生成的指标存储在 Elastic 中。过去我们考虑过 Druid,它看起来是个不错的项目。不过目前,我们可以使用一个更简单的解决方案。

仪表板和站点

该仪表板由 React 和 Redux 提供支持。我们的所有示例应用程序也都使用了 React 和 Redux:

该网站及其 API 由 Python、Django 和 Django Rest Framework 提供支持。Stream 赞助了 Django Rest Framework,因为它是一个非常优秀的开源项目。如果您需要快速构建 API,那么没有比 DRF 和 Python 更好的工具了。

我们使用 Imgix 来调整网站上的图片大小。对我们来说,Imgix 性价比高、速度快,总体来说是一项很棒的服务。Thumbor 是一个不错的开源替代方案。

即时的

我们的实时基础设施基于 Go、Redis 和优秀的 Gorilla WebSocket 库。它实现了 Bayeux 协议。在架构方面,它与基于节点的 Faye 库非常相似。

在 Hacker News 上读到“弃 Go 转 Node.js”的文章很有意思。作者为了提升性能从 Go 迁移到了 Node。实际上,我们做的恰恰相反,为了我们的实时系统,我们从 Node 迁移到了 Go。新的基于 Go 的基础设施每个节点的流量是原来的 8 倍。

DevOps、测试和多区域

就 DevOps 而言,实例的配置和配置是通过以下方式组合完全自动化的:

  • 云形成
  • 云初始化
  • 木偶
  • 博托和布料

由于我们的基础设施是在代码中定义的,因此启动新区域变得轻而易举。我们大量使用 CloudFormation。我们堆栈的每一部分都在 CloudFormation 模板中定义。如果需要,我们可以在几分钟内生成一个新的专用分片。此外,我们还使用 AWS 参数存储来保存应用程序设置。我们最大的部署位于美国东部,但我们在东京、新加坡和都柏林也设有区域。

我们结合使用 Puppet 和 Cloud-init 来配置实例。我们直接在 EC2 实例上运行独立的 Go 二进制文件,无需任何额外的容器化层。

我们服务的新版本发布由 Travis 负责。Travis 首先运行我们的测试套件。测试通过后,它会将新的二进制文件发布到 GitHub。诸如安装 Go 项目的依赖项或构建二进制文件等常见任务,都使用简单的 Makefile 自动完成。(我们知道,这很老套,对吧?)我们的二进制文件使用 UPX 压缩。

工具亮点:Travis

Travis 在过去的几年里取得了长足的进步。我以前在某些情况下更喜欢 Jenkins,因为它更容易调试构建失败的问题。现在,加上恰如其名的“调试构建”按钮,Travis 显然胜出。它易于使用,而且开源免费,无需任何维护。

特拉维斯

接下来,我们使用 Fabric 滚动部署到我们的 AWS 实例。如果部署过程中出现任何问题,部署都会暂停。我们非常重视稳定性:

  • 需要高水平的测试覆盖率。
  • 发布由 Travis 创建(如果不运行测试就很难部署)。
  • 代码至少由 2 名团队成员审查。
  • 我们广泛的 QA 集成测试套件评估所有 7 个组件是否仍然有效。

我们曾撰写过测试 Go 代码库的经验。一旦出现问题,我们会尽力透明地告知问题所在:

  • 状态页
  • Twilio 提供全天候电话支持
  • VictorOps 通知我们的团队
  • Slack 上的 #firefighting 频道
  • 用于客户支持的对讲机

VictorOps 是我们支持堆栈的最新成员。它使我们在持续存在的问题上进行协作变得非常容易。

工具亮点:VictorOps

VictorOps 最棒的地方在于他们使用时间线来促进团队成员之间的协作。VictorOps 巧妙地让我们的团队随时了解中断情况。它还能与 Slack 完美集成。这种设置使我们能够快速响应生产环境中出现的任何问题,并协同工作,更快地解决问题。

VictorOps

我们的绝大部分基础设施都在 AWS 上运行:

  • ELB 用于负载平衡
  • RDS 提供可靠的 Postgres 托管
  • 用于 Redis 托管的 ElastiCache
  • Route53 用于我们的 DNS
  • CloudFront 作为我们的 CDN
  • AWS ElasticSearch for ELK 并使用 Jaeger 进行跟踪

DevOps 的职责由整个团队共同承担。虽然我们确实有一位专门的 DevOps 工程师,但所有开发人员都必须了解并掌控整个工作流程。

监控

Stream 使用 OpenTracing 进行跟踪,并使用 Grafana 制作美观的仪表板。Grafana 的跟踪功能由 StatsD 完成。最终呈现出的效果如下:

仪表板

我们在 Sentry 中跟踪错误并使用 ELK 堆栈集中管理我们的日志。

工具亮点:OpenTracing + Jaegar

OpenTracing 是堆栈中的一个新成员。过去,我们使用 New Relic,它对 Python 来说运行良好,但无法自动测量 Go 的跟踪信息。OpenTracing 与 Jaeger 结合使用是一个很棒的解决方案,非常适合 Stream。它或许还拥有跟踪解决方案中最好的标志:

地鼠

结束语

Go 是一门非常出色的语言,在性能和开发者生产力方面取得了巨大的进步。我们使用 OpenTracing 和 Jaeger 进行跟踪。我们的监控运行在 StatsD 和 Graphite 上。集中式日志记录由 ELK 堆栈处理。

Stream 的主数据库是基于 RocksDB 和 Raft 构建的自定义解决方案。过去,我们使用 Cassandra,但发现它难以维护,而且与 RocksDB 相比,它的性能控制能力不足。

对于非核心竞争力的部分,我们会利用外部工具和解决方案。Redis 托管由 ElastiCache 负责,Postgres 由 RDS 负责,电子邮件由 Mailgun 负责,测试构建由 Travis 负责,错误报告由 Sentry 负责。

感谢您阅读我们的堆栈!如果您是Stream用户,请务必在 StackShare 上将 Stream 添加到您的堆栈中。如果您才华横溢,欢迎加入我们!最后,如果您还没有尝试过 Stream,请查看这个API快速教程。

鏂囩珷鏉ユ簮锛�https://dev.to/getstream_io/stream--go-news-feeds-for-over-300-million-end-users-3mci
PREV
用于 C# 开发的出色 Nuget 库
NEXT
只需一分钟即可修复,让您的 React 网站更加 Google 友好 🤝