Foryou 2018-09-12
点击上方关注,All in AI中国
Reddit如何使用Go构建其广告服务系统,以及从流程中汲取经验教训。
概要
Reddit工程团队近段时间将Go引入其堆栈,以编写新的广告服务系统来取代第三方系统。 Deval Shah向我们介绍了这个新服务的架构、Reddit团队第一次使用Go的经验,以及他们使用Go构建这个广告服务器所学到的所有课程。
Reddit简介
Reddit是互联网的首页,它是一个拥有数万个兴趣社区的社交网络,人们可以在那里讨论对他们来说重要的事情。
Reddit的数字排名:
而Reddit构建的任何系统都必须能够处理此级别的流量。
广告架构概述
广告服务器需要处理整个广告流程。广告服务器处理从广告显示到广告之后的任何后处理的所有内容。
广告服务@ Reddit
Reddit广告服务器有几个要求:
之前的广告服务@ Reddit:
之前,每当用户访问reddit.com时,reddit 的monolith后端都会向第三方广告服务器发送请求。第三方服务器将使用其选择的一个或多个广告进行响应,并将其返回给用户。
过了一会儿,他们意识到继续使用第三方广告服务器对他们来说不会有用,因为它:
我们决定建立一个广告服务器,建立一个由3人组成的团队。从infra开始,编写服务,然后将其推广到正在生产的系统。
广告服务基础设施:
广告服务器基础架构中使用的一些值得注意的工具:
广告服务器架构:
这是新广告服务器的架构:
简要概述它的工作原理:
在这种架构中,Go服务是:
Reddit的其他一些Go工具和服务则不会深入探讨:
我们的Go经验
这是Reddit与Go的第一次体验。德瓦尔表示,到目前为止,这段经验很棒。这项工作始于使用Go的两到三名工程师,现在已经发展到大约十几名在Go方面工作的工程师。
他们在Go看到的主要优势是:
得到教训
这是一系列面临的问题,Reddit如何处理这些问题,以及从这些挑战中学到的知识。
问题1:如何构建生产就绪的微服务?
Reddit以前有过为Python做过的经验,但不是Go。
最初的原型通过大量的StackOverflow读取和谷歌搜索工作,但显然不会与开发人员一起扩展。
他们看到的一些问题是:
他们意识到Go社区已经解决了这些问题,因此他们研究了解决这些问题的现有框架。他们遇到的一些选择:
他们认为Go-Kit最有意义。 Reddit选择Go-Kit的主要原因是:
这个架构有一些值得注意的事情。中心服务有2个实现:内存实现(这很好并可以用于原型),以及用于生产实现的RocksDB实现。本地开发仍然存在内存中实现。
有几个中间件层:跟踪、日志记录和度量。最后,Thrift运输处于顶层。这种结构使得更改变得容易。例如,如果他们想要将传输层从Thrift更改为gRPC,他们只需要更改顶层。
使用Go-Kit是有益的,因为它为团队提供了如何构建Go代码的良好例证。他们以前没有这方面的经验,因此使用Go-Kit有助于理解Go服务的典型结构。
教训1:使用框架/工具包。对于您使用Go的所有内容而言,并不是必需的,但对于需要度量、日志记录等的生产服务,请使用已解决问题的库而不是尝试自己完成。
问题2:如何安全快速地推出新系统?
最终目标是推出新的广告服务器,对Reddit用户、支付广告客户、依赖广告团队的其他内部团队影响最小。第三方广告服务器是一个黑盒子,Reddit需要一种快速迭代、学习和改进的方法。
这就像在飞行途中改变飞机。他们慢慢地在他们的第三方服务周围添加了新的基础设施,当它准备就绪时,他们会把它撕掉:
在这些Go特性的帮助下,Go允许他们安全轻松地迁移到新的广告服务器:
教训2:Go使快速迭代变得简单而安全。
问题3:如何调试延迟问题?
部署新广告服务器后,他们确实看到了一些缓慢,网络故障,部署不良等问题。
如果你确切知道哪个服务有问题,pprof就很棒。另一方面,分布式跟踪使您可以查看服务。他们没有支持广告方面的分布式跟踪,但他们确实在Reddit的堆栈上的其他地方支持它。
为什么跟踪有用?
跟踪通常很容易,你有一个客户端和服务器。在客户端,您提取跟踪标识符,并将它们注入您发送的服务器的请求中。在服务器端,当您获得请求和标识符时,将它们放入上下文对象并传递它们。使用HTTP和gRPC非常简单,没有理由不这样做。
但是,reddit正在处理Thrift,所以他们遇到了一些问题。
他们看了一下Thrift替代品,Facebook Thrift和Apache Thrift。他们正在寻找的两个关键功能是对标题和上下文对象的支持:
他们尝试使用FB thrift,但是存在一些问题,主要是缺少上下文对象,导致代码混乱和复杂化。在Apache thrift中,支持上下文对象,但它不支持头文件。因此,解决方案是:向Apache Thrift添加标头。这已经针对其他语言完成,但不适用于Go。因此,他们将THeader添加到Apache Thrift。这意味着现在支持上下文对象,并且头文件可以存储跟踪标识符。
如果您想查看这些更改,可以查看https://github.com/devalshah88/thrift。 Deval希望通过贡献流程获得更改并将其合并到上游。
这是一个跟踪代码。客户端包装器只从上下文对象中提取跟踪信息,并将其添加到headers:
服务器包装器从头部获取信息并将其注入上下文对象,以便它可以传递:
此代码来自https://github.com/devalshah88/thrift-tracing。
完成所有这些工作后,分布式跟踪被证明在调试延迟问题方面非常有用。然而,我们得到的结论是第三课:使用节俭和Go进行分布式跟踪是很困难的。
问题4:如何处理缓慢/超时?
在Reddit,他们希望系统能够优雅地处理缓慢。他们从不希望用户受到影响,因此如果速度缓慢,Reddit宁愿不展示广告也不愿降低用户体验。
他们的两个目标是:
使用上下文对象来强制服务中的超时:这是来自充实服务的代码,用于向上下文对象添加截止日期,传递它,并在截止日期到期时提前退出。
这样的结果是好的,但还不够:
第一张图显示了从充实服务获得响应所需的时间。这个特定的时间框架有一些缓慢,但它没有让用户等待超过25毫秒。
第二个图表显示,在服务器端,增强服务正在处理最长70毫秒的请求,因此服务器在客户端已经超时并且在不再需要响应之后,有些浪费资源。
通常要做的是使用HTTP传播截止日期。此代码添加了一个超时,它通过上下文对象传递给服务器:
Thrift使这很难。这里没有使用上下文对象。如果客户端超时,goroutine不知道并且不退出:
这个方法不是特别好,但有办法来解决这个问题:
一种选择是为请求有效负载添加截止时间。客户需要在请求中包含截止日期。服务器会将截止日期注入上下文对象,并使用它。这并不是很好,因为必须在所有端点进行此更改。
相反,他们通过截止日期作为节俭标题。这与它们传递跟踪标识符的方式类似。在此更改之后,在服务器端,他们看到类似于客户端的延迟:
教训4:在服务内部和服务之间使用截止日期。
问题5:如何确保新功能不会降低性能?
快速迭代和复杂的业务逻辑可能导致性能问题。广告服务团队需要流程和工具,以确保他们能够快速移动而不会违反延迟SLA。为此,他们使用了负载测试和基准测试。
使用弯曲器进行负载测试:
这就是你从Bender那里得到的回应:
负载测试对于在重负载下测试更改非常有用,并且允许开发人员再推送到生产之前优化新功能以实现高负载。
他们还利用所有关键系统的基准测试。此基准测试代码:
获取此输出:
基准测试有助于:
教训5:基准测试和负载测试很容易。做吧!
回顾:
结论: