关于负载平衡和分片 - Tim Bray

19-09-28 banq
                   

如果您确实需要处理大量流量,则只有一种方法:分片。也就是说,根据需要将传入请求分配给尽可能多的主机(或Lambda函数,消息代理或数据流)。一旦完成这项工作,您就可以处理几乎无限的请求量。当然,您必须选择如何在分片之间分配流量。自从我开始在AWS工作以来,我就对这些选项非常关注。

随机发送

这是可以想象的最简单的方法。对于每条消息,您都将创建一个UUID并将其用作分区密钥(如果下游使用该UUID进行分片),否则只需使用您喜欢的随机数生成器来选择目标分片即可。

假设前端可能是愚蠢的,无状态的且快速的。负载将在后端分片之间平均分配。

关于此主题的常见变体涉及自动缩放。例如,如果您有一群主机在处理来自SQS队列的消息,则基于队列深度自动缩放队列大小是一种很常见的做法。再一次,令人赞叹的简单。

“智能”分片 

如果某些分片超载而其他分片处于闲置状态。另一类情况是上游发送“毒丸”消息,这些消息导致接收碎片锁定或行为异常。

负载敏感度是一种“智能”方法。这样做的目的是跟踪每个分片上的负载,并有选择地将流量路由到负载较轻的负载,并远离繁忙的负载。最简单的事情是,如果您有某种负载度量标准,请始终选择具有最低流量的分片。

但这不是那么容易。您必须找出有意义的负载指标(队列深度?最近收到了多少流量?CPU负载?),并将其从每个分片传送到前端。

如果您担心使用药丸-一点也不罕见-最明智的方法通常是洗牌。请参阅 ColmMacCárthaigh关于该主题的精彩文章

相似性

也称为“会话相似性”。您有时听到的另一个术语是“粘性会话”;有关讨论,请参阅 Red Hat上的这篇文章。这个想法是通过使用某种形式的帐号或会话标识符作为分区键,将所有事件从同一上游源路由到同一分片。

如果来自同一用户的所有点击流点击或来自同一工作流或进入同一主机的任何状态更改事件,您都可以将相关状态保留在该主机的内存中,这意味着您可以更快地做出响应并达到目标数据库不太难。

很多事情都会出错。最明显的是:流量在上游源之间分布不均。

一次我正在处理一系列AWS客户事件,我认为按帐号将其分配给Kinesis分片是明智的。太糟糕了 -  每个帐户速率每秒的消息以经典的平方反比曲线分布在各个帐户中,就像边距一样。具体来说,在一项早期测试中,我注意到排名前10位的帐户占了流量的一半。

一般的教训是,如果不幸地将太多“热”上游源发送到同一分片,则很容易使它过载。最糟糕的情况是,当您面临单个“鲸鱼一样大型”客户端时,对于任何单个分片来说,其流量都过多。

所以在这种情况下,我们切换到随机发送,整个系统稳定下来并运行顺畅。除此以外,处理每条消息都需要咨询以帐号为键的数据库,而数据库的费用却很高。

因此,我想到了一个绝妙的主意,编写了一个“尽力而为的亲和力”库,该库试图将每个客户的请求聚集在尽可能少的分片上。它似乎工作正常,我们的数据库账单减少了六分之一,我感到很聪明。

从那时起,这变成了一场噩梦。遭遇我们从未想到的极端情况。

在某个主机中建立了每个会话状态,然后该主机崩溃时,会发生什么呢?(不一定是当机,也许您只需要打补丁即可。

现在,因为您是一位优秀的设计师,所以您一直在将所有更新内容都写入某种日志中,因此,当分片变成梨形时,您会为其找到另一个主机并重播事件日志。

为此,您需要:

  1. 可靠地检测节点何时发生故障。而不是停留在较长的GC周期中或等待缓慢的依赖关系。
  2. 查找新主机以放置分片。
  3. 找到正确的日志并重播以重建所有丢失的状态。

任何人都可以看到这需要很多工作,代码根本不简单。

将状态存储在碎片中是不行的,我们都应该回到无状态随机发送状态,那延迟呢?好吧,如果您使用分区键进行分片,则可能可以使用它从键/值存储中检索状态,例如DynamoDB或Cassandra或Mongo。如果一切设置正确,您可以期望稳态检索延迟(单位毫秒)。您可能可以使用DynamoDB的DAX之类的缓存加速器, 并且做得更好。

但是缓存是一种干扰。您将要获得的性能将取决于您的记录大小和更新方式,并且无论如何您都不关心P99的平均值或中位数 。

也就是说,运行一些测试。您可能会发现从数据库中获得了足够的性能,可以在各个分片之间进行随机发送,拥有无状态的前端,并在晚上自动缩放后端。