架构设计面试精讲
一、架构原理与技术认知
01 技术认知
- 1 架构设计的4点理解?
架构拆分其实是管理在技术上提效的一种手段
–** 为什么做架构拆分?**通常最直接目的就是做系统之间解耦、子系统之间解耦,或模块之间的解耦。
–**为什么要做系统解耦?**系统解耦后,使得原本错综复杂的调用逻辑能有序地分布到各个独立的系统中,从而使得拆封后的各个系统职责更单一,功能更为内聚。
–**为什么要做职责单一?**因为职责单一的系统功能逻辑的迭代速度会更快,会提高研发团队响应业务需求的速度,也就是提高了团队的开发效率。
–**为什么要关注开发效率?**研发迭代效率的提升是任何一家公司在业务发展期间都最为关注的问题,所以从某种程度上看,架构拆分是系统提效最直接的手段。 - 2 课程案例(后续以此扩展)
02 架构视角
- 1 问题:你之前如何设计这个系统的?–注重系统设计思路
- 2 一个优化实例
1)场景
在电商中,当用户发表一条商品评论,后台的逻辑是点评系统会调用一系列的远程 API 接口,如调用风控系统、广告系统、消息系统……几个甚至十几个系统的接口。
2)初期实现
实现:同步 RPC(Remote Procedure Call,远程过程调用)远程调用各系统接口。
优点:在系统少、逻辑简单的阶段很符合实际情况的设计。
缺陷:过多地依赖其他系统,导致评论发布的接口性能很低,可用性也容易受到其他系统影响。而且每当点评系统需求上线时,其他系统都需要跟着进行联调测试,导致需求迭代速度缓慢。
3)优化
思路:问题在于系统间的耦合度太高。解决办法就是采用异步化解耦,从而通过引入 MQ 消息管道,在架构上进行系统业务逻辑拆分,将原本强依赖的系统间的同步 RPC 调用变成异步消息触发。架构如下:
不加分回答: 说“我引入了 MQ 消息队列,做了系统解耦,采用异步消息通知的方式来触发系统调用”。
分析:网络答案,项目真实性质疑。
4)加分回答:
**分层表述:**四个层面的答案:
① 谈复杂来源;② 谈解决方案;③ 谈评估标准;④ 说技术实现。
1> 复杂来源
评估系统复杂度: 互联网软件通常分为功能性的复杂度和非功能性的复杂度两种。
功能性复杂度:要解决业务发展带来的系统耦合、开发效率缓慢问题。
非功能性复杂度:要保证系统的高可用性。
2> 解决方案
一般出2~3个备选方案,设计要考虑可行性和优缺点。
① 采用开源的 MQ 消息管道。目前 MQ 消息管道有很多开源解决方案,比如 Kafka、RocketMQ、RabbitMQ 等。在实际项目中,你可以根据不同的应用场景选择合适的成熟开源消息队列方案,这是很多公司常用的做法。
② 采用开源的 Redis 实现消息队列。方案 1 虽然应用了开源 MQ 实现点评消息的通信,但是因为引入一个消息中间件就会带来运维成本,所以方案 2 可以基于轻量级的 Redis 实现,以降低系统的维护成本和实现复杂度。
③ 采用内存队列 + MySQL 来实现。方案 2 中虽然应用了较为轻量级的 Redis 来实现,但是还需要引入一个缓存系统,同样也会带来运维成本,所以方案 3 是直接基于 MySQL 实现,即基于内存队列的方式,异步持久化到数据库,然后通过定时任务读取 MySQL 中的消息并处理。
3> 评估标准
目的:选择最合适的实现方案。
无优劣,更适合业务。
- 3 总结
二、分布式原理与设计
03 CAP分布式理论
- 1 CAP 理论
–概念:C(Consistency)是数据一致性、A(Availability)是服务可用性、P(Partition tolerance)是分区容错性。C、A、P 只能同时满足两个目标,而由于在分布式系统中,P 是必须要保留的,所以要在 C 和 A 间进行取舍。假如要保证服务的可用性,就选择 AP 模型,而要保证一致性的话,就选择 CP 模型。
–必要性:在绝大多数时间里并不存在网络分区(网络不会经常出现问题)。为什么还要进行三选二吗(CP 或者 AP)?原因:不同的分布式系统要根据业务场景和业务需求在 CAP 三者中进行权衡。CAP 理论用于指导在系统设计时需要衡量的因素,而非进行绝对地选择。 - 2 CAP原理
–现在有一个分布式系统 A,它有一个副本 A1,在正常情况下,客户端 Client 写数据到系统 A,然后数据从 A 节点同步到 A1 节点,再返回给 Client 成功状态。
–这时,客户端 Client 从任何节点 A 或 A1 读取数据,都能读取到最新写入的数据,说明 A 和 A1 的数据是一致的,并且 A 和 A1 也都是可用的。
–网络是不可靠的,节点 A 和 A1 的网络随时会因为中断而出现分区。所谓网络分区就是由于网络不通导致节点 A 和 A1 被隔离在不同的网络子集中,此时节点 A 的数据就不能及时同步到节点 A1 中了。
–出现网络分区时,根据 CAP 理论,需要在 A 和 C 中进行取舍,即要么保证系统的可用性,要么保证数据一致性。
- 3 实践经验
1)BASE 理论
–引入:分布式的设计方案是数据一致性和系统可用性的权衡,并不是非此即彼。所以即使无法做到强一致性(简单来讲强一致性就是在任何时刻所有的用户查询到的数据都是最新的),也可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。由此引出 BASE 理论。
–概念:是CAP 理论的延伸。BASE 是 Basically Available(基本可用)、Soft State(软状态)和 Eventually Consistent(最终一致性)三个单词的简写;
–作用:是保证系统的可用性,然后通过最终一致性来代替强一致性,它是目前分布式系统设计中最具指导意义的经验总结。
2)实际项目中,你如何通过 BASE 理论来指导设计实践呢?
1> 基本可用性
2> 软状态和最终一致性
指的是允许系统中的数据存在中间状态,这同样是为了系统可用性而牺牲一段时间窗内的数据一致性,从而保证最终的数据一致性的做法。
注:目前这种处理数据的方式几乎成了互联网的标配设计模式,最经典的例子是在用户下单的时候不需要真正地扣减库存,而是仅在前台计个数,然后通过异步任务在后台批量处理。 - 4 CAP理论面试回答
1)回答逻辑
1> 先充分理解理论原理,不能仅浮在概念上(这一点需要你课下下功夫);
2> 其次需要有自己的思考,表现出你思考能力的不同;
3> 然后将理论结合于实践,讨论实际中处理问题时的思考逻辑。
2)扩展
知识体系、技术判断
1> 分布式系统的理解
分布式系统看起来就像一个计算机。计算机包括五大体系结构(即冯诺依曼结构),它有五大部件:分别是控制器、运算器、存储器、输入及输出。你可以这么理解:一个分布式系统也包含这五大部件,其中最重要的是计算与存储。计算与存储由一系列网络节点组成,每个节点之间的通信就是输入与输出,各节点之间的调度管理就是控制器。
- 5 例子:Redis 是否可以作为分布式锁
解题思路:
1)说明现实存在的问题
一般使用 setnx 方法,通过 Redis 实现锁和超时时间来控制锁的失效时间。但是在极端的情况下,当 Reids 主节点挂掉,但锁还没有同步到从节点时,根据哨兵机制,从就变成了主,继续提供服务。这时,另外的线程可以再来请求锁,此时就会出现两个线程拿到了锁的情况。
2)回归理论的指导
根据对 CAP 理论的理解,Redis 的设计模型是 AP 模型,而分布式锁是一个 CP 场景,那么很明显,将 Redis 这种 AP 模型的架构应用于 CP 的场景,在底层的技术选型上就是错误的。
3)扩展到知识体系
Redis 属于分布式存储系统,你的头脑里就要有对分布式存储系统领域的知识体系。思考它的数据存储、数据分布、数据复制,以及数据一致性都是怎么做的,用了哪些技术来实现,为什么要做这样的技术或算法选型。你要学会从多维度、多角度去对比、分析同一分布式问题的不同方法,然后综合权衡各种方法的优缺点,最终形成自己的技术认知和技术判断力。
4)有技术的判断力
比如通过 Redis,你能想到目前分布式缓存系统的发展现状以及技术实现,如果让你造一个“Redis”出来,你会考虑哪些问题等。虽然在实际工作中不推荐重复“造轮子”,但在面试中要表现出自己具备“造轮子”的能力。
04 分布式系统原理问题-数据存储
- 0 综述
- 1 常见问题
–常见问题:
如何设计一个支持海量商品存储的高扩展性架构?
在做分库分表时,基于 Hash 取模和一致性 Hash 的数据分片是如何实现的?
在电商大促时期,如何对热点商品数据做存储策略 ?
强一致性和最终一致性的数据共识算法是如何实现的 ?
–问题核心:在分布式系统中,核心的考察点包括了分布式系统中数据的存储、分布、复制,以及相关协议和算法。
如“如何设计海量商品数据的存储?” - 2 分布式数据存储的问题
分布式数据存储的问题可以分成:数据分片、数据复制,以及数据一致性带来的相关问题
1)数据分片
–背景:为了解决单台存储设备的局限性,会把数据分布到多台存储节点上,以此实现数据的水平扩展。
–概念:数据分片即按照一定的规则将数据路由到相应的存储节点中,从而降低单存储节点带来的读写压力。
–常见方案:Hash(哈希分片)与 Range(范围分片)。
2)数据复制
–数据复制:数据复制会产生副本,而副本是分布式存储系统解决高可用的唯一手段,这也是我们熟知的主从模式,又叫 master-slave。
–作用:在分布式存储系统中,通常会设置数据副本的主从节点,当主节点出现故障时,从节点可以替代主节点提供服务,从而保证业务正常运行。
3)数据一致性
–解决:节点故障,完成从节点替代主节点。
–数据一致性,通常要考虑一致性强弱(即强一致性和最终一致性的问题)。而要解决一致性的问题,则要进行一系列的一致性协议:如两阶段提交协议(Two-Phrase Commit,2PC)、Paxos 协议选举、Raft 协议、Gossip 协议。
- 3 存储策略-分片
1)问题引入
–问题:假设你是一家电商网站的架构师,现在要将原有单点上百 G 的商品做数据重构,存储到多个节点上,你会如何设计存储策略?
–分析:因为是**商品存储扩容的设计问题,**很容易想到做数据的分库分表,**也就是重新设计数据的分片规则,*常用的分片策略有两种,即 Hash(哈希)分片和 Range(范围)分片。**从这一点出发会考察你Hash(哈希)分片的具体实现原理。**
**2)Hash分片**
–分片:商品表包括主键、商品 ID、商品名称、所属品类和上架时间等字段。如果以商品 ID 作为关键字进行分片,系统会通过一个 Hash 函数计算商品 ID 的 Hash 值,然后取模,就能得到对应的分片。模为 4 就表示系统一共有四个节点,每个节点作为一个分片。
–例子:假设Hash 函数为 “商品 ID % 节点个数 4”,通过计算可以得到每个数据应该存入的节点:计算结果为 0 的数据存入节点 A;结果为 1 的数据存入节点 B;结果为 2 的数据存入节点 C;计算为 3 的数据存储节点 D。
–优缺点:
优点在于可以保证数据非常均匀地分布到多个分片上,并且实现起来简单,但扩展性很差,因为分片的计算方式就是直接用节点取模,节点数量变动,就需要重新计算 Hash,就会导致大规模数据迁移的工作。
**3)一致性Hash**
–问题:如何解决 Hash 分片的缺点,既保证数据均匀分布,又保证扩展性?
–一致性Hash:它是指将存储节点和数据都映射到一个首尾相连的哈希环上。存储节点一般可以根据 IP 地址进行 Hash 计算,数据的存储位置是从数据映射在环上的位置开始,依照顺时针方向所找到的第一个存储节点。
–例子:在具体操作过程中,通常会选择带有虚拟节点的一致性 Hash。
存储节点–假设在这个案例中将虚拟节点的数量设定为 10 个,就形成 10 个分片,而这 10 个分片构成了整个 Hash 空间。现在让 A 节点对应虚拟节点 0 ~ 3,B 节点对应虚拟节点 4 ~ 6,C 节点对应虚拟节点 7 ~ 8,D 节点对应虚拟节点 9。
数据–同样根据哈希函数为 “商品 ID % 节点个数 10”得到每一个商品在 Hash 环上的位置,然后根据顺时针查找最近的存储节点,即数据实际映射的位置。计算结果为:0 ~ 3 的数据存入节点 A;结果为 4 ~ 6 的数据存入节点 B;结果为 7 ~ 8 的数据存入节点 C;计算为 9 的数据存储节点 D。
–新增:当我们新增一台服务器,即节点 E 时,受影响的数据仅仅是新服务器到所处环空间中前一台服务器(即沿着逆时针方向的第一台服务器)之间的数据。结合我们的示例,只有商品 100 和商品 101 从节点 A 被移动到节点 E,其他节点的数据保持不变。此后,节点 A 只存储 Hash 值为 2 和 3 的商品,节点 E 存储 Hash 值为 0 和 1 的商品。
–优缺点:一致性 Hash 分片的优点是数据可以较为均匀地分配到各节点,其并发写入性能表现也不错。
(初级到此步不会追问)
–缺点:
虽然一致性 Hash 提升了稳定性,使节点的加入和退出不会造成大规模的数据迁移,但本质上 Hash 分片是一种静态的分片方式,必须要提前设定分片的最大规模,**而且无法避免单一热点问题**, 某一数据被海量并发请求后,不论如何进行 Hash,数据也只能存在一个节点上,这势必会带来热点请求问题。比如案例中的电商商品,如果某些商品卖得非常火爆,通过 Hash 分片的方式很难针对热点商品做单独的架构设计。
**4)范围(Range)分片-分布式数据存储的理解**
–问题:解决单一热点问题?
–Range(范围)分片:与 Hash 分片不同的是,Range 分片能结合业务逻辑规则。
–例子:例如,我们用 “Category(商品类目)” 作为关键字进行分片时,不是以统一的商品一级类目为标准,而是可以按照一、二、三级类目进行灵活分片。例如,按业务品类分片,对于京东强势的 3C 品类,可以按照 3C 的三级品类设置分片;对于弱势品类,可以先按照一级品类进行分片,这样会让分片间的数据更加平衡。
要达到这种灵活性,前提是要有能力控制数据流向哪个分区,一个简单的实现方式是:预先设定主键的生成规则,根据规则进行数据的分片路由,但这种方式会侵入商品各条线主数据的业务规则,更好的方式是基于分片元数据(不过架构设计没有好坏,只有适合与否,所以在面试场景中,我建议你用擅长的解决方案来回答问题)。
基于分片元数据的方式,就是调用端在操作数据的时候,先问一下分片元数据系统数据在哪,然后在根据得到的地址操作数据。元数据中存储的是数据分片信息,分片信息就是数据分布情况。在一个分布式存储系统中,承担数据调度功能的节点是分片元数据,当客户端收到请求后,会请求分片元数据服务,获取分片对应的实际节点地址,才能访问真正的数据。而请求分片元数据获取的信息也不仅仅只有数据分片信息,还包括数据量、读写 QPS 和分片副本的健康状态等。
这种方式的灵活性在于分片规则不固定,易扩展,但是高灵活性就会带来高复杂性,从存储的角度看,元数据也是数据,特殊之处在于它类似一个路由表,每一次请求都要访问它,所以分片元数据本身就要做到高可用。如果系统支持动态分片,那么分片信息的变更数据还要在节点之间进行同步,这又带来*多副本之间的一致性问题,以此延伸出如何保证分片元数据服务的可用性和数据一致性?
最直接的方式是专门给元数据做一个服务集群,并通过一致性算法复制数据。在实现方式上,就是将元数据服务的高可用和数据一致性问题转嫁给外围协调组件,如 ETCD 集群,这样既保证了系统的可靠,数据同步的成本又比较低。知道了设计思路,那具体的架构实现上怎么做 ?
1 给分片元数据做集群服务,并通过 ETCD 存储数据分片信息。
2 每个数据存储实例节点定时向元数据服务集群同步心跳和分片信息。
3 当调用端的请求过来时,元数据服务节点只需要做好高可用和缓存即可。
给元数据分片:
- 4 共识算法
如果面试官想挖掘你的能力,还会深入聊到共识算法,在一致性共识算法和最终一致性共识算法方面提出类似的问题,比如, ETCD 是如何解决数据共识问题的?为什么要选择这种数据复制方式呢?
1)分布式系统中的一致性共识算法
对于这类问题,你要从一致性算法原理层面解答,思路是:清楚 ETCD 的共识算法是什么,还有哪些常用的共识算法,以及为什么 ETCD 会做这样的选型。
–问题:ETCD 的共识算法是基于 Raft 协议实现的强一致性算法,同类的强一致性算法还有 Paxos,在面试过程中,面试官很可能让你从自己的角度理解一下这两个算法,当然也会直接问:为什么没有选择 Paxos 而选择了 Raft ?这个问题对应聘高级研发的同学来讲很常见,主要考核你对以下内容的理解:
Paxos 算法解决了什么问题?
Basic Paxos 算法的工作流程是什么?
Paxos 算法和 Raft 算法的区别又是什么?
1> Paxos
在分布式系统中,造成系统不可用的场景很多,比如服务器硬件损坏、网络数据丢包等问题,解决这些问题的根本思路是多副本,副本是分布式系统解决高可用的唯一手段,也就是主从模式,那么如何在保证一致性的前提下,提高系统的可用性,Paxos 就被用来解决这样的问题,而 Paxos 又分为 Basic Paxos 和 Multi Paxos,然而因为它们的实现复杂,工业界很少直接采用 Paxos 算法,所以 ETCD 选择了 Raft 算法 (在面试过程中,面试官容易在这里设置障碍,来对候选者做技术分层)。
2> Raft
Raft 是 Multi Paxos 的一种实现,是通过一切以领导者为准的方式,实现一系列值的共识,然而不是所有节点都能当选 Leader 领导者,Raft 算法对于 Leader 领导者的选举是有限制的,只有最全的日志节点才可以当选。正因为 ETCD 选择了 Raft,为工业界提供了可靠的工程参考,就有更多的工程实现选择基于 Raft,如 TiDB 就是基于 Raft 算法的优化。
2)最终一致性算法
如果把问题设计的极端一些,考察你对最终一致性算法的掌握,还可以有一种思路:分片元数据服务毕竟是一个中心化的设计思路,而且基于强一致性的共识机制还是可能存在性能的问题,有没有更好的架构思路呢?
–可用性:既然要解决可用性的问题,根据 Base 理论,需要实现最终一致性,那么 Raft 算法就不适用了,因为 Raft 需要保证大多数节点正常运行后才能运行。这个时候,可以选择基于 Gossip 协议的实现方式。
–Gossip 的协议原理有一种传播机制叫谣言传播,指的是当一个节点有了新数据后,这个节点就变成了活跃状态,并周期性地向其他节点发送新数据,直到所有的节点都存储了该条数据。这种方式达成的数据一致性是 “最终一致性”,即执行数据更新操作后,经过一定的时间,集群内各个节点所存储的数据最终会达成一致,很适合动态变化的分布式系统。
–从图中你可以看到,节点 A 向节点 B、C 发送新数据,节点 B 收到新数据后,变成了活跃节点,然后节点 B 向节点 C、D 发送新数据。
3)一致性共识算法做个总结
到此,我们对一致性共识算法做个总结,共识算法的选择和数据副本数量的多少息息相关,如果副本少、参与共识的节点少,推荐采用广播方式,如 Paxos、Raft 等协议。如果副本多、参与共识的节点多,那就更适合采用 Gossip 这种最终一致性协议。
- 思考题
- 总结:
本章:分布式技术下的数据分片、存储、复制与一致性的原理性问题
05 分布式事务一致性
- 1 分布式事务
–背景:在互联网分布式场景中,原本一个系统被拆分成多个子系统,要想完成一次写入操作,你需要同时协调多个系统,这就带来了分布式事务的问题。
–概念:分布式事务是指:一次大的操作由多个小操作组成,这些小的操作分布在不同的服务器上,分布式事务需要保证这些小操作要么全部成功,要么全部失败
- 2 系统简例
–案例:以京东旅行系统为例,早期的交易系统是通过 .NET 实现的,所有的交易下单逻辑都写在一个独立的系统中。随着技术改造,我们用 Java 重写了核心系统,原本的系统也被拆分成多个子系统,如商品系统、促销系统、订单系统(为了方便理解,我只拿这三个系统举例)。当用户下单时,订单系统生成订单,商品系统扣减库存,促销系统扣减优惠券,只有当三个系统的事务都提交之后,才认为此次下单成功,否则失败。
–解决:这是一个很典型的分布式事务问题,解决方案也很多,有两阶段提交协议(Two-Phase Commit,2PC)、3PC 、TCC 和基于消息队列的实现方式。 - 2 如何实现系统之间的分布式事务一致性?
1)错误回答
所以当很多候选者听到“怎么实现系统之间的分布式一致性?”的问题之后,会信心满满地选择一个方案,回答说:方案很多,可以选择 2PC ,2PC 实现的流程是……
这种答题思路犯了一个很明显的错误,因为在实际工作中,很少采用前几种方案,**基本都是基于 MQ 的可靠消息投递的方式来实现。**所以一上来就说 2PC、3PC 或者 TCC 会让我觉得你并没有实际做过。
2)可借鉴流程
建议你先介绍目前主流实现分布式系统事务一致性的方案(也就是基于 MQ 的可靠消息投递的机制)然后回答出可实现方案和关键知识点。另外,为了和面试官进一步交流,你可以提出 2PC 或 TCC (这是一种交流方案)。因为 2PC 或 TCC 在工业界落地代价很大,不适合互联网场景,所以只有少部分的强一致性业务场景(如金融支付领域)会基于这两种方案实现。而你可以围绕它们的解决思路和方案弊端与面试官讨论,这会让你和面试官由不平等的“面试与被面试”变成平等且友好的“双方沟通”,是一种面试套路。
但要做到以上几点,需要建立在你深入掌握分布式事务一致性问题的基础之上,所以接下来,我们就解析一下面试中最为常见的两种实现方案。
1> 基于两阶段提交的解决方案
–概念:2PC 是分布式事务教父级协议,它是数据库领域解决分布式事务最典型的协议。它的处理过程分为准备和提交两个阶段,每个阶段都由协调者(Coordinator)和参与者(Participant)共同完成:
协调者就是事务管理器;
参与者就是具体操作执行的资源管理器。
–知识支持:
Java 程序员都知道,XA 是由 X/Open 组织提出的分布式事务的规范,规范主要定义了事务管理器(Transaction Manager)和资源管理器(Resource Manager)之间的接口,事务管理器负责全局事务的协调者,资源管理器负责管理实际资源(如 MySQL、Oracle 等数据库)。而Java 平台上事务规范 JTA(Java Transaction API)就是对 XA 分布式事务规范标准的实现。例如在 Spring 中就通过 JtaTransactionManager 来配置分布式事务,然后通过管理多个 ResourceManager 来管理多个数据源,进而操作多个数据库之间的事务。
–2PC的实现:
以课程开头的系统为例,订单数据、商品数据,以及促销数据被分别存储在多个数据库实例中,用户在执行下单的时候,交易主流程的业务逻辑则集中部署在一个应用服务器集群上,然后通过 Spring 容器访问底层的数据库实例,而容器中的 JTA 事务管理器在这里就作为事务管理器,Resource 资源管理器就作为底层的数据库实例的资源管理器。
我们假设订单数据,商品数据和促销数据分别保存在数据库 D1,数据库 D2 和数据库 D3 上。
准备阶段,事务管理器首先通知所有资源管理器开启事务,询问是否做好提交事务的准备。如资源管理器此时会将 undo 日志和 redo 日志计入事务日志中,并做出应答,当协调者接收到反馈 Yes 后,则准备阶段结束。
提交阶段,当收到所有数据库实例的 Yes 后,事务管理器会发出提交指令。每个数据库接受指令进行本地操作,正式提交更新数据,然后向协调者返回 Ack 消息,事务结束。
中断阶段,如果任何一个参与者向协调者反馈了 No 响应,例如用户 B 在数据库 D3 上面的余额在执行其他扣款操作,导致数据库 D3 的数据无法锁定,则只能向事务管理器返回失败。此时,协调者向所有参与者发出 Rollback 请求,参与者接收 Rollback 请求后,会利用其在准备阶段中记录的 undo 日志来进行回滚操作,并且在完成事务回滚之后向协调者发送 Ack 消息,完成事务回滚操作。
以上就是基于 2PC 实现分布式事务的原理。
当你和面试官交流 2PC 的原理时,往往不止于此,就像我们开篇提到的,我们并不会基于 2PC 来实现分布式事务一致性,虽然 2PC 可以借助数据库的本地事务操作,实现起来较为简单,不用侵入业务逻辑,但是它也存在着很多问题。
2PC 在准备阶段会要求每个资源管理器进行资源锁定,如 MySQL 的行锁。否则如果在提交阶段提交之前数据发生改变,就会出现数据不一致的情况。
–问题1:超卖
还是上面的例子,如果商品库存数据为 1,也就是数据库 D1 为 1,在准备阶段询问是否可以扣减库存,商品数据返回可以,此时如果不锁定数据,在提交阶段之前另外一个请求去扣减了数据库 D1 的数据,这时候,在提交阶段再去扣减库存时,数据库 D1 的数据就会超售变成了负 1。
–问题2:死锁
但正因为要加锁,会导致两阶段提交存在一系列问题,最严重的就是死锁问题,一旦发生故障,数据库就会阻塞,尤其在提交阶段,如果发生故障,数据都还处于资源锁定状态,将无法完成后续的事务提交操作。
–问题3:性能问题
其次是性能问题,数据库(如 MySQL )在执行过程中会对操作的数据行执行数据行锁,如果此时其他的事务刚好也要操作被锁定的数据行,那它们就只能阻塞等待,使分布式事务出现高延迟和性能低下。
–问题4:数据不一致
再有就是数据不一致性,在提交阶段,当事务管理器向参与者发送提交事务请求之后,如果此时出现了网络异常,只有部分数据库接收到请求,那么会导致未接收到请求的数据库无法提交事务,整个系统出现数据不一致性。
2> 基于 MQ 的可靠消息投递方案
–一般使用:基于 MQ 的可靠消息队列投递方案是目前互联网最为常用的方式,在应对高并发场景下的分布式事务问题时,种方案通过放弃强一致性,而选择最终一致性,来提高系统的可用性。
–case:还是拿下单场景举例,当订单系统调用优惠券系统时,将扣减优惠券的事件放入消息队列中,最终给优惠券系统来执行,然后只要保证事件消息能够在优惠券系统内被执行就可以了,因为消息已经持久化在消息中间件中,即使消息中间件发生了宕机,我们将它重启后也不会出现消息丢失的问题。
–基于 MQ 的可靠消息投递的方案不仅可以解决由于业务流程的同步执行而造成的阻塞问题,还可以实现业务解耦合流量削峰。这种方案中的可选型的 MQ 也比较多,比如基于 RabbitMQ 或者 RocketMQ,但并不是引入了消息队列中间件就万事大吉了,通常情况下,面试官会着重通过以下两个知识点来考察你对这种方案的掌握程度。
–问题1:MQ 自动应答机制导致的消息丢失
订阅消息事件的优惠券服务在接收订单服务投递的消息后,消息中间件(如 RabbitMQ)默认是开启消息自动应答机制,当优惠券系统消费了消息,消息中间件就会删除这个持久化的消息。
但在优惠券系统执行的过程中,很可能因为执行异常导致流程中断,那这时候消息中间件中就没有这个数据了,进而会导致消息丢失。
解决:因此你要采取编程的方式手动发送应答,也就是当优惠券系统执行业务成功之后,消息中间件才能删除这条持久化消息。
–问题2:在大促的时候,瞬时流量剧增,很多没能及时消费的消息积压在 MQ 队列中,这个问题如何解决呢?
高并发场景下的消息积压导致消息丢失:
导致原因—分布式部署环境基于网络进行通信,而在网络通信的过程中,上下游可能因为各种原因而导致消息丢失。比如优惠券系统由于流量过大而触发限流,不能保证事件消息能够被及时地消费,这个消息就会被消息队列不断地重试,最后可能由于超过了最大重试次数而被丢弃到死信队列中。
思路—但实际上,你需要人工干预处理移入死信队列的消息,于是在这种场景下,事件消息大概率会被丢弃。而这个问题源于订单系统作为事件的生产者进行消息投递后,无法感知它下游(即优惠券系统)的所有操作,那么优惠券系统作为事件的消费者,是消费成功还是消费失败,订单系统并不知道。
顺着这个思路,如果让订单知道消费执行结果的响应,即使出现了消息丢失的情况,订单系统也还是可以通过定时任务扫描的方式,将未完成的消息重新投递来进行消息补偿。这是基于消息队列实现分布式事务的关键,是一种双向消息确认的机制。
实现—那么如何落地实现呢?你可以先让订单系统把要发送的消息持久化到本地数据库里,然后将这条消息记录的状态设置为代发送,紧接着订单系统再投递消息到消息队列,优惠券系统消费成功后,也会向消息队列发送一个通知消息。当订单系统接收到这条通知消息后,再把本地持久化的这条消息的状态设置为完成。
这样做后,即使最终 MQ 出现了消息丢失,也可以通过定时任务从订单系统的本地数据库中扫描出一段时间内未完成的消息,进行重新投递,最终保证订单系统和优惠券系统的最终事务一致性。
06 分布式锁的实现原理
三、中间件常用组件的原理和设计
07 RPC造轮子的能力
public class BIOSever {
ServerSocket ss = new ServerSocket();
// 绑定端口 9090
ss.bind(new InetSocketAddress("localhost", 9090));
System.out.println("server started listening " + PORT);
try {
Socket s = null;
while (true) {
// 阻塞等待客户端发送连接请求
s = ss.accept();
new Thread(new ServerTaskThread(s)).start();
}
} catch (Exception e) {
// 省略代码...
} finally {
if (ss != null) {
ss.close();
ss = null;
}
}
public class ServerTaskThread implements Runnable {
// 省略代码...
while (true) {
// 阻塞等待客户端发请求过来
String readLine = in.readLine();
if (readLine == null) {
break;
}
// 省略代码...
}
// 省略代码...
}
总结:
08 MQ:消息队列的丢失、重复、积压问题
思考题:Kafaka如何实现高性能
双十一预约抢购
四、数据库原理与设计
09 MySQL索引原理与优化
10 MySQL事务隔离与锁机制
思考题:
11 读多写少:MySQL优化数据查询方案
- 读多写少:MySQL如何优化数据查询方案?
12 写多读少:MySQL优化数据存储
五、分布式缓存原理与设计
13 缓存原理:Redis需要掌握的原理
亮点:回答出6.0多线程处理网络请求,读写命令仍是单线程处理
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210418021012524.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4NzM0NDAz,size_16,col# 架构设计面试精讲
一、架构原理与技术认知
01 技术认知
- 1 架构设计的4点理解?
架构拆分其实是管理在技术上提效的一种手段
–** 为什么做架构拆分?**通常最直接目的就是做系统之间解耦、子系统之间解耦,或模块之间的解耦。
–**为什么要做系统解耦?**系统解耦后,使得原本错综复杂的调用逻辑能有序地分布到各个独立的系统中,从而使得拆封后的各个系统职责更单一,功能更为内聚。
–**为什么要做职责单一?**因为职责单一的系统功能逻辑的迭代速度会更快,会提高研发团队响应业务需求的速度,也就是提高了团队的开发效率。
–**为什么要关注开发效率?**研发迭代效率的提升是任何一家公司在业务发展期间都最为关注的问题,所以从某种程度上看,架构拆分是系统提效最直接的手段。 - 2 课程案例(后续以此扩展)
02 架构视角
- 1 问题:你之前如何设计这个系统的?–注重系统设计思路
- 2 一个优化实例
1)场景
在电商中,当用户发表一条商品评论,后台的逻辑是点评系统会调用一系列的远程 API 接口,如调用风控系统、广告系统、消息系统……几个甚至十几个系统的接口。
2)初期实现
实现:同步 RPC(Remote Procedure Call,远程过程调用)远程调用各系统接口。
优点:在系统少、逻辑简单的阶段很符合实际情况的设计。
缺陷:过多地依赖其他系统,导致评论发布的接口性能很低,可用性也容易受到其他系统影响。而且每当点评系统需求上线时,其他系统都需要跟着进行联调测试,导致需求迭代速度缓慢。
3)优化
思路:问题在于系统间的耦合度太高。解决办法就是采用异步化解耦,从而通过引入 MQ 消息管道,在架构上进行系统业务逻辑拆分,将原本强依赖的系统间的同步 RPC 调用变成异步消息触发。架构如下:
不加分回答: 说“我引入了 MQ 消息队列,做了系统解耦,采用异步消息通知的方式来触发系统调用”。
分析:网络答案,项目真实性质疑。
4)加分回答:
**分层表述:**四个层面的答案:
① 谈复杂来源;② 谈解决方案;③ 谈评估标准;④ 说技术实现。
1> 复杂来源
评估系统复杂度: 互联网软件通常分为功能性的复杂度和非功能性的复杂度两种。
功能性复杂度:要解决业务发展带来的系统耦合、开发效率缓慢问题。
非功能性复杂度:要保证系统的高可用性。
2> 解决方案
一般出2~3个备选方案,设计要考虑可行性和优缺点。
① 采用开源的 MQ 消息管道。目前 MQ 消息管道有很多开源解决方案,比如 Kafka、RocketMQ、RabbitMQ 等。在实际项目中,你可以根据不同的应用场景选择合适的成熟开源消息队列方案,这是很多公司常用的做法。
② 采用开源的 Redis 实现消息队列。方案 1 虽然应用了开源 MQ 实现点评消息的通信,但是因为引入一个消息中间件就会带来运维成本,所以方案 2 可以基于轻量级的 Redis 实现,以降低系统的维护成本和实现复杂度。
③ 采用内存队列 + MySQL 来实现。方案 2 中虽然应用了较为轻量级的 Redis 来实现,但是还需要引入一个缓存系统,同样也会带来运维成本,所以方案 3 是直接基于 MySQL 实现,即基于内存队列的方式,异步持久化到数据库,然后通过定时任务读取 MySQL 中的消息并处理。
3> 评估标准
目的:选择最合适的实现方案。
无优劣,更适合业务。
- 3 总结
二、分布式原理与设计
03 CAP分布式理论
- 1 CAP 理论
–概念:C(Consistency)是数据一致性、A(Availability)是服务可用性、P(Partition tolerance)是分区容错性。C、A、P 只能同时满足两个目标,而由于在分布式系统中,P 是必须要保留的,所以要在 C 和 A 间进行取舍。假如要保证服务的可用性,就选择 AP 模型,而要保证一致性的话,就选择 CP 模型。
–必要性:在绝大多数时间里并不存在网络分区(网络不会经常出现问题)。为什么还要进行三选二吗(CP 或者 AP)?原因:不同的分布式系统要根据业务场景和业务需求在 CAP 三者中进行权衡。CAP 理论用于指导在系统设计时需要衡量的因素,而非进行绝对地选择。 - 2 CAP原理
–现在有一个分布式系统 A,它有一个副本 A1,在正常情况下,客户端 Client 写数据到系统 A,然后数据从 A 节点同步到 A1 节点,再返回给 Client 成功状态。
–这时,客户端 Client 从任何节点 A 或 A1 读取数据,都能读取到最新写入的数据,说明 A 和 A1 的数据是一致的,并且 A 和 A1 也都是可用的。
–网络是不可靠的,节点 A 和 A1 的网络随时会因为中断而出现分区。所谓网络分区就是由于网络不通导致节点 A 和 A1 被隔离在不同的网络子集中,此时节点 A 的数据就不能及时同步到节点 A1 中了。
–出现网络分区时,根据 CAP 理论,需要在 A 和 C 中进行取舍,即要么保证系统的可用性,要么保证数据一致性。
- 3 实践经验
1)BASE 理论
–引入:分布式的设计方案是数据一致性和系统可用性的权衡,并不是非此即彼。所以即使无法做到强一致性(简单来讲强一致性就是在任何时刻所有的用户查询到的数据都是最新的),也可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。由此引出 BASE 理论。
–概念:是CAP 理论的延伸。BASE 是 Basically Available(基本可用)、Soft State(软状态)和 Eventually Consistent(最终一致性)三个单词的简写;
–作用:是保证系统的可用性,然后通过最终一致性来代替强一致性,它是目前分布式系统设计中最具指导意义的经验总结。
2)实际项目中,你如何通过 BASE 理论来指导设计实践呢?
1> 基本可用性
2> 软状态和最终一致性
指的是允许系统中的数据存在中间状态,这同样是为了系统可用性而牺牲一段时间窗内的数据一致性,从而保证最终的数据一致性的做法。
注:目前这种处理数据的方式几乎成了互联网的标配设计模式,最经典的例子是在用户下单的时候不需要真正地扣减库存,而是仅在前台计个数,然后通过异步任务在后台批量处理。 - 4 CAP理论面试回答
1)回答逻辑
1> 先充分理解理论原理,不能仅浮在概念上(这一点需要你课下下功夫);
2> 其次需要有自己的思考,表现出你思考能力的不同;
3> 然后将理论结合于实践,讨论实际中处理问题时的思考逻辑。
2)扩展
知识体系、技术判断
1> 分布式系统的理解
分布式系统看起来就像一个计算机。计算机包括五大体系结构(即冯诺依曼结构),它有五大部件:分别是控制器、运算器、存储器、输入及输出。你可以这么理解:一个分布式系统也包含这五大部件,其中最重要的是计算与存储。计算与存储由一系列网络节点组成,每个节点之间的通信就是输入与输出,各节点之间的调度管理就是控制器。
- 5 例子:Redis 是否可以作为分布式锁
解题思路:
1)说明现实存在的问题
一般使用 setnx 方法,通过 Redis 实现锁和超时时间来控制锁的失效时间。但是在极端的情况下,当 Reids 主节点挂掉,但锁还没有同步到从节点时,根据哨兵机制,从就变成了主,继续提供服务。这时,另外的线程可以再来请求锁,此时就会出现两个线程拿到了锁的情况。
2)回归理论的指导
根据对 CAP 理论的理解,Redis 的设计模型是 AP 模型,而分布式锁是一个 CP 场景,那么很明显,将 Redis 这种 AP 模型的架构应用于 CP 的场景,在底层的技术选型上就是错误的。
3)扩展到知识体系
Redis 属于分布式存储系统,你的头脑里就要有对分布式存储系统领域的知识体系。思考它的数据存储、数据分布、数据复制,以及数据一致性都是怎么做的,用了哪些技术来实现,为什么要做这样的技术或算法选型。你要学会从多维度、多角度去对比、分析同一分布式问题的不同方法,然后综合权衡各种方法的优缺点,最终形成自己的技术认知和技术判断力。
4)有技术的判断力
比如通过 Redis,你能想到目前分布式缓存系统的发展现状以及技术实现,如果让你造一个“Redis”出来,你会考虑哪些问题等。虽然在实际工作中不推荐重复“造轮子”,但在面试中要表现出自己具备“造轮子”的能力。
04 分布式系统原理问题-数据存储
- 0 综述
- 1 常见问题
–常见问题:
如何设计一个支持海量商品存储的高扩展性架构?
在做分库分表时,基于 Hash 取模和一致性 Hash 的数据分片是如何实现的?
在电商大促时期,如何对热点商品数据做存储策略 ?
强一致性和最终一致性的数据共识算法是如何实现的 ?
–问题核心:在分布式系统中,核心的考察点包括了分布式系统中数据的存储、分布、复制,以及相关协议和算法。
如“如何设计海量商品数据的存储?” - 2 分布式数据存储的问题
分布式数据存储的问题可以分成:数据分片、数据复制,以及数据一致性带来的相关问题
1)数据分片
–背景:为了解决单台存储设备的局限性,会把数据分布到多台存储节点上,以此实现数据的水平扩展。
–概念:数据分片即按照一定的规则将数据路由到相应的存储节点中,从而降低单存储节点带来的读写压力。
–常见方案:Hash(哈希分片)与 Range(范围分片)。
2)数据复制
–数据复制:数据复制会产生副本,而副本是分布式存储系统解决高可用的唯一手段,这也是我们熟知的主从模式,又叫 master-slave。
–作用:在分布式存储系统中,通常会设置数据副本的主从节点,当主节点出现故障时,从节点可以替代主节点提供服务,从而保证业务正常运行。
3)数据一致性
–解决:节点故障,完成从节点替代主节点。
–数据一致性,通常要考虑一致性强弱(即强一致性和最终一致性的问题)。而要解决一致性的问题,则要进行一系列的一致性协议:如两阶段提交协议(Two-Phrase Commit,2PC)、Paxos 协议选举、Raft 协议、Gossip 协议。
- 3 存储策略-分片
1)问题引入
–问题:假设你是一家电商网站的架构师,现在要将原有单点上百 G 的商品做数据重构,存储到多个节点上,你会如何设计存储策略?
–分析:因为是**商品存储扩容的设计问题,**很容易想到做数据的分库分表,**也就是重新设计数据的分片规则,*常用的分片策略有两种,即 Hash(哈希)分片和 Range(范围)分片。**从这一点出发会考察你Hash(哈希)分片的具体实现原理。**
**2)Hash分片**
–分片:商品表包括主键、商品 ID、商品名称、所属品类和上架时间等字段。如果以商品 ID 作为关键字进行分片,系统会通过一个 Hash 函数计算商品 ID 的 Hash 值,然后取模,就能得到对应的分片。模为 4 就表示系统一共有四个节点,每个节点作为一个分片。
–例子:假设Hash 函数为 “商品 ID % 节点个数 4”,通过计算可以得到每个数据应该存入的节点:计算结果为 0 的数据存入节点 A;结果为 1 的数据存入节点 B;结果为 2 的数据存入节点 C;计算为 3 的数据存储节点 D。
–优缺点:
优点在于可以保证数据非常均匀地分布到多个分片上,并且实现起来简单,但扩展性很差,因为分片的计算方式就是直接用节点取模,节点数量变动,就需要重新计算 Hash,就会导致大规模数据迁移的工作。
**3)一致性Hash**
–问题:如何解决 Hash 分片的缺点,既保证数据均匀分布,又保证扩展性?
–一致性Hash:它是指将存储节点和数据都映射到一个首尾相连的哈希环上。存储节点一般可以根据 IP 地址进行 Hash 计算,数据的存储位置是从数据映射在环上的位置开始,依照顺时针方向所找到的第一个存储节点。
–例子:在具体操作过程中,通常会选择带有虚拟节点的一致性 Hash。
存储节点–假设在这个案例中将虚拟节点的数量设定为 10 个,就形成 10 个分片,而这 10 个分片构成了整个 Hash 空间。现在让 A 节点对应虚拟节点 0 ~ 3,B 节点对应虚拟节点 4 ~ 6,C 节点对应虚拟节点 7 ~ 8,D 节点对应虚拟节点 9。
数据–同样根据哈希函数为 “商品 ID % 节点个数 10”得到每一个商品在 Hash 环上的位置,然后根据顺时针查找最近的存储节点,即数据实际映射的位置。计算结果为:0 ~ 3 的数据存入节点 A;结果为 4 ~ 6 的数据存入节点 B;结果为 7 ~ 8 的数据存入节点 C;计算为 9 的数据存储节点 D。
–新增:当我们新增一台服务器,即节点 E 时,受影响的数据仅仅是新服务器到所处环空间中前一台服务器(即沿着逆时针方向的第一台服务器)之间的数据。结合我们的示例,只有商品 100 和商品 101 从节点 A 被移动到节点 E,其他节点的数据保持不变。此后,节点 A 只存储 Hash 值为 2 和 3 的商品,节点 E 存储 Hash 值为 0 和 1 的商品。
–优缺点:一致性 Hash 分片的优点是数据可以较为均匀地分配到各节点,其并发写入性能表现也不错。
(初级到此步不会追问)
–缺点:
虽然一致性 Hash 提升了稳定性,使节点的加入和退出不会造成大规模的数据迁移,但本质上 Hash 分片是一种静态的分片方式,必须要提前设定分片的最大规模,**而且无法避免单一热点问题**, 某一数据被海量并发请求后,不论如何进行 Hash,数据也只能存在一个节点上,这势必会带来热点请求问题。比如案例中的电商商品,如果某些商品卖得非常火爆,通过 Hash 分片的方式很难针对热点商品做单独的架构设计。
**4)范围(Range)分片-分布式数据存储的理解**
–问题:解决单一热点问题?
–Range(范围)分片:与 Hash 分片不同的是,Range 分片能结合业务逻辑规则。
–例子:例如,我们用 “Category(商品类目)” 作为关键字进行分片时,不是以统一的商品一级类目为标准,而是可以按照一、二、三级类目进行灵活分片。例如,按业务品类分片,对于京东强势的 3C 品类,可以按照 3C 的三级品类设置分片;对于弱势品类,可以先按照一级品类进行分片,这样会让分片间的数据更加平衡。
要达到这种灵活性,前提是要有能力控制数据流向哪个分区,一个简单的实现方式是:预先设定主键的生成规则,根据规则进行数据的分片路由,但这种方式会侵入商品各条线主数据的业务规则,更好的方式是基于分片元数据(不过架构设计没有好坏,只有适合与否,所以在面试场景中,我建议你用擅长的解决方案来回答问题)。
基于分片元数据的方式,就是调用端在操作数据的时候,先问一下分片元数据系统数据在哪,然后在根据得到的地址操作数据。元数据中存储的是数据分片信息,分片信息就是数据分布情况。在一个分布式存储系统中,承担数据调度功能的节点是分片元数据,当客户端收到请求后,会请求分片元数据服务,获取分片对应的实际节点地址,才能访问真正的数据。而请求分片元数据获取的信息也不仅仅只有数据分片信息,还包括数据量、读写 QPS 和分片副本的健康状态等。
这种方式的灵活性在于分片规则不固定,易扩展,但是高灵活性就会带来高复杂性,从存储的角度看,元数据也是数据,特殊之处在于它类似一个路由表,每一次请求都要访问它,所以分片元数据本身就要做到高可用。如果系统支持动态分片,那么分片信息的变更数据还要在节点之间进行同步,这又带来*多副本之间的一致性问题,以此延伸出如何保证分片元数据服务的可用性和数据一致性?
最直接的方式是专门给元数据做一个服务集群,并通过一致性算法复制数据。在实现方式上,就是将元数据服务的高可用和数据一致性问题转嫁给外围协调组件,如 ETCD 集群,这样既保证了系统的可靠,数据同步的成本又比较低。知道了设计思路,那具体的架构实现上怎么做 ?
1 给分片元数据做集群服务,并通过 ETCD 存储数据分片信息。
2 每个数据存储实例节点定时向元数据服务集群同步心跳和分片信息。
3 当调用端的请求过来时,元数据服务节点只需要做好高可用和缓存即可。
给元数据分片:
- 4 共识算法
如果面试官想挖掘你的能力,还会深入聊到共识算法,在一致性共识算法和最终一致性共识算法方面提出类似的问题,比如, ETCD 是如何解决数据共识问题的?为什么要选择这种数据复制方式呢?
1)分布式系统中的一致性共识算法
对于这类问题,你要从一致性算法原理层面解答,思路是:清楚 ETCD 的共识算法是什么,还有哪些常用的共识算法,以及为什么 ETCD 会做这样的选型。
–问题:ETCD 的共识算法是基于 Raft 协议实现的强一致性算法,同类的强一致性算法还有 Paxos,在面试过程中,面试官很可能让你从自己的角度理解一下这两个算法,当然也会直接问:为什么没有选择 Paxos 而选择了 Raft ?这个问题对应聘高级研发的同学来讲很常见,主要考核你对以下内容的理解:
Paxos 算法解决了什么问题?
Basic Paxos 算法的工作流程是什么?
Paxos 算法和 Raft 算法的区别又是什么?
1> Paxos
在分布式系统中,造成系统不可用的场景很多,比如服务器硬件损坏、网络数据丢包等问题,解决这些问题的根本思路是多副本,副本是分布式系统解决高可用的唯一手段,也就是主从模式,那么如何在保证一致性的前提下,提高系统的可用性,Paxos 就被用来解决这样的问题,而 Paxos 又分为 Basic Paxos 和 Multi Paxos,然而因为它们的实现复杂,工业界很少直接采用 Paxos 算法,所以 ETCD 选择了 Raft 算法 (在面试过程中,面试官容易在这里设置障碍,来对候选者做技术分层)。
2> Raft
Raft 是 Multi Paxos 的一种实现,是通过一切以领导者为准的方式,实现一系列值的共识,然而不是所有节点都能当选 Leader 领导者,Raft 算法对于 Leader 领导者的选举是有限制的,只有最全的日志节点才可以当选。正因为 ETCD 选择了 Raft,为工业界提供了可靠的工程参考,就有更多的工程实现选择基于 Raft,如 TiDB 就是基于 Raft 算法的优化。
2)最终一致性算法
如果把问题设计的极端一些,考察你对最终一致性算法的掌握,还可以有一种思路:分片元数据服务毕竟是一个中心化的设计思路,而且基于强一致性的共识机制还是可能存在性能的问题,有没有更好的架构思路呢?
–可用性:既然要解决可用性的问题,根据 Base 理论,需要实现最终一致性,那么 Raft 算法就不适用了,因为 Raft 需要保证大多数节点正常运行后才能运行。这个时候,可以选择基于 Gossip 协议的实现方式。
–Gossip 的协议原理有一种传播机制叫谣言传播,指的是当一个节点有了新数据后,这个节点就变成了活跃状态,并周期性地向其他节点发送新数据,直到所有的节点都存储了该条数据。这种方式达成的数据一致性是 “最终一致性”,即执行数据更新操作后,经过一定的时间,集群内各个节点所存储的数据最终会达成一致,很适合动态变化的分布式系统。
–从图中你可以看到,节点 A 向节点 B、C 发送新数据,节点 B 收到新数据后,变成了活跃节点,然后节点 B 向节点 C、D 发送新数据。
3)一致性共识算法做个总结
到此,我们对一致性共识算法做个总结,共识算法的选择和数据副本数量的多少息息相关,如果副本少、参与共识的节点少,推荐采用广播方式,如 Paxos、Raft 等协议。如果副本多、参与共识的节点多,那就更适合采用 Gossip 这种最终一致性协议。
- 思考题
- 总结:
本章:分布式技术下的数据分片、存储、复制与一致性的原理性问题
05 分布式事务一致性
- 1 分布式事务
–背景:在互联网分布式场景中,原本一个系统被拆分成多个子系统,要想完成一次写入操作,你需要同时协调多个系统,这就带来了分布式事务的问题。
–概念:分布式事务是指:一次大的操作由多个小操作组成,这些小的操作分布在不同的服务器上,分布式事务需要保证这些小操作要么全部成功,要么全部失败
- 2 系统简例
–案例:以京东旅行系统为例,早期的交易系统是通过 .NET 实现的,所有的交易下单逻辑都写在一个独立的系统中。随着技术改造,我们用 Java 重写了核心系统,原本的系统也被拆分成多个子系统,如商品系统、促销系统、订单系统(为了方便理解,我只拿这三个系统举例)。当用户下单时,订单系统生成订单,商品系统扣减库存,促销系统扣减优惠券,只有当三个系统的事务都提交之后,才认为此次下单成功,否则失败。
–解决:这是一个很典型的分布式事务问题,解决方案也很多,有两阶段提交协议(Two-Phase Commit,2PC)、3PC 、TCC 和基于消息队列的实现方式。 - 2 如何实现系统之间的分布式事务一致性?
1)错误回答
所以当很多候选者听到“怎么实现系统之间的分布式一致性?”的问题之后,会信心满满地选择一个方案,回答说:方案很多,可以选择 2PC ,2PC 实现的流程是……
这种答题思路犯了一个很明显的错误,因为在实际工作中,很少采用前几种方案,**基本都是基于 MQ 的可靠消息投递的方式来实现。**所以一上来就说 2PC、3PC 或者 TCC 会让我觉得你并没有实际做过。
2)可借鉴流程
建议你先介绍目前主流实现分布式系统事务一致性的方案(也就是基于 MQ 的可靠消息投递的机制)然后回答出可实现方案和关键知识点。另外,为了和面试官进一步交流,你可以提出 2PC 或 TCC (这是一种交流方案)。因为 2PC 或 TCC 在工业界落地代价很大,不适合互联网场景,所以只有少部分的强一致性业务场景(如金融支付领域)会基于这两种方案实现。而你可以围绕它们的解决思路和方案弊端与面试官讨论,这会让你和面试官由不平等的“面试与被面试”变成平等且友好的“双方沟通”,是一种面试套路。
但要做到以上几点,需要建立在你深入掌握分布式事务一致性问题的基础之上,所以接下来,我们就解析一下面试中最为常见的两种实现方案。
1> 基于两阶段提交的解决方案
–概念:2PC 是分布式事务教父级协议,它是数据库领域解决分布式事务最典型的协议。它的处理过程分为准备和提交两个阶段,每个阶段都由协调者(Coordinator)和参与者(Participant)共同完成:
协调者就是事务管理器;
参与者就是具体操作执行的资源管理器。
–知识支持:
Java 程序员都知道,XA 是由 X/Open 组织提出的分布式事务的规范,规范主要定义了事务管理器(Transaction Manager)和资源管理器(Resource Manager)之间的接口,事务管理器负责全局事务的协调者,资源管理器负责管理实际资源(如 MySQL、Oracle 等数据库)。而Java 平台上事务规范 JTA(Java Transaction API)就是对 XA 分布式事务规范标准的实现。例如在 Spring 中就通过 JtaTransactionManager 来配置分布式事务,然后通过管理多个 ResourceManager 来管理多个数据源,进而操作多个数据库之间的事务。
–2PC的实现:
以课程开头的系统为例,订单数据、商品数据,以及促销数据被分别存储在多个数据库实例中,用户在执行下单的时候,交易主流程的业务逻辑则集中部署在一个应用服务器集群上,然后通过 Spring 容器访问底层的数据库实例,而容器中的 JTA 事务管理器在这里就作为事务管理器,Resource 资源管理器就作为底层的数据库实例的资源管理器。
我们假设订单数据,商品数据和促销数据分别保存在数据库 D1,数据库 D2 和数据库 D3 上。
准备阶段,事务管理器首先通知所有资源管理器开启事务,询问是否做好提交事务的准备。如资源管理器此时会将 undo 日志和 redo 日志计入事务日志中,并做出应答,当协调者接收到反馈 Yes 后,则准备阶段结束。
提交阶段,当收到所有数据库实例的 Yes 后,事务管理器会发出提交指令。每个数据库接受指令进行本地操作,正式提交更新数据,然后向协调者返回 Ack 消息,事务结束。
中断阶段,如果任何一个参与者向协调者反馈了 No 响应,例如用户 B 在数据库 D3 上面的余额在执行其他扣款操作,导致数据库 D3 的数据无法锁定,则只能向事务管理器返回失败。此时,协调者向所有参与者发出 Rollback 请求,参与者接收 Rollback 请求后,会利用其在准备阶段中记录的 undo 日志来进行回滚操作,并且在完成事务回滚之后向协调者发送 Ack 消息,完成事务回滚操作。
以上就是基于 2PC 实现分布式事务的原理。
当你和面试官交流 2PC 的原理时,往往不止于此,就像我们开篇提到的,我们并不会基于 2PC 来实现分布式事务一致性,虽然 2PC 可以借助数据库的本地事务操作,实现起来较为简单,不用侵入业务逻辑,但是它也存在着很多问题。
2PC 在准备阶段会要求每个资源管理器进行资源锁定,如 MySQL 的行锁。否则如果在提交阶段提交之前数据发生改变,就会出现数据不一致的情况。
–问题1:超卖
还是上面的例子,如果商品库存数据为 1,也就是数据库 D1 为 1,在准备阶段询问是否可以扣减库存,商品数据返回可以,此时如果不锁定数据,在提交阶段之前另外一个请求去扣减了数据库 D1 的数据,这时候,在提交阶段再去扣减库存时,数据库 D1 的数据就会超售变成了负 1。
–问题2:死锁
但正因为要加锁,会导致两阶段提交存在一系列问题,最严重的就是死锁问题,一旦发生故障,数据库就会阻塞,尤其在提交阶段,如果发生故障,数据都还处于资源锁定状态,将无法完成后续的事务提交操作。
–问题3:性能问题
其次是性能问题,数据库(如 MySQL )在执行过程中会对操作的数据行执行数据行锁,如果此时其他的事务刚好也要操作被锁定的数据行,那它们就只能阻塞等待,使分布式事务出现高延迟和性能低下。
–问题4:数据不一致
再有就是数据不一致性,在提交阶段,当事务管理器向参与者发送提交事务请求之后,如果此时出现了网络异常,只有部分数据库接收到请求,那么会导致未接收到请求的数据库无法提交事务,整个系统出现数据不一致性。
2> 基于 MQ 的可靠消息投递方案
–一般使用:基于 MQ 的可靠消息队列投递方案是目前互联网最为常用的方式,在应对高并发场景下的分布式事务问题时,种方案通过放弃强一致性,而选择最终一致性,来提高系统的可用性。
–case:还是拿下单场景举例,当订单系统调用优惠券系统时,将扣减优惠券的事件放入消息队列中,最终给优惠券系统来执行,然后只要保证事件消息能够在优惠券系统内被执行就可以了,因为消息已经持久化在消息中间件中,即使消息中间件发生了宕机,我们将它重启后也不会出现消息丢失的问题。
–基于 MQ 的可靠消息投递的方案不仅可以解决由于业务流程的同步执行而造成的阻塞问题,还可以实现业务解耦合流量削峰。这种方案中的可选型的 MQ 也比较多,比如基于 RabbitMQ 或者 RocketMQ,但并不是引入了消息队列中间件就万事大吉了,通常情况下,面试官会着重通过以下两个知识点来考察你对这种方案的掌握程度。
–问题1:MQ 自动应答机制导致的消息丢失
订阅消息事件的优惠券服务在接收订单服务投递的消息后,消息中间件(如 RabbitMQ)默认是开启消息自动应答机制,当优惠券系统消费了消息,消息中间件就会删除这个持久化的消息。
但在优惠券系统执行的过程中,很可能因为执行异常导致流程中断,那这时候消息中间件中就没有这个数据了,进而会导致消息丢失。
解决:因此你要采取编程的方式手动发送应答,也就是当优惠券系统执行业务成功之后,消息中间件才能删除这条持久化消息。
–问题2:在大促的时候,瞬时流量剧增,很多没能及时消费的消息积压在 MQ 队列中,这个问题如何解决呢?
高并发场景下的消息积压导致消息丢失:
导致原因—分布式部署环境基于网络进行通信,而在网络通信的过程中,上下游可能因为各种原因而导致消息丢失。比如优惠券系统由于流量过大而触发限流,不能保证事件消息能够被及时地消费,这个消息就会被消息队列不断地重试,最后可能由于超过了最大重试次数而被丢弃到死信队列中。
思路—但实际上,你需要人工干预处理移入死信队列的消息,于是在这种场景下,事件消息大概率会被丢弃。而这个问题源于订单系统作为事件的生产者进行消息投递后,无法感知它下游(即优惠券系统)的所有操作,那么优惠券系统作为事件的消费者,是消费成功还是消费失败,订单系统并不知道。
顺着这个思路,如果让订单知道消费执行结果的响应,即使出现了消息丢失的情况,订单系统也还是可以通过定时任务扫描的方式,将未完成的消息重新投递来进行消息补偿。这是基于消息队列实现分布式事务的关键,是一种双向消息确认的机制。
实现—那么如何落地实现呢?你可以先让订单系统把要发送的消息持久化到本地数据库里,然后将这条消息记录的状态设置为代发送,紧接着订单系统再投递消息到消息队列,优惠券系统消费成功后,也会向消息队列发送一个通知消息。当订单系统接收到这条通知消息后,再把本地持久化的这条消息的状态设置为完成。
这样做后,即使最终 MQ 出现了消息丢失,也可以通过定时任务从订单系统的本地数据库中扫描出一段时间内未完成的消息,进行重新投递,最终保证订单系统和优惠券系统的最终事务一致性。
06 分布式锁的实现原理
三、中间件常用组件的原理和设计
07 RPC造轮子的能力
public class BIOSever {
ServerSocket ss = new ServerSocket();
// 绑定端口 9090
ss.bind(new InetSocketAddress("localhost", 9090));
System.out.println("server started listening " + PORT);
try {
Socket s = null;
while (true) {
// 阻塞等待客户端发送连接请求
s = ss.accept();
new Thread(new ServerTaskThread(s)).start();
}
} catch (Exception e) {
// 省略代码...
} finally {
if (ss != null) {
ss.close();
ss = null;
}
}
public class ServerTaskThread implements Runnable {
// 省略代码...
while (true) {
// 阻塞等待客户端发请求过来
String readLine = in.readLine();
if (readLine == null) {
break;
}
// 省略代码...
}
// 省略代码...
}
总结:
08 MQ:消息队列的丢失、重复、积压问题
思考题:Kafaka如何实现高性能
双十一预约抢购
四、数据库原理与设计
09 MySQL索引原理与优化
10 MySQL事务隔离与锁机制
思考题:
11 读多写少:MySQL优化数据查询方案
- 读多写少:MySQL如何优化数据查询方案?
12 写多读少:MySQL优化数据存储
五、分布式缓存原理与设计
13 缓存原理:Redis需要掌握的原理
亮点:回答出6.0多线程处理网络请求,读写命令仍是单线程处理
14 缓存策略:缓存穿透、缓存雪崩等问题
15 系统的高可用![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/8ad9f52608552ac0dece28676dd6d8d5.png)
思考题:Redis实现一个计数器
六、高性能高可用设计
15 系统的高可用
总结:
16 回答系统容错、降级等高可用问题
- 保证系统高可用的有效手段
17 系统的高性能
- 怎么证明你做的系统是高性能的?
设计阶段,定义系统性能目标:你要在项目初期定义好系统大致的性能目标,比如希望单台服务器能够负载多少 TPS 的请求,因为不同的性能会影响到系统的架构设计,也会带来不同的成本,一旦过了设计阶段,再因为性能问题调整系统架构,成本极高。比如,当前单机性能是 80 TPS,要想优化到100 TPS,可以做一些小的性能优化,但要提升到 1000 TPS,就要进行架构改造了,代价非常大。
开发阶段,走查代码和业务流程:也就是评审代码,代码包括应用程序源代码、环境参数配置、程序整个调用流程和处理逻辑。比如,用户在 App 中触发了“立即下单”按钮,服务端的应用程序从线程池里取得了线程来处理请求,然后查询了几次缓存和数据库,都读取和写入了什么数据,再把最终的响应返回给 App,响应的数据报文格式是什么,有哪些状态码和异常值……
测试阶段,压测发现系统性能峰值:一般来说,你要在系统上线前,对系统进行全方位的压力测试,绘制出系统吞吐量和延迟曲线,然后找到最佳性能点,并在超过最佳性能点时做限流,如果达不到最佳性能点(比如多数系统的吞吐量,随着压力增大,吞吐量上不去)就需要考虑出现延迟和吞吐量的这几种情况。
1.定位延迟问题
你要本着端到端的策略,大到整体流程,小到系统模块调用,逐一排查时间消耗在哪里。
你可以使用 kill -3 PID, jstack 等命令打印系统当前的线程执行的堆栈;还可以用一些性能分析工具,如 JProfiler 来监控系统的内存使用情况、垃圾回收、线程运行状况,比如你发现了运行的 100 个线程里面,有 80 个卡在某一个锁的释放上面,这时极有可能这把锁造成的延迟问题。
\2. 对于吞吐量问题的定位
对于吞吐量指标要和 CPU使用率一起来看,在请求速率逐步增大时,经常会出现四种情况:
- 总结
18 应对千万级流量问题
- 如何设计高性能的架构
你在设计高性能系统架构时,首先是清楚认知系统的硬性性能指标,举个例子。
指标需求:我们要保证系统的 TP 99 为 2s;
表面意思:系统要保证 99% 的请求的响应时间都在 2s 内;
深层意思:对有经验的架构师来说,这样的需求其实是不明确的,任何系统都有自己的承载能力范围,换句话说就是在并发用户数限定范围之内,一定要保证系统的 TP 99 = 2s,例如“我们要保证系统并发数在 100 万用户内的时候,TP 99 为 2s”,对于系统设计者而言,要清楚系统有所能,有所不能。
所以,对一个架构师来说,要设计一个高性能的架构,至少要有以下四个系统设计的思考步骤。
明确指标: 比如当系统小于 100 万并发用户时,要保证系统的 TP 99 = 2s 。
保护系统: 当系统的并发用户数量超过 100 万,要做到保证有 100 万用户的 TP 99 = 2s ,然后保护系统,并拒绝其他用户的连接请求。
用户体验: 为了用户体验,要给系统承载容量外的用户提供优雅的体验,比如服务器排队机制,并附加具体、明确的信息提示。
快速扩容: 这一步很容易被一些同学忽略,如今系统的性能指标还有一点就是贵在快速反应,比如能承诺出现流量压力时,可以在 3 分钟内完成扩容,并保证扩容后能承载的并发用户数量的 TP 99 = 2s。
or_FFFFFF,t_70)
14 缓存策略:缓存穿透、缓存雪崩等问题
15 系统的高可用![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/8ad9f52608552ac0dece28676dd6d8d5.png)
思考题:Redis实现一个计数器
六、高性能高可用设计
15 系统的高可用
总结:
16 回答系统容错、降级等高可用问题
- 保证系统高可用的有效手段
17 系统的高性能
- 怎么证明你做的系统是高性能的?
设计阶段,定义系统性能目标:你要在项目初期定义好系统大致的性能目标,比如希望单台服务器能够负载多少 TPS 的请求,因为不同的性能会影响到系统的架构设计,也会带来不同的成本,一旦过了设计阶段,再因为性能问题调整系统架构,成本极高。比如,当前单机性能是 80 TPS,要想优化到100 TPS,可以做一些小的性能优化,但要提升到 1000 TPS,就要进行架构改造了,代价非常大。
开发阶段,走查代码和业务流程:也就是评审代码,代码包括应用程序源代码、环境参数配置、程序整个调用流程和处理逻辑。比如,用户在 App 中触发了“立即下单”按钮,服务端的应用程序从线程池里取得了线程来处理请求,然后查询了几次缓存和数据库,都读取和写入了什么数据,再把最终的响应返回给 App,响应的数据报文格式是什么,有哪些状态码和异常值……
测试阶段,压测发现系统性能峰值:一般来说,你要在系统上线前,对系统进行全方位的压力测试,绘制出系统吞吐量和延迟曲线,然后找到最佳性能点,并在超过最佳性能点时做限流,如果达不到最佳性能点(比如多数系统的吞吐量,随着压力增大,吞吐量上不去)就需要考虑出现延迟和吞吐量的这几种情况。
1.定位延迟问题
你要本着端到端的策略,大到整体流程,小到系统模块调用,逐一排查时间消耗在哪里。
你可以使用 kill -3 PID, jstack 等命令打印系统当前的线程执行的堆栈;还可以用一些性能分析工具,如 JProfiler 来监控系统的内存使用情况、垃圾回收、线程运行状况,比如你发现了运行的 100 个线程里面,有 80 个卡在某一个锁的释放上面,这时极有可能这把锁造成的延迟问题。
\2. 对于吞吐量问题的定位
对于吞吐量指标要和 CPU使用率一起来看,在请求速率逐步增大时,经常会出现四种情况:
- 总结
18 应对千万级流量问题
- 如何设计高性能的架构
你在设计高性能系统架构时,首先是清楚认知系统的硬性性能指标,举个例子。
指标需求:我们要保证系统的 TP 99 为 2s;
表面意思:系统要保证 99% 的请求的响应时间都在 2s 内;
深层意思:对有经验的架构师来说,这样的需求其实是不明确的,任何系统都有自己的承载能力范围,换句话说就是在并发用户数限定范围之内,一定要保证系统的 TP 99 = 2s,例如“我们要保证系统并发数在 100 万用户内的时候,TP 99 为 2s”,对于系统设计者而言,要清楚系统有所能,有所不能。
所以,对一个架构师来说,要设计一个高性能的架构,至少要有以下四个系统设计的思考步骤。
明确指标: 比如当系统小于 100 万并发用户时,要保证系统的 TP 99 = 2s 。
保护系统: 当系统的并发用户数量超过 100 万,要做到保证有 100 万用户的 TP 99 = 2s ,然后保护系统,并拒绝其他用户的连接请求。
用户体验: 为了用户体验,要给系统承载容量外的用户提供优雅的体验,比如服务器排队机制,并附加具体、明确的信息提示。
快速扩容: 这一步很容易被一些同学忽略,如今系统的性能指标还有一点就是贵在快速反应,比如能承诺出现流量压力时,可以在 3 分钟内完成扩容,并保证扩容后能承载的并发用户数量的 TP 99 = 2s。