# Tumblr：哈希处理每秒 23,000 个博客请求的方式

> 原文： [http://highscalability.com/blog/2014/8/4/tumblr-hashing-your-way-to-handling-23000-blog-requests-per.html](http://highscalability.com/blog/2014/8/4/tumblr-hashing-your-way-to-handling-23000-blog-requests-per.html)

![](img/b53e7c03eb181dee4f3635b07ef4a84e.png)

*这是 Tumblr 的 SRE 工程师 [](http://michael.tumblr.com/) [Michael Schenck](http://michael.tumblr.com/) 的特邀帖子。*

在 Tumblr，博客（或 Tumblelog）是我们互联网上访问量最高的面孔之一。 tumblelogs 最方便的方面之一是其高度可缓存的特性，这是奇妙的，因为 Tumblr 网络为我们的用户提供了很高的视图/发布比率。 就是说，扩展边界代理层（更不用说缓存层）对满足所有这些请求是必需的，这并非完全简单。

本文介绍了负责博客服务的外围部分的体系结构，这是我们流量更大的外围端点之一。

这是我们的方法。

### 统计信息

*   共有 278 名员工，团队中有 6 名员工负责 Tumblr 的所有外围（Perimeter-SRE），包括一名经理。

*   超过 2800 台服务器中，不到 20％的服务器用于博客服务功能

*   每秒（峰值）超过 23,000 个博客请求

*   每秒（峰值）超过 6,500 个博客缓存清除事件

*   超过 1.96 亿个博客

*   超过 930 亿个帖子

### 平台

*   [HAProxy](http://www.haproxy.org/)

*   [清漆](https://www.varnish-cache.org/)

*   [鸟](http://bird.network.cz/)

### 我们在哪里-基于地图的分区

在早期，我们只需要一台活动和一台备用代理服务器以及清漆节点。 两者都很容易管理，监视和高度可用。

但是，由于要满足所有用户请求，因此将达到容量限制，并且由于流行而导致停机前，必须准备好下一步步骤（即使不是理想的部署）。

### 超出单个代理节点

超出单个 活动 代理服务器的数量非常普遍，并且通常涉及 DNS。 像循环 A 记录这样基本的东西可能会满足您的需求，但通常值得花额外的钱进行健康检查 GSLB 配置（即使您只有一个地理位置）。

DNS 的缺点是，尽管名称服务器将以相当均匀的速率对每个 IP 进行响应，但不能保证每个查找都将用于相同数量的请求。 用户 A 可能在一分钟内向解析的 IP 发出 10 个请求，而机器人 B 可能在同一分钟内发出 100 个请求。 如果您有两个 IP，则用户 A 获得一个 IP，而机器人 B 获得另一个 IP，而它们是仅有的两个发出请求的客户端，那么您的一个代理的请求速率是另一个的 10 倍。

较低的 TTL 可以减轻此影响，以便 30 秒 TTL 可以平衡两个代理之间的那 110 个请求。 在最初的 30 秒内，用户 A 将转到代理 P1，而机器人 B 将转到代理 P2。 然后，他们的下一个解决方案可能会交换 IP，以便用户 A 将其请求发送到代理 P2，而机器人 B 将其请求发送到代理 P1。 在该分钟窗口结束时，每个代理将处理大约 60 个请求。 TTL 较低的缺点是查找更多，因此 DNS 成本较高（但 DNS 通常是您较便宜的第三方服务之一）。

### 超出单个清漆节点

虽然 DNS 可以为您增加代理层的时间，但是缩放清漆要复杂一些。 即使您使用单个清漆节点的容量限制围绕请求并发进行，您也不希望简单地在循环中添加两个清漆节点。 这降低了缓存命中率，清除了更多的资源/时间，并且实际上并没有增加缓存的工作量（仅复制它）。

超出单个清漆节点的最简单迭代是 静态分区 。 这涉及确定您的唯一标识符，并在两个清漆节点之间划分此空间。

对于 Tumblelogs，这是博客的主机名。 由于 DNS 不区分大小写，因此您只需要担心 40 个字符即可。 字母数字（a-z 和 0-9）和四个允许的字符（-_。〜）。 因此，对于两个清漆节点，博客主机名在这两个缓存节点之间被分割（在第一个字符上）。

### 均匀分布的分区-通过一致的哈希

前面的两个示例（DNS 循环和静态分区）都是朝着正确方向迈出的一步，但是在分区方面却提供了非常粗糙的粒度。 在足够小的规模上，这种粒度不一定是有问题的，但是随着您的流量开始增长，方差变得更加明显。 结果，减少最不热和最热节点处理的流量差异变得越来越重要。 这就是一致性哈希真正可以发挥作用的地方。

### 分区代理流量

如果您的服务器所处的环境可以影响服务器前面的路由器，用户和代理服务器之间的路由器的路由表，那么您可以利用等价的多 -路径路由（ECMP）。 ECMP 使您能够将代理视为同一哈希环中的多个切片，然后在这些切片之间映射请求者。

这是通过通知到特定目标 IP（高可用性 IP）的多个路径（代理服务器）的路由基础结构来实现的。 然后，ECMP 将对请求源进行哈希处理，以确定哪个代理应接收此请求会话的数据包。 典型的 ECMP 实现提供第 3 层（仅 IP）和第 3 + 4 层（IP：端口）哈希选项。 第 3 层意味着来自特定 IP 的所有请求将转到特定的代理，这可能有助于调试，但与使用单个 NAT IP 的大型网络不平衡。 第 3 + 4 层通常提供最佳的分发，但是调试特定的客户端变得更具挑战性。

有多种方法可以 通知 路由器多个路径，但是我建议使用 OSPF 或 iBGP 进行动态路由通告。 一个人只需要在环回接口上侦听高度可用的 IP，启用内部路由以及将自己的 IP 作为下一跃点广告到该高度可用的 IP。 我们发现 BIRD 提供了一种轻巧可靠的方式来执行来自代理的路由广告。

### 划分清漆流量

Tumblelog 通过其完全限定的域名（FQDN）进行标识，因此，博客的所有 URI 路径将始终在该博客 FQDN 下找到。 Tumblelog 的大部分是 tumblr.com 的子域，例如 [engineering.tumblr.com](http://engineering.tumblr.com/) ，但是我们也支持用户携带自己的自定义域名 。

考虑各种 FQDN 时，很明显，TLD 的变体数量最少，然后是域名（特别是由于绝大多数是 tumblr.com），然后是子域。 因此，我们最重要的位出现在可变长度字符串的最左侧。

### 了解问题域

![](img/acbf9820b55f36b5e6df32353fdd183a.png)

*   完美-演示将散列函数应用于我们的测试数据集时，其散列函数是否为 完美

*   constant_hdr-主机标头上的一致性哈希（最佳现实结果）

*   constant_hdr_use_domain_only-基本域名（即 tumblr.com 或 foo.net）上的一致性哈希，只有两个阵营 tumblr.com 和所有其他域名

*   mapbased_firstchar-将主机标头的第一个字符映射到清漆节点（我们的原始静态分区实现）

*   mapbased_hdr-基于主机标头的映射

虽然一致的散列显然是 tumblelog FQDN 的最平均分布的领先者，但是我们接着确定散列函数是否合适。 默认情况下，HAProxy 使用 SDBM 哈希函数。 但是，在进一步研究后，通过比较 SDBM，CRC，MD5，DJB2 等，我们确定 DJB2 提供了更好的分布。 这导致我们向 HAProxy 提交了补丁，以使哈希函数可配置（有关更多信息，请参见“感谢”部分）。

### 比较静态分区和一致性哈希

![](img/92b6bf9ab0a831619ca9f08d88d90435.png)

这显示了在移至最佳匹配哈希函数之前和之后，每个清漆节点每秒请求之间的方差变化。

### 其他注意事项

### 节点增长

在任一模型中，节点增长将意味着键空间移位，从而导致缓存失效。 在一致的哈希模型中，预测将失效的键的百分比要容易得多。 基本上为 1 / N（N 是添加新节点之前的缓存节点数）。 使用静态分区模型，除非您对要从中获取键空间的节点的请求进行分析，否则最糟糕的情况是小于或等于键上键的总百分比。 您从中获取键空间的节点。

### 节点故障

使用静态分区时，除非您提供故障转移选项，否则单节点故障将导致无法访问 1 / N 密钥。 HAProxy 确实允许您拥有一个备用节点，但是现在您可以决定了。 每个密钥空间有 2N 个缓存节点（一个活动，一个备用）或共享的备用节点。 一种极端情况是 浪费 占硬件的 50％，其中频谱的另一端（所有活动节点之间共享 1 个备用节点）意味着两个故障节点导致备用 支持其他活动节点的密钥空间的 2 倍。

使用一致的哈希，将自动处理节点故障。 删除一个节点后，将移动 1 / N 个键（导致 1 / N 个键无效），并且每个剩余的活动节点的键空间均匀增加。

### 正在清除缓存

将清除请求发送到单个清漆节点很容易，但是从多个清漆节点清除也应该很容易。 不必使代理服务器和清除服务器保持同步，最简单的方法是通过同一代理服务器发送所有清除请求。

拒绝来自非本地 IP 空间的清除尝试非常重要，以防止任何恶意的批量清除。

### 获得的经验教训

*   您知道尚未回答的问题的答案。 面对扩展挑战时，请不要忽视您已经在其他地方使用的模式。

*   通过简单扩展。 在短期内可能会增加复杂性以克服可伸缩性挑战，但最终会赶上您。

*   了解您的哈希函数。 您使用的哈希函数与决定如何处理哈希值同样重要。

*   降级，不失败。 建议您的代理人监视自己达到后端的能力。 如果不能，则不要停止发布路由，而只是发布非优先级路由（例如，使用 OSPF 会增加路径成本）。 这样，如果所有后端都无法正常运行，您仍然可以提供错误页面，而不是无法访问。

### 谢谢

##  *   [Blake Matheny](http://tumblr.mobocracy.net/) 和 [Andrew Terng](http://andrew.tumblr.com/) 进行了散列函数比较并为 HAProxy 创建了补丁。

*   [Bhaskar Maddala](http://maddalab.tumblr.com/) 与 HAProxy 社区合作，以获取 [此功能已添加到 HAProxy 1.5 版本](http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4.2-hash-type) 。

*   [Arnoud Vermeer](http://blog.arnoudvermeer.nl/) 和 [Aaron Prat](http://aaronprat.tumblr.com/) 与 ECMP / OSPF 流量分配有关。 

## 相关文章 [

*   [关于 HackerNews](https://news.ycombinator.com/item?id=8132473)
*   [雅虎以 10 亿美元的价格收购了 Tumblr 架构](http://highscalability.com/blog/2013/5/20/the-tumblr-architecture-yahoo-bought-for-a-cool-billion-doll.html)
*   [Tumblr Architecture-150 亿页面浏览量一个月，比 Twitter 难扩展](http://highscalability.com/blog/2012/2/13/tumblr-architecture-15-billion-page-views-a-month-and-harder.html)

对于任何混乱，我们深表歉意，共有 278 位 Tumblr 员工。

负责 Tumblr 所有外围（Perimeter-SRE）的团队由 6 人组成（包括一名经理）。

本文介绍了负责博客服务的外围部分的体系结构，这是我们流量更大的外围端点之一。

对于那些不了解哈希的人来说，它会使您变得 O（1）复杂，并且比对普通 b 树数据库的搜索要快。 令人惊讶的结果，我想这就是您扩展 tumblr 之类的方式的原因：）

非常有趣的文章。
但是有一个问题：您提到 BIRT 是 IP 平衡器，尽管 Haproxy 也是平衡器，但是对于 TCP / HTTP 级别，不是 IP。 那么负载均衡人员到底是谁呢？

感谢您分享这个幕后或“后台”视图。 “ 1.96 亿个博客”数量巨大，接近 1000 亿个帖子表明这些博客已被实际使用，而不是像其他平台那样，通常一旦注册就此后一直处于闲置状态。

从网络工程的角度来看，这非常有趣。 我猜想您的 HAProxy 框上正在运行 BIRD，然后将散列的流量平衡到 Varnish？ 是否还可以通过 Varnish 服务器从您的边缘路由器提供网络图？ 谢谢！