导读:《架构设计》系列为极客时间李运华老师《从0开始学架构》课程笔记。本文为第七部分,主要介绍异地多活,异地多活缩短了时延,提高可用性,但是带来复杂度和成本无疑是巨大的,不是一般公司可以承受的,只有在对可用性要求特别高的业务场景才建议使用。
应用场景
两个标准
- 正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务。
- 某个地方业务异常的时候,用户访问其他地方正常的业务系统,能够得到正确的业务服务。
代价很高
- 系统复杂度会发生质的变化,需要设计复杂的异地多活架构。
- 成本会上升,毕竟要多在一个或者多个机房搭建独立的一套业务系统。
架构模式
同城异区
- 结合复杂度、成本、故障发生概率来综合考虑,同城异区是应对机房级别故障的最优架构。
- 关键在于搭建高速网络将两个机房连接起来,达到近似一个本地机房的效果。架构设计上可以将两个机房当作本地机房来设计,无须额外考虑。
跨城异地
- 跨城异地距离较远带来的网络传输延迟问题,给异地多活架构设计带来了复杂性,如果要做到真正意义上的多活,业务系统需要考虑部署在不同地点的两个机房,在数据短时间不一致的情况下,还能够正常提供业务。
- 关键在于数据不一致的情况下,业务不受影响或者影响很小,这从逻辑的角度上来说其实是矛盾的,架构设计的主要目的就是为了解决这个矛盾。
- 这就引入了一个看似矛盾的地方:数据不一致业务肯定不会正常,但跨城异地肯定会导致数据不一致。
- 如果是强一致性要求的数据,例如银行存款余额、支付宝余额等,这类数据实际上是无法做到跨城异地多活的。
跨国异地
- 为不同地区用户提供服务
- 只读类业务做多活
技巧
- 保证核心业务的异地多活
- 保证核心数据最终一致性
- 尽量减少异地多活机房的距离,搭建高速网络
- 尽量减少数据同步,只同步核心业务相关的数据
- 保证最终一致性,不保证实时一致性
- 采用多种手段同步数据
- 消息队列方式:对于账号数据,由于账号只会创建,不会修改和删除(假设我们不提供删除功能),我们可以将账号数据通过消息队列同步到其他业务中心。
- 二次读取方式:第一次读取本地,本地失败后第二次读取对端
- 存储系统同步方式:对于密码数据,由于用户改密码频率较低,而且用户不可能在 1 秒内连续改多次密码,所以通过数据库的同步机制将数据复制到其他业务中心即可,用户信息数据和密码类似。
- 回源读取方式:当用户在 A 中心登录后,然后又在 B 中心登录,B 中心拿到用户上传的 session id 后,根据路由判断 session 属于 A 中心,直接去 A 中心请求 session 数据即可;反之亦然,A 中心也可以到 B 中心去获取 session 数据。
- 重新生成数据方式:对于“回源读取”场景,如果异常情况下,A 中心宕机了,B 中心请求 session 数据失败,此时就只能登录失败,让用户重新在 B 中心登录,生成新的 session 数据。
- 只保证绝大部分用户的异地多活
- 核心思想:采用多种手段,保证绝大部分用户的核心业务异地多活!
四步走
业务分级
按照一定的标准将业务进行分级,挑选出核心的业务,只为核心业务设计异地多活,降低方案整体复杂度和实现成本。常见的分级标准:
- 访问量大的业务
- 核心业务
- 产生大量收入的业务
数据分类
挑选出核心业务后,需要对核心业务相关的数据进一步分析,目的在于识别所有的数据及数据特征,这些数据特征会影响后面的方案设计。常见的数据特征分析维度:
- 数据量:这里的数据量包括总的数据量和新增、修改、删除的量。对异地多活架构来说,新增、修改、删除的数据就是可能要同步的数据,数据量越大,同步延迟的几率越高,同步方案需要考虑相应的解决方案。
- 唯一性:唯一性指数据是否要求多个异地机房产生的同类数据必须保证唯一。
- 数据的唯一性影响业务的多活设计,如果数据不需要唯一,那就说明两个地方都产生同类数据是可能的;
- 如果数据要求必须唯一,要么只能一个中心点产生数据,要么需要设计一个数据唯一生成的算法。
- 实时性:实时性要求越高,对同步的要求越高,方案越复杂。
- 可丢失性:可丢失性指数据是否可以丢失。
- 可恢复性:可恢复性指数据丢失后,是否可以通过某种手段进行恢复,如果数据可以恢复,至少说明对业务的影响不会那么大,这样可以相应地降低异地多活架构设计的复杂度。
数据同步
常见的数据同步方案:
- 存储系统同步:
- 优点:使用简单,因为几乎主流的存储系统都会有自己的同步方案
- 缺点:这类同步方案都是通用的,无法针对业务数据特点做定制化的控制。
- 消息队列同步:消息队列同步适合无事务性或者无时序性要求的数据。对于新注册的用户账号,我们可以采用消息队列同步了;而对于用户密码,就不能采用消息队列同步了。
- 重复生成:数据不同步到异地机房,每个机房都可以生成数据,这个方案适合于可以重复生成的数据。
异常处理
无论数据同步方案如何设计,一旦出现极端异常的情况,总是会有部分数据出现异常的:
- 同步延迟
- 数据丢失
- 数据不一致
异常处理主要目的
- 问题发生时,避免少量数据异常导致整体业务不可用。
- 问题恢复后,将异常的数据进行修正。
- 对用户进行安抚,弥补用户损失。
常见的异常处理措施
多通道同步
采取多种方式来进行数据同步,其中某条通道故障的情况下,系统可以通过其他方式来进行同步,这种方式可以应对同步通道处故障的情况。方案关键点:
- 一般情况下,采取两通道即可,采取更多通道理论上能够降低风险,但付出的成本也会增加很多。
- 数据库同步通道和消息队列同步通道不能采用相同的网络连接,否则一旦网络故障,两个通道都同时故障;可以一个走公网连接,一个走内网连接。
- 需要数据是可以重复覆盖的,即无论哪个通道先到哪个通道后到,最终结果是一样的。例如,新建账号数据就符合这个标准,而密码数据则不符合这个标准。
同步和访问结合
访问指异地机房通过系统的接口来进行数据访问。设计关键点:
- 接口访问通道和数据库同步通道不能采用相同的网络连接,不能让数据库同步和接口访问都走同一条网络通道,可以采用接口访问走公网连接,数据库同步走内网连接这种方式。
- 数据有路由规则,可以根据数据来推断应该访问哪个机房的接口来读取数据。
- 由于有同步通道,优先读取本地数据,本地数据无法读取到再通过接口去访问,这样可以大大降低跨机房的异地接口访问数量,适合于实时性要求非常高的数据。
日志记录
主要用于用户故障恢复后对数据进行恢复,其主要方式是每个关键操作前后都记录相关一条日志,然后将日志保存在一个独立的地方,当故障恢复后,拿出日志跟数据进行对比,对数据进行修复。常见日志保存方式:
- 服务器上保存日志,数据库中保存数据,这种方式可以应对单台数据库服务器故障或者宕机的情况。
- 本地独立系统保存日志,这种方式可以应对某业务服务器和数据库同时宕机的情况。
- 日志异地保存,这种方式可以应对机房宕机的情况。
用户补偿
- 无论多么完美的方案,故障的场景下总是可能有一小部分用户业务上出问题,系统无法弥补这部分用户的损失。
- 可以采用人工的方式对用户进行补偿,弥补用户损失,培养用户的忠诚度。简单来说,系统的方案是为了保证 99.99% 的用户在故障的场景下业务不受影响,人工的补偿是为了弥补 0.01% 的用户的损失。
个人思考
异地多活在前司个人感觉是个默认选项。比如 Redis 存储三地域双副本,一份数据要存6份:即本机房一主一从,然后同步到余下两个机房,而且是N 1冗余,也就是一个机房挂了,余下 N 个机房能够承接全部流量。这样做的好处就是:
- 用户都是就近访问的,速度快
- 可用性高,一个机房挂,直接自动切机房了,光缆挖断了、机房断电了也分分钟止损。
缺点就是:太浪费了,也比较复杂。比如我当时负责的几个 Redis 集群,有的是基础架构提供的多机房同步,有的是业务自己异步写远程机房,当然当时只是权衡之后这么搞的,如果要搞成 CP 就复杂了,也不适合业务自己搞。
现司就比较粗暴了,大部分业务只是单地域,然后通过同城多机房来做容灾。这种方式其实比较简单,比如在上海和苏州个有一个机房,但是对于业务来说这两个机房无差别,请求会均匀分配到两个机房的机器上,当上海的机房挂了,请求自然到了苏州了。简单粗暴,效果好。问题就是,如果服务都部署在深圳的机房了,北京的用户请求也要跑到深圳再回来,耗时多了几十毫秒。
简单的说,异地多活,是富家子的操作,追求最后的 0.00001 的收益。大厂可以搞搞,小厂如果不是对可用性有特别特别高的需求还是算了吧。
reference
- 从 0 开始学架构