# 一点点：建立一个可处理每月 60 亿次点击的分布式系统的经验教训

> 原文： [http://highscalability.com/blog/2014/7/14/bitly-lessons-learned-building-a-distributed-system-that-han.html](http://highscalability.com/blog/2014/7/14/bitly-lessons-learned-building-a-distributed-system-that-han.html)

![](img/d8f15c5b26ebe6302516a72aa276f0fa.png)

您是否想知道 [有点](http://bitly.is/1g3AhR6) 赚钱吗？ 一个 [URL 缩短器](http://en.wikipedia.org/wiki/URL_shortening) 不会那么难写，对吧？ bitt 的首席应用开发人员 [Sean O'Connor](http://linkd.in/1nt6KTN) 回答了如何立即在 [中产生一点可赚钱的问题 他在](http://bit.ly/1iuEAlb) [培根会议](http://devslovebacon.com/) 上发表的 演讲。

肖恩说，写一个可行的 URL 缩写很容易，写一个可缩放且高度可用的 URL 缩写并不容易。

Bitly 不会通过“缩短服务”服务获利，而是在分析产品上获利，该产品将 URL 点击数据与他们从网络上抓取的数据融合在一起，以帮助客户了解人们在网络上关注的内容 。 ，

Analytics 产品最初是一种用于爬网 Web 服务器日志的后端服务。 日志包含来自带注释的链接的数据以及 cookie 数据，以指示单击链接的页面，在何处单击，链接的内容等。但是，所有链接都回到了网站的域。 建立链接与您的域到另一个域以使第三者可以进行分析的想法是一个可怕的主张，但这也是一种天才。

尽管此话题不是关于尖锐的体系结构的，但它是对分布式系统的性质以及如何解决分布式系统的大难题的有思想的探索。

也许我从他的演讲中最喜欢的一课是这个（我的光泽）：

**SOA +队列+异步消息传递确实很强大** 。 这种方法可以隔离组件，让工作同时进行，让盒子独立发生故障，同时使组件仍然易于推理。

我也非常喜欢他的解释，说明为什么事件样式消息比命令样式消息更好。 我从来没有听过这样的话。

肖恩（Sean）从真实的经验谈起。 如果您想从单一思维方式转变为多思维方式，那么此演讲非常值得一看。 ，

因此，让我们看看 Sean 对分布式系统有何感想……

## 统计信息

*   每月有 60 亿点击

*   6 亿个每月缩短

*   公司内 50 名员工和大约 20 名工程师

*   400 台服务器。 并非所有 400 台服务器都在处理重定向。 约 30 台服务器处理来自外界的所有传入流量，包括缩短，重定向，api 请求，Web ui 等.400 台服务器的其余部分专用于存储和组织用户数据或提供各种形式的处理和分析（数据库）的各种服务 ，指标，网页抓取，文本分析等）。

*   每月抓取一亿个网页。

## 来源

*   [经验教训，了解建筑物的分布式系统](http://devslovebacon.com/conferences/bacon-2014/talks/lessons-learned-building-distributed-systems-at-bitly)

## 平台 [

请注意，这些只是谈话中提到的内容，而不是完整的列表。

*   HDFS

*   S3

*   Nagios

*   实时使用的实时分布式消息系统是 [Nsq](https://github.com/bitly/nsq) 。

*   稍微使用 [主机池](https://github.com/bitly/go-hostpool) 来管理主机池。

*   谈话的结尾已结束，但提到使用了很多不同的数据库。 ，

## 关于分布式系统的性质

*   任何真正高度可用的东西都将被固有地分发。 为了达到一定程度的可用性，您必须具有地域多样性。 根据定义，如果您在不同区域中具有独立的操作系统，则必须将其分发。

*   创建分布式系统的旧方法。 创建抽象，让您假装实际上没有分发。

    *   例如，NetApp 会产生无限磁盘的错觉。

    *   Oracle，是一个高度可用的数据库，可让您假装世界与实际不同。

    *   两者都可以解决实际问题，但是两者都很昂贵，因此请另辟 different 径。

*   新方法。 通过接受分布式系统的固有特性来解决问题，并将其内置到它们作为工具提供的抽象中。

    *   您对事物的看法发生了重大变化。 由于您的抽象与所构建内容的实际情况更加紧密一致，因此您可以进行更强大的折衷，从而更高效地完成工作。

*   带有 1x RAM 的 4 盒总是比带有 4x RAM 的 1 盒便宜。 这意味着分布式系统是扩展和实现高可用性的方法。

*   分布式系统出现问题，因为您要从一台计算机迁移到 N 台计算机。

    *   **组件的并发** 。 机器 A 与机器 B 同时工作。这就是获得水平缩放的方式。 功能强大，但代价是需要在机器之间进行协调。 例如，在锁定数据时，使框彼此等待，您将不再并发。

    *   **缺少全局时钟** 。 每个盒子的时钟都不完美。 如果有一台以上的机器，那么每台机器都会有自己的时间感。 这意味着无法根据时间对在不同计算机上发生的事件进行排序。 如果事件之间相隔 1 或 2 秒，则他们不知道先发生哪些事件。

    *   **独立故障** 。 如果方框 A 失败，则方框 B 应该继续工作。

*   有些问题（例如分析）太大，无法在一台计算机上处​​理。 或至少这样的机器不会在预算内。

## 实施分布式系统的策略

### 面向服务的体系结构

没有一个整体式的应用程序。 网络上有数十种相互通信的小型服务。

*   这是一种抽象，非常类似于代码中的功能，但是是系统级的。

*   不必将系统的所有细节都保留在脑海中。 可以只注意 API 边界和合同。

*   强大的开发和运营能力。

*   Bitly 是一家拥有 6 年历史的公司，并且具有很多代码。 您无需查看每一行代码。 您只使用该服务。

*   设计良好的服务仅几百行代码。

*   在操作上，很容易确定哪个系统有问题。 然后，您可以深入研究该系统以查找问题。

*   故障现在意味着功能减少而不是停机。 通常，每个服务都旨在回答特定问题。 由于如果其中之一发生故障，它们将完全与自己的持久层隔离，那么唯一丢失的就是回答该问题的能力，但是总体而言，服务仍处于运行状态。 出现故障的度量系统永远不会影响 URL 缩短请求。 ，

### 异步消息传递

*   发送消息而无需等待回复。

*   真的很容易在组件之间放置队列。 如果框 A 正在向框 B 发送邮件，并且框 B 出现问题，则框 B 恢复后，这些邮件将排队并且可以进行处理。

*   处理错误的更多选项。 如果包装盒出现问题，该消息将稍后处理。 方框 A 不必了解或关心方框 B 的麻烦。

*   不利的一面是，将执行的操作更自然地建模为同步请求。

*   URL 缩短请求是一个完全同步的过程，因为：

    *   **速度**。 希望尽快返回答复。 例如，不想处理队列备份。

    *   **一致性**。 不想将相同的缩写网址提供给多个用户。 而是提供一个错误而不是一个无效的链接。

*   指标系统完全异步。 单击一个小链接后，它会翻译并通过 HTTP 返回。 有关转换的数据会弹出到分析和其他下游系统的队列中。 如果在处理指标方面有一点延迟，那就不是世界末日了。

*   可以将消息视为命令或事件。

    *   命令说“做 X”。

    *   一个事件说“ X 发生了”。

*   事件比命令更有用。

    *   **可以更好地隔离系统**。 命令意味着知道谁接收命令，否则将毫无意义。 说了些什么意味着您不必在乎谁在使用消息。 只要宣告发生了 X，其他服务就可以增加统计信息或执行任何操作。

    *   **事件自然映射为让多个使用者处理消息**。 将位 URL 解码为 HTTP 重定向后，会将消息发送到多种服务：将其保存到 HDFS 和 S3 的存档服务，实时分析服务，长期历史记录分析，注释服务。 解码服务只需要发布事件，而无需了解下游服务。 下游服务只是收到一个事件，他们不在乎发送方式或发送者。

    *   **添加新消费者很容易**。 可以创建新服务，并且可以注册活动，而制作人不知道或不在乎。 服务处理事件的方式的变化同样也不是生产者所关心的。 生产者和消费者是孤立的，只能通过事件指定的合同进行联系。

## 使服务发挥出色

*   **在服务**之间使用背压。 如果某个服务繁忙，则应告知其他请求服务以限制其请求，从而减少它们在出现问题的服务上的负担。 例如，指数补偿。 帮助防止通过系统的级联错误。 还可以帮助系统更快地恢复。 例如，依赖缓存的服务需要一个预热期，如果请求的服务在预热期间没有回退，则数据库可能会崩溃。

*   **绕过故障**。 例如，服务之间的负载平衡。 Bitly 使用 [主机池](https://github.com/bitly/go-hostpool) 管理主机池。 客户端向主机请求服务。 然后，客户端会在每个请求上告诉 hostpool 请求是否失败。 基于此反馈，主机池可以管理主机分配以路由到更健康的主机。

## 监视 [

*   使用一台或两台服务器，不难知道何时发生故障，但是当您拥有数百台服务器时，就需要帮助。

*   **使用 Nagios 之类的工具检查服务器运行状况**。 检查统计信息，例如“盒子是否在交换？”

*   **运行完整性检查**。 例如，服务正在响应，但是返回的数据已损坏。

*   **集中记录**。 之所以重要，是因为您可以跨多个主机检测故障。 如果一个用户造成了您所有的错误，那么通过在机器之间跳来跳去将很难检测到。 集中式日志使检测整体问题变得更加容易，例如所有错误都来自一个 ip 地址。

*   **人机界面**。 您如何在正确的时间向正确的人获取正确的信息。 如何从工具中公开信息？

    *   例如， Nsq 有一个不错的管理界面，可提供有关队列行为的反馈。 这样您就可以看到队列正在备份，但这是因为一个主机。

    *   部署系统的 Web UI。 使部署和查看部署历史记录变得容易。 如果 Nagios 快要疯了并且刚刚部署了某人，则可能与他们有关。

## 经验教训 [

*   **知识就是力量** 。 您越了解工作性质，可以做出更好的决策，工作效率就越高。 效率意味着您可以以更低的成本制造更大，更快的系统。

*   **构建处理泄漏抽象的内容** 。 如果您正在使用抽象层来隐藏系统的基础分布式特性，那么它将最终冒泡。 您的代码必须理解并处理所有泄漏。

*   **如果可以将** 全部放在一个盒子中。 如果您不需要分布式系统，请不要构建一个。 它们复杂且建造昂贵。

*   **在面向服务的体系结构中，故障意味着功能减少而不是停机**。

*   **SOA +队列+异步消息传递确实很强大** 。 这种方法可以隔离组件，让工作同时进行，让盒子独立发生故障，同时使组件仍然易于推理。

*   **当速度和一致性至关重要时，请使用同步请求。** 。 给用户一个错误，而不是缓慢或错误的答案。

*   **事件样式消息比命令样式消息** 更好。 它们允许更好地隔离系统之间，并自然支持多个使用者。 帮助保持服务重点，而不用担心服务超出预期范围。

*   **注释而不是过滤器** 。 生产者级别的筛选器基于对下游服务关心的假设进行烘烤。 例如公共链接与私有链接。 从流中过滤出私人链接意味着对私人链接感兴趣的服务将无法获得所需的链接。 相反，请使用链接是私有链接还是公共链接来注释事件，并让服务仅在他们关心的事件类型上运行。

*   **服务应该相互配合** 。 使用反压可防止服务过载，并绕过故障服务。

*   **如果没有 Nagios 检查，则几乎可以确定是** 。 您只是还不知道。

*   **工具应将信息公开给人类** 。 在正确的时间向正确的人提供正确的信息。

## 相关文章

*   [关于黑客新闻](https://news.ycombinator.com/item?id=8031606)

假设所有负载平均分布在 8 个高峰时段，即每秒仅 17 个请求（6000000000/400/30/8/3600）。 他们在用 400 台服务器做什么？ 考虑到缩短的请求几乎不会导致负载，因此每秒 17 个请求是什么。 每个 CPU 内核应为> 100，并每秒。

这里是原始演讲的作者。 真的很受宠若惊。

首先，对文章进行了较小的更正，是〜20 名工程师（50 人公司）而不是 50 名工程师。

第二个建议：tobi，并非所有 400 台服务器都在处理重定向。 约 30 台服务器处理来自外界的所有传入流量，包括缩短，重定向，api 请求，Web ui 等。其余 400 台服务器专用于各种服务，包括存储和组织用户数据或提供各种形式的处理和分析（数据库） ，指标，网页抓取，文本分析等）。

@tobi 400 可能包括质量保证和生产环境，并且将分布在 HDFS /数据库集群，队列处理器，应用服务器等上。此外，该帖子似乎表明他们正在使用商品硬件，这可能意味着它们的主机不在 不是多租户。

tobi，“ 400 台服务器”！= 400 台 Web 服务器。

是的，加上单击！=流程，并且在 8 个小时内完全均匀地加载是一个可怕的假设（考虑到即使在 Twitter 高峰期间，他们也希望获得低延迟的同步响应，而互联网上没有“营业时间”之类的东西）。

这是一个仍然人为但更现实的示例：

[6000000000（点击）x 4（每次点击的平均进程数）]
/ [400/3]（每个进程平均 3 个服务器层）
/ 30（每月天数）
/ 24（小时） 每天）
* 10（峰值时间突发）
/ 3600（每小时秒数）

在突发期间没有故障的情况下，向任何特定服务器提供 700 msgs /秒的速度。

6B req / sec == 200M / day 平均值

尽管这可以达到〜3000 / sec，但必须意识到平均值和峰值相差很大。 您不能为平均水平设计架构师。 峰值可以提高 10 倍。

像这样的分布式系统最大的麻烦就是一致性：确保不会将相同的短 URL 分配给两个不同的 URL。 我想您可以通过规范化和哈希 URL 并在哈希上共享来缓解这种情况...

适用于所有 API 和 Web 的 30 台服务器非常合理。 用于分析和相关服务的 370 台服务器（占 92.5％）揭示了赚钱的地方。

我同意，仅 1 核 CPU 可以很好地满足 17 rps 的要求。 @bitlydevs，您应该阅读 snabswitch 如何仅通过一个节点即可提供 10M rps。

http://highscalability.com/blog/2014/2/13/snabb-switch-skip-the-os-and-get-40-million-requests-per-sec.html

“带有 1x RAM 的 4 盒总是比带有 4x RAM 的 1 盒便宜。”

怎么/为什么？

是的，在这里听 santosh，老兄显然知道更好