对架构设计的认知
为什么做架构拆分? ----》 为什么要做系统解耦? ----》 为什么要做职责单一? ----》 为什么要关注开发效率?
架构拆分其实是管理在技术上提效的一种手段
总结---------------学会思考,善于思考,单节点学习认知,多节点整合,融会贯通
提高对系统架构设计的认知能力
提高分析系统问题的认知能力
扩大能够驾驭系统的边界
你之前是如何设计这个系统的?
- 谈复杂来源
- 功能性复杂度:要解决业务发展带来的系统耦合、开发效率缓慢问题
- 非功能性复杂度:要保证系统的高可用性
- 谈解决方案
- 采用开源的MQ消息管道
- 采用开源的Redis实现消息队列
- 采用内存队列+MySQL来实现
- 谈评估标准
谈技术实现
- 在确定了具体的架构解决方案之后,需要进一步说明技术上的落地实现方式和深层原理
CAP理论
Consistency:数据一致性
Availability:服务可用性
Partition tolerance:分区容错性
BASE理论
Basically Available:基本可用
Soft state:软状态
Eventually Consistent:最终一致性
分布式系统的知识体系
存储器:即分布式存储系统,如NoSQL数据库存储
运算器:即分布式计算,如分布式并行计算
输入输出:即分布式系统通信,如同步RPC调用和异步消息队列
控制器:即调度管理,如流量调度、任务调度与资源调度
面试官会用“如何设计海量商品数据的存储?”这样一个问题,向面试者提问,从而考察分布式系统中数据的存储、分布、复制,以及相关协议和算法等考点
- 分布式数据存储
- 数据分片
- 数据复制
- 数据副本
- 数据容错
- 数据一致性
- 协议算法
Hash分片
商品存储扩容的设计问题,也就是重新设计数据的分片规则,从这一点出发会考察你Hash(哈希)分片的具体实现原理
如何解决Hash分片的缺点,既保证数据均匀分布,又保证扩展性?
使用一致性Hash:将存储节点和数据都映射到一个首尾相连的哈希环上,存储节点一般可以根据IP地址进行Hash计算,数据的存储位置是从数据映射在环上的位置开始,依照顺时针方向所找到的第一个存储节点。
一致性Hash分片的优点是数据可以较为均匀的分配到各节点,其并发写入性能表现也不错,虽然一致性Hash提升了稳定性,但通过Hash分片的方式很难针对热点商品做单独的架构设计
如何解决单一热点问题?
做Range(范围)分片
要达到这种灵活性,更好的方式是基于分片元数据,请求分片元数据获取的信息也不仅仅只有数据分片信息,还包括数据量、读写QPS和分片副本的健康状态等
如何保证分片元数据服务的可用性和数据一致性?
最直接的方式就是专门给元数据做一个服务集群,并通过一致性算法复制数据
- 给分片元数据做集群服务,并通过ETCD存储数据分片信息
- 每个数据存储实例节点定时向元数据服务集群同步心跳和分片信息
- 当调用端的请求过来时,元数据服务节点只需要做好高可用和缓存即可
ETCD
Etcd使用的是Raft算法
Etcd是CoreOS团队于2013年6月发起的开源项目,它的目标是构建一个高可用的分布式键值(key-value)数据库,基于Go语言实现。 接触过分布式系统的读者应该知道,分布式系统中,最基本最重要的问题就是各种信息的一致性,包括对服务的配置信息的管理、服务的发现、更新、同步等等。而要解决这些问题,往往需要基于一套能保证一致性的分布式数据库系统,比如经典的Apache ZooKeeper项目,通过维护文件目录信息来实现数据的一致性。
Etcd就是专门为集群环境设计,可以很好地实现数据一致性,提供集群节点状态管理和服务自动发现等。
受到Apache ZooKeeper项目和doozer项目 的启发,Etcd在进行设计的时候重点考虑了下面四个要素:
·简单:支持REST风格的HTTP+JSON API;
·安全:支持HTTPS方式的访问;
·快速:支持并发每秒一千次的写操作;
·可靠:支持分布式结构,基于Raft算法实现一致性。
Paxos算法解决了什么问题?
简单来说,paxos算法解决的就是一个最终一致性问题。假设一个集群有三个节点,Paxos可以让三个节点更快的达成一致。因为在Paxos中规定,如果某个节点写操作执行之前其他两个节点(也就是大多数的节点)的值已经相等并记为V,那么除非这次写入的值也为V,否则操作不能进行。而当其他两个节点的意见不统一时,采用高编号的节点的值。因此,最终一定会达到三个节点意见统一的状态。
(高编码的值:请求响应中编号最大的提案的值)
Basic Paxos算法的工作流程是什么?
- 提议者不管在第一阶段,还是第二阶段,收到大部分的同意选票后,就算成功
- 提议者 发送给各个接受者,接受者接受比自己目前记录的最大提议值大的提议,大多数节点同意后,发送给提议者,第一阶段提议结束
- 第二阶段,发送的提议,如果接受者当前记录的最大提议编号比当前发送的提议要大,直接抛弃这个执行提议
- 第一阶段,发送给接受者节点,根据响应中提案编号最大的提案的值,来设置接受请求中的值,如果当前没有接受者发过来的值,就设置自己的值。 如果接受者节点有值,根据该规则,提议者把自己的值改掉,然后执行第二阶段流程。
总结: Basic Paxos 算法是 分布式共识算法的基础,它的一些思想渗透进,其他 mutlip paxos算法的方方面面,虽然Basic Paxos算法没有什么落地价值,但是基于它的 Mutliple paxos算法具有很强的价值,Mutiple Paxos并不是一种特定的算法,它是一种思想,
Multiple paxos和Basic paxos的区别
个人理解:Multiple paxos是一种比较大的想法,就是3各节点,同时接收了3个请求,这3个请求对同一个值设置,选出一个值,3个节点设置一致。
这种选择某个值的具体算法,就是Basic paxos完成的任务。
Paxos算法和Raft算法的区别又是什么?
1.算法复杂度:Paxos算法相对来说比较复杂,需要理解多个阶段的消息交互过程,而Raft算法则相对简单,只需要理解Leader选举和日志复制两个基本概念。
2.Leader选举:在Paxos算法中,Leader的选举是通过多轮消息交互来完成的,而在Raft算法中,Leader的选举是通过心跳机制和超时机制来完成的。
3.日志复制:在Paxos算法中,每个节点都可以提议一个值,但只有一个值会被选中,而在Raft算法中,Leader节点负责接收客户端请求并将其转化为日志条目,然后将日志条目复制到其他节点。
4.可读性:由于Raft算法的设计目标是易于理解和实现,因此它的代码和文档更容易理解和阅读。
总的来说,Paxos算法相对来说更为复杂,但是在某些场景下可能更加高效,而Raft算法则更加易于理解和实现,适用于大多数分布式系统场景。
有没有更好的架构思路呢?
可以选择基于Gossip协议的实现方式
谣言传播,最终一致性
总结:
介绍了分布式系统的数据存储、分片,与数据一致性等分布式问题
- 面试官往往会通过“海量数据的存储设计”问题考察候选人对分布式系统技术的掌握情况,而回答号基于Hash取模、一致性Hash实现分库分表的解决方案
- 当你掌握了常规的Hash取模分片方式后,面试官会引入一个场景问题来考察你解决架构设计问题的思路,因为分布式系统架构设计离不开系统可用性与一致性之间的权衡,所以你的解题思路要站在这两个技术点之上
- 如果面试官满意你的表现,会进一步考察你算法原理,所以对于分布式系统中的一致性共识算法,如Basic Paxos、Multi Paxos、Raft、Zab、Gossip也是你要提前掌握的
一次完整的RPC流程
RPC通信流程中的核心组成部分包括了协议、序列化与反序列化,以及网络通信
步骤:
1.调用方持续把请求参数对象序列化成二进制数据,经过TCP传输到服务提供方;
2.服务提供方从TCP通道里面接收到二进制数据
3.根据RPC协议,服务提供方将二进制数据分割出不同的请求数据,经过反序列化将二进制数据还原出请求对象,找到对应的实现类,完成真正的方法调用
4.然后服务提供方再把执行结果序列化后,回写到对应的TCP通道里面
5.调用方获取到应答的数据包后,再反序列化成应答对象
如何设计一个高并发高吞吐的RPC框架
- 优化RPC的网络通信性能:高并发下选择高性能的网络编程I/O模型
- 选型合适的RPC序列化方式:选择合适的序列化方式,进而提升封包和解包的性能
如何选型序列化方式(考虑时间与空间开销,切勿忽略兼容性,首选Protobuf,Hessian)
- JSON:Key-Value结构的文本序列化框架,易用且应用最广泛,基于HTTP协议的RPC框架都会选择JSON序列化方式,但它的空间开销很大,在通信时需要更多的内存
- Hessian:一种紧凑的二进制序列化框架,在性能和体积上表现比较好
- Protobuf:Google公司的序列化标准,序列化后体积相比JSON、Hessian还要小,兼容性也做得不错
如何提升网络通信性能(实际开发中最常用的是 BIO和NIO)
- 同步阻塞I/O(BIO)
- 同步非阻塞I/O
- I/O多路复用(NIO)
- 信号驱动
- 异步I/O(AIO)
BIO:BIO的网络模型中,每当客户端发送一个连接请求给服务端,服务端都会启动一个新的线程去处理客户端连接的读写操作,这会导致服务器的资源不够用,无法实现高并发下的网络开发,所以BIO的网络模型只适用于Socket连接不多的场景
BIO网络模型的问题:
- Socket连接数量受限,不适用于高并发场景
- 有两处阻塞,分别是等待用户发起连接,和等待用户发送数据
NIO:NIO比BIO提高了服务端工作线程的利用率,并增加了一个调度者,来实现Socket连接与Socket数据读写之间的分离
个人理解:一个工作线程服务与多个Socket,增加的调度者来协调这些工作线程。
在面试中,对于高级研发工程师的考察,还会有两个技术扩展考核点
- Reactor模型,以及Reactor的3种线程模型,分别是单线程Reactor线程模型、多线程Reactor线程模型,以及主从Reactor线程模型
- Java中的高性能网络编程框架Netty
Reactor模型简单理解
1.单Reactor单线程,前台接待员和服务员是同一个人,全程为顾客服务
2.单Reactor多线程,1个前台接待员,多个服务员,接待员只负责接待
3.主从Reactor多线程,多个前台接待员,多个服务员
案例串联,如何让系统抗住双十一的预约抢购活动?
1.商品预约阶段:要掌握如何在高并发的场景下通过锁的方式,让每一个用户都获取到抢购资格,结合业务场景对于并发控制的需求诉求和成本的考虑,在商品预约阶段,你可以基于Redis来实现分布式锁
2.等待抢购阶段:此阶段对页面的查询请求会很高,尤其是临近抢购倒计时的流量突增,解决方案是做页面静态化和服务端限流
3.商品抢购阶段:瞬时流量会带来极大的压力,所以通过MQ做了同步转异步,实现对流量的削峰,从而让请求排队等待,然后有序且有限地进入到后端服务,而你必须掌握消息队列丢失、重复和积压问题的解决方案;另外在扣减库存的时候,为了解决扣减库存不超售的问题,同样还需要引入锁的机制
4.订单支付阶段:在用户支付完成后,系统通常还需要处理一些非核心操作,你可以通过MQ通知的方式来实现系统间的解耦和异步通信,但依旧要保证消息的可靠性(当然也可以通过RPC同步调用的方式来实现),所以你也要掌握RPC和MQ的相关知识点
通过非主键(辅助索引)查询商品数据的过程
如果你用商品编码查询商品,会先检索辅助索引中的B+Tree的商品编码,找到对应的叶子节点,获取主键值,然后再通过主键索引中的B+Tree树,查询到对应的叶子节点,然后获取整行数据,这个过程叫回表
索引失效
- 索引列上做了计算、函数、类型转换操作,这些情况下索引失效是因为查询过程需要扫描整个索引并回表,代价高于直接全表扫描;
- like匹配使用了前缀匹配符'%abc';
- 字符串不加引号导致类型转换;
如果MySQL查询优化器预估走索引的代价比全表扫描的代价还要大,则不走对应的索引,直接全表扫描,如果走索引比全表扫描代价小,则使用索引
常见优化索引的方法
前缀索引优化:前缀索引就是用某个字段中,字符串的前几个字符建立索引,比如我们可以在订单表上对商品名称字段的前5个字符建立索引。使用前缀索引是为了减小索引字段大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的字段作为索引时,使用前缀索引可以帮助我们减小索引项的大小。
但是,前缀索引有一定的局限性,例如order by就无法是使用前缀索引,无法把前缀索引用作覆盖索引。
覆盖索引优化:覆盖索引是指SQL中query的所有字段,在索引B+Tree的叶子节点上都能找得到的那些索引,从辅助索引中查询得到记录,而不需要通过聚簇索引查询获得。假设我们只需要查询商品的名称、价格,有什么方式可以避免回表呢
我们可以建立一个组合索引,即商品ID、名称、价格作为一个组合索引。如果索引中存在这些数据,查询将不会再次检索主键索引,从而避免回表。所以,使用覆盖索引的好处很明显,即不需要查询出包含整行记录的所有信息,也就减少了大量的I/O操作
联合索引:联合索引时,存在最左匹配原则,也就是按照最左优先的方式进行索引的匹配。比如联合索引(userpin,username),如果查询条件时WHERE userpin = 1 AND username = 2,就可以匹配联合索引;或者查询条件是WHERE userpin = 1,也能匹配上联合索引,但是如果查询条件是WHERE username=2,就无法匹配上联合索引。
另外,建立联合索引时的字段顺序,对索引效率也有很大影响。越靠前的字段被用于索引过滤的概率越高,实际开发工作中建立联合索引时,要把区分度大的字段排在前面,这样区分大的字段越有可能被更多的SQL使用到 区分度 = distinct(column) / count(*) ,区分度就是某个字段column不同值的个数除以表的总行数,比如性别的区分度就很小,不适合建立索引或不适合排在联合索引列的靠前的位置,而uuid这类字段就比较适合做索引或排在联合索引列的靠前的位置。
MVCC(Multi-Version Concurrency Control)是一种数据库并发控制的机制,它允许读操作与写操作同时进行,提高了数据库的并发性能。MVCC采用了快照读和当前读两种方式来实现并发控制。
快照读是指普通的SELECT语句,它在读写时不需要加锁,但可能会读取到历史数据。而当前读是一种悲观锁的操作,它会对当前读取的数据进行加锁,确保读取的数据是最新的,主要包括SELECT…FOR UPDATE、SELECT…LOCK IN SHARE MODE、UPDATE、INSERT和DELETE等操作。
在数据库并发的场景下,存在着读-读、读-写和写-写三种情况。在读-读的场景中,不会存在任何问题。但在读-写和写-写的场景中,会出现线程安全问题,例如脏读、幻读和不可重复读等。因此,通过使用MVCC机制,可以有效地解决这些并发问题,提高数据库的并发性能。
MySQL主从复制的原理
- MySQL主库在收到客户端提交事务的请求之后,会先写入binlog,再提交事务,更新存储引擎中的数据,事务提交完成之后,返回给客户端“操作成功”的响应
- 从库会创建一个专门的I/O线程,连接主库的log dump线程,来接收主库的binlog日志,再把binlog信息写入relay log的中继日志里,再返回给主库“复制成功”的响应
- 从库会创建一个用于回放binlog的线程,去读relay log中继日志,然后回放binlog更新存储引擎中的数据,最终实现主从的数据一致性
分库分表使用的场景不一样:分表是因为数据量比较大,导致事务执行缓慢,分库是因为单库的性能无法满足要求
NewSQL:NewSQL是新一代的分布式数据库,具备分布式存储系统的高性能、高可用,弹性扩容等能力,兼容传统关系型数据库的SQL标准。还提供了和传统关系型数据库不相上下的事务保证,是具备了支撑未来交易类业务能力的。
Redis只有单线程吗?
Redis是单线程的,主要是指Redis的网络I/O线程,Redis的持久化、集群同步等操作,则是由另外的线程来执行
Redis采用单线程为什么还这么快?
- Redis的大部分操作都在内存中完成
- 单线程模型避免了多线程之间的竞争
- Redis采用了I/O多路复用机制处理大量的客户端Socket请求
Redis版本
- Redis4.0版本之前,使用单线程速度快的原因就是上述的几个原因
- Redis4.0版本之后,Redis添加了多线程的支持,但这时的多线程主要体现在大数据的异步删除功能上
- Redis6.0版本之后,新增了多线程I/O的读写并发能力,在面试中如果你能补充Redis6.0多线程的原理,势必会增加面试官对你的认可
Redis为什么先执行命令,再把数据写入日志呢?
因为,Redis在写入日志之前,不对命令进行语法检查,所以,只记录执行成功的命令,避免了出现记录错误命令的情况,并且,在命令执行完之后再记录,不会阻塞当前的写操作
RDB做快照时会阻塞线程吗?
为了解决阻塞线程问题,Redis提供了两个命令来生成RDB快照文件,分别是save和bgsave。save命令在主线程中执行,会导致阻塞。而bgsave命令则会创建一个子进程,用于写入RDB文件的操作,避免了对主线程的阻塞。
RDB做快照的时候数据能修改吗?
如果主线程执行读操作,则主线程和bgsave子进程互相不影响。
如果主线程执行写操作,则被修改的数据会复制一份副本,然后bgsave子进程会把该副本数据写入RDB文件,在这个过程中,主线程仍然可以直接修改原来的数据
熔断设计的原理
- “关闭”转换“打开”:当服务调用失败的次数累计到一定的阈值时,服务熔断状态,将从关闭态切换到打开态
- “打开”转换“半打开”:当熔断处于打开状态时,我们会启动一个超时计时器,当计时器超时后,状态切换到半打开状态
- “半打开”转换“关闭”:在熔断处于半打开状态时,请求可以达到后端服务,如果累计一定的成功次数后,状态切换到关闭态
降级设计的原理
降级设计是站在系统整体可用性上考虑问题:当资源和访问量出现矛盾时,在有限的资源下,放弃部分非核心功能或者服务,保证整体的可用性,熔断也是降级的一种手段
服务降级可以分为读操作降级和写操作降级
- 读操作降级:做数据兜底服务,将兜底数据提前存储在缓存中,当系统触发降级时,读操作直接降级到缓存,从缓存中读取兜底数据,如果此时缓存中也不存在查询的数据,则返回默认值,不再请求数据库
- 写操作降级:将之前直接同步调用写数据库的操作,降级为先写缓存,然后再异步写入数据库
功能降级
功能降级就是在做产品功能上的取舍,既然在做服务降级时,已经取舍掉了非核心服务,那么同样的产品功能层面也要相应的进行简化。可以通过降级开关控制功能的可用或不可用。另外,在设计降级时,离不开降级开关的配置,一般是通过参数化配置的方式存储在配置中心,手动或自动开启开关,实现系统降级
用架构师的视角分析系统性能指标
高性能设计中的“术”
前端优化:
- 减少请求次数
- 页面静态化
- 边缘计算
后端优化:
- 基础设施层面
- 网络层面
- 架构层面
架构师知识体系
学习架构设计知识思路
- 从自己熟知的领域出发,这样你才有不断的正反馈,从而更有信心,容易理解新的知识
- 形成知识网络图谱,把自己的核心知识梳理出一个脉络清晰的结构图,然后结合已有知识,再逐步将零散的知识点补充到这张图谱之上
- 养成对技术判断力,针对同一问题有不同方法,不同维度、不同角度的分析和对比,以此提升在工作中对技术的领悟力