首页 > 数据库 > 数据库开发技术 > 异地多活设计4大技巧
根据地理位置的不同,异地多活架构可以分为三种类型:
同城异区
这种架构的核心是通过高速网络连接同城的两个数据中心,使其在网络性能上类似于单一机房。在设计上,这两个数据中心可以被视为一个本地机房,简化了设计过程,无需额外处理。
跨城异地
这种架构的挑战在于如何在数据不一致的情况下尽量减少对业务的影响。这种看似矛盾的目标是跨城异地架构设计的主要任务,旨在解决数据同步和业务连续性之间的冲突。
跨国异地
通常用于为不同地区的用户提供服务或支持只读类型的业务。这类架构的设计要求相对较低,主要是面向服务的地域性分布。
"异地多活" 目标是确保业务的高可用性,但设计时常见的误区是试图使所有业务模块都支持异地多活。
设想我们正在构建一个"用户子系统",涵盖了"注册"、"登录"和"用户信息"三个关键业务功能。为了处理大量用户,我们采用了"用户分区"的设计方法。在此架构中,通常情况下,用户数据被分配到一个主分区,并且每个分区会存有其他数据的备份。用户可以使用邮箱或手机号进行注册,路由层接收到邮箱或手机号后,将通过哈希运算确定用户数据应存储在哪个数据中心,再将请求转发至相应的业务中心。这一基本架构如下所示:

这样一个系统,如果 3 个业务要同时实现异地多活,会发现这些难以解决的问题:
1、注册问题
在注册业务中的异地多活实现中,我们遇到一个常见的问题。假设用户在A中心成功注册,但数据尚未同步至B中心时,A中心发生了故障。为了维持注册业务的多活性,我们可能会考虑让用户在B中心重新注册。这个解决方案初看之下似乎可行,但仔细分析会发现其中存在的问题:由于B中心未接收到A中心的数据,无法确认该手机号是否已被注册。若B中心允许重新注册,当A中心恢复后,将面临数据冲突的问题,即同一手机号关联多个账户,这违反了原有业务规则。
一个解决方案可能是修改业务规则,允许同一手机号注册多个账号。然而,这种做法实际上是不现实的。因为业务规则如“一个手机号只能注册一个账号”通常是核心的,改变这类核心业务规则将导致整个业务逻辑需要重构,这是一个代价极大的决定。简而言之,为了适应架构的需要而改变关键的业务规则是不经济的,因此在实际操作中往往不可取。
2.用户信息问题
在处理用户信息修改和注册的异地多活问题时,A和B两个中心可能同时对用户信息进行修改,这就会引发数据冲突的问题。
对于用户信息的修改,相比于账户注册,信息的关键性较低,可以采用基于时间戳的方法来解决冲突,即后修改的数据生效。但这种方法在实际操作中面临一个重要挑战:保证各个中心的时钟完全同步。在异地多中心的网络环境中,完全同步时间是非常困难的,即便有时间同步机制,也难以保证绝对的一致性。如果两个中心的时间误差超过一秒,就可能导致数据混乱,例如更早的修改反而覆盖了后来的修改。
另一种解决方案是使用全局唯一的递增ID,但这个方案的成本较高,因为全局唯一递增ID的生成系统本身也需要考虑异地多活和数据一致性的问题。
从上述分析来看,实现“注册”、“登录”和“用户信息”业务的完全异地多活是具有一定难度的,甚至有些问题可能没有解决方案。因此,在设计异地多活架构时,应优先保证核心业务的高可用。
以模拟案例为例,“登录”是最核心的业务。与“注册”和“用户信息”相比,登录的频率和业务影响更大。例如,在一个日活跃用户为1000万的平台上,注册用户可能每天只有几万,而修改用户信息的人数可能更少,但登录的用户却达到1000万。显然,保证登录业务的异地多活是最关键的。
对于新用户而言,暂时无法注册可能影响不大,因为他们还未开始使用服务。同样,用户信息的暂时无法修改也不会对他们产生太大影响。但是,如果数百万用户无法登录,将直接导致他们无法使用服务,这会对业务造成巨大影响。
实际上,实现登录的异地多活相对简单,因为每个中心都存有所有用户的账户和密码信息,用户可以在任何一个中心登录。即便用户在A中心修改了密码而数据尚未同步到B中心,他们在B中心可能会遇到登录问题,这就涉及到了其他的设计策略,这部分我们将在后续讨论。
技巧 2:保证核心数据最终一致性
异地多活架构的核心在于数据的异地冗余,以确保在极端情况下业务依然能正常运行。然而,很多架构师在设计时会陷入一种思维误区:追求所有数据的实时同步。
异地多活的一个基本问题是,由于物理限制(如光速限制和网络延迟),实时同步所有数据是理论上不可能达到的目标。这形成了一个基本矛盾:业务需要快速同步数据,而物理条件限制了这种快速同步的可能性。
面对这个无法完全解决的矛盾,我们可以采取以下几种策略来尽量减少其影响:
缩短数据中心之间的距离并搭建高速网络: 这类似于同城异区架构,尽管如此,跨城异地的高速网络建设成本远高 于同城,通常只有资金雄厚的大公司才能承担。
选择性数据同步: 只同步与核心业务直接相关的数据,不必要的或对业务影响不大的数据则不进行同步。例如,在用户子系统中,用户登录产生的token或session信息虽然数据量大,但并不需要同步到其他业务中心,因为一旦数据丢失,用户可以通过重新登录来重新获取。
保证数据的最终一致性而非实时一致性:基于BASE理论,业务不依赖于数据同步的实时性,只要求数据最终能保持一致。例如,用户在A机房注册后,不需要在极短时间内同步到所有机房,而是可以在一定时间(如5分钟)内完成同步。对于异常情况,甚至可以接受更长时间(如1小时或1天)达到数据一致。
在具体实施最终一致性时,需要针对不同数据特性采取不同处理策略。例如,对于新注册的账户信息,如果用户在5分钟内从A机房切换到B机房而B机房尚未同步该信息,B机房可以通过特定路由规则请求A机房的数据以保证业务的连续性。
对于用户信息,虽然延迟同步可能导致用户暂时看到过时的信息,但通常不需要立即采取措施补救。这种情况下,用户体验的影响可能需要通过其他方式来缓解。
以上就是异地多活架构设计中常见的思维误区及应对策略。尽管存在挑战,通过合理的设计和技术选择,我们仍然可以实现有效的异地多活解决方案。在后续内容中,我将进一步讨论这些策略的具体应用和实施细节。
数据同步是异地多活架构设计的关键要素,幸运的是,许多存储系统自带了强大的同步功能。如MySQL的主备复制、Redis的Cluster功能和Elasticsearch的集群功能,这些功能看似能够满足大多数的业务需求。然而,依赖存储系统自身的同步功能可能会导入一个思维误区,特别是在一些极端或复杂的业务场景中。
例如,在MySQL中,5.1版本的复制功能是单线程的,这可能导致在网络抖动或数据量大时出现显著的同步延迟,这些延迟可能从几秒到几十分钟不等。即便我们能通过监控系统发现这种延迟,也很难迅速采取有效措施来解决问题。
Redis在3.0之前版本中仅支持主从复制,没有集群功能。在2.8及更早版本中,如果从机宕机或与主机断开连接,需要重新执行全量复制。这种情况下,主机在生成内存快照时虽然可以继续服务,但从机将无法提供服务,如果数据量大,则数据恢复时间可能会非常长。
这些例子表明,仅依靠存储系统自带的同步功能在某些情况下可能不足以支持业务需求,尤其是在跨多地点部署时,可能会遇到各种复杂的异常情况。
为了真正实现异地多活的目标,我们需要拓宽思路,不应局限于使用存储系统自身的同步机制。可以考虑结合使用多种同步手段,或者开发定制的同步解决方案,以适应特定的业务需求和操作环境。这种方法可以帮助我们更好地控制数据的一致性和系统的响应速度,从而实现更为可靠的异地多活架构。
以“用户子系统”为案例,我们可以考虑以下几种数据同步策略:
消息队列同步对于账号数据,假设我们不提供删除功能,账号一旦创建就不再修改或删除。我们可以使用消息队列将账号数据同步到其他业务中心,确保数据的一致性。
二次读取如果出现消息队列同步延迟的情况,用户可能会在A中心注册后尝试在B中心进行操作,而B中心此时尚未同步到新的用户数据。为了解决这个问题,如果B中心本地数据读取失败,可以再次根据路由规则向A中心请求数据,即所谓的二次读取(首次尝试本地读取失败后,再尝试远程读取)。
存储系统同步对于密码数据,考虑到用户更改密码的频率相对较低,并且不太可能在短时间内多次更改密码,可以利用数据库的同步机制,将数据复制到其他业务中心。用户信息数据的同步也可采用类似的方法。
回源读取对于登录时生成的session数据,由于数据量较大,可以选择不进行数据同步。如果用户在A中心登录后又在B中心尝试登录,B中心可以通过路由规则识别出session数据属于A中心,直接向A中心请求相应的session数据;同理,A中心也可对B中心进行操作。
重新生成数据在“回源读取”场景中,如果A中心因异常宕机而无法提供服务,导致B中心无法获取必要的session数据,此时B中心无法继续用户的登录状态,只能要求用户在B中心重新登录以生成新的session数据。

在异地多活架构的设计中,一个常见的思维误区是追求100%的业务可用性。然而,由于物理规律的限制,如光速和网络传播速度,硬盘读写速度,以及极端异常情况的不可控,完全实现100%的数据同步和业务可用性是不可能的。我们必须接受在极端情况下可能会丢失部分数据或出现数据同步延迟的事实。
例如,在使用密码同步时,可能因为同步延迟而导致用户无法立即登录;或者用户信息更新不及时,导致用户看到的是过时信息。面对这些问题,理想的解决方案是不存在的,我们需要学会“忍受”这些小的业务或数据损失,以避免为了解决这最后0.01%的问题而影响到其他99.99%用户的服务质量。
以银行转账为例,如果我们尝试在不同的业务中心实现实时异地多活,可能会因为数据同步的延迟而导致重复转账的问题。为了避免这种情况,银行可能不提供实时的跨业务中心转账功能,而是通过转账申请和后台异步处理的方式来实现异地多活。这意味着用户提交转账后,转账并不立即执行,而是等待系统在后台处理,直到资金验证和同步完成。如果遇到系统中断,转账操作可以在系统恢复后继续尝试,如果资金不足,则通知用户转账失败。
尽管这种方式牺牲了用户的即时体验,但它有效地实现了异地多活,同时保持了系统的整体稳定性。此外,为了提高用户体验,可以采取以下措施:
发布公告:透明地告知用户当前存在的问题和基本原因。如果原因不明确或不宜公开,可以采用轻松有趣的方式表达,如“技术团队正在全力修复中”。
事后补偿:为受影响的用户提供补偿,如代金券、礼品包等,以减少用户的不满。
优化体验:对于因实现异地多活而带来的额外操作,如转账申请,可以在转账成功或失败后通过短信直接通知用户,减少他们需要频繁登录系统以确认状态的需求。
通过这些方法,虽然不能完全避免所有问题,但可以在确保业务连续性的同时,最大限度地减少对用户体验的影响。
异地多活设计的理念可以总结为一句话:采用多种手段,保证绝大部分用户的核心业务异地多活!
原文作者:二进制跳动;如有侵权,请联系管理员删除。