本地读写的多活数据存储架构设计要义

本文由公众号EAWorld翻译发表,转载需注明出处。数据库

 

做者:Parashar Borkotoky 安全

译者:白小白 网络

原文:http://t.cn/AiKO0q4P架构

原题:Design Considerations in a read local write local multi-master data storeapp

 

本地读写多活示例异步

 

本地读-本地写的多活数据存储架构是最难实现的数据模式之一。这一模式也常被称为“双活”或者“多主”,对于不一样行业大容量低延迟的事务类应用而言,这是一种必备的能力。分布式

 

系统的总体可用性取决于单独组件的可用性。用户界面层、服务层和消息层能够跨分区/跨地域进行横向扩展以提供高可用性,但对于事务性数据存储(尤为是写操做)而言,采用一样的处理方案仍旧充满挑战。不管部署在本地或者云端,不少关系型数据库和noSQL数据库都提供了这种开箱即用的能力。也有一些企业选择本身实现定制化的复制方案来达成多活的目标,对于强烈依赖低延迟的用户尤为如此。忽略方案的差别性,人们须要对一些通用的风险与权衡进行仔细考量。ide

 

同步复制与异步复制微服务

 

首当其冲须要考虑的就是在跨可用域的数据复制过程当中,是采用同步复制仍是异步复制的方案。翻译

 

同步复制技术须要实现多可用域/可用区间的同步写入。这将会带来不少的问题:

 

  1. 随着可用域的增长,写入延迟也将逐渐增长

  2. 域内的服务须要同步的访问域外资源,这一般会令域的回收和转移变得更加复杂

  3. 故障检测和恢复会变得愈来愈复杂。本地域的数据存储写入成功,对其余域的数据存储写入失败,这种状况该怎么处理?其余域的数据存储的不可用,是否应该影响本地域的服务可用性?

 

鉴于以上的这些因素,异步复制一般是首选方案,从而也引入了最终一致性的话题。

 

拥抱最终一致性

 

CAP定理

 

CAP定理指出,对于一个分布式的数据库系统而言,一致性(C)、可用性(A)和分区容忍性(P)三者仅能居其二。由于分区容忍性是现代分布式系统的必备要件,这将归结为在一致性和可用性之间取得一个平衡。PACELC定理进一步扩展了CAP的理念,并指出,即便不考虑分区的状况,仍旧须要在延迟(L)和一致性(C)之间进行必定的权衡。

 

译注:PACELC中的E即Else,也就是或者。对于有分区的状况,须要权衡AC,或者,在没分区的状况,须要权衡LC

 

在大量的用例中,最终一致性是可接受的妥协。好比审计或者听从性日志、产品目录或者搜索索引,对于此类的应用而言,数据存储的最终一致性是彻底可接受的。但对于其余一些场景,好比银行事务、航班预订或者股票市场而言,即便是毫秒级的不一致性,也将损害用户体验或者系统可信性。

 

在这样的状况下,值得评估一下多活的数据存储方案是否符合用户场景的须要。

 

  1. 本地读取-全局写入的方式提供了可用性和一致性之间的平衡,是一种可选的方案。在对某个可用域的主副本数据存储进行写入操做的同时,会在其余可用域生成只读副本。当主副本数据发生单点失败问题的时候,能够在其余可用域中选择一个新的主副本,从而实现快速的故障恢复,这种方式所提供的可用性,对于不少场景来讲是可接受的。

  2. 另外一种方式是分片写入或者分区写入,这将使得可用域中某一份单独的数据存储成为一部分数据的主副本。

 

一旦决定采用多活的数据存储方案,而且接纳了最终一致性的理念,接下来须要重点考虑的就是采用合适的技术,以缓解和减轻一致性问题所产生的影响。

 

采用事件流进行复制

 

在多活架构下,数据的异步复制一般采用事件流的方式实现。好处以下:

 

  1. 写入操做的顺序将得以保留。这对不少用户场景来讲是必须的。

  2. 对于写入失败或者存储不可用的状况,事件复制器将持续的尝试对副本数据的写入操做直到成功,以保证故障能够被恢复。

 

这一方案的挑战在于,如何让事件复制器处于高可用的状态。这须要在顺序复制和并行复制之间作出设计上的权衡。

 

写入前的业务验证

 

在数据复制的过程当中,复制器没有办法知道写入的发起者是谁,但写入自己可能存在不一致性或者错误的参数。为了不写入的过程对业务逻辑形成不可挽回的错误(尤为是在事件的顺序相当重要的场合),复制过程将被阻塞,直到当前的事件已经成功处理,才会继续进行下一项操做。

 

所以,在写入前进行足够的业务验证是十分必要的,这将避免状况变得难以收拾。一些与网络或者与系统的不可用有关的问题,都是可恢复的,复制器能够用重试的方式对此类状况进行处理。

 

更新操做带来的问题

 

考虑以下的业务场景:顾客在创建订单后进行了更新或者取消操做。

 

  • 步骤1:生成订单 

  • 步骤2:更新订单

 

在这种状况下,对于第2步操做来讲,应用一般须要获取订单的信息(读),而且更新订单的状态(写)。这是全部订单系统的通用场景,如订票、生产制造、贸易或者零售。

 

在最终一致性的架构下,若是步骤1和步骤2分属于不一样的可用域,这一般会引起一些问题。好比,当步骤1没能及时的复制到步骤2所在的可用域的时候,步骤2的更新操做就可能失败。

 

更新操做带来的问题示意

 

以上的场景带来的不仅是用户体验的不理想,更重要的是分引起数据不一致的问题:一种状况是,步骤2的更新的操做可能基于一份过期的数据,甚至步骤1的复制操做覆盖了步骤2的更新操做。

 

事件与状态

 

在上面的例子中,考虑了两个事件:订单生成和订单更新。假定接下来又发生了两个事件:订单更新和订单取消。

 

大多数的数据存储方案会将全部这些事件存储在一个历史实体、审计实体或者细节实体中,用以表征单独的事件。咱们称之为“事件实体”。

 

在不少状况下,订单的当前状态也会被记录,如“已取消”。咱们称之为“状态实体”。

 

对于每个新的事件而言,有两个必不可少的操做,对事件实体的插入操做,和对状态实体的更新操做。

 

订单事件示意

 

没有状态实体,咱们就须要去汇总全部的事件或者获取最新的事件来获知订单的状态。

 

在有些状况下,数据存储仅支持插入而不支持更新。这样就只有事件实体而没有状态实体。这是否有助于补救更新操做所产生的问题呢?彻底不会。尤为是在上文提到的读取过期数据的情形下。固然,当复制器之间或者服务写入之间发生冲突的时候,这确实有助于确保数据不会被覆盖。

 

在此情形下,写入操做的“只插”策略,或者事件溯源的设计方法将很实用。

 

粘滞会话

 

在上文所提到的更新问题中,插入、读取和更新操做分属于不一样的可用域,但却共享一个相同的上下文(如订单ID),只有在这种状况才会发生问题。虽然在理想状况下,服务请求应该是无状态的,但若是与某一个上下文(如用户或者订单)有关的状态能够用Session、Cookie或规则的方式保存在流量管理器中,就能够确保在多数状况下,与某一个特定的上下文有关的一组事件能够被路由到一个共同的可用域。这种程度的会话粘滞或者会话亲和性能够极大的改善数据过期的问题。

 

冲突解决方案

 

做为预防性措施,业务验证、会话粘滞或者事件溯源能够在很大程度上缓解相关的风险,让本地读写多活方案的实现更加健壮。然而,冲突是不可避免的。对于一些场景,尤为是高频的副本复制的场景下,须要认真考虑冲突的解决方案。

 

一些冲突解决方案包括:

 

  1. 基于时间戳的解决方案:好比,以最新写入的为准

  2. 基于规则的解决方案:好比,在进行副本复制的过程当中,若是符合某种特定条件,则忽略事件的写入。

 

虽然多数冲突解决方案能够自动执行,对于一些特定的场景,咱们仍旧须要创建一个可视化的用户界面来手动的解决冲突问题。

 

结语

 

跨可用域的本地读写的多活实现是一项复杂的的任务,一般须要在应用的数据层之外解决不少问题。这是一种很难实现和治理的模型,仅在低延迟和高可用性不可或缺的场景下才须要考虑。仔细的分析和规划相关的设计考量、妥协和治理要素,将有助于达成最优的解决方案。同时,也须要考虑采用节流方法来理解延迟和可用性之间的平衡。

 

译注:节流方法(throttled approach):相似机场或者地铁的安检方式,当流量较大的时候,一部分人会被滞留在一个等待区内,直到前面的一批已经安全顺利经过。

 

关于EAWorld:微服务,DevOps,数据治理,移动架构原创技术分享,长按二维码关注

相关文章
相关标签/搜索