几年前,我在一个会议上,我们一小群人正在讨论扩展软件。讨论中的一个人提出了公司“洪流日”的想法。“洪流日”是指您的组织及其软件系统经历最大负载的一天。如果您是一家美国的披萨外卖连锁店,那么“洪流日”可能是超级碗周日。如果您是花店,那很可能是情人节。也许最著名的“洪流日”是黑色星期五——感恩节后的那个特殊的日子,美国各地的消费者涌入商店抢购商品。
但是您的“洪流日”并不总是按日历安排的。几年来,我曾在一家大型金融服务公司担任软件工程师——具体来说是一家财产和意外保险公司——我们的“洪流”是名副其实的灾难。灾难的名字像桑迪飓风和卡特里娜飓风。
我们有一些警告。飓风不是预定的,但它们也不会再偷偷摸摸地靠近你。即便如此,它们也给我们的组织和我们的理赔处理系统带来了巨大的压力。还有我们的财务状况。
现在,许多组织都发生了类似的事件。COVID-19 病毒迫使数亿人呆在家里。整个互联网——以及通过互联网提供服务的公司——都看到了前所未有的负载,因为很多人都在家工作。我们为远古世界设计了我们的软件,但现在必须应对洪水。我们如何才能更好地为这种环境设计它?
我不知道我是否拥有这个答案。我不确定是否有人拥有唯一的正确答案。软件是权衡、选择和后果的领域。但是,我确实有一些想法可能会有所帮助。
第一个也是最简单的扩展技巧是扩展您的硬件。获得一台功能更强大的机器,拥有更多的处理器、更多的内存……更多的所有东西。虽然这对于您的 Twitch 流媒体设备需要一些额外的动力来说效果很好,但对于即使是中等规模的应用程序来说,它的用途也有限。盒子只能变得这么大。最终,您将开始看到这种模式
收益递减,然后是平台期。您不断增加处理器和内存以及所有东西,但它只是没有变得更快。我曾在一家公司工作过,这家公司已经将自己设计成了一个死胡同,这就是它的解决方案。短期内有效。但是,即使添加的能力越来越少,成本也急剧增加。
下一个解决方案是向外扩展您的硬件。与其购买越来越大的盒子,不如购买更多的盒子。通过添加节点,您可以处理更多的请求。这种常见的技术可以带您走得很远。如果您做得对,它可以带您到达大部分路程。但是,如果您做得不对,您会再次遇到收益递减和平台期。
您需要的是一种允许您线性扩展的解决方案。像这样
但是我们该怎么做呢?继续阅读。
线性可扩展性的秘诀是无状态性。状态——无论是在磁盘、闪存还是内存中——都是导致应用程序等待的原因。它是我们的线程、我们的进程和我们的服务器争夺的东西。
这种等待是收益递减的根源。如果我们能够消除状态,那将解决我们所有的问题,对吗?从技术上讲,是的,但这就像禁止乘客乘坐飞机可以减少空难中的死亡人数一样。状态是必要的。
每个应用程序都必须存储和管理某种状态。我现在正在使用文字处理器来存储我的文字。这就是状态。没有状态,应用程序毫无用处。
与其试图实现完全无状态的不可能的目标,我更喜欢颠倒这个想法,并问:我们如何避免有状态性?这让我们能够考虑最小化状态,并以不会束缚我们的应用程序的方式处理我们必须接受的状态。优化我们管理状态的方式让我们更接近线性可扩展性的目标。
这是开发人员和架构师都必须考虑的事情。作为开发人员,您可以执行以下四个简单的操作来编写更少有状态的代码
作为软件架构师,您可以使用以下三个想法来阻止开发人员编写有状态的代码
这些技术有所帮助,但它们只是等式的一部分。它们主要将状态从代码中推出。但是每个应用程序都必须以某种方式存储其状态。那么,我们如何存储我们的状态,并且仍然获得接近线性的可扩展性?
扩展存储可能有点棘手。有时,您可以仅仅扩展数据库集群来解决问题。沿着这条路可能会有收益递减,但您的特定洪流可能不会遇到它们。
如果这不起作用(对于许多应用程序来说不会起作用),您有两个基本选项
(还有选项 3。将分布式数据类型与非常,非常,非常可笑的快速数据库一起使用。)
分布式数据类型是一个有趣的话题,有一些值得一看,但我只想谈谈一个:无冲突复制数据类型,或CRDT。
CRDT 允许在数据库中的节点之间复制更新,而无需主节点。所有节点都是平等的,任何节点都可以接受更新。但是,来自任何给定节点的读取不能保证在任何给定时间都具有所有更新,因为它们可能尚未复制。但是,它将具有最终一致性。这允许一些非常好的可扩展性,但权衡是您的读取可能不是最新的。对于许多应用程序来说,这是一个很好的交换。
Redis 是第二个选项的显而易见的答案。它在内存中运行,这在延迟和吞吐量方面比存储在磁盘上的任何东西都快几个数量级。它是单线程和基于事件的,就像您的应用程序一样,如果您遵循了上述建议。它确实具有状态,但它足够快,需要大量的负载才能导致客户端开始阻塞。
对于第三种技术——为什么不两者都用选项——Redis Enterprise 通过使用 CRDT 的Active-Active复制完美地解决了这个问题。
我错了吗?哦。您可能指的是缓存和排队。
缓存是我们都熟悉的东西。从您的数据库中获取常见读取,并将它们保存在内存中。像这样的缓存将缓慢的远程磁盘访问转移到快速的本地内存访问。它实际上只是一种将慢速数据库转换为更快数据库以进行某些操作的方法。如果您正在使用类似于传统关系数据库的东西,那么缓存通常是提高性能的绝佳选择。
队列 有些许不同,并且是处理峰值负载的重要部分。许多任务的运行时间很长,比如发送电子邮件。一种常见的做法是将发送电子邮件的请求放入队列中。然后,另一个节点将接收该消息并发送电子邮件。这让用户感到满意,因为他们获得了更快的响应,并且可以防止请求节点在发送电子邮件时占用大量线程。
然而,这种方法对于运行时间较短的流程也可能有效。通过将应用程序分解成许多小块——每个小块都在自己的节点上——并通过队列将它们连接起来,我们可以创建许多小的、线性可扩展的服务(即微服务)。然后,可以根据负载扩展这些服务——甚至可能自动扩展——以满足所需的负载。
关于这个话题我还有很多可以说的。流处理浮现在我的脑海中。微服务是一个庞大的主题,我的同事 Kyle 和 Loris 为此写了一本书。自动扩容是 DevOps 团队自动扩展和收缩节点池以满足需求的绝佳工具。
我对“大洪流日”的想法一直很着迷。我喜欢它所揭示的许多企业的本质。在接触到这个想法之前,我从来没有想到披萨和鸡翅店需要为超级碗星期天做准备。
我很乐意听到您公司的大洪流日以及您如何解决它们。 欢迎在 Twitter 上联系我并分享!