译者 | 涂承烨
审校 | 重楼
Saga模式是实现持久微服务执行的好工具,但它会使维护变得困难。这里有一个让它为你的系统工作的方法。
1.Sagas的问题
在项目中,我们都经历过这样的时刻:我们意识到软件过程比我们想象的要复杂得多。处理这种过程的复杂性传统上是痛苦的,但不必如此。
30多年来,一个被称为Saga设计模式的具有里程碑意义的软件开发手册已经帮助我们处理了过程复杂性。它已经为数以千计的公司提供了服务,帮助他们构建更复杂的软件来满足更高要求的业务流程。
这种模式的缺点是成本和复杂性较高。
在这篇文章中,我们将首先分析Saga模式处理事务复杂性的传统编码方式,并看看它为什么不起作用。然后,我们将更深入地解释那些不关注这个管道代码问题的开发团队会发生什么。最后,我们将展示如何避免随之而来的项目烂摊子。
满足持久执行的需要
Saga模式的出现是为了应对复杂软件过程中的迫切需求:持久执行。当你正在编写的事务进行一个简单的数据库调用并获得快速响应时,你不需要在代码中容纳该事务之外的任何代码。然而,当事务依赖于多个数据库(或者实际上依赖于其他事务执行)来完成任务时,事情就变得更加困难了。
例如,预订汽车的应用程序可能需要确认客户的帐户是否良好,其次确认他们的位置,再确认该区域内有哪些汽车。然后,它进行预订行程,通知司机和客户,然后在行程结束时收取客户的付款,将所有内容写入中央存储,更新司机和客户的帐户历史。
此类处理依赖事务的流程,需要在整个事件序列中跟踪数据和状态。它们必须能够应对事务流中出现的问题。如果事务需要比预期更长的时间来返回结果(可能是网络连接中断了一会儿,或者数据库达到峰值负载,需要更长的时间来响应),那么软件必须进行调整。
它必须等待必要的事务完成,重新尝试直到成功,并协调执行队列中的其他事务。如果事务在完成之前崩溃,流程必须能够回滚到执行前的一致状态,以保持整个应用程序的完整性。
在需要在几秒钟内响应的用例中,这已经足够困难了。一些应用程序可能在数小时或数天内完成执行,这取决于事务的性质及其支持的流程。开发人员面临的挑战是在整个执行期间维护流程的状态。
这种可靠性(不会失败或超时的事务)被称为强执行保证。它与不稳定执行相反,不稳定执行可以在任何时候停止,而不完成它应该做的所有事情。不稳定执行会使系统处于不一致的状态。
一开始看似简单的事情变成了以我们的软件为中心人物的传奇故事。开发人员必须在完成过程中经历多个步骤,确保我们在发生某些事情时能够保留其状态。
理解Saga模式
Saga模式为这段旅程提供了一个路线图。该模式在1987年的一篇论文中首次讨论,它通过允许复杂流程相互通信,为它们带来持久执行。中央控制器管理该服务通信和事务状态。
该模式为开发人员提供了持久执行所需的三样东西。它可以将事务串在一起,以支持长时间运行的流程,并通过在发生故障时重试来保证它们的执行。它还通过确保流程完全完成或根本不完成来提供一致性。
然而,使用Saga模式需要付出沉重的代价。虽然这个概念在原则上没有问题,但一切都取决于实现。传统上,开发人员必须自己编写此模式的代码,作为应用程序的一部分。这使得它的设计、部署和维护非常困难,以至于应用程序可能成为模式的奴隶,最终占用了开发人员的大部分时间。
最终,随着添加更多事务,开发人员将花费更多时间维护管道代码。线性开发工作负载现在变成了指数级。随着每一个新的变化,花在开发上的时间不成比例地增加。
手动编码Saga模式需要将一个连贯的流程分解成块,然后用管理其操作的代码包装它们,包括在它们失败时重新尝试它们。开发人员还必须在相互依赖的不同流程之间管理这些任务的调度和协调。它们必须使用数据库、队列和计时器来管理进程间的通信。
增加软件流程和依赖关系的数量需要更多的开发人员时间来创建和维护管道基础设施,这反过来又增加了应用程序成本。这种日益增加的复杂性也使开发人员更难证明其代码的可靠性和安全性,这对操作和合规性都有影响。
抽象是关键
抽象是保留Saga模式持久执行优势的关键,同时抛弃其负面包袱。我们必须通过将事务序列抽象到另一个级别来向他们隐藏事务序列,而不是让开发人员将模式编码到他们的应用程序中。
在计算中,抽象是一个很好理解的过程。它给每个应用程序一种错觉,即它拥有一切,消除了开发人员适应它的需要。虚拟化系统在管理程序的帮助下做到这一点。TCP栈通过自动重试网络连接来实现,这样开发人员就不必编写自己的握手代码。关系数据库在不可见地回滚失败的事务以保持它们的一致性时就会这样做。
通过创建Temporal所谓的工作流,运行一个单独的平台来管理持久执行,为事务排序带来了这些好处。开发人员仍然可以控制工作流,但是他们不需要关心底层机制。
将持久执行抽象到工作流除了易于实现之外还有其他好处。经过尝试和测试的工作流管理层使复杂的事务序列比自制的特别管道代码更不容易失败。为每个项目消除数千行自定义代码还可以使代码更容易维护,并减少技术债务。
开发人员在调试时可以很清楚地看到这些好处。当你不得不模拟和管理管道代码时,根本原因分析和补救也会成倍地困难。工作流隐藏了整个潜在问题层。
高效的开发人员是快乐的开发人员
基于工作流的持久执行提升了开发人员的体验。他们没有消失在事务管理的漩涡里,而是开始做对他们来说真正重要的事情。这样可以提高员工的士气,很可能有助于留住他们。2021年至2031年间,美国软件工程师的空缺职位预计将增长25%,对人才的竞争非常激烈。公司承受不起太多的人员流失。
公司在使用Saga模式来处理软件过程中的上下文切换方面,一直在朝着正确的方向前进。但是,他们可以更进一步,将这些Saga模式从应用程序层抽象到单独的服务中。做好这一点可以将组织中的软件成熟度提前几年实现。
2.避免临界点
在这篇文章的前半部分,我们谈到了在应用程序层协调事务和保存状态是多么繁重。现在,我们将讨论软件项目是如何偏离正轨,以及你可以对此做些什么。
任何规模合理的软件工程项目都需要持久的执行。
理想情况下,创建新软件功能所涉及的成本和时间应该是一致的并且是可计算的。为持久性编写代码破坏了这种一致性。它使开发所涉及的努力看起来更像曲棍球棒曲线,而不是像线性斜率那样。
临界点是花在编码新功能上的时间和精力开始激增的地方。这是管理长事务真正变得清晰起来的时候。我将描述它是什么,为什么会发生,以及为什么匆忙编写管道代码不是处理它的正确方法。
是什么触发了临界点
在临界点之前的生活通常是好的,因为开发人员的体验是线性的。开发人员正在使用的应用程序框架支持开发人员添加的每一个新特性,没有令人讨厌的意外。这使得开发团队能够通过可预测的新功能实现时间来扩展应用程序。
只要开发者做出定量的改变,添加更多相同的内容,这种线性规模就能发挥作用。当某人必须进行与其他更改不同的更改并发现应用程序框架中的缺陷时,事情往往会发生破坏。这通常是一个质变,要求改变应用程序的工作方式。
此更改可能涉及对多个数据库的调用,或首次依赖于多个相关事务。它可能调用一个流程,该流程需要不可预知的时间来给出结果。
这种变化可能不足以在一开始就达到临界点,但开发人员的生活将开始改变。他们可能会编写管道代码来管理进程间通信,以保证执行并保持事务的一致性。但这仅仅是个开始。编写这些代码需要时间,现在,开发人员必须对其进行扩展,以应对他们引入的每一个新的质变。
他们还会继续这样做一段时间,但情况会越来越糟。最终,随着添加更多事务,开发人员将花费更多时间维护管道代码。线性开发工作负载现在变成了指数级。随着每一个新的变化,花在开发上的时间不成比例地增加。
“末日会议”
有些人直到它发生时才意识到临界点。没有经验的初级开发人员经常不知不觉地走了进去。资深开发人员往往处于最糟糕的境地,他们知道临界点即将到来,但日常的事务往往使他们无能为力,只能等待并收拾残局。
最终,有人提出了一个改变,使问题浮出水面。这是压垮骆驼的最后一根稻草。也许某个变更打破了软件交付计划,有影响力的人会抱怨。因此,有人称之为“末日会议”。
在这次会议上,团队承认他们目前的方法是不可持续的。应用程序变得如此复杂,以至于这些特别的管道更改不再支持项目进度或预算。
这一认识让开发者经历了悲伤的五个阶段:
- 否认。这种情况会持续一段时间。人们试图忽略这个问题,认为继续这样就好了。这需让位于……
- 愤怒。有人在会议上解释说这是不行的。他们的预算被打破了;他们的时间表被打乱了;这个问题需要解决。他们不接受否定的答复。所以人们试着…
- 讨价还价。人们会想出创造性的方法,用更特别的改变来支撑事情更长时间。但最终,他们意识到这是不可扩展的,导致……
- 抑郁。最后,开发人员意识到他们将不得不进行更基本的架构更改。他们的临时管道规范已经自生自灭了。这与……密切相关。
- 接受。每个人都带着一种厄运的感觉离开会议,知道在这之后不会有什么好结果。是时候取消几个周末,开始工作了。
末日的感觉是有道理的。正如我所解释的,管道代码很难编写和维护。从临界点开始,随着开发人员发现代码更难编写和维护,事情变得更加困难。突然之间,他们所习惯的线性编程经验消失了。他们花更多的时间编写事务管理代码,而不是在看板上使用软件功能。这将导致开发者精疲力竭,并最终导致人员流失。
防止临界点
我们如何避免这个临界点,就像使曲棍球杆曲线变得平滑,并保持软件功能和开发时间之间的线性比例?第一个建议通常是接受这次的失败,并保证下次从头开始编写管道代码,或者重用已经拼凑起来的代码。
这行不通。这给我们留下了同样的问题,即管道代码最终将变得无法管理。与其说这是一个临界点,发展会更早地失去线性。你会从项目开始就逐渐陷入开发焦虑。
相反,开发团队需要做的是它一开始就应该做的事情:进行重大的架构变更,以支持系统地持久执行。
我们已经讨论了抽象作为前进的方向。在编写更多的项目代码之前,首先将管道功能从应用程序层抽象到它们自己的服务层。这将通过消除非线性工作来减轻开发人员的负担,使他们能够扩展并保持实现新功能所需的时间不变。
这种抽象维护了程序员的线性体验。他们总觉得自己能掌控自己的时间,而且确信自己能把事情做好。他们将不再需要考虑缓存和排队等任务的战略决策。他们也不必担心将庞大的软件工具和库组合在一起来管理这些任务。
拥有抽象的事务工作流集的项目经理将和开发人员一样高兴。确定性和可预测性是它们的关键要求,这使得打破线性发展的临界点尤其成问题。抽象事务排序的任务消除了意想不到的开发人员工作量,并保持了线性,为他们提供了满足调度和预算承诺所需的确定性。
支持这种抽象和将管道代码转换为可管理工作流的工具将帮助您保持可预测的软件开发实践,消除可怕的临界点,并为您节省项目补救的压力。部署这些抽象服务的最佳时间是在项目开始之前,但是即使你的团队现在处于危机之中,它也提供了一种摆脱困境的方法。
译者介绍
涂承烨,51CTO社区编辑,省政府采购专家、省综合性评标专家、公 E 采招标采购专家,获得信息系统项目管理师、信息系统监理师、PMP,CSPM-2等认证,拥有15年以上的开发、项目管理、咨询设计等经验。对项目管理、前后端开发、微服务、架构设计、物联网、大数据、咨询设计等较为关注。
原文标题:Saga Without the Headaches,作者:Dominik Tornow