【翻译】Twitter 如何处理每秒 3000 张图像

在今天的 Twitter, 每秒钟就有 3000 张新图片(200 GB)被创建和持久存储。更可观的是,2015 年 Twitter 通过改进媒体文件存储策略节省了六百万美元。

图片负担不是从一开始就那么大的。2012 年的 Twitter 还是以文字为主的,就像是墙上的画不会动的霍格沃茨。到 2016 年,Twitter 已经进入了富媒体时代。在这个转变过程中发挥了重要作用的,是 Twitter 开发的一个新媒体文件平台。它支持图片预览、多图、动图、短视频和内嵌视频。

Twitter 的一位软件工程师 Henna Kermani 在一场精彩的演讲(Mobile @Scale London: 3,000 images per second)中讲述了这一平台的故事。她重点讲述了图片处理的流水线,但也表示绝大部分的细节同样适用于其他种类的媒体文件。

从她的演讲中可以学到这么一些有趣的事情:

  • 试图用最简单的方式解决问题一定会出问题。 把上传推文这个操作当作一个简单的原子操作来处理就是自缚手脚。这没有可拓展性,在慢速网络上更是表现奇差,也使 Twitter 很难为产品添加新的特性。

  • 解耦。 通过将媒体文件的上传和推文的发送这两个过程分离,Twitter 得以将它们分别优化,并且运行起来更灵活。

  • 移动文件 handle 而不是文件本身。 不要在系统中迁移大量的数据,这将无谓地消耗带宽,并使得数据迁移路径上所有的服务的性能都收到影响。正确的做法是把数据存放在一个地方,再用一个 handle 指向它。

  • 使用 分段断点续传 大大降低了媒体文件上传的失败率。

  • 研究和实验。 Twitter 发现将原图的缩放图(缩略图、小图、大图等)的 TTL (Time To Live,存活时间) 设为 20 天可以达到存储和计算的平衡点。这些图像在 20 天以后被访问到的概率很低,可以删除。这每天就能为 Twitter 节省下 4 TB 的数据存储空间,并将所需要的计算服务器的数量减少了将近一半。

  • 按需呈现。 旧的缩放图可以被删除,因为它们可以在被请求时现场生产,而不需要预先准备好。按需服务能提高灵活性,使我们更好地协调计算任务的运行,并加强了对运行过程的控制。

  • Progressive JPEG 是一种非常优秀的图像格式。它在前后端都有不错的支持,在慢速网络上也有很好的表现。

在 Twitter 向富媒体的未来前进的道路上,发生了许多事。让我们来学习他们是怎么做到的。

老路 - 2012 年的 Twitter

写入

  • 用户通过 APP 撰写推文,有时会附加一张图片。

    • 客户端将推文上传到一个 monolithic 的端点。 图片与其他数据一同被打包,上传途中会经过涉及到的所有服务。

    • 这个端点就是旧设计中很多问题的根源。

  • 问题 #1:浪费网络带宽

    • 创建推文和媒体文件上传被耦合在同一个操作中。

    • 一次上传要么完全成功,要么完全失败。一旦由于任何原因(网络故障,瞬时错误等)导致上传失败,就要全部重新上传,包括媒体文件在内。一个上传操作完成到 95% 时失败照样需要重新开始。

  • 问题 #2:在引入更大的媒体文件时可拓展性不强

    • 当媒体文件变大时,比如视频,这个方法就没法拓展。文件越大,上传失败的可能性就越大。尤其在巴西、印度和印度尼西亚这些新市场。这些地方的网络不仅速度慢还不可靠,用户都希望能提高上传成功率。
  • 问题 #3:对内部带宽的利用不够充分

    • 端点连接到 TFE(Twitter Front End,处理用户认证和路由),将用户导向一个 Image Service。

    • Image Service 告诉 Variant Generator 生成原图的不同尺寸。这些经过缩放的图片之后就被存储到 BlobStore(一个专为图片和视频这类大文件优化的 key-value 存储)中,从此就一直躺在那里。

    • 创建和持久存储一条推文的过程中包括了一大批不同的服务。因为端点是 monolithic 的,把媒体文件和推文数据打包在一起,这个很大的包就会经过所有涉及的服务,而这些服务大多都并不负责处理图像。它们不是图像处理流水线的一部分,却也被迫为转发大载荷数据包而优化。这种做法很浪费内部带宽。

  • 问题 #4:不断膨胀的存储空间

    • 那些几个月甚至几年前的推文图片,尽管不再被请求,却依然会永久存储在 BlobStore 中,占用大量的空间。甚至有时候,在推文已经被删除的情况下,图片还会待在 BlobStore 中。没有垃圾回收机制。

读取

  • 用户看到一条推文和附上的一张图片。这图片从哪来呢?

  • 客户端从一个 CDN 请求图片的一个缩放尺寸。而这个 CDN 可能需要向源头的 TFE 请求原图片。这个请求最终会被转化成在 BlobStore 中对一个给定的 URL 以及尺寸查找对应的图片。

  • 问题 #5:无法添加新的缩放尺寸

    • 这个设计不是很灵活。添加新的缩放尺寸需要将所有已有的图片的对应尺寸填充到 BlobStore 中。没有按需呈现机制。

    • 这使得 Twitter 很难为客户端添加新特性。

新路 - 2016 年的 Twitter

写入

将媒体文件上传与推文发送解耦。

  • 上传被当做一等公民对待。创建专门用于将原始图片存放到 BlobStore 的上传端点。

  • 这为上传操作带来很多灵活性。

  • 客户端与 TFE 通信,TFE 告诉 Image Serivce 将图片存放到 BlobStore 并将元数据添加到 Metadata Store。这样就完成了,而不再有一大堆服务将媒体文件传来传去。

  • Image Service 返回一个唯一的 mediaID。当客户端想要创建一条推文或是更新个人头像时,这个 MediaID 就会被作为 handle 来指向实际的图像,而不需要再次提供图像。

  • 假设我们想要用刚刚上传的媒体文件创建一条推文,整个流程是这样的:

    • 客户端将 MediaID 放在推文里面发送给更新端点,推文会到达 TFE 并被导向对应的服务。比如创建推文对应的服务是 TweetyPie,个人资料对应的又是另一个服务。所有这些服务都会向 Image Service 请求对应的资源。Image Service 维护着一个后处理队列(负责处理脸部识别、儿童色情检测等任务),当图片被处理完成时它就会告诉 ImageBird(对应图片)或是 VideoBird(对应视频)去生成缩放图或是进行视频转码。最后生成的资源都会被存放到 BlobStore 中。

    • 整个过程中没有任何媒体文件进行传递,节省了很多带宽。

分段断点续传

  • 用户在上传过程中走进地铁,十分钟后再出来,上传过程会从中断的地方恢复上传。对用于来说这个过程是完全无缝的。

  • 客户端通过上传 API 发起一个上传 session,后端会提供一个 MediaID 来标识这次上传。

  • 一张图片会被分割成多段,每段都用相同的 MediaID 来调用 API 进行上传。当所有小段都上传完成时这张图片就可以使用了。

  • 这个方法对网络错误的容忍性很好,每个小段都可以分别重试。如果网络莫名其妙断了,用户可以点暂停,等网络恢复以后再从中断的地方继续。

  • 这个简单的方案带来了巨大的收益。对于大小超过 50 KB 的文件,上传失败的概率在巴西降低了 33%,在印度降低了 30%,在印度尼西亚则降低了 19%。

读取

引入一个 CDN Origin Server,称为 MinaBird。

  • 如果某种尺寸的缩放图或是某种格式的转码视频不存在,MinaBird 可以告诉 ImageBird 或是 VideoBird 去就地生成它们。

  • MinaBird 在处理客户端请求时更加灵活。例如某些内容可能违反 DMCA,MinaBird 的存在使得屏蔽这些内容或是取消屏蔽变得很容易。

  • 可以就地生成缩放图和转码视频使得 Twitter 省下许多存储空间。

    • 按需呈现机制使得缩放图等不需要被事先存储在 BlobStore 中。

    • 原始图像将一直保留到删除位置,而生成的缩放图和转码视频则只保留 20 天。媒体文件平台的团队做了很多研究来找到最佳的保留时间。他们发现大约 50% 的请求都不涉及 15 天前的内容。再早的内容如果继续保留,收益就会越来越小。而且很有可能根本没什么人会去请求更早的数据。统计上来看 15 天以后的长尾很显著。

    • 如果不设定 TTL,媒体文件的存储每天要花掉 6 TB 的空间。而完全按需呈现的方法则只需要 1.5 TB 的空间。它们的中间点,即不保留超过 20 天的内容,不比 1.5 TB 多花多少存储空间,同时又节省了大量的计算。完全按需呈现需要 150 台 ImageBird 服务器,而 20 天的 TTL 则只需要 75 台。所以 20 天 TTL 是一个很好的存储和计算之间的平衡点

    • 节省空间和计算就是节省金钱。2015 年 Twitter 通过引入 20 天的 TTL 节省了六百万美元。

客户端改进(Android)

  • 用谷歌开发的图像格式 WebP 进行了六个月的实验。

    • 图片大小平均比 PNG 和 JPEG 格式小 25%

    • 用户主动性增强,尤其是在新兴市场,更小的图片给网络的压力就更小。

    • iOS 不支持此格式

    • Android 4.0+ 才支持此格式

    • 平台支持的缺乏使得支持 WebP 格式很费成本。

  • Twitter 尝试了另一种格式:Progressive JPEG。它通过连续的扫描来渲染图片。第一次扫描后看到的图片可能比较模糊,但之后的扫描会逐步提高图像质量。

    • 性能更好。

    • 后端支持更普遍。

    • 编码速度比普通 JPEG 慢 60%。 但由于编码只发生一次所以这不是什么大问题。

    • 不支持透明度,所以透明的 PNG 还是保持原样,而其他格式都在慢慢向 Progressive JPEG 靠拢。

    • 客户端的支持通过 Facebook 的 Fresco 实现。Fresco 是一个很优秀的库。2G 信道上的测试结果很显著,Progressive JPEG 的第一次扫描只需要 10 kb 流量,所以很快就能显示出可认的图像。而这时原生的图像渲染还是一片空白。

    • 推文详情的负载测试结果是,p50 加载时间下降了 9%,p95 加载时间下降了 27%,失败率下降了 74%。使用慢速网络连接的用户实实在在感受到了改善。

译注

译者:章凌豪

原文链接

相关文章

On HackerNews

The Architecture Twitter Uses To Deal With 150M Active Users, 300K QPS, A 22 MB/S Firehose, And Send Tweets In Under 5 Seconds

How Twitter Uses Redis To Scale - 105TB RAM, 39MM QPS, 10,000+ Instances

DataSift Architecture: Realtime Datamining At 120,000 Tweets Per Second