在技术环境里,系统很难做到万无一失,失败时有发生。基于 Cell 的架构是颇具潜力的解决方案,凭借独特优势能够有效地接受各类失败情况,将故障进行隔离处理,确保整个系统依然可以稳定地运行下去。
然而,该架构的设计与实现涉及诸多复杂环节与因素,带来不少难题。鉴于此,本文应运而生。我们将深入剖析组织在采用基于 Cell 的架构时,能够助力其取得成功的一系列最佳实践方法,同时也会详细探讨在此过程中可能会遇到的各类问题,并为大家呈上一份详尽的采用指南,旨在帮助组织更加顺畅、高效地运用这一架构,使其在应对系统故障挑战时能够游刃有余,实现系统的稳定可靠运行以及业务的持续发展。
基于单元架构的最佳实践要点
组织采用基于单元的架构时,为提升系统可管理性与弹性,有如下最佳实践需考量:
▏考虑用例
此架构构建及运行较复杂、成本高,并非所有系统都需其高规格。要斟酌使用案例与额外投资是否值得,其适用于:需高可用性、大规模扩展且避级联故障、RTO 极低、自动化测试难覆全用例的系统。
此外,还需考虑系统大小,不同组织的 Cell 构成有别。有的架构模式表现为每个 Cell 等同于一个完整的堆栈,也就是说每个服务都会被部署在各个 Cell 里,而且这些 Cell 之间不存在相互通信的情况,像 DoorDash、Slack 便是如此;有的是每个 Cell 都有着自身明确界定的业务上下文,整个系统是由多个能够相互通信的 Cell 共同构成的,比如 WSO2 以及 Uber 的 DOMA 项目。这种由多个相互通信的 Cell 组成系统的模式在灵活性方面表现更为突出,但其复杂程度也明显更高。
▏明确 Cell 所有权
若多单元层相互通信,理想状况下各单元应由一个团队负责,该团队能构建单元功能并交付生产。可依 “团队大小” 设单元边界,借助域驱动设计、事件风暴等技术明确边界,便于确立所有权及依业务发展系统。
▏分离cell
单元要尽量彼此隔离,以缩小爆炸半径,保障可靠性与安全性。但现实中完全隔离不易,共享资源时更要谨慎,因这会大幅削减单元架构优势。
在 AWS 上,为各单元设单独账户是确保隔离的好办法。虽账户管理有难度,但默认可提供出色的 Blast Radius 保护,因跨账户访问数据和资源需明确授权。
同时,需权衡单个单元的部署方式,考虑是将其置于单个可用区,还是在多个可用区复制其服务来利用物理隔离优势。要结合实际情况综合考量,做出合适决策,从而在保障系统性能、安全等方面达到较好效果。
▏单可用区
在单个可用区设计中,每个 cell 在单个可用区中运行:
优点:
当出现 AZ(可用区)故障时,能够及时检测到该故障情况,并且可以迅速采取相应的处理措施。比如,可将所有相关请求灵活地路由至其他区域。
缺点:
一方面,倘若发生故障需要进行恢复操作,由于必须把单元内的相关内容物复制到另外一个 AZ当中,这一过程可能会使得恢复工作变得颇为复杂,甚至有可能对单元设计原本所具备的分离特性造成破坏,影响到整体架构的稳定性;
另一方面,依据路由器的具体设计情况,客户端有可能需要对特定于区域的终端节点有清晰的了解,这就增加了客户端的使用复杂性以及对特定配置的依赖程度。
▏多个可用区
在多可用区设计中,每个 cell 跨两个或多个可用区运行:
优点:
当某个单个区域出现故障时,借助如 Amazon DynamoDB 这类区域云资源,能够让 Cell 展现出更强的弹性,有效保障系统的持续稳定运行,降低故障对整体业务的影响。
缺点
当服务仅在其中一个可用区遭遇问题时,可能会引发灰色故障现象,导致在尝试从特定单元中仅仅排除出现问题的这个可用区时,操作难度较大。
还可能会产生额外的跨可用区数据传输费用,增加运营成本。值(得一提的是,DoorDash 通过运用监控手段以及具备可用区感知路由的服务网格,尽可能地将流量维持在同一可用区内,以此实现了成本的优化,为应对此类问题提供了一种可行的参考范例。)
▏Cell 故障转移
在单可用区设计中,如果可用区(AZ)出现故障无法使用了,那会怎样呢?受影响的用户请求又会被路由到哪里去呢?
一种应对方式是完全不进行故障转移处理:单元在设计之初就是为了隔离故障的。在受影响的单元重新投入使用之前,必须先排除故障才行。
另一种选择是采用灾难恢复策略,将单元数据复制到位于不同可用区的另一个单元中,然后开始把请求路由至这个新单元。这里存在的风险在于,数据复制操作可能会削弱单元之间的隔离性,而复制过程将取决于数据需求以及底层的数据存储情况。
▏自动化部署
与微服务类似,若要大规模地运行单元,就必须具备在短短几小时甚至几分钟内完成部署的能力,而非耗时数天之久。快速部署需要一种标准化、自动化的单元管理方式,这一点至关重要,并且还依赖于在相关工具、监控机制以及流程等方面的投入情况。
标准化并非要求每个团队都统一使用相同的编程语言、数据库或者技术手段。但无论如何,应当建立起一种通俗易懂的标准方法,以便能将应用程序顺利地打包,并部署到新的或者已有的单元当中。
从理想的角度来看,配置与部署管道需要赋予团队多项功能:
1、创建新的单元格;
2、可对单元格的健康状况进行实时监控;
3、方便为单元格部署更新后的代码;
4、能够监测部署工作的具体状态;
5、实现对单元格进行限制以及灵活缩放的操作。
这样的部署管道能够降低平台用户所面临的复杂性,减轻其认知负担。至于它具体呈现出何种模样、如何运作,在很大程度上会受到组织规模大小以及所采用的技术堆栈情况的影响。
▏使路由可靠
单元上方的路由器可以说是系统中最关键的部分:一旦缺少它,系统的其他所有部分都将无法正常运转,而且路由器本身还存在成为单点故障的隐患,所以务必要尽可能将其设计得简洁明了。为此,以下几个要点值得重点考虑:
技术选择,有 DNS、API Gateway、自定义服务可选,各有优缺点,像 DNS 就涉及管理生存时间的问题,得权衡选用。
利用高可用服务,若路由器要存客户 Cell 信息,选 SLA 高的 S3或 DynamoDB,别用单个 MySQL 实例,可提升稳定性。
分开控制平面和数据平面,比如把客户单元放 S3,路由器从桶里找数据,用单独控制平面管桶内容,这样控制平面故障不影响路由。
考虑身份验证位置,在路由器做能简化下游服务,但故障时影响范围大;在单元格做会增加其复杂性和重复操作。
路由器得掌握 Cell 位置和运行状况,以便故障或耗尽时把请求路由出去,保障服务连续。
▏限制单元通讯
当存在多个 cell 层相互通信时,务必要借助定义清晰明确的 API 来实现通信。这样的 API 有着重要作用,它既能对 cell 的逻辑进行有效封装,又可以让 cell 内的服务在发展过程中,不会过度破坏 API 协定。至于这个 API 由谁来直接对外公开,要依据复杂性要求来定,有可能是 Cell 中的服务,也可能是位于 Cell 边缘的网关。
要极力避免 Cell 之间出现那种无意义的闲聊式通信。对单元之间的依赖关系加以限制,这对于保持它们的故障隔离状态以及规避级联故障是很有帮助的。
或许你会考虑运用内部层来对单元之间的流量进行编排,像服务网格、API 网关或者自定义路由器都是可选择的方式。不过在此过程中,一定要格外留意,要确保所使用的任何一种方式都不会成为单点故障。只要消息传递层具备可靠的性能,那么异步消息传递这种方式说不定也能派上用场,助力通信的顺利开展。
▏利用高可用性云服务
就像在路由相关内容中提到的那样,如今有许多云服务都已经构建起了高可用性(通常是借助EBS 和 Azure AD 等单元来实现的)。这些现成的高可用性云服务能够为我们提供便利,简化我们在技术选择方面的决策过程,让我们无需再去重复摸索,做一些重复 “造轮子” 的工作。
同时,还需要认真考量云服务的服务水平协议(SLA),不管这些云服务是面向全球的、区域级别的,还是限定在某个特定区域的,都要分析清楚一旦某个给定的云服务出现故障,将会给整个系统的性能带来怎样的影响,以便提前做好应对措施,保障系统稳定运行。
基于Cell 的架构的潜在问题
▏获得支持
基于 Cell 的架构在构建与运行方面往往较为复杂,而且成本也相对偏高,这和众多技术项目类似,若想取得成功,离不开组织层面的支持。
对于管理层而言,将关注点聚焦于业务影响会颇有益处,比如它能够提升团队部署新代码的速度,让团队更有底气去进行部署操作,同时还可增强系统的可用性,进而收获满意的客户以及塑造更好的企业声誉。
不仅如此,该架构还需要架构团队、DevOps 团队以及开发团队在多方面提供支持,并投入相应资源,如此才能打造出具备充足隔离性、完善监控机制以及高度自动化的单元。所以,务必尽早让这些团队参与到项目当中,使其能够在整个过程中发挥指导作用,助力架构的顺利落地与运行。
▏避免在单元格之间共享
在单元格之间共享诸如数据库这类资源,乍一看似乎是一种既能降低复杂性,又可削减成本的不错办法。然而,这么做却会削弱单元之间原本应有的隔离效果,导致一旦某个单元出现故障,其他单元受其影响的可能性就会大大增加。
这里的关键在于,要思考如果共享资源出现故障,究竟会波及多少个单元。要是受影响的单元数量众多,那就意味着出现了问题,基于单元的架构所具备的优势也就难以充分展现出来了。虽然共享数据库在向基于单元的架构迁移过程中,可能会是一个有用的过渡步骤,但绝不能长时间维持共享状态,而是应当提前制定好拆分数据库的详细计划,以保障架构的稳定性和独立性。
▏避免创建过于复杂的路由器
路由器存在成为单点故障的风险,而且随着其功能复杂性的不断提升,遭遇故障的可能性也会相应增加。有时候,为了简化蜂窝服务而给路由器添加功能确实颇具吸引力,但在做每一个相关决策时,都必须慎重权衡这对整个系统可靠性所带来的影响。
为此,应当执行一些故障模式分析工作,以便精准地识别出路由器中存在的故障点,并尽可能地减少这些故障隐患。例如,当路由器需要从数据库里查找单元映射信息时,在启动路由器时就把相关数据库存储在内存当中,相较于每次请求都去访问数据库获取数据的方式,前者或许速度更快,可靠性也更高。
▏重视 Cell 之间的复制和迁移
在项目起始阶段,很容易将单元迁移当作一项可有可无的高阶功能而选择忽略它,但实际上,它对于基于单元架构的成功起着至关重要的作用。
想象一下,如果某个 cell 出现故障或者负载过重的情况(比如有两个大客户最终都集中使用了同一个 cell),这时就需要将部分客户迁移到其他的 cell 中去。而在实际操作时,具体该如何进行迁移,很大程度上会受到路由以及数据分区情况的影响。不过,大致的思路主要包含以下几个关键步骤:
确定客户将要迁移到的目标单元,这个目标单元可以是那些尚有容量的现有单元,也可以是新创建出来的单元。
把旧单元格数据库里相关的必要数据,完整地复制到目标单元格对应的数据库当中,确保数据的连贯性与可用性。
对路由器的配置进行相应更新,通过这样的操作使得目标单元能够对相关客户处于可正常使用的活跃状态,保障客户请求能够顺利被处理。
除此之外,还必须与路由层进行深度集成,只有这样才能保证在恰当的时间,把客户的请求准确无误地路由到正确的 cell 里,维持系统服务的顺畅性。
单元故障有可能会触发复制行为,或者也可以提前将单元进行复制,让另一个单元时刻处于准备就绪的状态,以便随时应对突发情况。而这种复制操作所产生的具体效果,则取决于单元自身的数据架构以及恢复点目标(RPO)和恢复时间目标(RTO)等方面的要求。在具体实现复制的方式上,像数据库级别的复制、消息收发机制以及利用 S3 等都是可供选择的有效选项,可根据实际情况灵活选用。
▏避免对云资源的限制
在基于单元的系统架构中,倘若每个单元对云资源的消耗量较大,那就很有可能会触碰到云服务提供商所设定的软限制或者硬限制。对于软限制而言,用户可以向提供商提出提高额度的请求,然而硬限制通常是由服务本身的特性或者硬件条件所决定的,往往是固定不变的,很难进行调整。
在 AWS 平台上,可以通过为每个单元格使用单独的账户来避免许多限制。
▏平衡逻辑和数据重复
在构建系统时,需要在尽力维持 cell 的隔离性与避免服务之间出现逻辑及数据重复这两方面进行权衡考量,这一点和微服务以及DRY原则所面临的权衡情况是类似的。
随着系统规模不断扩大,为了更好地避免服务之间形成紧密耦合的关系,我们可以采取在不同单元中的服务之间进行代码复制的做法,甚至在合理的情形下,可以对数据也进行复制操作。不过,关于这个问题,并没有一个放之四海而皆准的绝对正确或者错误的答案,而是应当依据具体的实际情况来进行综合评估。通过执行故障模式分析,能够协助我们判断出单元之间的依赖关系在什么时候可能会引发问题,以及在何种情况下应当借助重复操作来消除这些潜在的问题,从而让系统在整体架构的合理性、稳定性以及运行效率等方面达到一个相对较优的平衡状态。
采用指南
已经确定基于单元的架构很合适了,接下来怎么办?
▏迁移
引用 Martin Fowler 的话:如果你做一个大爆炸式的重写,你唯一确定的就是大爆炸。
将现有微服务架构迁移到基于 Cell 的架构可能很棘手。常见的第一步是将第一个 cell 定义为现有系统,并在顶部放置路由器,然后将服务剥离到新的 cell 中;同样,可能会发生从整体式架构到微服务的迁移。
组织可以使用许多整体式到微服务策略。例如:
使用域驱动设计(DDD) 定义边界上下文并帮助决定新单元格中的内容。
首先将服务逻辑迁移到单独的 cell 中,然后在后续阶段将共享数据拆分到特定于 cell 的数据库中。
在首先选择要拆分为单元格的内容时,请考虑哪些业务领域将从更大的弹性中受益。
确保足够的自动化和可观测性来管理新的、更复杂的系统。
▏部署
在基于单元的体系结构中,部署单元是一个单元。应首先将新的应用程序版本部署到单个单元中,以测试它们如何与系统的其余部分交互,同时将大范围损坏的风险降至最低。使用 Canary 或蓝/绿部署等技术进行增量更改,并在继续推出之前验证系统是否仍按预期运行(通常分批推出)。
如果新版本有问题,则应回滚更改,并暂停部署,直到进一步调查可以查明问题。
“烘焙时间”的概念对于确保新小区有足够的时间提供真实流量以进行监控以检测问题也至关重要。确切的时间会有所不同 - 几分钟或几小时,具体取决于系统的类型、风险偏好和复杂性。
▏可观察性
除了以正确的方式监控微服务之外,还应该有额外的单元监控和仪表板来查看以下各项的聚合和单元级视图:
单元格数。
单元健康。
部署波次的状态。
对单元格很重要的任何 SLO 指标。
其中许多可以从标准云指标派生,但可能需要其他标记标准才能获得单元格级视图。
由于基于单元的架构可能会增加云使用量,从而增加成本,因此必须跟踪资源使用情况和每个单元的成本。目标是允许团队提出诸如“我的单元成本是多少”、“如何更有效地利用资源”和“单元大小是否优化”等问题。
▏缩放
在基于 cell 的架构中,扩展单位是一个 cell:可以水平部署更多 cell 以响应负载。确切的扩展标准将取决于工作负载,但可能包括请求数量、资源使用情况、客户规模等。可以进行扩展的程度取决于单元的隔离程度 - 任何共享资源都会限制可扩展性。
架构还应注意了解信元的限制,并避免向其发送超出其资源可以处理的流量,例如,通过路由器或信元本身的负载卸载。
▏单元大小
确定每个单元格的大小是一个关键的权衡。许多较小的 cell 意味着更小的冲击半径,因为每个 cell 处理的用户请求较少。小型单元还可以更容易地进行测试和管理(例如,更快的部署时间)。
另一方面,更大的 cell 可以更好地利用可用容量,更容易将大客户放入单个 cell 中,并且由于单元较少,因此使整个系统更易于管理。
考虑一下是个好主意:
爆炸半径;
性能:一个 cell 可以容纳多少流量,它如何影响其性能;
现有 cell 需要开始处理来自故障 cell 的流量的 Headroom;
平衡分配的资源,以确保电池不会动力不足,无法处理其预期的负载,但不会动力过大且成本过高。
更小单元的优点是:
具有较小的冲击半径,因此任何故障都会影响较小比例的用户;
不太可能达到任何云提供商配额限制;
降低了测试新部署的风险,因为定位较小的用户集更容易;
每个单元的用户数减少意味着迁移和故障转移可以更快。
较大单元的优点是:
它们更易于操作和复制,因为它们的数量较少;
他们更有效地利用容量;
降低必须将大型用户拆分到多个 cell 中的风险。
正确的选择在很大程度上取决于正在构建的确切系统。许多组织从较大的单元开始,随着信心和工具的提高而转向较小的单元。
▏数据分区
与单元大小密切相关的是分区数据并决定应路由到哪些单元客户流量。许多因素可以通知分区方法,包括业务要求、数据属性的基数和单元格的最大大小。
如果请求可以拆分为不同的客户,则分区键可以是客户 ID。每个单元格都分配了一定比例的客户,以便同一单元格始终为同一客户提供服务。如果某些客户比其他客户大,则应注意没有单个客户大于单元格的最大大小。
其他选项包括地理区域、市场类型、循环或基于负载。
无论使用哪种方法,覆盖路由器并手动将客户放置在特定单元中以测试和隔离某些工作负载也可能是有益的。
▏映射
使用客户 ID 意味着路由器需要将客户映射到 cells。存储映射数据的最直接方法是将每个客户映射到单元格的表:
显着优势是它实施起来非常简单,并简化了在单元之间迁移客户的过程:只需更新数据库中的映射即可。
这种方法的缺点是它需要一个数据库,这可能是单点故障并导致性能问题。
其他方法是一致性哈希和将一系列键映射到单元格。然而,它们的灵活性都较差,因为它们存在热蜂窝的风险,这使得迁移更具挑战性。
▏衡量成功
理想情况下,组织应考虑采用基于单元的架构来实现特定的业务目标,例如通过提高技术平台的稳定性来提高客户满意度。
通过迁移,应该可以衡量实现这些目标的进展情况。通常,目标是面对失败时的弹性,其中一些量化指标很有用:
运行状况指标,包括错误率或正常运行时间(例如,当 EBS 移动到单元时,错误率急剧下降);
MTTR(平均修复时间);
性能指标请求处理时间,以查看额外的层是否对延迟产生不利影响。如果现在使用比以前的系统更小的单元为客户提供服务,性能可能会提高;
资源使用,以确保成本不会失控,并在必要时进行优化。
这些都意味着良好的可观察性来衡量性能、可靠性和成本。
★ 结 论 ★
基于 Cell 的架构实施有难度且复杂,但微服务开发者对其中不少良好实践较熟悉。这类规模的架构都需涵盖部署自动化、可观察性、扩展与故障恢复,Cell 架构也不例外,设计相关策略时要考虑这些因素。
关键决策围绕数据分区,其与请求流量分配、映射到 cells 紧密相关。简便方法虽易实现,却常缺大规模运行所需灵活性。
公有云提供商的高可用性服务可提升可靠性、简化设计,AWS 在 Cell 架构方面参与度高,分享了应用经验与实施建议。
组织要确保 Cell 架构适合自身,迁移时不能制造更多问题。可分步骤迁移现有系统,减少中断,验证更改效果。
当下构建分布式系统挑战增多,基于 Cell 的架构很宝贵,它能在面对故障时接受、隔离故障,保持系统可靠运行,为应对复杂情况、保障业务稳定提供有力支撑,值得各组织结合自身情况合理运用。
https://mp.weixin.qq.com/s/fbm5nddtVMLgCBtuEcPVEw