组织和抽象层

2020-12-15 05:43:54

这篇文章反映了成为将近五年的基础架构成熟的一部分。

当我加入Shopify的生产工程部门时,当时只有30人。我们在那段时间去参加团队晚宴,我记得我的经理是如何用他的信用卡付款的,因为积分。现在我们有200多人,在大流行前的最后一个场外,我们预定了一个宽敞的宴会厅来准备晚餐。显然,这并没有从个人的信用卡上计费。

在那的那几年里,我看到了如何通过引入边界和新的抽象级别来解决可伸缩性和增长的问题。

现在,我也注意到了其他公司的这种模式,我相信管理抽象层是解决可伸缩性问题的关键工具。

与大多数Ruby on Rails商店一样,我们一直在Redis上使用Resque gem运行作业队列(您可能还与Resque的后继者Sidekiq一起工作)。这两个库都建立在Redis之上,Redis是用C编写的键值数据库,还提供了诸如List和Hash之类的原语。作为数据库,Redis将这些列表/队列保存在内存中,如果启用了持久性,则每隔一段时间将它们转储到磁盘中。

Resque(或Sidekiq)API的设计方式,您可以通过调用Resque.redis来获取Redis客户端,并直接向Redis查询任何其他操作。这很方便,并且到那时Shopify拥有数百名开发人员,所有人都可以轻松地将非工作数据转储到Redis,这归功于Rails应用程序中对Resque.redis的轻松访问。

这种易于使用的API的副作用是,Redis不仅因后台作业的吞吐量而超载,而且由于人们插入Resque.redis以存储临时键(例如油门或清单数据)的所有任意功能而变得超载。

第1课:易于访问的API可能会造成危害,特别是在较小规模的API设计且大规模滥用的情况下。

我们为所有非作业功能引入了单独的Redis实例,有一段时间,Resque.redis仅用于作业。

但是由于Resque.redis并没有作为公共API消失(即使不鼓励在口头上使用它),因此开发了许多新功能写入Resque.redis,主要是因为这是开发人员的习惯。

我们花费了大量的精力来完全将Resque.redis删除为公共访问器(请参阅shitlist驱动的开发),并采取行动避免直接在任何地方公开Redis客户。我们没有提供直接给Redis访问的权限,而是提供了一些包装Redis访问权限的Ruby类,例如ActiveJob,Throttle或DisposableCounter。

第2课:在操作的子集受到限制且其客户未直接向开发人员了解的情况下,扩展数据存储将变得更加容易。

务必要指出的是,Redis是单线程的,这意味着它使用的CPU不会超过一个。它的作者建议通过引入更多在其他CPU上运行的Redis实例,并使您的应用以某种方式将数据分片到多个Redis实例中(或使用Redis Cluster)来进行扩展。

而且,更多客户端连接对Redis开放时,最忙的将是单个CPU。我们开始注意到,连接限制和CPU负载将成为保持平台正常运行的两个最大瓶颈。

正如我们对其他商店(例如MySQL和memcached)所做的那样,我们在Redis之前引入了TCP代理,该代理将多路复用客户端<后端连接并减轻Redis CPU的压力。通过在Redis / MySQL / memcached前​​面放置一个代理,您可以为扩展性购买多少空间,这是令人难以置信的。

现在,对Redis的所有操作都通过代理进行,并且作业或节流阀等每个功能都有其自己的Redis。但是每个Redis仍然是单线程的,一旦我们在工作或节流阀上增加了额外的负载,该Redis就会在CPU上达到极限,并且某些操作会排队并超时。我们必须为每个功能水平缩放Redis。

在数据库前放置代理的好处在于,您现在可以更改后端而无需更改客户端。多亏了Envoy代理,我们才能将代理后面的单个Redis与多个Redis实例池进行交换,并可以通过键进行分区操作。

最初我们没有抽象,开发人员习惯于直接调用Redis客户端进行任何操作。我们从提供Redis客户端到提供在下面与Redis一起使用的原语,已经脱离了以往。

后来,我们不再将应用程序直接连接到Redis,而是给它们提供了类似于Redis的东西,但实际上它是将命令转发到基础架构团队管理的多个Redis实例的代理。这种抽象还将使我们能够将代理上的这些后端与我们想要的另一个数据库(例如KeyDB)交换。

这两个步骤从应用程序代码中提取了Redis访问权限,并从基础架构中分离了代码,这是对负载和开发人员数量进行适当调整的关键。

您可以看到与Vitess类似的模式,这使客户端相信它正在与MySQL对话,而实际上是在与应用某些逻辑并将这些MySQL查询转发到其他地方的Go服务对话。 Vitess已被YouTube,Github和Slack所使用,作为一种在不增加客户端复杂性的情况下水平扩展数据库访问的方式而受到欢迎。

我相信,越来越多的抽象是为可伸缩性付出的合理代价。 它也以另一种方式起作用:如果堆栈的某些部分是抽象的,而某些部分不是抽象的,则那些未抽象的部分将首先成为可伸缩性瓶颈。