课程:《架构设计面试精讲》刘海丰(拉勾)
1 架构设计的4点理解?
架构拆分其实是管理在技术上提效的一种手段
–** 为什么做架构拆分?**通常最直接目的就是做系统之间解耦、子系统之间解耦,或模块之间的解耦。
–**为什么要做系统解耦?**系统解耦后,使得原本错综复杂的调用逻辑能有序地分布到各个独立的系统中,从而使得拆封后的各个系统职责更单一,功能更为内聚。
–**为什么要做职责单一?**因为职责单一的系统功能逻辑的迭代速度会更快,会提高研发团队响应业务需求的速度,也就是提高了团队的开发效率。
–**为什么要关注开发效率?**研发迭代效率的提升是任何一家公司在业务发展期间都最为关注的问题,所以从某种程度上看,架构拆分是系统提效最直接的手段。
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”出来,你会考虑哪些问题等。虽然在实际工作中不推荐重复“造轮子”,但在面试中要表现出自己具备“造轮子”的能力。
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 这种最终一致性协议。
总结:
本章:分布式技术下的数据分片、存储、复制与一致性的原理性问题
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;
}
// 省略代码...
}
// 省略代码...
}
思考题:Kafaka如何实现高性能
亮点:回答出6.0多线程处理网络请求,读写命令仍是单线程处理
思考题:Redis实现一个计数器
设计阶段,定义系统性能目标:你要在项目初期定义好系统大致的性能目标,比如希望单台服务器能够负载多少 TPS 的请求,因为不同的性能会影响到系统的架构设计,也会带来不同的成本,一旦过了设计阶段,再因为性能问题调整系统架构,成本极高。比如,当前单机性能是 80 TPS,要想优化到100 TPS,可以做一些小的性能优化,但要提升到 1000 TPS,就要进行架构改造了,代价非常大。
开发阶段,走查代码和业务流程:也就是评审代码,代码包括应用程序源代码、环境参数配置、程序整个调用流程和处理逻辑。比如,用户在 App 中触发了“立即下单”按钮,服务端的应用程序从线程池里取得了线程来处理请求,然后查询了几次缓存和数据库,都读取和写入了什么数据,再把最终的响应返回给 App,响应的数据报文格式是什么,有哪些状态码和异常值……
测试阶段,压测发现系统性能峰值:一般来说,你要在系统上线前,对系统进行全方位的压力测试,绘制出系统吞吐量和延迟曲线,然后找到最佳性能点,并在超过最佳性能点时做限流,如果达不到最佳性能点(比如多数系统的吞吐量,随着压力增大,吞吐量上不去)就需要考虑出现延迟和吞吐量的这几种情况。
1.定位延迟问题
你要本着端到端的策略,大到整体流程,小到系统模块调用,逐一排查时间消耗在哪里。
你可以使用 kill -3 PID, jstack 等命令打印系统当前的线程执行的堆栈;还可以用一些性能分析工具,如 JProfiler 来监控系统的内存使用情况、垃圾回收、线程运行状况,比如你发现了运行的 100 个线程里面,有 80 个卡在某一个锁的释放上面,这时极有可能这把锁造成的延迟问题。
2. 对于吞吐量问题的定位
对于吞吐量指标要和 CPU使用率一起来看,在请求速率逐步增大时,经常会出现四种情况: