# 扩大流量的设计决策

> 原文： [http://highscalability.com/blog/2013/10/28/design-decisions-for-scaling-your-high-traffic-feeds.html](http://highscalability.com/blog/2013/10/28/design-decisions-for-scaling-your-high-traffic-feeds.html)

![](img/4d2c9efe35022d4b456cdd4f9c5e6185.png)

*Fashiolista.com 的创始人/ CTO Thierry Schellenbach 的来宾帖子，在 Twitter 和 Github 上关注@tschellenbach*

[Fashiolista](http://www.fashiolista.com/) 最初是我们在侧面开发的一项业余爱好项目。 我们绝对不知道它将成长为最大的在线时尚社区之一。 整个第一个版本的开发花费了大约两周的时间，而我们的 feed 实现却非常简单。 自那时以来，我们已经走了很长一段路，我想分享我们在缩放 Feed 系统方面的经验。

Feed 是 Pinterest，Instagram，Wanelo 和 Fashiolista 等许多大型初创公司的核心组成部分。 在 Fashiolista，供稿系统为[统一供稿](http://www.fashiolista.com/feed/?feed_type=F)，[聚合供稿](http://www.fashiolista.com/feed/?feed_type=A)和[通知](http://www.fashiolista.com/my_style/notification/)系统提供动力。 本文将说明在扩展 Feed 时遇到的麻烦以及与构建自己的解决方案有关的设计决策。 随着越来越多的应用程序依赖它们，了解这些供稿系统如何工作的基础至关重要。

此外，我们开源了 [Feedly](https://github.com/tschellenbach/Feedly) ，它是为 Feed 提供动力的 Python 模块。 在适用的情况下，我将参考如何使用它来快速构建自己的供稿解决方案。

## 提要简介

缩放进纸系统的问题已被广泛讨论，但让我首先阐明基础知识。 该解决方案旨在使诸如 Facebook 新闻提要，Twitter 流或 [Fashiolista](http://www.fashiolista.com/) feed 之类的页面在高流量条件下工作。 所有这些系统的共同点在于，它们显示了您所关注的人的活动。 在我们的案例中，我们基于活动流的标准将活动对象基于[。 活动示例包括“ Thierry 在 Fashiolista 上的列表中添加了项目”或“ Tommaso 发了一条消息”。](http://activitystrea.ms/specs/atom/1.0/)

There are two strategies for building feed systems:

1.  **拉**，读取时将在其中收集提要

2.  **按下**，在写入期间所有提要均已预先计算。

大多数实际的实时应用程序将结合使用这两种方法。 将活动推向所有关注者的过程称为扇出。

## 历史&背景

Fashiolista 的 Feed 系统经历了三项重大的重新设计。 第一个版本在 PostgreSQL 数据库上工作，第二个版本在 Redis 上使用，第三个当前版本在 Cassandra 上运行。 为了让您了解这些解决方案何时以及为什么会失败，我将简要介绍一些历史。

### 第一部分-数据库

我们的第一个设置只是查询了一个 PostgreSQL 数据库，看起来像这样

选择* from love，其中 user_id 位于（...）

最令人惊讶的是该系统的强大功能。 我们通过了 1M 的爱，并且继续发挥作用，在达到 5M 的爱之后，它仍然继续起作用。 我们敢打赌，经过一千万次恋爱，它会破裂，但它一直保持平稳运行。 进行了一些数据库调整，但是这个简单的系统在大约 1 亿个爱人和 1 百万个用户中占有优势。 大约在那时，该解决方案的性能开始波动。 通常，它可以继续工作，但是对于某些用户而言，延迟会飙升至数秒。 阅读了许多有关 feed 设计的文章之后，我们使用 Redis 构建了 Feedly 的第一个版本。

### 第二部分-Redis & Feedly

我们的第二种方法是在 Redis 中为每个用户存储一个提要。 当您喜欢某件商品时，此活动就会散发给所有关注者。 我们使用了一些巧妙的技巧来保持较低的内存使用率，这将在下一部分中介绍。 Redis 真的很容易设置和维护。 我们使用 Nydus 在数台 Redis 机器上进行了分片，并使用 [Sentinel](http://redis.io/topics/sentinel) 进行自动故障转移。 （当前，我们建议使用 [Twemproxy](https://github.com/twitter/twemproxy) 代替 [Nydus](https://github.com/disqus/nydus) ）

Redis was a good solution, but several reasons made us look for an alternative. Firstly we wanted to support multiple content types, which made falling back to the database harder and increased our storage requirements. In addition the database fallback we were still relying on became slower as our community grew. Both these problems could be addressed by storing more data in Redis, but doing so was prohibitively expensive. ### 第三部分-Cassandra & Feedly

We briefly looked at [HBase](http://hbase.apache.org/), [DynamoDB](http://aws.amazon.com/dynamodb/) and [Cassandra 2.0](http://cassandra.apache.org/download/). Eventually we opted for Cassandra since it has few moving parts, is used by Instagram and is supported by [Datastax](http://www.datastax.com/). Fashiolista currently does a full push flow for the flat feed and a combination between push and pull for the aggregated feed. We store a maximum of 3600 activities in your feed, which currently takes up 2.12TB of storage. The fluctuations caused by high profile users are mitigated using priority queues, overcapacity and auto scaling.

## 饲料设计

我认为我们的历史可以很好地代表其他公司所经历的过程。 当需要构建自己的供稿系统（希望使用 Feedly）时，需要考虑一些重要的设计决策。

### 1.归一化与归一化

There are two approaches you can choose here. The feed with the activities by people you follow either contains the ids of the activities (normalized) or the full activity (denormalized).

仅存储 ID 会大大减少您的内存使用量。 但是，这也意味着每次加载 Feed 时都要再次访问数据存储。 要考虑的因素之一是非规范化时多久复制一次数据。 如果您要构建通知系统或新闻提要系统，则将产生巨大的变化。 对于通知，您通常会针对发生的每个操作通知 1 或 2 个用户。 但是，对于基于关注的 Feed 系统，该操作可能会复制到数千个关注者中。

此外，最佳选择实际上取决于您的存储后端。 使用 Redis，您需要注意内存使用情况。 另一方面，Cassandra 具有足够的存储空间，但是如果您对数据进行规范化，则很难使用。

对于通知供稿和基于 Cassandra 构建的供稿，我们建议对数据进行非规范化。 对于基于 Redis 的供稿，您希望最大程度地减少内存使用并使数据规范化。 [Feedly](https://github.com/tschellenbach/Feedly) 允许您选择自己喜欢的方法。

### 2.基于生产者的选择性扇出

In their paper [Yahoo’s Adam Silberstein et.al.](http://research.yahoo.com/files/sigmod278-silberstein.pdf ) argue for a selective approach for pushing to users feeds. A similar approach is currently used by [Twitter](http://highscalability.com/blog/2013/7/8/the-architecture-twitter-uses-to-deal-with-150m-active-users.html ). The basic idea is that doing fan-outs for high profile users can cause a high and sudden load on your systems. This means you need a lot of spare capacity on standby to keep things real time (or be ok with waiting for autoscaling to kick in). In their paper they suggest reducing the load caused by these high profile users by selectively disabling fanouts. Twitter has apparently seen great performance improvements by disabling fanout for high profile users and instead loading their tweets during reads (pull).

### 3.基于消费者的选择性扇出

Another possibility of selective fanouts is to only fan-out to your active users. (Say users who logged in during the last week). At Fashiolista we used a modified version of this idea, by storing the last 3600 activities for active users, but only 180 activities for inactive ones. After those 180 items we would fallback to the database. This setup slows down the experience for inactive users returning to your site, but can really reduce your memory usage and costs.

Silberstein 等。 通过查看消费者和生产者对，使事情变得更有趣。 基本直觉是，在以下情况下，推送方法最有意义：

Fortunately such a complex solution hasn’t been needed yet for Fashiolista. I’m curious at which scale you need such solutions. Be sure to let us know in the comments.

### 4.优先事项

An alternative strategy is using different priorities for the fan-out tasks. You simply mark fan-outs to active users as high priority and fan-outs to inactive users as low priority. At Fashiolista we keep a higher buffer of capacity for the high priority cluster allowing us to cope with spikes. For the low priority cluster we rely on autoscaling and spot instances. In practice this means that less active user’s feeds may occasionally lag a few minutes behind. Using priorities reduces the impact high profile users have on system load. It doesn’t solve the problem, but greatly reduces the magnitude of the spikes.

### 5\. Redis vs 卡桑德拉

Both Fashiolista and [Instagram](http://planetcassandra.org/blog/post/instagram-making-the-switch-to-cassandra-from-redis-75-instasavings) started out with Redis but eventually switched to Cassandra. I would recommend starting with Redis as it’s just so much easier to setup and maintain.

但是，Redis 有一些限制。 您的所有数据都需要存储在 RAM 中，这最终会变得昂贵。 此外，Redis 中不支持分片。 这意味着您必须滚动自己的系统才能在节点之间进行分片。 （ [Twemproxy](https://github.com/twitter/twemproxy) 是一个很好的选择）。 在节点之间进行分片非常容易，但是在添加或删除节点时移动数据很麻烦。 您可以通过使用 Redis 作为缓存并回退到数据库来解决这些限制。 一旦难以回退到数据库，我会考虑从 Redis 迁移到 Cassandra。

Cassandra Python 生态系统仍在快速变化。 [CQLEngine](https://github.com/cqlengine/cqlengine) 和 [Python-Driver](https://github.com/datastax/python-driver) 都是出色的项目，但是它们需要一些[分叉](https://github.com/tbarbugli/cqlengine)才能一起工作。 如果您选择 Cassandra，则需要准备好花一些时间来了解 Cassandra 并为客户库做贡献。

## 结论

There are many factors to take into account when building your own feed solution. Which storage backend do you choose, how do you handle spikes in load caused by high profile users and to what extend do you denormalize your data? I hope this blogpost has provided you with some inspiration.

[Feedly](https://github.com/tschellenbach/Feedly) 不会为您做出任何选择。 这是一个构建供稿系统的框架，由您自己决定哪种方法最适合您的用例。 有关 Feedly 的介绍，请阅读[自述文件](https://github.com/tschellenbach/Feedly)或本教程，以构建 [Pinterest](http://www.mellowmorning.com/2013/10/18/scalable-pinterest-tutorial-feedly-redis/) [样式](http://www.mellowmorning.com/2013/10/18/scalable-pinterest-tutorial-feedly-redis/) [应用程序](http://www.mellowmorning.com/2013/10/18/scalable-pinterest-tutorial-feedly-redis/)。 如果您尝试一下，请务必在遇到问题时通知我们。

请注意，只有在数据库中获得数百万个活动后，才需要解决此问题。 在 Fashiolista，简单的数据库解决方案使我们接触到了最初的 1 亿爱人和 100 万用户。

要了解有关 Feed 设计的更多信息，我强烈建议您阅读我们基于 Feedly 的一些文章：

 *   [Yahoo Research Paper](http://research.yahoo.com/files/sigmod278-silberstein.pdf)
*   [Twitter 2013](http://highscalability.com/blog/2013/7/8/the-architecture-twitter-uses-to-deal-with-150m-active-users.html) 基于 Redis，具有后备功能
*   [Instagram 上的 Cassandra](http://planetcassandra.org/blog/post/instagram-making-the-switch-to-cassandra-from-redis-75-instasavings)
*   [Etsy Feed 缩放比例](http://www.slideshare.net/danmckinley/etsy-activity-feeds-architecture/)
*   [Facebook 历史记录](http://www.infoq.com/presentations/Facebook-Software-Stack)
*   [Django 项目，具有良好的命名约定。](http://justquick.github.com/django-activity-stream/) （但仅限数据库）
*   [http://activitystrea.ms/specs/atom/1.0/](http://activitystrea.ms/specs/atom/1.0/) （演员，动词​​，宾语，目标）
*   [有关最佳做法的 Quora 帖子](http://www.quora.com/What-are-best-practices-for-building-something-like-a-News-Feed?q=news+feeds)
*   [Quora 扩展了社交网络供稿](http://www.quora.com/What-are-the-scaling-issues-to-keep-in-mind-while-developing-a-social-network-feed)
*   [Redis 红宝石示例](http://web.archive.org/web/20130525202810/http://blog.waxman.me/how-to-build-a-fast-news-feed-in-redis)
*   [FriendFeed 方法](http://backchannel.org/blog/friendfeed-schemaless-mysql)
*   [Thoonk 设置](http://blog.thoonk.com/)
*   [Twitter 的方法](http://www.slideshare.net/nkallen/q-con-3770885) 

很棒的文章！ 我经常想知道为什么要对大规模流立即做出扇出的决定。 [Collabinate](http://www.collabinate.com "Collabinate") 活动供稿 API 使用了 Rene Pickhardt 令人惊叹的 [Graphity 算法](http://www.rene-pickhardt.de/graphity-an-efficient-graph-model-for-retrieving-the-top-k-news-feeds-for-users-in-social-networks "Graphity")，这是一种图形数据库支持的 Feed 算法，具有极高的吞吐量，且无需重复。 它依靠图数据库通过 n 路合并（“拉”）完成所有操作。 我想谈谈您在原始实现中看到的延迟峰值，Redis 遇到的内存利用率问题以及现在的情况。 它将真正帮助我们的未来客户过渡到 Collabinate。 我会大声疾呼，以谈论有关 Feedly 的更多信息。

是否考虑使用 Postgresql 复制？
一个写 DB 和多个从数据库为只读。

Feedly 开源软件包一直在快速增长。 目前，我们正在对 Feedly 背后的团队构建的托管解决方案进行 Beta 测试。 您可以在 https://getstream.io 上找到它

我一直想学习如何做到这一点！ 你是最好的！ 感谢您提供这个值得推荐的帖子。