七篇国外大科技公司工程博客摘录


有选择地从科技公司的工程博客中挑选博客文章:

1、[Airbnb]用HTTP流提高性能
讨论HTTP流如何提高页面性能,以及Airbnb如何在现有的代码库中启用它。

流式传输的含义,想象一下,我们有一个水龙头和两个选择:

  • 装满一个大杯子,然后把它全部倒进管子里("缓冲buffer "策略)。
  • 将水龙头直接连接到管子上("流 stream"的策略)。

1、在缓冲buffer策略中,一切都按顺序发生:
我们的服务器首先将整个响应生成一个缓冲区(装满杯子),然后花更多时间在网络上发送(倒掉)。

2、流streaming策略则是平行发生的。
我们把响应分成几块,一旦准备好就立即发送。服务器可以在以前的块仍在发送时开始处理下一个块,而客户端(例如,浏览器)可以在完全收到响应之前开始处理。

流streaming有明显的优势,但今天大多数网站仍然依赖缓冲的方式来产生响应。
其中一个原因是需要额外的工程努力来将页面分解成独立的块状。这有时是不可行的。例如,如果页面上的所有内容都依赖于一个缓慢的后端查询,那么在该查询完成之前,我们将无法发送任何东西。

然而,有一个用例是普遍适用的。我们可以使用流streaming来减少网络瀑布network waterfalls的出现。
这个术语指的是当一个网络请求触发另一个请求时,会导致一系列连续的请求的级联。这在Chrome的Waterfall这样的工具中很容易发现。

大多数网页都依赖链接在HTML中的外部JavaScript和CSS文件,从而导致网络瀑布Waterfall:下载HTML会引发JavaScript和CSS的下载。

因此,最好的做法是将所有CSS和JavaScript标签放在靠近HTML开头的<head>标签中。这可以确保浏览器更早看到它们。通过流,我们可以进一步减少这种延迟,首先发送<head>标签的那一部分。

早期发送<head>标签的最直接方法是将一个标准响应分成两部分。这种技术被称为早期刷新Early Flush,因为一个部分在另一个部分之前被发送("刷新")。

第一部分包含快速计算的东西,可以快速发送。在Airbnb,我们包括字体、CSS和JavaScript的标签,这样我们就能获得上述的浏览器好处。第二部分包含页面的其余部分,包括依靠API或数据库查询来计算的内容。最终的结果看起来是这样的:

早期数据块:

<html>
  <head>
    <script src=… defer />
    <link rel=”stylesheet” href=… />
    <!--lots of other <meta> and other tags… ->

后面数据快

<!-- <head> tags that depend on data go here ->
  </head>
  <body>
    <! — Body content here →
  </body>
</html>

我们不得不重组我们的应用程序,使之成为可能......

2、[Meta] 在Meta构建和部署MySQL Raft
在 Meta,我们运行着世界上最大的 MySQL 部署之一。该部署为社交图谱以及许多其他服务提供支持,例如消息、广告和提要。在过去的几年里,我们实现了 MySQL Raft,这是一个 Raft 共识引擎,与 MySQL 集成以构建复制状态机。我们已经将大部分部署迁移到 MySQL Raft ,并计划用它完全替换当前的 MySQL半同步数据库。

  • 我们正在推出MySQL Raft,目的是最终取代我们目前的MySQL半同步数据库。
  • MySQL Raft最大的胜利是简化了操作,让MySQL服务器负责选举成员的工作。这给了Raft可证明的安全性,减少了重大的操作痛苦。
  • 让MySQL服务器成为真正的分布式系统也为下游系统利用它提供了可能性。其中一些想法已经开始成形。

该项目为 Meta 的 MySQL 部署带来了显着的好处,包括更高的可靠性、可证明的安全性、故障转移时间的显着改进以及操作的简单性——所有这些都具有相同或可比的写入性能。

为了实现高可用性、容错和扩展读取,Meta 的 MySQL 数据存储是一个大规模分片、地理复制部署,具有数百万个分片,保存 PB 级数据。部署包括在多个地区和多个大洲的数据中心运行的数千台机器。

以前,我们的复制解决方案使用MySQL半同步(semisync)复制协议。这是一个仅有数据路径的协议:
MySQL主站将使用半同步复制到主站区域内的两个仅有日志的副本(logtailers),但在主站的故障域之外。这两个日志尾巴将充当半同步ACKER(ACK是对主服务器的确认,即事务已被本地写入)。  
这将允许数据路径具有非常低的延迟(亚毫秒)提交,并为写入提供高可用性/耐用性。
常规的MySQL主站到复制站的异步复制被用于更广泛地分发到其他地区。

随着时间的推移,这种自动化维护变得越来越复杂,越来越难以维护,因为越来越多的角落案例需要被修补。

我们决定采取一个完全不同的方法。我们加强了MySQL,使其成为一个真正的分布式系统。意识到控制面的操作,如选举成员是大多数问题的触发因素,我们希望控制面和数据面的操作是同一个复制日志的一部分。

为此,我们使用了广为人知的共识协议Raft。这也意味着成员资格和领导的真实性来源转移到了服务器内部(mysqld)。这是引入Raft的最大贡献,因为它实现了可证明的正确性(安全属性),跨越了MySQL服务器中的晋升和成员变化。

我们的 Raft for MySQL 实现基于Apache Kudu。我们根据 MySQL 和部署的需要对其进行了显着增强。我们将此分支作为开源项目kuduraft发布。
我们添加到 kuduraft 的一些关键特性是:

  • FlexiRaft — 支持两种不同的交叉仲裁:数据仲裁和领导者选举仲裁
  • 代理——使用代理中间节点减少网络带宽的能力
  • 压缩——我们在分发之前压缩一次二进制日志(事务)有效负载
  • 日志抽象——支持不同的物理日志文件实现
  • Primary ban——阻止某些实体暂时成为主要实体的能力

我们还必须对 MySQL 复制进行相对较大的更改以与 Raft 接口。为此,我们创建了一个名为 MyRaft 的新闭源 MySQL 插件。MySQL 将通过插件 API 与 MyRaft 接口(类似的 API 也已用于半同步),而我们为 MyRaft 创建了一个单独的 API 以与 MySQL 服务器接口(回调)。

.....

3、[Dropbox]调查HTTP3对搜索网络延迟的影响
Dropbox 以存储用户文件而闻名,但是当我们在 2022 年 7 月进行一项研究时,最常见的抱怨之一是搜索仍然太慢。 这些用户说,如果搜索速度更快,他们就更有可能定期使用 Dropbox。

当时,我们发现搜索网页需要大约 400-450 毫秒 (p75) 才能提交查询并从服务器接收响应——对于我们期望更快结果的用户来说太慢了。它让我们寻找可以改善搜索延迟的方法。

在我们的早期分析中,我们了解到获取搜索查询结果所花费的时间中,大约一半的时间花在往返 Dropbox 服务器的传输过程中(也称为网络延迟),而另一半时间花在确定要接收哪些搜索结果上返回(又名服务器延迟)。我们决定同时解决等式的两边。

网络延迟比服务器延迟的变化要大得多。这取决于本地网络条件、用户与 Dropbox 数据中心的距离,甚至一天中的时间。在工作时间,许多用户在互联网连接良好的办公室工作,但在晚上,他们在家中互联网连接较弱。与大多数 Dropbox 数据中心所在的北美相比,欧洲的延迟最高可达两倍,亚洲最高可达三倍。考虑到 25% 的搜索请求来自欧洲,15% 来自亚洲,很大一部分 Dropbox 用户将受益于较低的网络延迟。

此时,我们意识到我们无法单独解决网络延迟问题。我们与 Traffic 团队合作,考虑了我们的选择并决定测试一个可能的解决方案:HTTP3。

Dropbox.com 目前使用 HTTP2,一种基于TCP 的协议。最新版本 HTTP3 使用UDP。这通过以下方式加快了建立连接和处理并行请求的时间:

  • 在连接开始时引入零往返时间 (0RTT)。与 HTTP2 相比,HTTP3 减少了一次往返,因为它避免了基于 TCP 协议的强制性三次握手。此外,对于 0RTT,后续的 HTTP3 连接建立安全连接并在同一个数据包中发出实际请求,而在 HTTP2 中,这些数据片段必须单独发送。
  • 消除队头阻塞。TCP 是面向流的,因此需要以严格的顺序处理数据包。如果一个流中的数据包丢失,后续流中的数据包可能会在客户端的 TCP 堆栈中延迟,即使这些流彼此无关。但是对于 UDP,如果一个流被阻塞,其他流仍然可以向应用程序传送数据。

HTTP3 听起来很有前途。从理论上讲,它不仅可以加快搜索请求的速度,还可以加快整个 Dropbox 的操作——从文件上传到内容建议。然而,尚不清楚现实世界的影响是什么。HTTP3 完全有可能(尽管不太可能)比HTTP2。  
我们需要确保 Dropbox 会从迁移到 HTTP3 中受益。我们没有采取未知的飞跃,而是决定先在一部分 Dropbox 流量上测试 HTTP3。

我们的实验成功证明 HTTP3 显着改善了 90% 及以上的延迟。尽管 HTTP3 仅显着降低了我们 10% 的用户的延迟,但这些用户将遭受高延迟的困扰,并且最希望得到改进。HTTP3 的最大受益者将是我们的国际用户,因为最高延迟不成比例地出现在北美以外的地区。

我们从大规模实验中获得了两个主要见解:

  • 0RTT 的好处不太重要,因为几乎所有与 dropbox.com 的连接都是长期存在的。
  • HTTP3 处理head-of-line阻塞的方式显着减少了延迟,尤其是在更可能发生数据包丢失的网络中。

在我们开始调查网络延迟时,我们只知道 HTTP3 的假设好处。现在我们对 HTTP3 可以带来的实际影响有了更好的了解——不仅对搜索,而且对整个 Dropbox,包括文件操作和机器学习的内容建议。

...

4、[Cockroachlabs]什么是运营弹性以及如何实现它
运营弹性是指组织适应和响应中断或意外事件的能力,同时保持持续运营,不间断地向客户提供产品和服务。

实现运营弹性涉及识别、分析和管理运营风险,例如网络攻击、自然灾害、供应链中断和(最重要的)技术故障。

大多数机构都是在单一的云供应商上运行,因为这通常是最经济和最直接的途径,几乎所有的使用情况都是如此。直到现在,随着运营弹性法规看起来越来越不可避免,大多数人还没有一个令人信服的理由来考虑多云战略。这意味着,他们不需要深入考虑,甚至不需要有意识地认识到单一供应商战略的技术影响。

普遍的看法是,你的云供应商只是给你一个平台,而你只是在它上面进行建设。合乎逻辑的结论是,那么,基本上将你的应用程序提升并转移到第二个云供应商上并不是什么大问题,它应该只需要重新布线一些网络连接和API。但实际情况是,你使用的每一项服务,你的应用程序的每一个部分--无论它是本地的还是第三方的,定制编码的还是开源的--都必须与这个新的平台对话。但是,对于你的应用程序中的每项服务需要如何与之沟通,每个云供应商都有不同的专有方式。

你的整个应用程序必须按照这个新平台自己独特的专有标准进行重写,而不是简单的提升和转移。这种需要为每个云供应商验证不同标准的现实,使得通过多云方法实现运营弹性变得非常复杂。

例如,使用Kubernetes来实现工作负载的可移植性是一个普遍接受的应用架构最佳实践。但考虑到你的Kubernetes运营商需要同样的可移植性。当最初架构你的应用程序时,阻力最小和最方便的路径是GKE或EKS或任何你的供应商的本地解决方案 - 直到它改变或增加一个不同的云供应商,甚至是与物理数据中心混合的时刻。然后,突然间,K8s就显得不那么便携了。


5、[Trivago] 用Go建立我们的第一个GraphQL服务器实施指南 
trivago 为旅行者提供广泛的酒店系列,使他们能够比较价格并发现最佳假期优惠。有了如此多的特殊选项,我们引入了一个名为“收藏夹”的新功能来简化导航过程。此功能使用户能够毫不费力地保存他们喜欢的住宿并在以后访问它们,确保易用性。

我们已经开始实施强大的后端解决方案来支持该功能,并付出必要的努力以确保其成功。我们最初的计划是使用 GraphQL 服务器(GraphQL 单体)作为 API 和传入请求之间的中间层。这种方法意味着请求在到达 API 之前必须经过另一个服务。

值得注意的是,我们的 GraphQL 设置是联合的,这意味着请求通过 GraphQL 网关分布在子图中。我们的 GraphQL 单体就是这样一个子图。然而,通过单体与网关通信会增加延迟和依赖性,因此我们试图避免这种情况。由于我们已经有一个 GraphQL 网关来减少资源消耗和提高响应时间,我们选择通过在网关后面实现一个专用的 GraphQL 服务器来利用它,从而绕过单体。此更改为我们提供了以下好处:

  • 客户端始终使用网关的架构,在幕后更改内容不会影响任何客户端。
  • 由于我们通过绕过单体跳过了一步,因此客户端可能会直接从最喜欢的 GraphQL 服务器本身接收到更快的响应。
  • 维护和支持更改更容易,因为我们只需要更改最喜欢的 GraphQL 服务器上的代码和模式,而不必调整单体应用。
  • Go 支持在具有不同路由的同一服务器中运行 GraphQL 服务器和 REST API,这意味着在需要时迁移现有 REST API 总是很容易。
  • 在 Go 中实现 GraphQL 服务器非常容易,唯一需要更换的是接口,其余实现保持不变,这意味着没有额外的复杂实现。
  • Favorite GraphQL Server 可以完全依赖 GraphQL Gateway 进行所有的凭证认证。

....

6、[Booking.com] 生成量身定制的旅游推荐 
在Expedia集团 ,我们的目标是提供高质量的旅游推荐,以鼓励预订和激发探索。

传统上,推荐模型是通过比较相关性和排名指标来评估的,即NDCG(归一化贴现累积收益)、精度。
虽然准确度很重要,但用户更喜欢我们向他们展示的东西有一些多样性。
根据经验,我们认识到,我们的旅行者在探索和计划他们的下一次旅行时,可能有兴趣看到广泛的选择。

考虑一个对价格敏感的旅行者的例子,他正在寻找预订一个最有价值的房产,如果我们推荐一组价格非常相似的房产,这个旅行者可能会变得不高兴,不愿意在网站上进一步探索。相反,最好是有不同的房产,让我们的用户更容易区分。

7、[Cloudflare]TCP对接收缓冲区的内存使用不受限制,以及我们如何解决这个问题
详细解释了TCP会话中的过度内存分配如何影响性能。

在 Cloudflare,我们不断监控和优化我们系统的性能和资源利用率。最近,我们注意到我们的一些 TCP 会话分配的内存比预期的多。
Linux 内核允许匹配某些特征的 TCP 会话忽略自动调整设置的内存分配限制并分配过多的内存,一直到 net.ipv4.tcp_rmem max(每个会话限制)。

在 Cloudflare 的生产网络上,服务器上经常有很多这样的 TCP 会话,导致分配的 TCP 内存总量达到 net.ipv4.tcp_mem 阈值(服务器范围的限制)。

当发生这种情况时,内核会对所有 TCP 会话施加内存使用限制,而不仅仅是导致问题的会话。这些限制对用户的吞吐量和延迟有负面影响。在内核内部,有问题的会话会触发 TCP 崩溃处理、“OFO”修剪(丢弃已接收到的无序队列中的数据包)以及丢弃新到达的数据包。

这篇博文详细描述了问题的根本原因,并展示了解决方案的测试结果。

在我们的测试过程中,我们碰巧注意到 SNMP 指标TCPWantZeroWindowAdv正在增加。接收方没有在应该发送的时候发送 ZeroWindows。所以我们的注意力落在了窗口计算逻辑上,我们找到了所有问题的根本原因。

根本原因:
问题与接收窗口大小的计算方式有关。这是接收方发送给发送方的 TCP 标头中的值。它与 ACK 值一起向发送方传达窗口的右边缘是什么。
TCP 滑动窗口的工作方式在 Stevens 的“TCP/IP Illustrated, Volume 1”, section 20.3 中有描述。

在 Internet 的早期,广域通信链路提供低带宽(相对于今天),因此 TCP 标头中的 16 位足以表示实现最佳吞吐量所需的接收窗口大小。然后未来发生了,现在这些 16 位窗口值根据 TCP 3 次握手期间设置的乘数进行缩放。

窗口缩放因子使我们能够在现代网络上达到高吞吐量,但它也引入了一个我们现在必须讨论的问题。

解决方案只有三个选项需要考虑:

  1. 让窗户变大
  2. 丢弃传入的数据包
  3. 缩小窗口

#1让窗户变大
让窗口增长与忽略自动调整设置的内存限制相同。它会导致无故分配过多的内存。当我们被迫从其他两个选项之一中进行选择时,这实际上只是把罐子往下踢,直到分配的内存达到 net.ipv4.tcp_rmem max。

#2丢弃传入的数据包
丢弃传入的数据包将导致发送方重新传输丢弃的数据包,指数退避,直到最终超时(取决于客户端读取速率),这会中断连接。永远不会发送 ZeroWindows。通过重传我们知道不会成功传送到接收方 L7 的数据包,这会浪费带宽和处理资源。对于窗口已满的情况,这在功能上是不正确的。

#3缩小窗口
缩小窗口涉及在接近窗口满状态时将窗口的右边缘向左移动。当窗口已满时发送 ZeroWindow。没有浪费的内存,没有浪费的带宽,也没有断开的连接。

目前的情况是我们让窗口增长(选项#1),当达到 net.ipv4.tcp_rmem max 时,我们正在丢弃数据包(选项#2)。
我们需要停止执行选项 #1。当到达 sk_rcvbuf 时,我们可以丢弃数据包(选项 #2)。这避免了过多的内存使用,但对于窗口已满的情况在功能上仍然不正确。或者我们可以缩小窗口(选项 #3)。

事实证明,缩小窗口问题(选项 #3)已经在 RFC 7323 中得到解决。

可以在此处找到我们编写的用于启用 TCP 窗口收缩的 Linux 内核补丁。该补丁也将向上游提交。