在Ifood开发一个每秒处理超过3万个请求的微服务


【编者的话】本文分享了作者在Ifood公司开发一个每秒处理超过3万个请求的微服务的心路历程,重点探讨了该微服务的当前架构:使用AWS提供的DynamoDB,写入请求采用SNS和SQS,请求和真正写入分离,读取请求采用AWS提供的DAX作为内置缓存,并采用双重重试机制。最后以S3作为备份服务,保证微服务的可用性。

Ifood是一家巴西食品技术公司,每天提供超过100万个订单,并且每年还在以110%的速度增长。作为食品技术公司,该平台的浏览时间集中在午餐和晚餐时段,周末流量更高。

在某些特殊日子(例如:由于营销活动),平台能打破历史记录,获得有史以来最高的浏览记录。去年6月12日就是这样的一天。我们看到一个微服务达到了每分钟200万个请求

一些背景故事

我已经在Ifood平台工程领域的帐户和身份团队工作了大约一年半。这是一段漫长的旅程,由于公司的快速发展,我们有时会面临很多挑战。在设计新解决方案时,我们始终必须牢记一个想法,即几个月后系统使用量将增长2到3倍。

我今天要讲的故事就是其中一个例子。我们在2018年左右开发了一个系统,当时公司每月交付1300万个订单,如今它已超过3000万。在这种情况下,虽然并非总是如此,但是,系统使用率的增长确实与公司的增长比例相同,后来又开始更加激进地增长。

在内部,我们将此微服务称为帐户元数据。即使名称比较通用,它也可以说明服务的用途:它处理帐户的元数据。什么是帐户元数据?好吧,大部分都不是主要/基本的客户信息。举个例子:用户更喜欢通过SMS还是电子邮件获取通知,他最喜欢的食物类型(如汉堡,面食,日本料理等),用户的一些特征标记,用户已完成的订单数等等。汇总不同位置的数据,并轻松将其提供给移动应用及其他微服务,这是司空见惯的事情,因为他们只需要调用一个微服务即可,而不是十个。

早在2018年,帐户元数据的构建主要是为了放置一些随机(使用也不频繁)的信息,说实话,没有其他地方可以放置它们。我们需要一些结构和查询功能,并且要求很容易扩展它,因此我们选择了AWS提供的DynamoDB。在此明确说明一下,我们意识到系统要能扩展,且公司已经相当大,平均负载也极具挑战性。但是,我们完全无法预料到系统会从每分钟1万个请求(rpm)增加到每分钟20万个请求,最后到了每分钟200万个请求

该微服务首次发布后,使用率不高(与“帐户和身份”团队中的其他微服务相比)。但是几周后,在做出一些业务决策后,该系统变得非常重要,它将成为移动应用程序获取所有有关客户信息的第一个呼叫服务之一。

在那个业务决策后的几个月,其他团队开始将帐户元数据视为放置信息的好地方,他们要放置的信息原本将被拆分为多个位置并且很难依赖。另外,我们开始创建更多的聚合,使得其他微服务的生命周期真正变得更轻松,从而增加了系统的重要性,并且在其他团队中传播了其流行度和重要性。现在,每次用户打开应用程序时,许多团队都会在不同的上下文中调用帐户元数据。

这是对该系统从2018年产生到如今变得非常重要的整个过程的简短总结。在此期间,团队(我很幸运能和其他八位非常聪明的人一起工作)积极地工作在这上面,但也不是唯一的工作。我们仍在修补,开发和维护我们拥有的其他十种微服务。

我们做了很多更改,描述我们所经历的所有场景将花费很多时间,因此,下面我将写一下该服务目前的架构是如何支撑我们正常地每分钟发送200万个请求最后,是时候深入探讨技术部分了

深入技术部分

如上所述,该微服务存储关于客户的元数据。在数据库中,我们将此元数据拆分为不同的上下文(或如我们在代码中写的:命名空间)。客户(customer_id作为分区键)可以具有一到N个命名空间(作为排序键),每个命名空间都有一个固定的刚性模式,该模式由jsonschema定义和检查(在插入之前)。这样,我们可以确保将数据插入命名空间(稍后会对此进行更多详细介绍)将遵循该架构和正确的用法。

我们之所以使用这种方法,是因为对该系统的读写操作实际上是由公司的不同部门完成的。

插入是由数据科学团队完成的,因为他们每天通过内部API将数百万条记录从其内部工具导出到此微服务,并通过API将该数百万条记录分成约500个项目。因此,该微服务在一天的给定时间内会收到数百万次调用(以10到20分钟为间隔),以将数据插入DynamoDB。如果接收约500个项目的批处理API将它们直接写入数据库,我们在扩展Dynamo时可能会遇到一些问题,并且可能很难保持较低的响应时间。解决此瓶颈的一种方法是,数据团队将其数据直接写入数据库中,但是,我们必须检查这些项目是否遵守存储这些项目的命名空间所定义的jsonschema,这是该微服务的责任。

因此,解决方案是该API将接收一批项目并将其发布到SNS/SQS上,然后由应用程序的另一部分使用,这部分程序将对这些项目进行验证,如果可以的话,将其保存在Dynamo上。通过这种方法,接收一批项目的端点(endpoints)可以非常快速地响应,并且我们可以在不依赖HTTP连接的情况下进行写操作(这很重要,因为与Dynamo的通信可能会失败并再次尝试可能会使HTTP响应时间变得很长)。另一个好处是,通过控制使用者,我们可以控制要从SQS读取数据并将其写入Dynamo的速度快/慢。

在此工作流程之外,每次在平台收到订单时,另一个服务也会调用该帐户元数据,以更新有关该订单的某些信息。鉴于Ifood每天执行的订单超过100万个,微服务也将收到该数量的呼叫。

尽管微服务有一些非常繁重的写入过程,但其95%的负载来自读取数据的API调用。如我所说,写入和记录是由完全不同的团队完成的,而读取调用是由许多团队和移动应用程序完成的。幸运的是,这种微服务需要读取的数据要比写入的多,因此扩展它要容易一些。由于任何读取大量数据的系统都需要缓存,因此不需要使用Redis或类似的东西,AWS提供DAX作为DynamoDB的“kinda内置”缓存。要使用它,你只需要更改客户端并了解在不同查询操作中可能发生的复制延迟。

接收这么多请求,有时出些异常情况是很正常的。在我们的案例中,我们开始看到Dynamo中的某些查询花费了超过2到3秒钟的时间,而99.99%的呼叫时间不到17毫秒。即使每天只有几千个,我们也希望为团队提供更好的SLA。因此,如果Dynamo超时,我们决定重试。我们还与团队进行了交谈,以便他们在调用我们的API时配置较低的超时时间。他们大多数HTTP客户端的默认值为2s,因此我们更改为100ms。如果他们超时了(假设微服务向Dynamo重试但仍失败了),他们可以重试,并且很可能会得到响应。

要部署它,我们使用的是Kubernetes(可容纳约70个Pod),并随着每秒请求的增长而扩展。 DynamoDB设置为provisioned,而不是on-demand。

为确保系统在高吞吐量的情况下能够正常运行,我们每天对其进行负载/压力测试,以确保从上一天开始的部署不会降低性能,并且一切都还好,运作良好。根据此负载测试的结果,我们可以跟踪端点随着时间和开发的发展情况是好是坏。

随着时间的流逝,对于某些团队来说,这种微服务变得非常重要,如果由于某种原因系统出现故障,这将成为一个严重问题。为了解决这个问题,我们要求团队通过Kong(我们的API网关)调用微服务,并在此处配置后备。因此,如果微服务掉线或返回500,Kong将激活后备,客户端将得到响应。在这种情况下,后备是一个S3存储桶,其中包含系统将提供的数据副本。可能已经过时了,但是总比没有响应要好。

总之,这就是它的工作原理。还有其他一些工作流程,但是没有那么重要。

对于团队来说,下一步还不是很清楚。微服务的使用可能会增长甚至更多,我们可能会达到这样一个程度,即它开始变得越来越难于扩展。另一种选择是将其拆分为不同的微服务(甚至可能具有不同的数据库),或者聚合更多数据以更好地为它们服务。无论如何,我们仍将继续进行测试,查找瓶颈并尝试解决它们。

原文链接:Developing a Microservice to Handle Over 30k Requests Per Second at Ifood(翻译:池剑锋)

0 个评论

要回复文章请先登录注册