翻译|崔浩策划|YunZhao分布式系统设计是一个难题,难点在于设计过程中缺乏直接的反馈。往往一些问题是设计出来的,比如可扩展性问题、弹性问题、数据问题。然而,通常的解决方案是治标不治本,而不是永久解决方案——系统只是打补丁以保持其运行,但潜在的设计问题仍然存在,并可能在不同情况下再次爆发。当系统在生产环境中出现故障时,需要花费更多的精力和大量的组织辩论来分析与设计相关的根本原因。与分布式系统代码审查一样,本文提供了一个简单的清单,列出了在审查分布式系统功能(多个系统协同工作)的设计时需要注意的事项。本文从三个角度考虑分布式设计问题:一致性和可用性、领域耦合和可观察性。前两者经常相互渗透,因为分布式系统就像一个复杂的网格,其中每个设计选择都会影响其他多个事物。每个方面都可以作为一个更大的主题进行讨论,因此以下指南代表了任何设计审查的底线。在根据问题用例的上下文检查了这些基础知识之后,是时候深入研究特定的研究方向了。相反,如果在检查中发现问题,则需要格外小心。一致性或可用性需要提前声明,本文中的“系统”指的是一组独立的系统,多个这样的“系统”以不同的方式相互协作,为用户提供最终的服务。一致性和可用性适用于多个协作系统。CPA定理告诉我们,可以根据系统的一致性、可用性和分区容忍度来选择其中的任意两个。如果分区容错是生活的一部分,那是不可避免的。那么CAP定理的真正选择将落在一致性和可用性之间——即选择“AP”系统(分区容错下的可用性)还是“CP”系统(分区容错下的一致性)。软件架构的一个基本规则是所有软件都会失败。假设设计正常运行需要三个组件之间的一致性。这种做法使功能变得脆弱,因为如果任何一个组件都可能发生故障,那么功能将无法正常工作。此时我们需要面对“单点故障”问题——我们实际上有三个组件可能会失败!!!当我们努力保持整个系统的一致性时,它会更容易因最轻微的影响而失败。保持同步的组件越多,这种情况就越糟糕。幸运的是,这个问题有一个解决方案。在单个系统中,CP和AP是二元的选择(比如MySQL一致,Cassandra不一致),即非此即彼的关系。但在分布式系统中,情况并非如此。它们不是非黑即白,而是存在灰色地带。每个组件可能是一致的(订单、库存和付款),但整个系统可以设计为最终一致。这种做法也为我们提升系统的易用性留下了空间。因此,我们的指导方针是针对整个系统的可用性进行设计,这样即使个别子系统不可用,也可以随着时间的推移实现整个系统的可用性。使用异步消息进行通信是帮助我们消除一致性压力的有力武器。异步消息通信的引入有利于将可用性和特性作为ReactiveManifesto的主要标准。考虑在组件之间启用异步通信(通过消息代理传递消息),而不是直接使用请求-响应API调用。如果同步通信是硅谷的可卡因,那么同步API调用就是分布式系统设计的可卡因。大家可以想一想——如果两个系统不必保持一致,我们为什么要通过同步立即完成调用呢?(根据同步通信模型的要求)。请求-响应模型在调用者和被调用者之间创建了一种时间耦合形式(“现在服务这个请求!”),如果后者变得不可用,导致调用者的调用失败,从而将失败级联给调用者。异步通信允许被调用系统按照自己的节奏处理请求,减少可用性压力。虽然异步消息传递是一个强大的工具,但在采用它时必须牢记一些事项。定义最低可接受的用户体验-对于每个最终用户体验,定义最低的一致体验。例如,如果用户在网络游戏中获胜,是否必须全部或全部记入奖励积分,奖励他在排行榜上的新位置,通知他所有的朋友,并向他发送通知?很明显,我们越能在核心、一致的体验之外做更多的事情,我们遇到系统故障的可能性就越小。在讨论需求时,必须对此毫不留情,并支持它所要求的最低限度——其他一切都应该异步完成。通过这个例子,我们可以根据网络游戏的各种用户相关链接进行拆解设计。奖励积分不一定在游戏结束时给出,而是可以在游戏过程中异步更新。同理:排行榜和发送通知的功能也可以异步执行。这些东西在需求设计阶段就需要定义好,只要把满足用户最低要求的游戏体验拆解异步就好了。保证最终一致性:无论是单个用户操作还是客户端请求,都可以跨多个组件修改数据,因此需要设计保证所有系统在指定时间对用户请求达成共识——即使通过分布式回滚的方式.系统地保证SLA的一致性:这是非常有价值的,可能有一个最终一致性的计划,但如果没有设置和执行设定时间范围的机制,就不可能从缓慢的处理中检测到故障。由于我们无法确定事件是否在处理过程中引发错误或消息在网络中丢失,因此需要通过显式时间硬绑定来维护最终一致性保证。域耦合好的分布式系统设计在正确的抽象层次上分解不同的事物。拆分后的分界线称为领域边界,使用独特的通信语言和领域特定的功能接口来标识领域边界。例如,消息代理域封装了消息、传递保证、存储介质等信息。支付域封装了交易和支付网关等信息。虽然这里会提到微服务中限界上下文的概念,但这个概念的适用范围很广。例如,支付可以是一个域,其中支付网关和交易可以是支付域的子域。根据分布式系统架构的域设计理念,主要思想是在父域级别建立内聚的同时,在同一级别解耦域。例如,在上面的例子中,支付网关和交易试图尽可能地相互分离,但作为同一个域的一部分应该需要一致的术语和数据模型。创建域边界:随着微服务采用率的急剧上升,通过将组件与底层技术分离来识别组件的域变得更加重要。识别哪些组件属于哪个域以及外部系统如何与这些系统通信也很重要。我们可能会使用多种服务来处理货物(跟踪服务、扫描服务、审计服务等),但外部用户应该使用聚合的“物流”域实体和API来访问域中的功能组件。构建域边界的最佳工具是API网关,它通过域本身抽象出更高级别API背后的内部细节。使用标准领域语言在系统之间进行通信:两个组件之间的通信应该通过:现有实体+实体的状态+实体可能执行的操作来完成,不要创建一些不属于两个域的构造并让它们进行通信。这可以使用通信双方的构造来实现,具体取决于我们需要事件还是消息。如果你发现两个组件之间的通信需要创建一些特殊的语言,那么这意味着组件的拆分方式有问题,或者你需要通过一个中间组件,它存在于这两个组件之间,并使用中间组件的方式让两个组件进行通信。将多域/多组件操作分离到工作流中——域耦合发生的一种常见方式是当组件开始对多组件工作流进行端到端控制时。这意味着当前域了解各种其他域、它们的行为以及其边界外“工作流”的性质。这种意识将组件耦合到现有的工作流程,使它们难以扩展。如果一个功能需要调用多个组件,则该功能应该从对应的领域核心服务中分离出来,归类为有状态组件。该组件可以应用于专门的编排服务或某种BPM系统。有状态组件意味着可以从一个地方获得重试、错误报告、SLA等等。如果我们不能对所有事情都使用显式编排,使用编排构建工作流是一个可行的选择,但需要某种跟踪任务完成SLA的方法来减少长期工作流的脆弱性。跨组件建模业务流程:上述观点的必然结果是我们应该端到端地建模业务流程,而不管技术边界。由于我们通过将编排移动到工作流中来解耦域,因此在狭窄的团队边界内构建工作流没有意义。工作流应尽可能多地包含整个业务流程——这将建立一个业务知识的中央存储库,并提供对运营状态的可见性。事件优先与消息传递:解耦域更喜欢使用发布-订阅(发布-订阅)模型的事件功能,而不是目标消息。虽然这取决于许多其他因素,但它增强了对其他领域的不可知性,因为发布者不关心发布-订阅模型中的消费者,因此消除了发布者和消费者领域之间的耦合。可观察性用CharityMajors的话来说,可观察性可以用来回答关于系统的新问题,而无需窥视系统内部。我认为有两种类型的可观察性:技术和业务。我们应该能够解读系统的技术状态,我们可以通过业务指标来确定它的运行状态。使用事件数据构建指标:任何系统的基本工作单元都是事件,系统用自己的领域语言解释事件。事件可以是任何类型(例如ORDER_CREATED、REQUEST_RECEIVED、ERROR_RESPONSE_RETURNED)。一个有抱负的想法是,我们将整个系统信息建模,通过发送事件与外部系统进行通信,同时保存和分析事件以获取更多信息,而不是通过常见的日志跟踪方式来获取信息。拥有原始事件数据的好处是它可以与领域建模方法同步,并且可以随时使用原始数据并获得新的指标。这比浏览非结构化文本日志、跨度、跟踪和任意Prometheus/Statsd类型指标的组合要好得多。将原始数据存储在一个中央位置:遵守上面给出的可观察性定义的唯一方法是存储来自系统的原始数据,因为当我们开始只处理预定义的指标时,我们会被它们锁定并失去答案”解决新问题的能力。反对使用原始数据的一个常见论点是需要更多的容量来存储它们,但是可以采取一些方法来缓解容量问题(例如:采样等)。所提供的诊断能力变得非常宝贵。在分布式系统中,将可观察性数据集中存储显然比分布式孤岛更好,这更有利于提高对整体系统的洞察力GeneralGuidelinesImplementusinganasynchronousframework-我之前写过关于如何扩展系统使用异步编程。因此,在调用远程系统时,应确保异步进行,以免阻塞应用程序线程。这需要考虑在实施/设计的早期,如果应用程序框架不允许这样做,或者应用程序的基础框架不是为此设计的,那么你就不走运了。如果异步是您的选择,请始终接受它——当出现意外的流量高峰时,您的系统和您的团队会感谢您。了解你的来电者——根据定义,没有人对分布式系统负责。因此,我们应该像对待外部系统一样,对内部系统采取尽可能多的预防措施。如果可以的话,至少要强制执行速率限制,同时注意你的呼叫者。如果出现问题,这将有助于隔离问题的根源-到系统着火时,使用IP地址来识别来源为时已晚。知道什么时候失败——我已经谈了很多关于如何使系统在不同条件下可用的问题。但在某些情况下,失败总比做错事好。在许多情况下,一致性是完全可以接受的选择,我们应该注意不要过度补偿它们。译者介绍崔浩,社区编辑,资深架构师。他拥有18年的软件开发和架构经验,以及10年的分布式架构经验。他曾经是惠普的技术专家。乐于分享,撰写了多篇阅读量超过60万的热门技术文章。《分布式架构原理与实践》作者。
