软件弹性是任何可扩展、高性能和容错软件的必备品质。
软件从意外事件中恢复的能力是软件弹性。这意味着软件工程师必须预测意外事件并对其进行解释。创建这种容错的解决方案可以在代码中或在基础设施层上。
分布式系统会失败,一个有弹性的软件系统不会试图避免失败,而是期待它并优雅地响应。
在这篇文章中,我们将研究您需要注意的一些方面,以实现软件弹性。
目录
- 什么是软件弹性
- 弹性软件因素
- 逐步推出/部署
- 重试软件弹性
- 弹性软件的超时
- 倒退
- 幂等操作支持软件弹性
- 数据库事务
- 速率限制
- 其他需要考虑的事项
- 结论
什么是软件弹性
卡内基梅隆大学软件工程学院博客指出:
基本上,如果一个系统在逆境中继续执行其任务(即,如果它提供所需的能力,尽管可能导致中断的过度压力)。具有弹性很重要,因为无论系统设计得多么好,现实迟早会合谋破坏系统。
如果软件系统在发生意外事件时能够部分正常运行,这就是软件弹性。在基础设施层面,有 NetFlix 臭名昭著的 Chaos Monkey。Chaos Monkey 进入您的生产环境并随机开始杀死实例。这可以作为软件弹性的压力测试。
软件弹性也受到爆炸半径的影响。如果就其可以覆盖的半径而言,变更风险较低,则更容易进行变更。如果爆炸半径非常大,您可能还需要考虑其他事情。
弹性软件因素
有多个因素是软件弹性方程的一部分。以下是我在十多年的软件工程职业生涯中的一些经验。
下面提到的示例将与电子商务有关,因为我已经在时尚电子商务领域工作了将近 9 年。
让我们开始吧。
逐步推出/部署
逐步推出或部署是允许访问部分版本的能力。它可能是金丝雀部署或蓝绿色部署,或者只是一个功能标志,甚至是滚动部署。您可以以丰富多彩的方式阅读有关这些部署技术的更多信息。
这里的重点是,即使这是一项手动任务,它对于弹性软件也非常重要。想象一下,您正在更改电子商务网站的支付网关。如果你进行一次大爆炸,100% 的交易从以前的支付网关 A 转到新的支付网关 B,你将陷入困境。
但是,如果您可以像 1% 的客户一样试用 1 周,那么通过新的网关集成来消除任何错误会很有帮助,并且爆炸半径仅为交易的 1%。
慢慢地,你可以从 1 到 5,然后到 10,最后到 100,充满信心。在部署时进行健康检查也是如此。如果运行状况检查失败,部署将自动回滚。根据服务的不同,您甚至可以逐步推出,这意味着这个特定版本只能获得 2% 的流量。运行在基础设施层而非代码层上的 Google Cloud 等服务支持逐步推出。
弹性软件的另一个重要考虑因素是部署而不是发布。
重试软件弹性
如果您调用另一个系统,您总是需要期望它们可能会失败。因此,在这种情况下,重试机制会有所帮助。例如,您正在调用产品评论服务来创建新的产品评论。
如果它未能创建评论,您可以轻松地重试 1 或 2 次以获得成功的响应。
下面是一个非常简单的 curl 示例:
curl -i --retry 3 http://httpbin.org/status/500
这里的 curl 总是会重试 3 次,因为它会返回 500 错误。下面的 curl 只会运行一次,因为它会在第一次尝试时返回 200:
curl -i --retry 3 http://httpbin.org/status/200
重试是使软件更具弹性的一种简单但有效的方法。
弹性软件的超时
外部系统可能很慢,您无法控制它们的响应时间。这反过来又会使您开发的系统变慢。一旦我们与“流行”的快递服务集成。不幸的是,他们创建货件的响应时间是几秒钟而不是几毫秒。
我们通过最佳超时解决了这个问题,并在可能的情况下推动任务异步。这确实有助于保持软件弹性完好无损。
这解除了进行质量检查并将物品放入盒子中以运送给客户的人员的障碍。当箱子从 QC 站运送到包装站时,将创建装运并打印运输标签。尽管盒子从 QC 到包装站需要几秒钟的时间,但这足以让我们创建货物。如果某些发货失败,有一个简单的重试选项,即按需致电快递员。
故事的寓意,总是添加相关的超时并快速失败。根据需要为用户提供一种在需要时手动重试的方法。超时非常重要。
倒退
回退是一个非常简单的概念。如果主要的东西不起作用,请使用备份。对于 Web 系统来说,主要的事情可以是来自 API 的响应。因此,如果您的 API 调用在重试后仍然失败,您可以回退到响应的本地副本。
另一个纯代码的例子可以很简单:
const shippingFee = fees.shipping ? fees.shipping: 10.00;
在上面的代码片段中,它会查找 fee.shipping 如果不可用,它会回退到 10.00 的值。我们可以在 API 调用中实现相同的功能,如果我们没有从 API 调用中得到想要的结果,它将优雅地降级为使用默认值。
回退似乎很明显,但有时我看到它们被遗忘或省略。
这可能会导致高流量系统出现问题。
幂等操作支持软件弹性
一个堆栈溢出答案总结得很好:
在计算中,幂等操作是指使用相同的输入参数多次调用它时不会产生额外影响的操作。
在现实生活中,它就像公共汽车上的那个停止按钮。停车标志亮起后按一次或100次,效果相同,指示公交车司机在下一个公交车站停车。例如,API 中的 GET 操作是幂等的。这对于设计弹性系统很重要,让我用一个例子来解释一下。
您正在设计一个 API 来将消息标记为已读。
无论调用多少次 API 将单条消息标记为已读,第一个都将其从未读设置为已读,并且所有其他都不会更改状态。
这是一个易于理解的幂等性示例。在使您的系统具有弹性时,您可以安全地忽略第二个和以后的请求,以保留您的资源。
数据库事务
理解数据库事务的最简单方法是全有或全无。如果您有 3 个步骤来完成一项任务,并且在第 2 步中存在问题,它将回滚整个操作。
一个典型的例子是两个银行账户之间的汇款,要么全部通过,要么什么也没发生。
不应该出现A账户扣款但B账户没有充值的情况。数据库事务对于数据的一致性非常重要。
通过充分利用隔离级别,我们可以使用数据库事务来应对竞争条件。例如,一个 cron 将更新 20 条记录,如果这些行与另一个系统(如 ERP)成功同步,则名为 synced 的标志将设置为 true。可以通过以下步骤完成,以避免另一个 cron 同时执行相同的任务:
准备基础任务,例如将这些行与企业资源规划 (ERP) 软件同步
启动数据库事务
SELECT … FOR UPDATE,隔离级别已提交,会话超时时间比平时长
将行与 ERP 同步
使用更新查询将所选行的同步标志设置为 1
提交交易
如果有任何问题,回滚整个事务
因此,在上述情况下,如果第 4 步失败,事务将回滚。当行被 select for update 锁定时,另一个 cron 将无法读取它,因为它被锁定为 UPDATE 并且在隔离级别读取提交的情况下完成。
这有助于通过停止同步相同的行两次来创建容错和弹性软件。如果另一个 cron 甚至在第一个 cron 运行时错误地运行,它将等待这些行可以被新的 SELECT ... FOR UPDATE 查询自由读取。
速率限制
到现在为止,您肯定已经发现,要使软件更具弹性,就需要以最佳方式使用资源。这个速率限制因素正在避免我们的资源被滥用。例如,Twitter API 速率限制调用。让我们以 Twitter API 上的 /statuses/user_timeline 为例,它显示“900 个请求/15 分钟窗口(用户身份验证)”和“100,000 个请求/24 小时窗口(应用程序级别)”。因此,如果作为消费者,您拨打超过 900 次电话以获取用户的雕像,则会收到状态码为 429 的响应。
开发 API 时必须遵循相同的原则,即使它们被其他内部服务使用。让我们假设如果其他内部服务之一有一个错误配置的无限循环,那么当它开始疯狂地攻击您的服务时,您的服务将停机。
如果您有一个良好的速率限制,其他服务将尽早开始发现错误,他们可以更快地解决问题。
最后,您的服务不会占用资源,也不会通过更快地失败来保持正常。
其他需要考虑的事项
为了获得更好的软件弹性,还有许多其他事情需要考虑。数据库读写隔离是一种很好的做法。其中有一个主要用于写入和多个读取副本的主数据库。
在这种情况下,读取的大部分操作在读取副本之间进行负载平衡,并且主节点获得写入。
主服务器与只读副本同步可能会有“几秒钟”的延迟,但这是您应该愿意为它提供的弹性支付的成本。
另一个重要的软件弹性模式是断路器模式。
类似于您家的断路器,如果您的软件系统多次无法访问另一个软件系统,它会破坏标记它打开的电路。它会定期检查其他系统是否已恢复。
当另一个系统恢复时,电路再次闭合。微软博客对断路器模式有很好的解释。
弹性软件系统自动扩展。它们根据负载累加资源。这一点也和软件的可扩展性有关,一般软件可扩展性和弹性是齐头并进的。自动缩放系统依赖于健康检查。
对于具有弹性负载的系统,它们应该能够在负载高时添加资源,并在流量下降时降低资源。
这使软件保持弹性并且成本也处于最佳状态。
结论
弹性和自我修复软件对于高正常运行时间非常重要。
即使在逆境的情况下,软件性能可能会降低但功能性能是弹性软件的标志。
软件弹性是通过始终质疑如果失败会发生什么来实现的,尤其是在与数据库或外部 API 等外部服务通信时。我希望这可以帮助您构建更具弹性的软件。如果您还有其他方面要分享,请不要忘记发表评论。