This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
<audio id="audio" title="22 | 想成为架构师你必须知道CAP理论" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8d/de/8d94e8a7c5654c18ace7558935ddbdde.mp3"></audio>
CAP定理CAP theorem又被称作布鲁尔定理Brewer&#39;s theorem是加州大学伯克利分校的计算机科学家埃里克·布鲁尔Eric Brewer在2000年的ACM PODC上提出的一个猜想。2002年麻省理工学院的赛斯·吉尔伯特Seth Gilbert和南希·林奇Nancy Lynch发表了布鲁尔猜想的证明使之成为分布式计算领域公认的一个定理。对于设计分布式系统的架构师来说CAP是必须掌握的理论。
布鲁尔在提出CAP猜想的时候并没有详细定义Consistency、Availability、Partition Tolerance三个单词的明确定义因此如果初学者去查询CAP定义的时候会感到比较困惑因为不同的资料对CAP的详细定义有一些细微的差别例如
>
**Consistency**: where all nodes see the same data at the same time.
**Availability**: which guarantees that every request receives a response about whether it succeeded or failed.
**Partition tolerance**: where the system continues to operate even if any one part of the system is lost or fails.
([https://console.bluemix.net/docs/services/Cloudant/guides/cap_theorem.html#cap-](https://console.bluemix.net/docs/services/Cloudant/guides/cap_theorem.html#cap-))
>
**Consistency**: Every read receives the most recent write or an error.
**Availability**: Every request receives a (non-error) response without guarantee that it contains the most recent write.
**Partition tolerance**: The system continues to operate despite an arbitrary number of messages being dropped (or delayed) by the network between nodes.
([https://en.wikipedia.org/wiki/CAP_theorem#cite_note-Brewer2012-6](https://en.wikipedia.org/wiki/CAP_theorem#cite_note-Brewer2012-6))
>
**Consistency**: all nodes have access to the same data simultaneously.
**Availability**: a promise that every request receives a response, at minimum whether the request succeeded or failed.
**Partition tolerance**: the system will continue to work even if some arbitrary node goes offline or cant communicate.
([https://www.teamsilverback.com/understanding-the-cap-theorem/](https://www.teamsilverback.com/understanding-the-cap-theorem/))
为了更好地解释CAP理论我挑选了Robert Greiner[http://robertgreiner.com/about/](http://robertgreiner.com/about/)的文章作为参考基础。有趣的是Robert Greiner对CAP的理解也经历了一个过程他写了两篇文章来阐述CAP理论第一篇被标记为“outdated”有一些中文翻译文章正好参考了第一篇我将对比前后两篇解释的差异点通过对比帮助你更加深入地理解CAP理论。
## CAP理论
第一版解释:
>
Any distributed system cannot guaranty C, A, and P simultaneously.
[http://robertgreiner.com/2014/06/cap-theorem-explained/](http://robertgreiner.com/2014/06/cap-theorem-explained/)
简单翻译为对于一个分布式计算系统不可能同时满足一致性Consistence、可用性Availability、分区容错性Partition Tolerance三个设计约束。
第二版解释:
>
In a distributed system (a collection of interconnected nodes that share data.), you can only have two out of the following three guarantees across a write/read pair: Consistency, Availability, and Partition Tolerance - one of them must be sacrificed.
[http://robertgreiner.com/2014/08/cap-theorem-revisited/](http://robertgreiner.com/2014/08/cap-theorem-revisited/)
简单翻译为在一个分布式系统指互相连接并共享数据的节点的集合当涉及读写操作时只能保证一致性Consistence、可用性Availability、分区容错性Partition Tolerance三者中的两个另外一个必须被牺牲。
对比两个版本的定义,有几个很关键的差异点:
<li>第二版定义了什么才是CAP理论探讨的分布式系统强调了两点interconnected和share data为何要强调这两点呢 因为**分布式系统并不一定会互联和共享数据**。最简单的例如Memcache的集群相互之间就没有连接和共享数据因此Memcache集群这类分布式系统就不符合CAP理论探讨的对象而MySQL集群就是互联和进行数据复制的因此是CAP理论探讨的对象。
</li>
<li>第二版强调了write/read pair这点其实是和上一个差异点一脉相承的。也就是说**CAP关注的是对数据的读写操作而不是分布式系统的所有功能**。例如ZooKeeper的选举机制就不是CAP探讨的对象。
</li>
相比来说,第二版的定义更加精确。
虽然第二版的定义和解释更加严谨但内容相比第一版来说更加难记一些所以现在大部分技术人员谈论CAP理论时更多还是按照第一版的定义和解释来说的因为第一版虽然不严谨但非常简单和容易记住。
第二版除了基本概念,三个基本的设计约束也进行了重新阐述,我来详细分析一下。
1.一致性Consistency
第一版解释:
>
All nodes see the same data at the same time.
简单翻译为:所有节点在同一时刻都能看到相同的数据。
第二版解释:
>
A read is guaranteed to return the most recent write for a given client.
简单翻译为:对某个指定的客户端来说,读操作保证能够返回最新的写操作结果。
第一版解释和第二版解释的主要差异点表现在:
- 第一版从节点node的角度描述第二版从客户端client的角度描述。
相比来说,第二版更加符合我们观察和评估系统的方式,即站在客户端的角度来观察系统的行为和特征。
- 第一版的关键词是see第二版的关键词是read。
第一版解释中的see其实并不确切因为节点node是拥有数据而不是看到数据即使要描述也是用have第二版从客户端client的读写角度来描述一致性定义更加精确。
- 第一版强调同一时刻拥有相同数据same time + same data第二版并没有强调这点。
这就意味着实际上对于节点来说可能同一时刻拥有不同数据same time + different data这和我们通常理解的一致性是有差异的为何做这样的改动呢其实在第一版的详细解释中已经提到了具体内容如下
>
A system has consistency if a transaction starts with the system in a consistent state, and ends with the system in a consistent state. In this model, a system can (and does) shift into an inconsistent state during a transaction, but the entire transaction gets rolled back if there is an error during any stage in the process.
参考上述的解释,对于系统执行事务来说,**在事务执行过程中,系统其实处于一个不一致的状态,不同的节点的数据并不完全一致**因此第一版的解释“All nodes see the same data at the same time”是不严谨的。而第二版强调client读操作能够获取最新的写结果就没有问题因为事务在执行过程中client是无法读取到未提交的数据的只有等到事务提交后client才能读取到事务写入的数据而如果事务失败则会进行回滚client也不会读取到事务中间写入的数据。
2.可用性Availability
第一版解释:
>
Every request gets a response on success/failure.
简单翻译为:每个请求都能得到成功或者失败的响应。
第二版解释:
>
A non-failing node will return a reasonable response within a reasonable amount of time (no error or timeout).
简单翻译为:非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。
第一版解释和第二版解释主要差异点表现在:
- 第一版是every request第二版强调了A non-failing node。
第一版的every request是不严谨的因为只有非故障节点才能满足可用性要求如果节点本身就故障了发给节点的请求不一定能得到一个响应。
- 第一版的response分为success和failure第二版用了两个reasonablereasonable response 和reasonable time而且特别强调了no error or timeout。
第一版的success/failure的定义太泛了几乎任何情况无论是否符合CAP理论我们都可以说请求成功和失败因为超时也算失败、错误也算失败、异常也算失败、结果不正确也算失败即使是成功的响应也不一定是正确的。例如本来应该返回100但实际上返回了90这就是成功的响应但并没有得到正确的结果。相比之下第二版的解释明确了不能超时、不能出错结果是合理的**注意没有说“正确”的结果**。例如应该返回100但实际上返回了90肯定是不正确的结果但可以是一个合理的结果。
3.分区容忍性Partition Tolerance
第一版解释:
>
System continues to work despite message loss or partial failure.
简单翻译为:出现消息丢失或者分区错误时系统能够继续运行。
第二版解释:
>
The system will continue to function when network partitions occur.
简单翻译为:当出现网络分区后,系统能够继续“履行职责”。
第一版解释和第二版解释主要差异点表现在:
- 第一版用的是work第二版用的是function。
work强调“运行”只要系统不宕机我们都可以说系统在work返回错误也是work拒绝服务也是work而function强调“发挥作用”“履行职责”这点和可用性是一脉相承的。也就是说只有返回reasonable response才是function。相比之下第二版解释更加明确。
- 第一版描述分区用的是message loss or partial failure第二版直接用network partitions。
对比两版解释第一版是直接说原因即message loss造成了分区但message loss的定义有点狭隘因为通常我们说的message loss丢包只是网络故障中的一种第二版直接说现象即发生了**分区现象**,不管是什么原因,可能是丢包,也可能是连接中断,还可能是拥塞,只要导致了网络分区,就通通算在里面。
## CAP应用
虽然CAP理论定义是三个要素中只能取两个但放到分布式环境下来思考我们会发现必须选择P分区容忍要素因为网络本身无法做到100%可靠有可能出故障所以分区是一个必然的现象。如果我们选择了CA而放弃了P那么当发生分区现象时为了保证C系统需要禁止写入当有写入请求时系统返回error例如当前系统不允许写入这又和A冲突了因为A要求返回no error和no timeout。因此分布式系统理论上不可能选择CA架构只能选择CP或者AP架构。
1.CP - Consistency/Partition Tolerance
如下图所示为了保证一致性当发生分区现象后N1节点上的数据已经更新到y但由于N1和N2之间的复制通道中断数据y无法同步到N2N2节点上的数据还是x。这时客户端C访问N2时N2需要返回Error提示客户端C“系统现在发生了错误”这种处理方式违背了可用性Availability的要求因此CAP三者只能满足CP。
<img src="https://static001.geekbang.org/resource/image/6e/d7/6e7d7bd54d7a4eb67918080863d354d7.png" alt="">
2.AP - Availability/Partition Tolerance
如下图所示为了保证可用性当发生分区现象后N1节点上的数据已经更新到y但由于N1和N2之间的复制通道中断数据y无法同步到N2N2节点上的数据还是x。这时客户端C访问N2时N2将当前自己拥有的数据x返回给客户端C了而实际上当前最新的数据已经是y了这就不满足一致性Consistency的要求了因此CAP三者只能满足AP。注意这里N2节点返回x虽然不是一个“正确”的结果但是一个“合理”的结果因为x是旧的数据并不是一个错乱的值只是不是最新的数据而已。
<img src="https://static001.geekbang.org/resource/image/2c/d6/2ccafe41de9bd7f8dec4658f004310d6.png" alt="">
## 小结
今天我为你讲了CAP理论通过对比两个不同版本的CAP理论解释详细地分析了CAP理论的准确定义希望对你有所帮助。
这就是今天的全部内容留一道思考题给你吧基于Paxos算法构建的分布式系统属于CAP架构中的哪一种谈谈你的分析和理解。
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)

View File

@@ -0,0 +1,115 @@
<audio id="audio" title="23 | 想成为架构师你必须掌握的CAP细节" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c1/ab/c1ec340e0ad516184f36cbeb89a287ab.mp3"></audio>
理论的优点在于清晰简洁、易于理解但缺点就是高度抽象化省略了很多细节导致在将理论应用到实践时由于各种复杂情况可能出现误解和偏差CAP理论也不例外。如果我们没有意识到这些关键的细节点那么在实践中应用CAP理论时就可能发现方案很难落地。
而且当谈到数据一致性时CAP、ACID、BASE难免会被我们拿出来讨论原因在于这三者都是和数据一致性相关的理论如果不仔细理解三者之间的差别则可能会陷入一头雾水的状态不知道应该用哪个才好。
今天我来讲讲CAP的具体细节简单对比一下ACID、BASE几个概念的关键区别点。
## CAP关键细节点
埃里克·布鲁尔Eric Brewer在《CAP理论十二年回顾“规则”变了》[http://www.infoq.com/cn/articles/cap-twelve-years-later-how-the-rules-have-changed](http://www.infoq.com/cn/articles/cap-twelve-years-later-how-the-rules-have-changed)一文中详细地阐述了理解和应用CAP的一些细节点可能是由于作者写作风格的原因对于一些非常关键的细节点一句话就带过了这里我特别提炼出来重点阐述。
- CAP关注的粒度是**数据**,而不是整个系统。
原文就只有一句话:
>
C与A之间的取舍可以在同一系统内以非常细小的粒度反复发生而每一次的决策可能因为具体的操作乃至因为牵涉到特定的数据或用户而有所不同。
但这句话是理解和应用CAP理论非常关键的一点。CAP理论的定义和解释中用的都是system、node这类系统级的概念这就给很多人造成了很大的误导认为我们在进行架构设计时整个系统要么选择CP要么选择AP。但在实际设计过程中每个系统不可能只处理一种数据而是包含多种类型的数据有的数据必须选择CP有的数据必须选择AP。而如果我们做设计时从整个系统的角度去选择CP还是AP就会发现顾此失彼无论怎么做都是有问题的。
以一个最简单的用户管理系统为例用户管理系统包含用户账号数据用户ID、密码、用户信息数据昵称、兴趣、爱好、性别、自我介绍等。通常情况下用户账号数据会选择CP而用户信息数据会选择AP如果限定整个系统为CP则不符合用户信息数据的应用场景如果限定整个系统为AP则又不符合用户账号数据的应用场景。
所以在CAP理论落地实践时我们需要将系统内的数据按照不同的应用场景和要求进行分类每类数据选择不同的策略CP还是AP而不是直接限定整个系统所有数据都是同一策略。
- CAP是忽略网络延迟的。
这是一个非常隐含的假设布鲁尔在定义一致性时并没有将延迟考虑进去。也就是说当事务提交时数据能够瞬间复制到所有节点。但实际情况下从节点A复制数据到节点B总是需要花费一定时间的。如果是相同机房耗费时间可能是几毫秒如果是跨地域的机房例如北京机房同步到广州机房耗费的时间就可能是几十毫秒。这就意味着CAP理论中的C在实践中是不可能完美实现的在数据复制的过程中节点A和节点B的数据并不一致。
不要小看了这几毫秒或者几十毫秒的不一致对于某些严苛的业务场景例如和金钱相关的用户余额或者和抢购相关的商品库存技术上是无法做到分布式场景下完美的一致性的。而业务上必须要求一致性因此单个用户的余额、单个商品的库存理论上要求选择CP而实际上CP都做不到只能选择CA。也就是说只能单点写入其他节点做备份无法做到分布式情况下多点写入。
需要注意的是,这并不意味着这类系统无法应用分布式架构,只是说“单个用户余额、单个商品库存”无法做分布式,但系统整体还是可以应用分布式架构的。例如,下面的架构图是常见的将用户分区的分布式架构。
<img src="https://static001.geekbang.org/resource/image/66/43/66476fd7ffd5d6f80f4f9ba0938d0443.png" alt="">
我们可以将用户id为0 ~ 100的数据存储在Node 1将用户id为101 ~ 200的数据存储在Node 2Client根据用户id来决定访问哪个Node。对于单个用户来说读写操作都只能在某个节点上进行对所有用户来说有一部分用户的读写操作在Node 1上有一部分用户的读写操作在Node 2上。
这样的设计有一个很明显的问题就是某个节点故障时这个节点上的用户就无法进行读写操作了但站在整体上来看这种设计可以降低节点故障时受影响的用户的数量和范围毕竟只影响20%的用户肯定要比影响所有用户要好。这也是为什么挖掘机挖断光缆后,支付宝只有一部分用户会出现业务异常,而不是所有用户业务异常的原因。
- 正常运行情况下不存在CP和AP的选择可以同时满足CA。
CAP理论告诉我们分布式系统只能选择CP或者AP但其实这里的前提是系统发生了“分区”现象。如果系统没有发生分区现象也就是说P不存在的时候节点间的网络连接一切正常我们没有必要放弃C或者A应该C和A都可以保证这就要求架构设计的时候**既要考虑分区发生时选择CP还是AP也要考虑分区没有发生时如何保证CA**。
同样以用户管理系统为例即使是实现CA不同的数据实现方式也可能不一样用户账号数据可以采用“消息队列”的方式来实现CA因为消息队列可以比较好地控制实时性但实现起来就复杂一些而用户信息数据可以采用“数据库同步”的方式来实现CA因为数据库的方式虽然在某些场景下可能延迟较高但使用起来简单。
- 放弃并不等于什么都不做,需要为分区恢复后做准备。
CAP理论告诉我们三者只能取两个需要“牺牲”sacrificed另外一个这里的“牺牲”是有一定误导作用的因为“牺牲”让很多人理解成什么都不做。实际上CAP理论的“牺牲”只是说在分区过程中我们无法保证C或者A但并不意味着什么都不做。因为在系统整个运行周期中大部分时间都是正常的发生分区现象的时间并不长。例如99.99%可用性俗称4个9的系统一年运行下来不可用的时间只有50分钟99.999%俗称5个9可用性的系统一年运行下来不可用的时间只有5分钟。分区期间放弃C或者A并不意味着永远放弃C和A我们可以在分区期间进行一些操作从而让分区故障解决后系统能够重新达到CA的状态。
最典型的就是在分区期间记录一些日志当分区故障解决后系统根据日志进行数据恢复使得重新达到CA状态。同样以用户管理系统为例对于用户账号数据假设我们选择了CP则分区发生后节点1可以继续注册新用户节点2无法注册新用户这里就是不符合A的原因因为节点2收到注册请求后会返回error此时节点1可以将新注册但未同步到节点2的用户记录到日志中。当分区恢复后节点1读取日志中的记录同步给节点2当同步完成后节点1和节点2就达到了同时满足CA的状态。
而对于用户信息数据假设我们选择了AP则分区发生后节点1和节点2都可以修改用户信息但两边可能修改不一样。例如用户在节点1中将爱好改为“旅游、美食、跑步”然后用户在节点2中将爱好改为“美食、游戏”节点1和节点2都记录了未同步的爱好数据当分区恢复后系统按照某个规则来合并数据。例如按照“最后修改优先规则”将用户爱好修改为“美食、游戏”按照“字数最多优先规则”则将用户爱好修改为“旅游美食、跑步”也可以完全将数据冲突报告出来由人工来选择具体应该采用哪一条。
## ACID
ACID是数据库管理系统为了保证事务的正确性而提出来的一个理论ACID包含四个约束下面我来解释一下。
1.Atomicity原子性
一个事务中的所有操作,要么全部完成,要么全部不完成,不会在中间某个环节结束。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
2.Consistency一致性
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
3.Isolation隔离性
数据库允许多个并发事务同时对数据进行读写和修改的能力。隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别包括读未提交Read uncommitted、读提交read committed、可重复读repeatable read和串行化Serializable
4.Durability持久性
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
可以看到ACID中的AAtomicity和CAP中的AAvailability意义完全不同而ACID中的C和CAP中的C名称虽然都是一致性但含义也完全不一样。ACID中的C是指数据库的数据完整性而CAP中的C是指分布式节点中的数据一致性。再结合ACID的应用场景是数据库事务CAP关注的是分布式系统数据读写这个差异点来看其实CAP和ACID的对比就类似关公战秦琼虽然关公和秦琼都是武将但其实没有太多可比性。
## BASE
BASE是指基本可用Basically Available、软状态 Soft State、最终一致性 Eventual Consistency核心思想是即使无法做到强一致性CAP的一致性就是强一致性但应用可以采用适合的方式达到最终一致性。
1.基本可用Basically Available
分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
这里的关键词是“**部分**”和“**核心**”,具体选择哪些作为可以损失的业务,哪些是必须保证的业务,是一项有挑战的工作。例如,对于一个用户管理系统来说,“登录”是核心功能,而“注册”可以算作非核心功能。因为未注册的用户本来就还没有使用系统的业务,注册不了最多就是流失一部分用户,而且这部分用户数量较少。如果用户已经注册但无法登录,那就意味用户无法使用系统。例如,充了钱的游戏不能玩了、云存储不能用了……这些会对用户造成较大损失,而且登录用户数量远远大于新注册用户,影响范围更大。
2.软状态Soft State
允许系统存在中间状态而该中间状态不会影响系统整体可用性。这里的中间状态就是CAP理论中的数据不一致。
3.最终一致性Eventual Consistency
系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
这里的关键词是“一定时间” 和 “最终”“一定时间”和数据的特性是强关联的不同的数据能够容忍的不一致时间是不同的。举一个微博系统的例子用户账号数据最好能在1分钟内就达到一致状态因为用户在A节点注册或者登录后1分钟内不太可能立刻切换到另外一个节点但10分钟后可能就重新登录到另外一个节点了而用户发布的最新微博可以容忍30分钟内达到一致状态因为对于用户来说看不到某个明星发布的最新微博用户是无感知的会认为明星没有发布微博。“最终”的含义就是不管多长时间最终还是要达到一致性的状态。
BASE理论本质上是对CAP的延伸和补充更具体地说**是对CAP中AP方案的一个补充**。前面在剖析CAP理论时提到了其实和BASE相关的两点
- CAP理论是忽略延时的而实际应用中延时是无法避免的。
这一点就意味着完美的CP场景是不存在的即使是几毫秒的数据复制延迟在这几毫秒时间间隔内系统是不符合CP要求的。因此CAP中的CP方案实际上也是实现了最终一致性只是“一定时间”是指几毫秒而已。
- AP方案中牺牲一致性只是指分区期间而不是永远放弃一致性。
这一点其实就是BASE理论延伸的地方分区期间牺牲一致性但分区故障恢复后系统应该达到最终一致性。
综合上面的分析ACID是数据库事务完整性的理论CAP是分布式系统设计理论BASE是CAP理论中AP方案的延伸。
## 小结
今天我为你讲了深入理解CAP理论所需要特别关注的细节点以及ACID和BASE两个相似的术语这些技术细节在架构设计中非常关键希望对你有所帮助。
这就是今天的全部内容留一道思考题给你吧假如你来设计电商网站的高可用系统按照CAP理论的要求你会如何设计
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)

View File

@@ -0,0 +1,204 @@
<audio id="audio" title="24 | FMEA方法排除架构可用性隐患的利器" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fc/0d/fc5b12b40e7dad407c252dc8bf09b70d.mp3"></audio>
我在前面的专栏分析高可用复杂度的时候提出了一个问题:高可用和高性能哪个更复杂,大部分同学都分析出了正确的答案:高可用更复杂一些,主要原因在于异常的场景很多,只要有一个场景遗漏,架构设计就存在可用性隐患,而根据墨菲定律“可能出错的事情最终都会出错”,架构隐患总有一天会导致系统故障。因此,我们在进行架构设计的时候必须全面分析系统的可用性,那么如何才能做到“全面”呢?
我今天介绍的FMEA方法就是保证我们做到全面分析的一个非常简单但是非常有效的方法。
## FMEA介绍
FMEAFailure mode and effects analysis故障模式与影响分析又称为失效模式与后果分析、失效模式与效应分析、故障模式与后果分析等专栏采用“**故障模式与影响分析**”因为这个中文翻译更加符合可用性的语境。FMEA是一种在各行各业都有广泛应用的可用性分析方法通过对系统范围内潜在的故障模式加以分析并按照严重程度进行分类以确定失效对于系统的最终影响。
FMEA最早是在美国军方开始应用的20世纪40年代后期美国空军正式采用了FMEA。尽管最初是在军事领域建立的方法但FMEA方法现在已广泛应用于各种各样的行业包括半导体加工、餐饮服务、塑料制造、软件及医疗保健行业。FMEA之所以能够在这些差异很大的领域都得到应用根本原因在于FMEA是一套分析和思考的方法而不是某个领域的技能或者工具。
回到软件架构设计领域FMEA并不能指导我们如何做架构设计而是当我们设计出一个架构后再使用FMEA对这个架构进行分析看看架构是否还存在某些可用性的隐患。
## FMEA方法
在架构设计领域FMEA的具体分析方法是
<li>
给出初始的架构设计图。
</li>
<li>
假设架构中某个部件发生故障。
</li>
<li>
分析此故障对系统功能造成的影响。
</li>
<li>
根据分析结果,判断架构是否需要进行优化。
</li>
FMEA分析的方法其实很简单就是一个FMEA分析表常见的FMEA分析表格包含下面部分。
1.**功能点**
当前的FMEA分析涉及的功能点注意这里的“功能点”指的是从用户角度来看的而不是从系统各个模块功能点划分来看的。例如对于一个用户管理系统使用FMEA分析时 “登录”“注册”才是功能点而用户管理系统中的数据库存储功能、Redis缓存功能不能作为FMEA分析的功能点。
2.**故障模式**
故障模式指的是系统会出现什么样的故障包括故障点和故障形式。需要特别注意的是这里的故障模式并不需要给出真正的故障原因我们只需要假设出现某种故障现象即可例如MySQL响应时间达到3秒。造成MySQL响应时间达到3秒可能的原因很多磁盘坏道、慢查询、服务器到MySQL的连接网络故障、MySQL bug等我们并不需要在故障模式中一一列出来而是在后面的“故障原因”一节中列出来。因为在实际应用过程中不管哪种原因只要现象是一样的对业务的影响就是一样的。
此外故障模式的描述要尽量精确多使用量化描述避免使用泛化的描述。例如推荐使用“MySQL响应时间达到3秒”而不是“MySQL响应慢”。
3.**故障影响**
当发生故障模式中描述的故障时,功能点具体会受到什么影响。常见的影响有:功能点偶尔不可用、功能点完全不可用、部分用户功能点不可用、功能点响应缓慢、功能点出错等。
故障影响也需要尽量准确描述。例如推荐使用“20%的用户无法登录”而不是“大部分用户无法登录”。要注意这里的数字不需要完全精确比如21.25%这样的数据其实是没有必要的我们只需要预估影响是20%还是40%。
4.**严重程度**
严重程度指站在业务的角度故障的影响程度,一般分为“致命/高/中/低/无”五个档次。严重程度按照这个公式进行评估:严重程度 = 功能点重要程度 × 故障影响范围 × 功能点受损程度。同样以用户管理系统为例登录功能比修改用户资料要重要得多80%的用户比20%的用户范围更大,完全无法登录比登录缓慢要更严重。因此我们可以得出如下故障模式的严重程度。
<li>
致命超过70%用户无法登录。
</li>
<li>
超过30%的用户无法登录。
</li>
<li>
所有用户登录时间超过5秒。
</li>
<li>
10%的用户登录时间超过5秒。
</li>
<li>
中:所有用户都无法修改资料。
</li>
<li>
20%的用户无法修改头像。
</li>
对于某个故障的影响到底属于哪个档次,有时会出现一些争议。例如,“所有用户都无法修改资料”,有的人认为是高,有的人可能认为是中,这个没有绝对标准,一般建议相关人员讨论确定即可。也不建议花费太多时间争论,争执不下时架构师裁定即可。
5.**故障原因**
“故障模式”中只描述了故障的现象,并没有单独列出故障原因。主要原因在于不管什么故障原因,故障现象相同,对功能点的影响就相同。那为何这里还要单独将故障原因列出来呢?主要原因有这几个:
- 不同的故障原因发生概率不相同
例如导致MySQL查询响应慢的原因可能是MySQL bug也可能是没有索引。很明显“MySQL bug”的概率要远远低于“没有索引”而不同的概率又会影响我们具体如何应对这个故障。
- 不同的故障原因检测手段不一样
例如磁盘坏道导致MySQL响应慢那我们需要增加机器的磁盘坏道检查这个检查很可能不是当前系统本身去做而是另外运维专门的系统如果是慢查询导致MySQL慢那我们只需要配置MySQL的慢查询日志即可。
- 不同的故障原因的处理措施不一样
例如如果是MySQL bug我们的应对措施只能是升级MySQL版本如果是没有索引我们的应对措施就是增加索引。
6.**故障概率**
这里的概率就是指某个具体故障原因发生的概率。例如磁盘坏道的概率、MySQL bug的概率、没有索引的概率。一般分为“高/中/低”三档即可,具体评估的时候需要有以下几点需要重点关注。
- 硬件
硬件随着使用时间推移故障概率会越来越高。例如新的硬盘坏道几率很低但使用了3年的硬盘坏道几率就会高很多。
- 开源系统
成熟的开源系统bug率低刚发布的开源系统bug率相比会高一些自己已经有使用经验的开源系统bug率会低刚开始尝试使用的开源系统bug率会高。
- 自研系统
和开源系统类似,成熟的自研系统故障概率会低,而新开发的系统故障概率会高。
高中低是相对的只是为了确定优先级以决定后续的资源投入没有必要绝对量化因为绝对量化是需要成本的而且很多时候都没法量化。例如XX开源系统是3个月故障一次还是6个月才故障一次是无法评估的。
7.**风险程度**
风险程度就是综合严重程度和故障概率来一起判断某个故障的最终等级,风险程度 = 严重程度 × 故障概率。因此可能出现某个故障影响非常严重但其概率很低最终来看风险程度就低。“某个机房业务瘫痪”对业务影响是致命的但如果故障原因是“地震”那概率就很低。例如广州的地震概率就很低5级以上地震的20世纪才1次1940年如果故障的原因是“机房空调烧坏”则概率就比地震高很多了可能是2年1次如果故障的原因是“系统所在机架掉电”这个概率比机房空调又要高了可能是1年1次。同样的故障影响不同的故障原因有不同的概率最终得到的风险级别就是不同的。
8.**已有措施**
针对具体的故障原因,系统现在是否提供了某些措施来应对,包括:检测告警、容错、自恢复等。
- 检测告警
最简单的措施就是检测故障,然后告警,系统自己不针对故障进行处理,需要人工干预。
- 容错
检测到故障后系统能够通过备份手段应对。例如MySQL主备机当业务服务器检测到主机无法连接后自动连接备机读取数据。
- 自恢复
检测到故障后系统能够自己恢复。例如Hadoop检测到某台机器故障后能够将存储在这台机器的副本重新分配到其他机器。当然这里的恢复主要还是指“业务”上的恢复一般不太可能将真正的故障恢复。例如Hadoop不可能将产生了磁盘坏道的磁盘修复成没有坏道的磁盘。
9.**规避措施**
规避措施指为了降低故障发生概率而做的一些事情,可以是技术手段,也可以是管理手段。例如:
<li>
技术手段为了避免新引入的MongoDB丢失数据在MySQL中冗余一份。
</li>
<li>
管理手段为了降低磁盘坏道的概率强制统一更换服务时间超过2年的磁盘。
</li>
10.**解决措施**
解决措施指为了能够解决问题而做的一些事情,一般都是技术手段。例如:
<li>
为了解决密码暴力破解,增加密码重试次数限制。
</li>
<li>
为了解决拖库导致数据泄露,将数据库中的敏感数据加密保存。
</li>
<li>
为了解决非法访问,增加白名单控制。
</li>
一般来说如果某个故障既可以采取规避措施又可以采取解决措施那么我们会优先选择解决措施毕竟能解决问题当然是最好的。但很多时候有些问题是系统自己无法解决的例如磁盘坏道、开源系统bug这类故障只能采取规避措施系统能够自己解决的故障大部分是和系统本身功能相关的。
11.**后续规划**
综合前面的分析,就可以看出哪些故障我们目前还缺乏对应的措施,哪些已有措施还不够,针对这些不足的地方,再结合风险程度进行排序,给出后续的改进规划。这些规划既可以是技术手段,也可以是管理手段;可以是规避措施,也可以是解决措施。同时需要考虑资源的投入情况,优先将风险程度高的系统隐患解决。
例如:
<li>
地震导致机房业务中断:这个故障模式就无法解决,只能通过备份中心规避,尽量减少影响;而机柜断电导致机房业务中断:可以通过将业务机器分散在不同机柜来规避。
</li>
<li>
敏感数据泄露:这个故障模式可以通过数据库加密的技术手段来解决。
</li>
<li>
MongoDB断电丢数据这个故障模式可以通过将数据冗余一份在MySQL中在故障情况下重建数据来规避影响。
</li>
## FMEA实战
下面我以一个简单的样例来模拟一次FMEA分析。假设我们设计一个最简单的用户管理系统包含登录和注册两个功能其初始架构是
<img src="https://static001.geekbang.org/resource/image/f2/5a/f2e22565b6b60cd3cdce52fcc3711b5a.jpg" alt="">
初始架构很简单MySQL负责存储Memcache以下简称MC负责缓存Server负责业务处理。我们来看看这个架构通过FMEA分析后能够有什么样的发现下表是分析的样例注意这个样例并不完整感兴趣的同学可以自行尝试将这个案例补充完整
<img src="https://static001.geekbang.org/resource/image/58/8d/583fd4cc24680d407e248a3e15fb138d.jpg" alt="">
经过上表的FMEA分析将“后续规划”列的内容汇总一下我们最终得到了下面几条需要改进的措施
<li>
MySQL增加备机。
</li>
<li>
MC从单机扩展为集群。
</li>
<li>
MySQL双网卡连接。
</li>
改进后的架构如下:
<img src="https://static001.geekbang.org/resource/image/42/ab/4250e8b6eb07023e2b55fa6fdbayyeab.png" alt="">
## 小结
今天我为你讲了FMEA高可用分析方法并且给出了一个简单的案例描述如何操作。FMEA是高可用架构设计的一个非常有用的方法能够发现架构中隐藏的高可用问题希望对你有所帮助。
这就是今天的全部内容留一道思考题给你吧请使用FMEA方法分析一下HDFS系统的架构看看HDFS是如何应对各种故障的并且分析一下HDFS是否存在高可用问题。
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)

View File

@@ -0,0 +1,259 @@
<audio id="audio" title="25 | 高可用存储架构:双机架构" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e3/e6/e336e1423796e3e4d4703b33caafcae6.mp3"></audio>
存储高可用方案的本质都是通过将数据复制到多个存储设备,通过数据冗余的方式来实现高可用,其复杂性主要体现在如何应对复制延迟和中断导致的数据不一致问题。因此,对任何一个高可用存储方案,我们需要从以下几个方面去进行思考和分析:
<li>
数据如何复制?
</li>
<li>
各个节点的职责是什么?
</li>
<li>
如何应对复制延迟?
</li>
<li>
如何应对复制中断?
</li>
常见的高可用存储架构有主备、主从、主主、集群、分区,每一种又可以根据业务的需求进行一些特殊的定制化功能,由此衍生出更多的变种。由于不同业务的定制功能难以通用化,今天我将针对业界通用的方案,来分析常见的双机高可用架构:主备、主从、主备/主从切换和主主。
## 主备复制
主备复制是最常见也是最简单的一种存储高可用方案几乎所有的存储系统都提供了主备复制的功能例如MySQL、Redis、MongoDB等。
1.基本实现
下面是标准的主备方案结构图:
<img src="https://static001.geekbang.org/resource/image/24/d2/24646c3e5db8533186c9yy5ae2f870d2.jpg" alt="">
其整体架构比较简单,主备架构中的“备机”主要还是起到一个备份作用,并不承担实际的业务读写操作,如果要把备机改为主机,需要人工操作。
2.优缺点分析
主备复制架构的优点就是简单,表现有:
<li>
对于客户端来说,不需要感知备机的存在,即使灾难恢复后,原来的备机被人工修改为主机后,对于客户端来说,只是认为主机的地址换了而已,无须知道是原来的备机升级为主机。
</li>
<li>
对于主机和备机来说,双方只需要进行数据复制即可,无须进行状态判断和主备切换这类复杂的操作。
</li>
主备复制架构的缺点主要有:
<li>
备机仅仅只为备份,并没有提供读写操作,硬件成本上有浪费。
</li>
<li>
故障后需要人工干预无法自动恢复。人工处理的效率是很低的可能打电话找到能够操作的人就耗费了10分钟甚至如果是深更半夜出了故障都没人知道。人工在执行恢复操作的过程中也容易出错因为这类操作并不常见可能1年就2、3次实际操作的时候很可能遇到各种意想不到的问题。
</li>
综合主备复制架构的优缺点,内部的后台管理系统使用主备复制架构的情况会比较多,例如学生管理系统、员工管理系统、假期管理系统等,因为这类系统的数据变更频率低,即使在某些场景下丢失数据,也可以通过人工的方式补全。
## 主从复制
主从复制和主备复制只有一字之差,“从”意思是“随从、仆从”,“备”的意思是备份。我们可以理解为仆从是要帮主人干活的,这里的干活就是承担“读”的操作。也就是说,主机负责读写操作,从机只负责读操作,不负责写操作。
1.基本实现
下面是标准的主从复制架构:
<img src="https://static001.geekbang.org/resource/image/03/b7/03715e39bde946f363643a70d4cb02b7.jpg" alt="">
与主备复制架构比较类似,主要的差别点在于从机正常情况下也是要提供读的操作。
2.优缺点分析
主从复制与主备复制相比,优点有:
<li>
主从复制在主机故障时,读操作相关的业务可以继续运行。
</li>
<li>
主从复制架构的从机提供读操作,发挥了硬件的性能。
</li>
缺点有:
<li>
主从复制架构中,客户端需要感知主从关系,并将不同的操作发给不同的机器进行处理,复杂度比主备复制要高。
</li>
<li>
主从复制架构中,从机提供读业务,如果主从复制延迟比较大,业务会因为数据不一致出现问题。
</li>
<li>
故障时需要人工干预。
</li>
综合主从复制的优缺点一般情况下写少读多的业务使用主从复制的存储架构比较多。例如论坛、BBS、新闻网站这类业务此类业务的读操作数量是写操作数量的10倍甚至100倍以上。
## 双机切换
1.设计关键
主备复制和主从复制方案存在两个共性的问题:
<li>
主机故障后,无法进行写操作。
</li>
<li>
如果主机无法恢复,需要人工指定新的主机角色。
</li>
双机切换就是为了解决这两个问题而产生的,包括主备切换和主从切换两种方案。简单来说,这两个方案就是在原有方案的基础上增加“切换”功能,即系统自动决定主机角色,并完成角色切换。由于主备切换和主从切换在切换的设计上没有差别,我接下来以主备切换为例,一起来看看双机切换架构是如何实现的。
要实现一个完善的切换方案,必须考虑这几个关键的设计点:
- 主备间状态判断
主要包括两方面:状态传递的渠道,以及状态检测的内容。
**状态传递的渠道**:是相互间互相连接,还是第三方仲裁?
**状态检测的内容**:例如机器是否掉电、进程是否存在、响应是否缓慢等。
- 切换决策
主要包括几方面:切换时机、切换策略、自动程度。
**切换时机**什么情况下备机应该升级为主机是机器掉电后备机才升级还是主机上的进程不存在就升级还是主机响应时间超过2秒就升级还是3分钟内主机连续重启3次就升级等。
**切换策略**:原来的主机故障恢复后,要再次切换,确保原来的主机继续做主机,还是原来的主机故障恢复后自动成为新的备机?
**自动程度**:切换是完全自动的,还是半自动的?例如,系统判断当前需要切换,但需要人工做最终的确认操作(例如,单击一下“切换”按钮)。
- 数据冲突解决
当原有故障的主机恢复后新旧主机之间可能存在数据冲突。例如用户在旧主机上新增了一条ID为100的数据这个数据还没有复制到旧的备机此时发生了切换旧的备机升级为新的主机用户又在新的主机上新增了一条ID为100的数据当旧的故障主机恢复后这两条ID都为100的数据应该怎么处理
以上设计点并没有放之四海而皆准的答案不同的业务要求不一样所以切换方案比复制方案不只是多了一个切换功能那么简单而是复杂度上升了一个量级。形象点来说如果复制方案的代码是1000行那么切换方案的代码可能就是10000行多出来的那9000行就是用于实现上面我所讲的3个设计点的。
2.常见架构
根据状态传递渠道的不同,常见的主备切换架构有三种形式:互连式、中介式和模拟式。
**互连式**
故名思议,互连式就是指主备机直接建立状态传递的渠道,架构图请注意与主备复制架构对比。
<img src="https://static001.geekbang.org/resource/image/d5/b2/d5bed3e75be97013154e73003c151fb2.jpg" alt="">
你可以看到,在主备复制的架构基础上,主机和备机多了一个“状态传递”的通道,这个通道就是用来传递状态信息的。这个通道的具体实现可以有很多方式:
<li>
可以是网络连接(例如,各开一个端口),也可以是非网络连接(用串口线连接)。
</li>
<li>
可以是主机发送状态给备机,也可以是备机到主机来获取状态信息。
</li>
<li>
可以和数据复制通道共用,也可以独立一条通道。
</li>
<li>
状态传递通道可以是一条,也可以是多条,还可以是不同类型的通道混合(例如,网络+串口)。
</li>
为了充分利用切换方案能够自动决定主机这个优势,客户端这里也会有一些相应的改变,常见的方式有:
<li>
为了切换后不影响客户端的访问主机和备机之间共享一个对客户端来说唯一的地址。例如虚拟IP主机需要绑定这个虚拟的IP。
</li>
<li>
客户端同时记录主备机的地址,哪个能访问就访问哪个;备机虽然能收到客户端的操作请求,但是会直接拒绝,拒绝的原因就是“备机不对外提供服务”。
</li>
互连式主备切换主要的缺点在于:
<li>
如果状态传递的通道本身有故障(例如,网线被人不小心踢掉了),那么备机也会认为主机故障了从而将自己升级为主机,而此时主机并没有故障,最终就可能出现两个主机。
</li>
<li>
虽然可以通过增加多个通道来增强状态传递的可靠性,但这样做只是降低了通道故障概率而已,不能从根本上解决这个缺点,而且通道越多,后续的状态决策会更加复杂,因为对备机来说,可能从不同的通道收到了不同甚至矛盾的状态信息。
</li>
**中介式**
中介式指的是在主备两者之外引入第三方中介,主备机之间不直接连接,而都去连接中介,并且通过中介来传递状态信息,其架构图如下:
<img src="https://static001.geekbang.org/resource/image/f6/65/f69d33c8d7d3d85e510d8eb54d5dd065.jpg" alt="">
对比一下互连式切换架构,我们可以看到,主机和备机不再通过互联通道传递状态信息,而是都将状态上报给中介这一角色。单纯从架构上看,中介式似乎比互连式更加复杂了,首先要引入中介,然后要各自上报状态。然而事实上,中介式架构在状态传递和决策上却更加简单了,这是为何呢?
**连接管理更简单**:主备机无须再建立和管理多种类型的状态传递连接通道,只要连接到中介即可,实际上是降低了主备机的连接管理复杂度。
例如,互连式要求主机开一个监听端口,备机来获取状态信息;或者要求备机开一个监听端口,主机推送状态信息到备机;如果还采用了串口连接,则需要增加串口连接管理和数据读取。采用中介式后,主备机都只需要把状态信息发送给中介,或者从中介获取对方的状态信息。无论是发送还是获取,主备机都是作为中介的客户端去操作,复杂度会降低。
**状态决策更简单**:主备机的状态决策简单了,无须考虑多种类型的连接通道获取的状态信息如何决策的问题,只需要按照下面简单的算法即可完成状态决策。
<li>
无论是主机还是备机,初始状态都是备机,并且只要与中介断开连接,就将自己降级为备机,因此可能出现双备机的情况。
</li>
<li>
主机与中介断连后,中介能够立刻告知备机,备机将自己升级为主机。
</li>
<li>
如果是网络中断导致主机与中介断连,主机自己会降级为备机,网络恢复后,旧的主机以新的备机身份向中介上报自己的状态。
</li>
<li>
如果是掉电重启或者进程重启,旧的主机初始状态为备机,与中介恢复连接后,发现已经有主机了,保持自己备机状态不变。
</li>
<li>
主备机与中介连接都正常的情况下按照实际的状态决定是否进行切换。例如主机响应时间超过3秒就进行切换主机降级为备机备机升级为主机即可。
</li>
虽然中介式架构在状态传递和状态决策上更加简单,但并不意味着这种优点是没有代价的,其关键代价就在于如何实现中介本身的高可用。如果中介自己宕机了,整个系统就进入了双备的状态,写操作相关的业务就不可用了。这就陷入了一个递归的陷阱:为了实现高可用,我们引入中介,但中介本身又要求高可用,于是又要设计中介的高可用方案……如此递归下去就无穷无尽了。
MongoDB的Replica Set采取的就是这种方式其基本架构如下
<img src="https://static001.geekbang.org/resource/image/19/f9/1902b97ea2c6dda74dc0270f945725f9.jpg" alt="">
MongoDB(M)表示主节点MongoDB(S)表示备节点MongoDB(A)表示仲裁节点。主备节点存储数据,仲裁节点不存储数据。客户端同时连接主节点与备节点,不连接仲裁节点。
幸运的是开源方案已经有比较成熟的中介式解决方案例如ZooKeeper和Keepalived。ZooKeeper本身已经实现了高可用集群架构因此已经帮我们解决了中介本身的可靠性问题在工程实践中推荐基于ZooKeeper搭建中介式切换架构。
**模拟式**
模拟式指主备机之间并不传递任何状态数据,而是备机模拟成一个客户端,向主机发起模拟的读写操作,根据读写操作的响应情况来判断主机的状态。其基本架构如下:
<img src="https://static001.geekbang.org/resource/image/fb/57/fbd04e44f3ddab37b40a1e459af1af57.jpg" alt="">
对比一下互连式切换架构,我们可以看到,主备机之间只有数据复制通道,而没有状态传递通道,备机通过模拟的读写操作来探测主机的状态,然后根据读写操作的响应情况来进行状态决策。
模拟式切换与互连式切换相比,优点是实现更加简单,因为省去了状态传递通道的建立和管理工作。
简单既是优点同时也是缺点。因为模拟式读写操作获取的状态信息只有响应信息例如HTTP 404超时、响应时间超过3秒等没有互连式那样多样除了响应信息还可以包含CPU负载、I/O负载、吞吐量、响应时间等基于有限的状态来做状态决策可能出现偏差。
## 主主复制
主主复制指的是两台机器都是主机,互相将数据复制给对方,客户端可以任意挑选其中一台机器进行读写操作,下面是基本架构图。
<img src="https://static001.geekbang.org/resource/image/55/22/555c5714ec983ca5506191156430df22.jpg" alt="">
相比主备切换架构,主主复制架构具有如下特点:
<li>
两台都是主机,不存在切换的概念。
</li>
<li>
客户端无须区分不同角色的主机,随便将读写操作发送给哪台主机都可以。
</li>
从上面的描述来看,主主复制架构从总体上来看要简单很多,无须状态信息传递,也无须状态决策和状态切换。然而事实上主主复制架构也并不简单,而是有其独特的复杂性,具体表现在:如果采取主主复制架构,必须保证数据能够双向复制,而很多数据是不能双向复制的。例如:
<li>
用户注册后生成的用户ID如果按照数字增长那就不能双向复制否则就会出现X用户在主机A注册分配的用户ID是100同时Y用户在主机B注册分配的用户ID也是100这就出现了冲突。
</li>
<li>
库存不能双向复制。例如一件商品库存100件主机A上减了1件变成99主机B上减了2件变成98然后主机A将库存99复制到主机B主机B原有的库存98被覆盖变成了99而实际上此时真正的库存是97。类似的还有余额数据。
</li>
因此主主复制架构对数据的设计有严格的要求一般适合于那些临时性、可丢失、可覆盖的数据场景。例如用户登录产生的session数据可以重新登录生成、用户行为的日志数据可以丢失、论坛的草稿数据可以丢失等。
## 小结
今天我为你讲了高可用存储架构中常见的双机架构,分析了每类架构的优缺点以及适应场景,希望对你有所帮助。
这就是今天的全部内容,留一道思考题给你吧,如果你来设计一个政府信息公开网站的信息存储系统,你会采取哪种架构?谈谈你的分析和理由。
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)

View File

@@ -0,0 +1,183 @@
<audio id="audio" title="26 | 高可用存储架构:集群和分区" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ac/80/ac2f4e0b1fa590c5b25bf7a937e48480.mp3"></audio>
上一期我讲了高可用存储架构中常见的双机架构,分别为主备复制、主从复制、双机切换和主主复制,并分析了每类架构的优缺点以及适应场景。
今天我们一起来看看另外两种常见的高可用存储架构:数据集群和数据分区。
## 数据集群
主备、主从、主主架构本质上都有一个隐含的假设主机能够存储所有数据但主机本身的存储和处理能力肯定是有极限的。以PC为例Intel 386时代服务器存储能力只有几百MBIntel 奔腾时代服务器存储能力可以有几十GBIntel 酷睿多核时代的服务器可以有几个TB。单纯从硬件发展的角度来看似乎发展速度还是挺快的但如果和业务发展速度对比那就差得远了。早在2013年Facebook就有2500亿张上传照片当时这些照片的容量就已经达到了250 PB字节250 × 1024TB平均一天上传的图片有3亿5000万张。如此大量的数据单台服务器肯定是无法存储和处理的我们必须使用多台服务器来存储数据这就是数据集群架构。
简单来说集群就是多台机器组合在一起形成一个统一的系统这里的“多台”数量上至少是3台相比而言主备、主从都是2台机器。根据集群中机器承担的不同角色来划分集群可以分为两类数据集中集群、数据分散集群。
1.数据集中集群
数据集中集群与主备、主从这类架构相似我们也可以称数据集中集群为1主多备或者1主多从。无论是1主1从、1主1备还是1主多备、1主多从数据都只能往主机中写而读操作可以参考主备、主从架构进行灵活多变。下图是读写全部到主机的一种架构
<img src="https://static001.geekbang.org/resource/image/80/f3/80850dd83b959910a9b5c455d2524af3.jpg" alt="">
虽然架构上是类似的,但由于集群里面的服务器数量更多,导致复杂度整体更高一些,具体体现在:
- 主机如何将数据复制给备机
主备和主从架构中,只有一条复制通道,而数据集中集群架构中,存在多条复制通道。多条复制通道首先会增大主机复制的压力,某些场景下我们需要考虑如何降低主机复制压力,或者降低主机复制给正常读写带来的压力。
其次,多条复制通道可能会导致多个备机之间数据不一致,某些场景下我们需要对备机之间的数据一致性进行检查和修正。
- 备机如何检测主机状态
主备和主从架构中,只有一台备机需要进行主机状态判断。在数据集中集群架构中,多台备机都需要对主机状态进行判断,而不同的备机判断的结果可能是不同的,如何处理不同备机对主机状态的不同判断,是一个复杂的问题。
- 主机故障后,如何决定新的主机
主从架构中,如果主机故障,将备机升级为主机即可;而在数据集中集群架构中,有多台备机都可以升级为主机,但实际上只能允许一台备机升级为主机,那么究竟选择哪一台备机作为新的主机,备机之间如何协调,这也是一个复杂的问题。
目前开源的数据集中集群以ZooKeeper为典型ZooKeeper通过ZAB算法来解决上述提到的几个问题但ZAB算法的复杂度是很高的。
2.数据分散集群
数据分散集群指多个服务器组成一个集群,每台服务器都会负责存储一部分数据;同时,为了提升硬件利用率,每台服务器又会备份一部分数据。
数据分散集群的复杂点在于如何将数据分配到不同的服务器上,算法需要考虑这些设计点:
- 均衡性
算法需要保证服务器上的数据分区基本是均衡的,不能存在某台服务器上的分区数量是另外一台服务器的几倍的情况。
- 容错性
当出现部分服务器故障时,算法需要将原来分配给故障服务器的数据分区分配给其他服务器。
- 可伸缩性
当集群容量不够,扩充新的服务器后,算法能够自动将部分数据分区迁移到新服务器,并保证扩容后所有服务器的均衡性。
数据分散集群和数据集中集群的不同点在于,数据分散集群中的每台服务器都可以处理读写请求,因此不存在数据集中集群中负责写的主机那样的角色。但在数据分散集群中,必须有一个角色来负责执行数据分配算法,这个角色可以是独立的一台服务器,也可以是集群自己选举出的一台服务器。如果是集群服务器选举出来一台机器承担数据分区分配的职责,则这台服务器一般也会叫作主机,但我们需要知道这里的“主机”和数据集中集群中的“主机”,其职责是有差异的。
Hadoop的实现就是独立的服务器负责数据分区的分配这台服务器叫作Namenode。Hadoop的数据分区管理架构如下
<img src="https://static001.geekbang.org/resource/image/79/b2/7916f0fdcd9f06aeb07f21080e5123b2.jpg" alt="" title="图片来源网络">
下面是Hadoop官方的解释能够说明集中式数据分区管理的基本方式。
>
<p>HDFS采用master/slave架构。一个HDFS集群由一个Namenode和一定数目的Datanodes组成。<br>
Namenode是一个中心服务器负责管理文件系统的名字空间namespace以及客户端对文件的访问。<br>
集群中的Datanode一般是一个节点一个负责管理它所在节点上的存储。HDFS暴露了文件系统的名字空间用户能够以文件的形式在上面存储数据。从内部看一个文件其实被分成一个或多个数据块这些块存储在一组Datanode上。<br>
Namenode执行文件系统的名字空间操作比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode节点的映射。Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制操作。</p>
与Hadoop不同的是Elasticsearch集群通过选举一台服务器来做数据分区的分配叫作master node其数据分区管理架构是
<img src="https://static001.geekbang.org/resource/image/12/55/12151a94f98ee7fcb25a6ce933ba6455.jpg" alt="">
其中master节点的职责如下
>
The master node is responsible for lightweight cluster-wide actions such as creating or deleting an index, tracking which nodes are part of the cluster, and deciding which shards to allocate to which nodes. It is important for cluster health to have a stable master node.
来源:[https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html)
数据集中集群架构中客户端只能将数据写到主机数据分散集群架构中客户端可以向任意服务器中读写数据。正是因为这个关键的差异决定了两种集群的应用场景不同。一般来说数据集中集群适合数据量不大集群机器数量不多的场景。例如ZooKeeper集群一般推荐5台机器左右数据量是单台服务器就能够支撑而数据分散集群由于其良好的可伸缩性适合业务数据量巨大、集群机器数量庞大的业务场景。例如Hadoop集群、HBase集群大规模的集群可以达到上百台甚至上千台服务器。
## 数据分区
前面我们讨论的存储高可用架构都是基于硬件故障的场景去考虑和设计的,主要考虑当部分硬件可能损坏的情况下系统应该如何处理,但对于一些影响非常大的灾难或者事故来说,有可能所有的硬件全部故障。例如,新奥尔良水灾、美加大停电、洛杉矶大地震等这些极端灾害或者事故,可能会导致一个城市甚至一个地区的所有基础设施瘫痪,这种情况下基于硬件故障而设计的高可用架构不再适用,我们需要基于地理级别的故障来设计高可用架构,这就是数据分区架构产生的背景。
数据分区指将数据按照一定的规则进行分区,不同分区分布在不同的地理位置上,每个分区存储一部分数据,通过这种方式来规避地理级别的故障所造成的巨大影响。采用了数据分区的架构后,即使某个地区发生严重的自然灾害或者事故,受影响的也只是一部分数据,而不是全部数据都不可用;当故障恢复后,其他地区备份的数据也可以帮助故障地区快速恢复业务。
设计一个良好的数据分区架构,需要从多方面去考虑。
1.数据量
数据量的大小直接决定了分区的规则复杂度。例如使用MySQL来存储数据假设一台MySQL存储能力是500GB那么2TB的数据就至少需要4台MySQL服务器而如果数据是200TB并不是增加到800台的MySQL服务器那么简单。如果按照4台服务器那样去平行管理800台服务器复杂度会发生本质的变化具体表现为
<li>
800台服务器里面可能每周都有一两台服务器故障从800台里面定位出2台服务器故障很多情况下并不是一件容易的事情运维复杂度高。
</li>
<li>
增加新的服务器分区相关的配置甚至规则需要修改而每次修改理论上都有可能影响已有的800台服务器的运行不小心改错配置的情况在实践中太常见了。
</li>
<li>
如此大量的数据,如果在地理位置上全部集中于某个城市,风险很大,遇到了水灾、大停电这种灾难性的故障时,数据可能全部丢失,因此分区规则需要考虑地理容灾。
</li>
因此,数据量越大,分区规则会越复杂,考虑的情况也越多。
2.分区规则
地理位置有近有远,因此可以得到不同的分区规则,包括洲际分区、国家分区、城市分区。具体采取哪种或者哪几种规则,需要综合考虑业务范围、成本等因素。
通常情况下,洲际分区主要用于面向不同大洲提供服务,由于跨洲通讯的网络延迟已经大到不适合提供在线服务了,因此洲际间的数据中心可以不互通或者仅仅作为备份;国家分区主要用于面向不同国家的用户提供服务,不同国家有不同语言、法律、业务等,国家间的分区一般也仅作为备份;城市分区由于都在同一个国家或者地区内,网络延迟较低,业务相似,分区同时对外提供服务,可以满足业务异地多活之类的需求。
3.复制规则
数据分区指将数据分散在多个地区,在某些异常或者灾难情况下,虽然部分数据受影响,但整体数据并没有全部被影响,本身就相当于一个高可用方案了。但仅仅做到这点还不够,因为每个分区本身的数据量虽然只是整体数据的一部分,但还是很大,这部分数据如果损坏或者丢失,损失同样难以接受。因此即使是分区架构,同样需要考虑复制方案。
常见的分区复制规则有三种:集中式、互备式和独立式。
**集中式**
集中式备份指存在一个总的备份中心,所有的分区都将数据备份到备份中心,其基本架构如下:
<img src="https://static001.geekbang.org/resource/image/3f/0f/3fd32ca52652e208d8cf07f0cabd2a0f.jpg" alt="">
集中式备份架构的优缺点是:
<li>
设计简单,各分区之间并无直接联系,可以做到互不影响。
</li>
<li>
扩展容易,如果要增加第四个分区(例如,武汉分区),只需要将武汉分区的数据复制到西安备份中心即可,其他分区不受影响。
</li>
<li>
成本较高,需要建设一个独立的备份中心。
</li>
**互备式**
互备式备份指每个分区备份另外一个分区的数据,其基本架构如下:
<img src="https://static001.geekbang.org/resource/image/9f/73/9f7b87dcaf2e7ae49482b3ab0ba2ae73.jpg" alt=""><br>
互备式备份架构的优缺点是:
<li>
设计比较复杂,各个分区除了要承担业务数据存储,还需要承担备份功能,相互之间互相关联和影响。
</li>
<li>
扩展麻烦,如果增加一个武汉分区,则需要修改广州分区的复制指向武汉分区,然后将武汉分区的复制指向北京分区。而原有北京分区已经备份了的广州分区的数据怎么处理也是个难题,不管是做数据迁移,还是广州分区历史数据保留在北京分区,新数据备份到武汉分区,无论哪种方式都很麻烦。
</li>
<li>
成本低,直接利用已有的设备。
</li>
**独立式**
独立式备份指每个分区自己有独立的备份中心,其基本架构如下:
<img src="https://static001.geekbang.org/resource/image/24/55/241ed707c8ec922f160760e0cb075b55.jpg" alt="">
有一个细节需要特别注意,各个分区的备份并不和原来的分区在一个地方。例如,北京分区的备份放到了天津,上海的放到了杭州,广州的放到了汕头,这样做的主要目的是规避同城或者相同地理位置同时发生灾难性故障的极端情况。如果北京分区机房在朝阳区,而备份机房放在通州区,整个北京停电的话,两个机房都无法工作。
独立式备份架构的优缺点是:
<li>
设计简单,各分区互不影响。
</li>
<li>
扩展容易,新增加的分区只需要搭建自己的备份中心即可。
</li>
<li>
成本高,每个分区需要独立的备份中心,备份中心的场地成本是主要成本,因此独立式比集中式成本要高很多。
</li>
## 小结
今天我为你讲了大数据量存储的两种高可用存储架构:集群架构和分区架构,并介绍了其中的关键设计点,希望对你有所帮助。
这就是今天的全部内容,留一道思考题给你吧,既然数据集群就可以做到不同节点之间复制数据,为何不搭建一个远距离分布的集群来应对地理位置级别的故障呢?
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)

View File

@@ -0,0 +1,178 @@
<audio id="audio" title="27 | 如何设计计算高可用架构?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d7/61/d7777a96a51b30c4c0c5d2032f3b6161.mp3"></audio>
计算高可用的主要设计目标是当出现部分硬件损坏时,计算任务能够继续正常运行。因此计算高可用的本质是通过冗余来规避部分故障的风险,单台服务器是无论如何都达不到这个目标的。所以计算高可用的设计思想很简单:通过增加更多服务器来达到计算高可用。
计算高可用架构的设计复杂度主要体现在**任务管理**方面,即当任务在某台服务器上执行失败后,如何将任务重新分配到新的服务器进行执行。因此,计算高可用架构设计的关键点有下面两点。
1.哪些服务器可以执行任务
第一种方式和计算高性能中的集群类似,每个服务器都可以执行任务。例如,常见的访问网站的某个页面。
第二种方式和存储高可用中的集群类似只有特定服务器通常叫“主机”可以执行任务。当执行任务的服务器故障后系统需要挑选新的服务器来执行任务。例如ZooKeeper的Leader才能处理写操作请求。
2.任务如何重新执行
第一种策略是对于已经分配的任务即使执行失败也不做任何处理,系统只需要保证新的任务能够分配到其他非故障服务器上执行即可。
第二种策略是设计一个任务管理器来管理需要执行的计算任务,服务器执行完任务后,需要向任务管理器反馈任务执行结果,任务管理器根据任务执行结果来决定是否需要将任务重新分配到另外的服务器上执行。
需要注意的是:“任务分配器”是一个逻辑的概念,并不一定要求系统存在一个独立的任务分配器模块。例如:
<li>
Nginx将页面请求发送给Web服务器而CSS/JS等静态文件直接读取本地缓存。这里的Nginx角色是反向代理系统但是承担了任务分配器的职责而不需要Nginx做反向代理后面再来一个任务分配器。
</li>
<li>
对于一些后台批量运算的任务,可以设计一个独立的任务分配系统来管理这些批处理任务的执行和分配。
</li>
<li>
ZooKeeper中的Follower节点当接收到写请求时会将请求转发给Leader节点处理当接收到读请求时就自己处理这里的Follower就相当于一个逻辑上的任务分配器。
</li>
接下来,我将详细阐述常见的计算高可用架构:主备、主从和集群。
## 主备
主备架构是计算高可用最简单的架构,和存储高可用的主备复制架构类似,但是要更简单一些,因为计算高可用的主备架构无须数据复制,其基本的架构示意图如下:
<img src="https://static001.geekbang.org/resource/image/83/d6/83e0a9670c16yyf0a074776325f840d6.jpg" alt="">
主备方案的详细设计:
<li>
主机执行所有计算任务。例如,读写数据、执行操作等。
</li>
<li>
当主机故障(例如,主机宕机)时,任务分配器不会自动将计算任务发送给备机,此时系统处于不可用状态。
</li>
<li>
如果主机能够恢复(不管是人工恢复还是自动恢复),任务分配器继续将任务发送给主机。
</li>
<li>
如果主机不能够恢复(例如,机器硬盘损坏,短时间内无法恢复),则需要人工操作,将备机升为主机,然后让任务分配器将任务发送给新的主机(即原来的备机);同时,为了继续保持主备架构,需要人工增加新的机器作为备机。
</li>
根据备机状态的不同,主备架构又可以细分为冷备架构和温备架构。
**冷备**:备机上的程序包和配置文件都准备好,但备机上的业务系统没有启动(注意:备机的服务器是启动的),主机故障后,需要人工手工将备机的业务系统启动,并将任务分配器的任务请求切换发送给备机。
**温备**:备机上的业务系统已经启动,只是不对外提供服务,主机故障后,人工只需要将任务分配器的任务请求切换发送到备机即可。冷备可以节省一定的能源,但温备能够大大减少手工操作时间,因此一般情况下推荐用温备的方式。
主备架构的优点就是简单主备机之间不需要进行交互状态判断和切换操作由人工执行系统实现很简单。而缺点正好也体现在“人工操作”这点上因为人工操作的时间不可控可能系统已经发生问题了但维护人员还没发现等了1个小时才发现。发现后人工切换的操作效率也比较低可能需要半个小时才完成切换操作而且手工操作过程中容易出错。例如修改配置文件改错了、启动了错误的程序等。
和存储高可用中的主备复制架构类似,计算高可用的主备架构也比较适合与内部管理系统、后台管理系统这类使用人数不多、使用频率不高的业务,不太适合在线的业务。
## 主从
和存储高可用中的主从复制架构类似,计算高可用的主从架构中的从机也是要执行任务的。任务分配器需要将任务进行分类,确定哪些任务可以发送给主机执行,哪些任务可以发送给备机执行,其基本的架构示意图如下:
<img src="https://static001.geekbang.org/resource/image/7e/5e/7e19db520ec219ec68c83c7d0639455e.jpg" alt="">
主从方案详细设计:
<li>
正常情况下主机执行部分计算任务如图中的“计算任务A”备机执行部分计算任务如图中的“计算任务B”
</li>
<li>
当主机故障(例如,主机宕机)时,任务分配器不会自动将原本发送给主机的任务发送给从机,而是继续发送给主机,不管这些任务执行是否成功。
</li>
<li>
如果主机能够恢复不管是人工恢复还是自动恢复任务分配器继续按照原有的设计策略分配任务即计算任务A发送给主机计算任务B发送给从机。
</li>
<li>
如果主机不能够恢复(例如,机器硬盘损坏,短时间内无法恢复),则需要人工操作,将原来的从机升级为主机(一般只是修改配置即可),增加新的机器作为从机,新的从机准备就绪后,任务分配器继续按照原有的设计策略分配任务。
</li>
主从架构与主备架构相比,优缺点有:
<li>
优点:主从架构的从机也执行任务,发挥了从机的硬件性能。
</li>
<li>
缺点:主从架构需要将任务分类,任务分配器会复杂一些。
</li>
## 集群
主备架构和主从架构通过冗余一台服务器来提升可用性,且需要人工来切换主备或者主从。这样的架构虽然简单,但存在一个主要的问题:人工操作效率低、容易出错、不能及时处理故障。因此在可用性要求更加严格的场景中,我们需要系统能够自动完成切换操作,这就是高可用集群方案。
高可用计算的集群方案根据集群中服务器节点角色的不同可以分为两类一类是对称集群即集群中每个服务器的角色都是一样的都可以执行所有任务另一类是非对称集群集群中的服务器分为多个不同的角色不同的角色执行不同的任务例如最常见的Master-Slave角色。
需要注意的是计算高可用集群包含2台服务器的集群这点和存储高可用集群不太一样。存储高可用集群把双机架构和集群架构进行了区分而在计算高可用集群架构中2台服务器的集群和多台服务器的集群在设计上没有本质区别因此不需要进行区分。
**对称集群**
对称集群更通俗的叫法是负载均衡集群,因此接下来我使用“负载均衡集群”这个通俗的说法,架构示意图如下:
<img src="https://static001.geekbang.org/resource/image/2f/a8/2f4079bceeb190987d495b06a744c9a8.jpg" alt="">
负载均衡集群详细设计:
<li>
正常情况下,任务分配器采取某种策略(随机、轮询等)将计算任务分配给集群中的不同服务器。
</li>
<li>
当集群中的某台服务器故障后,任务分配器不再将任务分配给它,而是将任务分配给其他服务器执行。
</li>
<li>
当故障的服务器恢复后,任务分配器重新将任务分配给它执行。
</li>
负载均衡集群的设计关键点在于两点:
<li>
任务分配器需要选取分配策略。
</li>
<li>
任务分配器需要检测服务器状态。
</li>
任务分配策略比较简单,轮询和随机基本就够了。状态检测稍微复杂一些,既要检测服务器的状态,例如服务器是否宕机、网络是否正常等;同时还要检测任务的执行状态,例如任务是否卡死、是否执行时间过长等。常用的做法是任务分配器和服务器之间通过心跳来传递信息,包括服务器信息和任务信息,然后根据实际情况来确定状态判断条件。
例如一个在线页面访问系统正常情况下页面平均会在500毫秒内返回那么状态判断条件可以设计为1分钟内响应时间超过1秒包括超时的页面数量占了80%时,就认为服务器有故障。
例如一个后台统计任务系统正常情况下任务会在5分钟内执行完成那么状态判断条件可以设计为单个任务执行时间超过10分钟还没有结束就认为服务器有故障。
通过上面两个案例可以看出,不同业务场景的状态判断条件差异很大,实际设计时要根据业务需求来进行设计和调优。
**非对称集群**
非对称集群中不同服务器的角色是不同的不同角色的服务器承担不同的职责。以Master-Slave为例部分任务是Master服务器才能执行部分任务是Slave服务器才能执行。非对称集群的基本架构示意图如下
<img src="https://static001.geekbang.org/resource/image/ca/14/cae563655fd7cea154932yy6ca7db114.jpg" alt=""><br>
非对称集群架构详细设计:
<li>
集群会通过某种方式来区分不同服务器的角色。例如通过ZAB算法选举或者简单地取当前存活服务器中节点ID最小的服务器作为Master服务器。
</li>
<li>
任务分配器将不同任务发送给不同服务器。例如图中的计算任务A发送给Master服务器计算任务B发送给Slave服务器。
</li>
<li>
当指定类型的服务器故障时需要重新分配角色。例如Master服务器故障后需要将剩余的Slave服务器中的一个重新指定为Master服务器如果是Slave服务器故障则并不需要重新分配角色只需要将故障服务器从集群剔除即可。
</li>
非对称集群相比负载均衡集群,设计复杂度主要体现在两个方面:
<li>
任务分配策略更加复杂:需要将任务划分为不同类型并分配给不同角色的集群节点。
</li>
<li>
角色分配策略实现比较复杂例如可能需要使用ZAB、Raft这类复杂的算法来实现Leader的选举。
</li>
我以ZooKeeper为例
<li>
任务分配器ZooKeeper中不存在独立的任务分配器节点每个Server都是任务分配器Follower收到请求后会进行判断如果是写请求就转发给Leader如果是读请求就自己处理。
</li>
<li>
角色指定ZooKeeper通过ZAB算法来选举Leader当Leader故障后所有的Follower节点会暂停读写操作开始进行选举直到新的Leader选举出来后才继续对Client提供服务。
</li>
## 小结
今天我为你讲了几种常见的计算高可用架构,并分析了不同方案的详细设计,希望对你有所帮助。
这就是今天的全部内容,留一道思考题给你吧,计算高可用架构从形式上和存储高可用架构看上去几乎一样,它们的复杂度是一样的么?谈谈你的理解。
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)

View File

@@ -0,0 +1,107 @@
<audio id="audio" title="28 | 业务高可用的保障:异地多活架构" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3c/7f/3ce36949b3666a8b0714da3cefaca47f.mp3"></audio>
无论是高可用计算架构还是高可用存储架构其本质的设计目的都是为了解决部分服务器故障的场景下如何保证系统能够继续提供服务。但在一些极端场景下有可能所有服务器都出现故障。例如典型的有机房断电、机房火灾、地震、水灾……这些极端情况会导致某个系统所有服务器都故障或者业务整体瘫痪而且即使有其他地区的备份把备份业务系统全部恢复到能够正常提供业务花费的时间也比较长可能是半小时也可能是12小时。因为备份系统平时不对外提供服务可能会存在很多隐藏的问题没有发现。如果业务期望达到即使在此类灾难性故障的情况下业务也不受影响或者在几分钟内就能够很快恢复那么就需要设计异地多活架构。
今天我来聊聊异地多活架构,接下来还会再讲异地多活架构的设计技巧和流程。
## 应用场景
顾名思义,异地多活架构的关键点就是异地、多活,其中异地就是指地理位置上不同的地方,类似于“不要把鸡蛋都放在同一篮子里”;多活就是指不同地理位置上的系统都能够提供业务服务,这里的“活”是活动、活跃的意思。判断一个系统是否符合异地多活,需要满足两个标准:
<li>
正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务。
</li>
<li>
某个地方业务异常的时候,用户访问其他地方正常的业务系统,能够得到正确的业务服务。
</li>
与“活”对应的是字是“备”,备是备份,正常情况下对外是不提供服务的,如果需要提供服务,则需要大量的人工干预和操作,花费大量的时间才能让“备”变成“活”。
单纯从异地多活的描述来看,异地多活很强大,能够保证在灾难的情况下业务都不受影响。那是不是意味着不管什么业务,我们都要去实现异地多活架构呢?其实不然,因为实现异地多活架构不是没有代价的,相反其**代价很高**,具体表现为:
<li>
系统复杂度会发生质的变化,需要设计复杂的异地多活架构。
</li>
<li>
成本会上升,毕竟要多在一个或者多个机房搭建独立的一套业务系统。
</li>
因此异地多活虽然功能很强大但也不是每个业务不管三七二十一都要上异地多活。例如常见的新闻网站、企业内部的IT系统、游戏、博客站点等如果无法承受异地多活带来的复杂度和成本是可以不做异地多活的只需要做异地备份即可。因为这类业务系统即使中断对用户的影响并不会很大例如A新闻网站看不了用户换个新闻网站即可。而共享单车、滴滴出行、支付宝、微信这类业务就需要做异地多活了这类业务系统中断后对用户的影响很大。例如支付宝用不了就没法买东西了滴滴用不了用户就打不到车了。
当然,如果业务规模很大,能够做异地多活的情况下还是尽量。首先,这样能够在异常的场景下给用户提供更好的体验;其次,业务规模很大肯定会伴随衍生的收入,例如广告收入,异地多活能够减少异常场景带来的收入损失。同样以新闻网站为例,虽然从业务的角度来看,新闻类网站对用户影响不大,反正用户也可以从其他地方看到基本相同的新闻,甚至用户几个小时不看新闻也没什么问题。但是从网站本身来看,几个小时不可访问肯定会影响用户对网站的口碑;其次几个小时不可访问,网站上的广告收入损失也会很大。
## 架构模式
根据地理位置上的距离来划分,异地多活架构可以分为同城异区、跨城异地、跨国异地。接下来我详细解释一下每一种架构的细节与优缺点。
1.同城异区
同城异区指的是将业务部署在同一个城市不同区的多个机房。例如,在北京部署两个机房,一个机房在海淀区,一个在通州区,然后将两个机房用专用的高速网络连接在一起。
如果我们考虑一些极端场景(例如,美加大停电、新奥尔良水灾),同城异区似乎没什么作用,那为何我们还要设计同城异区这种架构呢?答案就在于“同城”。
同城的两个机房,距离上一般大约就是几十千米,通过搭建高速的网络,同城异区的两个机房能够实现和同一个机房内几乎一样的网络传输速度。这就意味着虽然是两个不同地理位置上的机房,但逻辑上我们可以将它们看作同一个机房,这样的设计大大降低了复杂度,减少了异地多活的设计和实现复杂度及成本。
那如果采用了同城异区架构,一旦发生新奥尔良水灾这种灾难怎么办呢?很遗憾,答案是无能为力。但我们需要考虑的是,这种极端灾难发生概率是比较低的,可能几年或者十几年才发生一次。其次,除了这类灾难,机房火灾、机房停电、机房空调故障这类问题发生的概率更高,而且破坏力一样很大。而这些故障场景,同城异区架构都可以很好地解决。因此,结合复杂度、成本、故障发生概率来综合考虑,同城异区是应对机房级别故障的最优架构。
2.跨城异地
跨城异地指的是业务部署在不同城市的多个机房,而且距离最好要远一些。例如,将业务部署在北京和广州两个机房,而不是将业务部署在广州和深圳的两个机房。
为何跨城异地要强调距离要远呢?前面我在介绍同城异区的架构时提到同城异区不能解决新奥尔良水灾这种问题,而两个城市离得太近又无法应对如美加大停电这种问题,跨城异地其实就是为了解决这两类问题的,因此需要在距离上比较远,才能有效应对这类极端灾难事件。
跨城异地虽然能够有效应对极端灾难事件但“距离较远”这点并不只是一个距离数字上的变化而是量变引起了质变导致了跨城异地的架构复杂度大大上升。距离增加带来的最主要问题是两个机房的网络传输速度会降低这不是以人的意志为转移的而是物理定律决定的即光速真空传播大约是每秒30万千米在光纤中传输的速度大约是每秒20万千米再加上传输中的各种网络设备的处理实际还远远达不到理论上的速度。
除了距离上的限制中间传输各种不可控的因素也非常多。例如挖掘机把光纤挖断、中美海底电缆被拖船扯断、骨干网故障等这些线路很多是第三方维护针对故障我们根本无能为力也无法预知。例如广州机房到北京机房正常情况下RTT大约是50毫秒左右遇到网络波动之类的情况RTT可能飙升到500毫秒甚至1秒更不用说经常发生的线路丢包问题那延迟可能就是几秒几十秒了。
以上描述的问题,虽然同城异区理论上也会遇到,但由于同城异区距离较短,中间经过的线路和设备较少,问题发生的概率会低很多。而且同城异区距离短,即使是搭建多条互联通道,成本也不会太高,而跨城异区距离太远,搭建或者使用多通道的成本会高不少。
跨城异地距离较远带来的网络传输延迟问题,给异地多活架构设计带来了复杂性,如果要做到真正意义上的多活,业务系统需要考虑部署在不同地点的两个机房,在数据短时间不一致的情况下,还能够正常提供业务。这就引入了一个看似矛盾的地方:数据不一致业务肯定不会正常,但跨城异地肯定会导致数据不一致。
如何解决这个问题呢?重点还是在“数据”上,即根据数据的特性来做不同的架构。如果是强一致性要求的数据,例如银行存款余额、支付宝余额等,这类数据实际上是无法做到跨城异地多活的。我们来看一个假设的例子,假如我们做一个互联网金融的业务,用户余额支持跨城异地多活,我们的系统分别部署在广州和北京,那么如果挖掘机挖断光缆后,会出现如下场景:
<li>
用户A余额有10000元钱北京和广州机房都是这个数据。
</li>
<li>
用户A向用户B转了5000元钱这个操作是在广州机房完成的完成后用户A在广州机房的余额是5000元。
</li>
<li>
由于广州和北京机房网络被挖掘机挖断广州机房无法将余额变动通知北京机房此时北京机房用户A的余额还是10000元。
</li>
<li>
用户A到北京机房又发起转账此时他看到自己的余额还有10000元于是向用户C转账10000元转账完成后用户A的余额变为0。
</li>
<li>
用户A到广州机房一看余额怎么还有5000元于是赶紧又发起转账转账5000元给用户D此时广州机房用户A的余额也变为0了。
</li>
最终本来余额10000元的用户A却转了20000元出去给其他用户。
对于以上这种假设场景,虽然普通用户很难这样自如地操作,但如果真的这么做,被黑客发现后,后果不堪设想。正因为如此,支付宝等金融相关的系统,对余额这类数据,一般不会做跨城异地的多活架构,而只能采用同城异区这种架构。
而对数据一致性要求不那么高,或者数据不怎么改变,或者即使数据丢失影响也不大的业务,跨城异地多活就能够派上用场了。例如,用户登录(数据不一致时用户重新登录即可)、新闻类网站(一天内的新闻数据变化较少)、微博类网站(丢失用户发布的微博或者评论影响不大),这些业务采用跨城异地多活,能够很好地应对极端灾难的场景。
3.跨国异地
跨国异地指的是业务部署在不同国家的多个机房。相比跨城异地跨国异地的距离就更远了因此数据同步的延时会更长正常情况下可能就有几秒钟了。这种程度的延迟已经无法满足异地多活标准的第一条“正常情况下用户无论访问哪一个地点的业务系统都能够得到正确的业务服务”。例如假设有一个微博类网站分别在中国的上海和美国的纽约都建了机房用户A在上海机房发表了一篇微博此时如果他的一个关注者B用户访问到美国的机房很可能无法看到用户A刚刚发表的微博。虽然跨城异地也会有此类同步延时问题但正常情况下几十毫秒的延时对用户来说基本无感知的而延时达到几秒钟就感觉比较明显了。
因此,跨国异地的“多活”,和跨城异地的“多活”,实际的含义并不完全一致。跨国异地多活的主要应用场景一般有这几种情况:
- 为不同地区用户提供服务
例如,亚马逊中国是为中国用户服务的,而亚马逊美国是为美国用户服务的,亚马逊中国的用户如果访问美国亚马逊,是无法用亚马逊中国的账号登录美国亚马逊的。
- 只读类业务做多活
例如,谷歌的搜索业务,由于用户搜索资料时,这些资料都已经存在于谷歌的搜索引擎上面,无论是访问英国谷歌,还是访问美国谷歌,搜索结果基本相同,并且对用户来说,也不需要搜索到最新的实时资料,跨国异地的几秒钟网络延迟,对搜索结果是没有什么影响的。
## 小结
今天我为你讲了异地多活架构的应用场景和常见架构模式,希望对你有所帮助。
这就是今天的全部内容留一道思考题给你吧假设我们做了前面提到的高可用存储架构中的数据分区备份又通过自动化运维能够保证1分钟就能将全部系统正常启动那是否意味着没有必要做异地多活了
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)

View File

@@ -0,0 +1,171 @@
<audio id="audio" title="29 | 异地多活设计4大技巧" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/84/af/845ff7a36dcb9a6ac559c2ad945d43af.mp3"></audio>
专栏上一期我介绍了三种不同类型的异地多活架构,复习一下每个架构的关键点:
- 同城异区
关键在于搭建高速网络将两个机房连接起来,达到近似一个本地机房的效果。架构设计上可以将两个机房当作本地机房来设计,无须额外考虑。
- 跨城异地
关键在于数据不一致的情况下,业务不受影响或者影响很小,这从逻辑的角度上来说其实是矛盾的,架构设计的主要目的就是为了解决这个矛盾。
- 跨国异地
主要是面向不同地区用户提供业务,或者提供只读业务,对架构设计要求不高。
基于这个分析跨城异地多活是架构设计复杂度最高的一种接下来我将介绍跨城异地多活架构设计的一些技巧和步骤今天我们先来看4大技巧掌握这些技巧可以说是完成好设计步骤的前提。
## 技巧1保证核心业务的异地多活
“异地多活”是为了保证业务的高可用,但很多架构师在考虑这个“业务”时,会不自觉地陷入一个思维误区:我要保证所有业务都能“异地多活”!
假设我们需要做一个“用户子系统”这个子系统负责“注册”“登录”“用户信息”三个业务。为了支持海量用户我们设计了一个“用户分区”的架构即正常情况下用户属于某个主分区每个分区都有其他数据的备份用户用邮箱或者手机号注册路由层拿到邮箱或者手机号后通过Hash计算属于哪个中心然后请求对应的业务中心。基本的架构如下
<img src="https://static001.geekbang.org/resource/image/88/72/88c512c2cd50bb8a5f597c2819d30472.jpg" alt="">
这样一个系统如果3个业务要同时实现异地多活会发现这些难以解决的问题
- 注册问题
A中心注册了用户数据还未同步到B中心此时A中心宕机为了支持注册业务多活可以挑选B中心让用户去重新注册。看起来很容易就支持多活了但仔细思考一下会发现这样做会有问题一个手机号只能注册一个账号A中心的数据没有同步过来B中心无法判断这个手机号是否重复如果B中心让用户注册后来A中心恢复了发现数据有冲突怎么解决实际上是无法解决的因为同一个手机号注册的账号不能以后一次注册为准而如果B中心不支持本来属于A中心的业务进行注册注册业务的多活又成了空谈。
如果我们修改业务规则,允许一个手机号注册多个账号不就可以了吗?
这样做是不可行的,类似一个手机号只能注册一个账号这种规则,是核心业务规则,修改核心业务规则的代价非常大,几乎所有的业务都要重新设计,为了架构设计去改变业务规则(而且是这么核心的业务规则)是得不偿失的。
- 用户信息问题
用户信息的修改和注册有类似的问题即A、B两个中心在异常的情况下都修改了用户信息如何处理冲突
由于用户信息并没有账号那么关键一种简单的处理方式是按照时间合并即最后修改的生效。业务逻辑上没问题但实际操作也有一个很关键的“坑”怎么保证多个中心所有机器时间绝对一致在异地多中心的网络下这个是无法保证的即使有时间同步也无法完全保证只要两个中心的时间误差超过1秒数据就可能出现混乱即先修改的反而生效。
还有一种方式是生成全局唯一递增ID这个方案的成本很高因为这个全局唯一递增ID的系统本身又要考虑异地多活同样涉及数据一致性和冲突的问题。
综合上面的简单分析可以发现,如果“注册”“登录”“用户信息”全部都要支持异地多活,实际上是挺难的,有的问题甚至是无解的。那这种情况下我们应该如何考虑“异地多活”的架构设计呢?答案其实很简单:**优先实现核心业务的异地多活架构!**
对于这个模拟案例来说“登录”才是最核心的业务“注册”和“用户信息”虽然也是主要业务但并不一定要实现异地多活主要原因在于业务影响不同。对于一个日活1000万的业务来说每天注册用户可能是几万修改用户信息的可能还不到1万但登录用户是1000万很明显我们应该保证登录的异地多活。
对于新用户来说,注册不了的影响并不明显,因为他还没有真正开始使用业务。用户信息修改也类似,暂时修改不了用户信息,对于其业务不会有很大影响。而如果有几百万用户登录不了,就相当于几百万用户无法使用业务,对业务的影响就非常大了:公司的客服热线很快就被打爆,微博、微信上到处都在传业务宕机,论坛里面到处是抱怨的用户,那就是互联网大事件了!
而登录实现“异地多活”恰恰是最简单的因为每个中心都有所有用户的账号和密码信息用户在哪个中心都可以登录。用户在A中心登录A中心宕机后用户到B中心重新登录即可。
如果某个用户在A中心修改了密码此时数据还没有同步到B中心用户到B中心登录是无法登录的这个怎么处理这个问题其实就涉及另外一个设计技巧了我卖个关子稍后再谈。
## 技巧2保证核心数据最终一致性
异地多活本质上是通过异地的数据冗余,来保证在极端异常的情况下业务也能够正常提供给用户,因此数据同步是异地多活架构设计的核心。但大部分架构师在考虑数据同步方案时,会不知不觉地陷入完美主义误区:我要所有数据都实时同步!
数据冗余是要将数据从A地同步到B地从业务的角度来看是越快越好最好和本地机房一样的速度最好。但让人头疼的问题正在这里异地多活理论上就不可能很快因为这是物理定律决定的我在上一期已有说明
因此异地多活架构面临一个无法彻底解决的矛盾:业务上要求数据快速同步,物理上正好做不到数据快速同步,因此所有数据都实时同步,实际上是一个无法达到的目标。
既然是无法彻底解决的矛盾,那就只能想办法尽量减少影响。有几种方法可以参考:
- 尽量减少异地多活机房的距离,搭建高速网络
这和我上一期讲到的同城异区架构类似,但搭建跨城异地的高速网络成本远远超过同城异区的高速网络,成本巨大,一般只有巨头公司才能承担。
- 尽量减少数据同步,只同步核心业务相关的数据
简单来说就是不重要的数据不同步,同步后没用的数据不同步,只同步核心业务相关的数据。
以前面的“用户子系统”为例用户登录所产生的token或者session信息数据量很大但其实并不需要同步到其他业务中心因为这些数据丢失后重新登录就可以再次获取了。
这时你可能会想到:这些数据丢失后要求用户重新登录,影响用户体验!
确实如此,毕竟需要用户重新输入账户和密码信息,或者至少要弹出登录界面让用户点击一次,但相比为了同步所有数据带来的代价,这个影响完全可以接受。为什么这么说呢,还是卖个关子我会在后面分析。
- 保证最终一致性,不保证实时一致性
最终一致性就是[专栏第23期](http://time.geekbang.org/column/article/9390)在介绍CAP理论时提到的BASE理论即业务不依赖数据同步的实时性只要数据最终能一致即可。例如A机房注册了一个用户业务上不要求能够在50毫秒内就同步到所有机房正常情况下要求5分钟同步到所有机房即可异常情况下甚至可以允许1小时或者1天后能够一致。
最终一致性在具体实现时还需要根据不同的数据特征进行差异化的处理以满足业务需要。例如对“账号”信息来说如果在A机房新注册的用户5分钟内正好跑到B机房了此时B机房还没有这个用户的信息为了保证业务的正确B机房就需要根据路由规则到A机房请求数据。
而对“用户信息”来说5分钟后同步也没有问题也不需要采取其他措施来弥补但还是会影响用户体验即用户看到了旧的用户信息这个问题怎么解决呢好像又是一个解决不了的问题和前面我留下的两个问题一起在最后我来给出答案。
## 技巧3采用多种手段同步数据
数据同步是异地多活架构设计的核心幸运的是基本上存储系统本身都会有同步的功能。例如MySQL的主备复制、Redis的Cluster功能、Elasticsearch的集群功能。这些系统本身的同步功能已经比较强大能够直接拿来就用但这也无形中将我们引入了一个思维误区只使用存储系统的同步功能
既然说存储系统本身就有同步功能,而且同步功能还很强大,为何说只使用存储系统是一个思维误区呢?因为虽然绝大部分场景下,存储系统本身的同步功能基本上也够用了,但在某些比较极端的情况下,存储系统本身的同步功能可能难以满足业务需求。
以MySQL为例MySQL 5.1版本的复制是单线程的复制在网络抖动或者大量数据同步时经常发生延迟较长的问题短则延迟十几秒长则可能达到十几分钟。而且即使我们通过监控的手段知道了MySQL同步时延较长也难以采取什么措施只能干等。
Redis又是另外一个问题Redis 3.0之前没有Cluster功能只有主从复制功能而为了设计上的简单Redis 2.8之前的版本,主从复制有一个比较大的隐患:从机宕机或者和主机断开连接都需要重新连接主机,重新连接主机都会触发全量的主从复制。这时主机会生成内存快照,主机依然可以对外提供服务,但是作为读的从机,就无法提供对外服务了,如果数据量大,恢复的时间会相当长。
综合上面的案例可以看出,存储系统本身自带的同步功能,在某些场景下是无法满足业务需要的。尤其是异地多机房这种部署,各种各样的异常情况都可能出现,当我们只考虑存储系统本身的同步功能时,就会发现无法做到真正的异地多活。
解决的方案就是拓开思路,避免只使用存储系统的同步功能,可以将多种手段配合存储系统的同步来使用,甚至可以不采用存储系统的同步方案,改用自己的同步方案。
还是以前面的“用户子系统”为例,我们可以采用如下几种方式同步数据:
- 消息队列方式
对于账号数据,由于账号只会创建,不会修改和删除(假设我们不提供删除功能),我们可以将账号数据通过消息队列同步到其他业务中心。
- 二次读取方式
某些情况下可能出现消息队列同步也延迟了用户在A中心注册然后访问B中心的业务此时B中心本地拿不到用户的账号数据。为了解决这个问题B中心在读取本地数据失败时可以根据路由规则再去A中心访问一次这就是所谓的二次读取第一次读取本地本地失败后第二次读取对端这样就能够解决异常情况下同步延迟的问题。
- 存储系统同步方式
对于密码数据由于用户改密码频率较低而且用户不可能在1秒内连续改多次密码所以通过数据库的同步机制将数据复制到其他业务中心即可用户信息数据和密码类似。
- 回源读取方式
对于登录的session数据由于数据量很大我们可以不同步数据但当用户在A中心登录后然后又在B中心登录B中心拿到用户上传的session id后根据路由判断session属于A中心直接去A中心请求session数据即可反之亦然A中心也可以到B中心去获取session数据。
- 重新生成数据方式
对于“回源读取”场景如果异常情况下A中心宕机了B中心请求session数据失败此时就只能登录失败让用户重新在B中心登录生成新的session数据。
注意:以上方案仅仅是示意,实际的设计方案要比这个复杂一些,还有很多细节要考虑。
综合上述的各种措施,最后“用户子系统”同步方式整体如下:
<img src="https://static001.geekbang.org/resource/image/85/84/8562cb9a3b35441fc83990e731de3184.jpg" alt="">
## 技巧4只保证绝大部分用户的异地多活
前面我在给出每个思维误区对应的解决方案时留下了几个小尾巴某些场景下我们无法保证100%的业务可用性,总是会有一定的损失。例如,密码不同步导致无法登录、用户信息不同步导致用户看到旧的信息等,这个问题怎么解决呢?
其实这个问题涉及异地多活架构设计中一个典型的思维误区我要保证业务100%可用但极端情况下就是会丢一部分数据就是会有一部分数据不能同步有没有什么巧妙能做到100%可用呢?
很遗憾答案是没有异地多活也无法保证100%的业务可用这是由物理规律决定的光速和网络的传播速度、硬盘的读写速度、极端异常情况的不可控等都是无法100%解决的。所以针对这个思维误区我的答案是“忍”也就是说我们要忍受这一小部分用户或者业务上的损失否则本来想为了保证最后的0.01%的用户的可用性做一个完美方案结果却发现99.99%的用户都保证不了了。
对于某些实时强一致性的业务实际上受影响的用户会更多甚至可能达到1/3的用户。以银行转账这个业务为例假设小明在北京XX银行开了账号如果小明要转账一定要北京的银行业务中心才可用否则就不允许小明自己转账。如果不这样的话假设在北京和上海两个业务中心实现了实时转账的异地多活某些异常情况下就可能出现小明只有1万元存款他在北京转给了张三1万元然后又到上海转给了李四1万元两次转账都成功了。这种漏洞如果被人利用后果不堪设想。
当然针对银行转账这个业务虽然无法做到“实时转账”的异地多活但可以通过特殊的业务手段让转账业务也能实现异地多活。例如转账业务除了“实时转账”外还提供“转账申请”业务即小明在上海业务中心提交转账请求但上海的业务中心并不立即转账而是记录这个转账请求然后后台异步发起真正的转账操作如果此时北京业务中心不可用转账请求就可以继续等待重试假设等待2个小时后北京业务中心恢复了此时上海业务中心去请求转账发现余额不够这个转账请求就失败了。小明再登录上来就会看到转账申请失败原因是“余额不足”。
不过需要注意的是“转账申请”的这种方式虽然有助于实现异地多活,但其实还是牺牲了用户体验的,对于小明来说,本来一次操作的事情,需要分为两次:一次提交转账申请,另外一次是要确认是否转账成功。
虽然我们无法做到100%可用性,但并不意味着我们什么都不能做,为了让用户心里更好受一些,我们可以采取一些措施进行安抚或者补偿,例如:
- 挂公告
说明现在有问题和基本的问题原因,如果不明确原因或者不方便说出原因,可以发布“技术哥哥正在紧急处理”这类比较轻松和有趣的公告。
- 事后对用户进行补偿
例如,送一些业务上可用的代金券、小礼包等,减少用户的抱怨。
- 补充体验
对于为了做异地多活而带来的体验损失,可以想一些方法减少或者规避。以“转账申请”为例,为了让用户不用确认转账申请是否成功,我们可以在转账成功或者失败后直接给用户发个短信,告诉他转账结果,这样用户就不用时不时地登录系统来确认转账是否成功了。
## 核心思想
异地多活设计的理念可以总结为一句话:**采用多种手段,保证绝大部分用户的核心业务异地多活!**
## 小结
今天我为你讲了异地多活的设计技巧这些技巧是结合CAP、BASE等理论以及我在具体业务实践的经验和思考总结出来的希望对你有所帮助。
这就是今天的全部内容留一道思考题给你吧异地多活的4大技巧需要结合业务进行分析取舍这样没法通用如果底层存储采用OceanBase这种分布式强一致性的数据存储系统是否就可以做到和业务无关的异地多活
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)

View File

@@ -0,0 +1,179 @@
<audio id="audio" title="30 | 异地多活设计4步走" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/63/c1/6305006c022189926481c8772b0a9dc1.mp3"></audio>
上一期基于异地多活架构设计复杂度最高的“跨城异地”我结合自己的经验总结了异地多活设计的4个技巧及其核心思想我认为掌握这些技巧是进入具体设计步骤的前提。
今天在掌握这4大技巧的基础上我来讲讲跨城异地多活架构设计的4个步骤。
## 第1步业务分级
按照一定的标准将业务进行分级,挑选出核心的业务,只为核心业务设计异地多活,降低方案整体复杂度和实现成本。
常见的分级标准有下面几种:
- 访问量大的业务
以用户管理系统为例,业务包括登录、注册、用户信息管理,其中登录的访问量肯定是最大的。
- 核心业务
以QQ为例QQ的主场景是聊天QQ空间虽然也是重要业务但和聊天相比重要性就会低一些如果要从聊天和QQ空间两个业务里面挑选一个做异地多活那明显聊天要更重要当然此类公司如腾讯应该是两个都实现了异地多活的
- 产生大量收入的业务
同样以QQ为例聊天可能很难为腾讯带来收益因为聊天没法插入广告而QQ空间反而可能带来更多收益因为QQ空间可以插入很多广告因此如果从收入的角度来看QQ空间做异地多活的优先级反而高于QQ聊天了。
以我们一直在举例的用户管理系统为例,“登录”业务符合“访问量大的业务”和“核心业务”这两条标准,因此我们将登录业务作为核心业务。
## 第2步数据分类
挑选出核心业务后,需要对核心业务相关的数据进一步分析,目的在于识别所有的数据及数据特征,这些数据特征会影响后面的方案设计。
常见的数据特征分析维度有:
- 数据量
这里的数据量包括总的数据量和新增、修改、删除的量。对异地多活架构来说,新增、修改、删除的数据就是可能要同步的数据,数据量越大,同步延迟的几率越高,同步方案需要考虑相应的解决方案。
- 唯一性
唯一性指数据是否要求多个异地机房产生的同类数据必须保证唯一。例如用户ID如果两个机房的两个不同用户注册后生成了一样的用户ID这样业务上就出错了。
数据的唯一性影响业务的多活设计,如果数据不需要唯一,那就说明两个地方都产生同类数据是可能的;如果数据要求必须唯一,要么只能一个中心点产生数据,要么需要设计一个数据唯一生成的算法。
- 实时性
实时性指如果在A机房修改了数据要求多长时间必须同步到B机房实时性要求越高对同步的要求越高方案越复杂。
- 可丢失性
可丢失性指数据是否可以丢失。例如写入A机房的数据还没有同步到B机房此时A机房机器宕机会导致数据丢失那这部分丢失的数据是否对业务会产生重大影响。
例如登录过程中产生的session数据就是可丢失的因为用户只要重新登录就可以生成新的session而用户ID数据是不可丢失的丢失后用户就会失去所有和用户ID相关的数据例如用户的好友、用户的钱等。
- 可恢复性
可恢复性指数据丢失后,是否可以通过某种手段进行恢复,如果数据可以恢复,至少说明对业务的影响不会那么大,这样可以相应地降低异地多活架构设计的复杂度。
例如,用户的微博丢失后,用户重新发一篇一模一样的微博,这个就是可恢复的;或者用户密码丢失,用户可以通过找回密码来重新设置一个新密码,这也算是可以恢复的;而用户账号如果丢失,用户无法登录系统,系统也无法通过其他途径来恢复这个账号,这就是不可恢复的数据。
我们同样以用户管理系统的登录业务为例,简单分析如下表所示。
<img src="https://static001.geekbang.org/resource/image/15/c5/156d1d5fea580ebbb8a18a3c3500e5c5.jpg" alt="">
## 第3步数据同步
确定数据的特点后,我们可以根据不同的数据设计不同的同步方案。常见的数据同步方案有:
- 存储系统同步
这是最常用也是最简单的同步方式。例如使用MySQL的数据主从数据同步、主主数据同步。
这类数据同步的优点是使用简单因为几乎主流的存储系统都会有自己的同步方案缺点是这类同步方案都是通用的无法针对业务数据特点做定制化的控制。例如无论需要同步的数据量有多大MySQL都只有一个同步通道。因为要保证事务性一旦数据量比较大或者网络有延迟则同步延迟就会比较严重。
- 消息队列同步
采用独立消息队列进行数据同步常见的消息队列有Kafka、ActiveMQ、RocketMQ等。
消息队列同步适合无事务性或者无时序性要求的数据。例如用户账号两个用户先后注册了账号A和B如果同步时先把B同步到异地机房再同步A到异地机房业务上是没有问题的。而如果是用户密码用户先改了密码为m然后改了密码为n同步时必须先保证同步m到异地机房再同步n到异地机房如果反过来同步后用户的密码就不对了。因此对于新注册的用户账号我们可以采用消息队列同步了而对于用户密码就不能采用消息队列同步了。
- 重复生成
数据不同步到异地机房每个机房都可以生成数据这个方案适合于可以重复生成的数据。例如登录产生的cookie、session数据、缓存数据等。
我们同样以用户管理系统的登录业务为例,针对不同的数据特点设计不同的同步方案,如下表所示。
<img src="https://static001.geekbang.org/resource/image/22/0b/22627c817a22851b09c7d94b02b84c0b.jpg" alt="">
## 第4步异常处理
无论数据同步方案如何设计,一旦出现极端异常的情况,总是会有部分数据出现异常的。例如,同步延迟、数据丢失、数据不一致等。异常处理就是假设在出现这些问题时,系统将采取什么措施来应对。异常处理主要有以下几个目的:
<li>
问题发生时,避免少量数据异常导致整体业务不可用。
</li>
<li>
问题恢复后,将异常的数据进行修正。
</li>
<li>
对用户进行安抚,弥补用户损失。
</li>
常见的异常处理措施有这几类:
1.多通道同步
多通道同步的含义是采取多种方式来进行数据同步,其中某条通道故障的情况下,系统可以通过其他方式来进行同步,这种方式可以应对同步通道处故障的情况。
以用户管理系统中的用户账号数据为例我们的设计方案一开始挑选了消息队列的方式进行同步考虑异常情况下消息队列同步通道可能中断也可能延迟很严重为了保证新注册账号能够快速同步到异地机房我们再增加一种MySQL同步这种方式作为备份。这样针对用户账号数据同步系统就有两种同步方式MySQL主从同步和消息队列同步。除非两个通道同时故障否则用户账号数据在其中一个通道异常的情况下能够通过另外一个通道继续同步到异地机房如下图所示。
<img src="https://static001.geekbang.org/resource/image/0a/0c/0aacf8b1f0a23e75901e85a1cf2ce30c.jpg" alt="">
多通道同步设计的方案关键点有:
<li>
一般情况下,采取两通道即可,采取更多通道理论上能够降低风险,但付出的成本也会增加很多。
</li>
<li>
数据库同步通道和消息队列同步通道不能采用相同的网络连接,否则一旦网络故障,两个通道都同时故障;可以一个走公网连接,一个走内网连接。
</li>
<li>
需要数据是可以重复覆盖的,即无论哪个通道先到哪个通道后到,最终结果是一样的。例如,新建账号数据就符合这个标准,而密码数据则不符合这个标准。
</li>
2.同步和访问结合
这里的访问指异地机房通过系统的接口来进行数据访问。例如业务部署在异地两个机房A和BB机房的业务系统通过接口来访问A机房的系统获取账号信息如下图所示。
<img src="https://static001.geekbang.org/resource/image/3a/e6/3a754a4bacyy5c4368d244ce975de7e6.jpg" alt="">
同步和访问结合方案的设计关键点有:
<li>
接口访问通道和数据库同步通道不能采用相同的网络连接,不能让数据库同步和接口访问都走同一条网络通道,可以采用接口访问走公网连接,数据库同步走内网连接这种方式。
</li>
<li>
数据有路由规则可以根据数据来推断应该访问哪个机房的接口来读取数据。例如有3个机房A、B、CB机房拿到一个不属于B机房的数据后需要根据路由规则判断是访问A机房接口还是访问C机房接口。
</li>
<li>
由于有同步通道,优先读取本地数据,本地数据无法读取到再通过接口去访问,这样可以大大降低跨机房的异地接口访问数量,适合于实时性要求非常高的数据。
</li>
3.日志记录
日志记录主要用于用户故障恢复后对数据进行恢复,其主要方式是每个关键操作前后都记录相关一条日志,然后将日志保存在一个独立的地方,当故障恢复后,拿出日志跟数据进行对比,对数据进行修复。
为了应对不同级别的故障,日志保存的要求也不一样,常见的日志保存方式有:
<li>
服务器上保存日志,数据库中保存数据,这种方式可以应对单台数据库服务器故障或者宕机的情况。
</li>
<li>
本地独立系统保存日志,这种方式可以应对某业务服务器和数据库同时宕机的情况。例如,服务器和数据库部署在同一个机架,或者同一个电源线路上,就会出现服务器和数据库同时宕机的情况。
</li>
<li>
日志异地保存,这种方式可以应对机房宕机的情况。
</li>
上面不同的日志保存方式,应对的故障越严重,方案本身的复杂度和成本就会越高,实际选择时需要综合考虑成本和收益情况。
4.用户补偿
无论采用什么样的异常处理措施都只能最大限度地降低受到影响的范围和程度无法完全做到没有任何影响。例如双同步通道有可能同时出现故障、日志记录方案本身日志也可能丢失。因此无论多么完美的方案故障的场景下总是可能有一小部分用户业务上出问题系统无法弥补这部分用户的损失。但我们可以采用人工的方式对用户进行补偿弥补用户损失培养用户的忠诚度。简单来说系统的方案是为了保证99.99%的用户在故障的场景下业务不受影响人工的补偿是为了弥补0.01%的用户的损失。
常见的补偿措施有送用户代金券、礼包、礼品、红包等有时为了赢得用户口碑付出的成本可能还会比较大但综合最终的收益来看还是很值得的。例如暴雪《炉石传说》2017年回档故障暴雪给每个用户大约价值人民币200元的补偿结果玩家都求暴雪再来一次回档形象地说明了玩家对暴雪补偿的充分认可。
>
<p>只要在2017年1月18日18点之前登录过国服《炉石传说》的玩家均可获得与25卡牌包等值的补偿具体如下<br>
1000游戏金币<br>
15个卡牌包经典卡牌包x5、上古之神的低语卡牌包x5、龙争虎斗加基森卡牌包x5。</p>
## 小结
今天我为你讲了异地多活设计的具体步骤,分别是业务分级、数据分类、数据同步和异常处理,希望对你有所帮助。
这就是今天的全部内容留一道思考题给你吧业务分级讨论的时候产品说A也很重要因为影响用户使用B也很重要因为影响公司收入C也很重要因为会导致客户投诉……这种情况下我们该如何处理业务分级
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)

View File

@@ -0,0 +1,112 @@
<audio id="audio" title="31 | 如何应对接口级的故障?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4a/cc/4ac67798692625f187af5d6488d420cc.mp3"></audio>
异地多活方案主要应对系统级的故障,例如,机器宕机、机房故障、网络故障等问题,这些系统级的故障虽然影响很大,但发生概率较小。在实际业务运行过程中,还有另外一种故障影响可能没有系统级那么大,但发生的概率较高,这就是今天我要与你聊的如何应对接口级的故障。
接口级故障的典型表现就是系统并没有宕机,网络也没有中断,但业务却出现问题了。例如,业务响应缓慢、大量访问超时、大量访问出现异常(给用户弹出提示“无法连接数据库”),这类问题的主要原因在于系统压力太大、负载太高,导致无法快速处理业务请求,由此引发更多的后续问题。例如,最常见的数据库慢查询将数据库的服务器资源耗尽,导致读写超时,业务读写数据库时要么无法连接数据库、要么超时,最终用户看到的现象就是访问很慢,一会访问抛出异常,一会访问又是正常结果。
导致接口级故障的原因一般有下面几种:
- 内部原因
程序bug导致死循环某个接口导致数据库慢查询程序逻辑不完善导致耗尽内存等。
- 外部原因
黑客攻击、促销或者抢购引入了超出平时几倍甚至几十倍的用户,第三方系统大量请求,第三方系统响应缓慢等。
解决接口级故障的核心思想和异地多活基本类似:**优先保证核心业务**和**优先保证绝大部分用户**。
## 降级
降级指系统将某些业务或者接口的功能降低可以是只提供部分功能也可以是完全停掉所有功能。例如论坛可以降级为只能看帖子不能发帖子也可以降级为只能看帖子和评论不能发评论而App的日志上传接口可以完全停掉一段时间这段时间内App都不能上传日志。
降级的核心思想就是丢车保帅优先保证核心业务。例如对于论坛来说90%的流量是看帖子那我们就优先保证看帖的功能对于一个App来说日志上传接口只是一个辅助的功能故障时完全可以停掉。
常见的实现降级的方式有:
- 系统后门降级
简单来说就是系统预留了后门用于降级操作。例如系统提供一个降级URL当访问这个URL时就相当于执行降级指令具体的降级指令通过URL的参数传入即可。这种方案有一定的安全隐患所以也会在URL中加入密码这类安全措施。
系统后门降级的方式实现成本低,但主要缺点是如果服务器数量多,需要一台一台去操作,效率比较低,这在故障处理争分夺秒的场景下是比较浪费时间的。
- 独立降级系统
为了解决系统后门降级方式的缺点,将降级操作独立到一个单独的系统中,可以实现复杂的权限管理、批量操作等功能。其基本架构如下:
<img src="https://static001.geekbang.org/resource/image/1b/63/1b38f4a954d60546dd254c8cefd94463.jpg" alt="">
## 熔断
熔断和降级是两个比较容易混淆的概念,因为单纯从名字上看好像都有禁止某个功能的意思,但其实内在含义是不同的,原因在于降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况。
假设一个这样的场景A服务的X功能依赖B服务的某个接口当B服务的接口响应很慢的时候A服务的X功能响应肯定也会被拖慢进一步导致A服务的线程都被卡在X功能处理上此时A服务的其他功能都会被卡住或者响应非常慢。这时就需要熔断机制了A服务不再请求B服务的这个接口A服务内部只要发现是请求B服务的这个接口就立即返回错误从而避免A服务整个被拖慢甚至拖死。
熔断机制实现的关键是需要有一个统一的API调用层由API调用层来进行采样或者统计如果接口调用散落在代码各处就没法进行统一处理了。
熔断机制实现的另外一个关键是阈值的设计例如1分钟内30%的请求响应时间超过1秒就熔断这个策略中的“1分钟”“30%”“1秒”都对最终的熔断效果有影响。实践中一般都是先根据分析确定阈值然后上线观察效果再进行调优。
## 限流
降级是从系统功能优先级的角度考虑如何应对故障,而限流则是从用户访问压力的角度来考虑如何应对故障。限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃。
虽然“丢弃”这个词听起来让人不太舒服,但保证一部分请求能够正常响应,总比全部请求都不能响应要好得多。
限流一般都是系统内实现的,常见的限流方式可以分为两类:基于请求限流和基于资源限流。
- 基于请求限流
基于请求限流指从外部访问的请求角度考虑限流,常见的方式有:限制总量、限制时间量。
限制总量的方式是限制**某个指标的累积上限**常见的是限制当前系统服务的用户总量例如某个直播间限制总用户数上限为100万超过100万后新的用户无法进入某个抢购活动商品数量只有100个限制参与抢购的用户上限为1万个1万以后的用户直接拒绝。限制时间量指限制**一段时间内某个指标的上限**例如1分钟内只允许10000个用户访问每秒请求峰值最高为10万。
无论是限制总量还是限制时间量共同的特点都是实现简单但在实践中面临的主要问题是比较难以找到合适的阈值例如系统设定了1分钟10000个用户但实际上6000个用户的时候系统就扛不住了也可能达到1分钟10000用户后其实系统压力还不大但此时已经开始丢弃用户访问了。
即使找到了合适的阈值基于请求限流还面临硬件相关的问题。例如一台32核的机器和64核的机器处理能力差别很大阈值是不同的可能有的技术人员以为简单根据硬件指标进行数学运算就可以得出来实际上这样是不可行的64核的机器比32核的机器业务处理性能并不是2倍的关系可能是1.5倍甚至可能是1.1倍。
为了找到合理的阈值,通常情况下可以采用性能压测来确定阈值,但性能压测也存在覆盖场景有限的问题,可能出现某个性能压测没有覆盖的功能导致系统压力很大;另外一种方式是逐步优化,即:先设定一个阈值然后上线观察运行情况,发现不合理就调整阈值。
基于上述的分析,根据阈值来限制访问量的方式更多的适应于业务功能比较简单的系统,例如负载均衡系统、网关系统、抢购系统等。
- 基于资源限流
基于请求限流是从系统外部考虑的,而基于资源限流是从系统内部考虑的,即:找到系统内部影响性能的关键资源,对其使用上限进行限制。常见的内部资源有:连接数、文件句柄、线程数、请求队列等。
例如采用Netty来实现服务器每个进来的请求都先放入一个队列业务线程再从队列读取请求进行处理队列长度最大值为10000队列满了就拒绝后面的请求也可以根据CPU的负载或者占用率进行限流当CPU的占用率超过80%的时候就开始拒绝新的请求。
基于资源限流相比基于请求限流能够更加有效地反映当前系统的压力,但实践中设计也面临两个主要的难点:如何确定关键资源,如何确定关键资源的阈值。通常情况下,这也是一个逐步调优的过程,即:设计的时候先根据推断选择某个关键资源和阈值,然后测试验证,再上线观察,如果发现不合理,再进行优化。
## 排队
排队实际上是限流的一个变种限流是直接拒绝用户排队是让用户等待一段时间全世界最有名的排队当属12306网站排队了。排队虽然没有直接拒绝用户但用户等了很长时间后进入系统体验并不一定比限流好。
由于排队需要临时缓存大量的业务请求单个系统内部无法缓存这么多数据一般情况下排队需要用独立的系统去实现例如使用Kafka这类消息队列来缓存用户请求。
下面是1号店的“双11”秒杀排队系统架构
<img src="https://static001.geekbang.org/resource/image/f2/2a/f207dbbb5eee122yy592f7d015553e2a.jpg" alt="">
其基本实现摘录如下:
>
<p>【排队模块】<br>
负责接收用户的抢购请求,将请求以先入先出的方式保存下来。每一个参加秒杀活动的商品保存一个队列,队列的大小可以根据参与秒杀的商品数量(或加点余量)自行定义。<br>
&nbsp;<br>
【调度模块】<br>
负责排队模块到服务模块的动态调度,不断检查服务模块,一旦处理能力有空闲,就从排队队列头上把用户访问请求调入服务模块,并负责向服务模块分发请求。这里调度模块扮演一个中介的角色,但不只是传递请求而已,它还担负着调节系统处理能力的重任。我们可以根据服务模块的实际处理能力,动态调节向排队系统拉取请求的速度。<br>
&nbsp;<br>
【服务模块】<br>
负责调用真正业务来处理服务,并返回处理结果,调用排队模块的接口回写业务处理结果。</p>
## 小结
今天我为你讲了接口级故障的四种应对方法,分别是降级、熔断、限流和排队,希望对你有所帮助。
这就是今天的全部内容,留一道思考题给你吧,如果你来设计一个整点限量秒杀系统,包括登录、抢购、支付(依赖支付宝)等功能,你会如何设计接口级的故障应对手段?
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)