Java面试题精选:分布式(一)
一、分布性幂等性如何设计?
重要性:
幂等性在高并发的场景架构中是必须要保证的。比如支付功能,用户发起支付请求,如果后台没有做幂等校验,用户不小心多点了几点,于是后台就会受到同一个订单的多次请求。不做幂等很容易会重复支付,这在业务需求中是不允许的。
解决方法:
首先,查询和删除不在幂等的讨论范围内。第一次删除后,后面再来删除直接返回0,也算是删除成功
- 建唯一索引
唯一索引或唯一组合索引用来防止新增数据存在脏数据(当表存在唯一索引,并发时若新增异常,直接再查询一次就可以,数据应该已经存在,返回结果即可) - token机制
由于重复点击、网络重发或nginx重发等情况会导致数据被重复提交。前端在数据提交前要向后端服务申请token,token放在redis或jvm内存中,token有有效时长。前端提交后,后台验证token,同时删除token,生成新的token返回。
Redis要用删除操作来判断token,删除成功则代表token验证通过;如果通过select+delete来校验token,存在并发问题,不建议使用 - 悲观锁
悲观锁一般会伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用(另外还要考虑id是否为主键,如果id不是主键或者不是InnoDB存储引擎,那么就会出现锁全表)。——针对update使用 - 乐观锁
给数据库表增加一个version字段,通过这个二字段来判断是否已经修改 - 分布式锁
比如 Redis 、 Zookeeper 的分布式锁。单号为key,然后给Key设置有效期(防止支付失败后,锁一直不释放),来一个请求 - 还有一个保底方案,就是先查询是否存在此单,不存在进行支付,存在就直接返回支付结果。
二、说说你对分布式事务的理解?
场景:多个服务或者多个库,要保持在一个事务中。在微服务架构中,几乎可以说是无法避免。
理论:
- ACID:指数据库事务正确执行的四个基本要素
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
- CAP(cp / ap)
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容忍性(Partition tolerance)。
CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。- 一致性:在分布式系统中的所有数据备份,在同一时刻是否同样的值
- 可用性:在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。
- 分区容忍性:以实际效果而言,分区相当于对通信的时限要求。
系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
- BASE理论
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
Basically Available(基本可用)
Soft state(软状态)
Eventually consistent(最终一致性)
解决方案:seata,消息队列+本地事件表,事务消息,最大努力通知方案,tcc
三 、CAP定理
CAP定理,又叫布鲁尔定理。指的是在一个分布式系统中,最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。
-
l C:一致性(Consistency),数据在多个副本中保持一致,可以理解成两个用户访问两个系统A和B,当A系统数据有变化时,及时同步给B系统,让两个用户看到的数据是一致的。
-
l A:可用性(Availability),系统对外提供服务必须一直处于可用状态,在任何故障下,客户端都能在合理时间内获得服务端非错误的响应。
-
l P:分区容错性(Partition tolerance),在分布式系统中遇到任何网络分区故障,系统仍然能对外提供服务。网络分区,可以这样理解,在分布式系统中,不同的节点分布在不同的子网络中,有可能子网络中只有一个节点,在所有网络正常的情况下,由于某些原因导致这些子节点之间的网络出现故障,导致整个节点环境被切分成了不同的独立区域,这就是网络分区。
用户1和用户2分别访问系统A和系统B,系统A和系统B通过网络进行同步数据。理想情况是用户1访问系统A对数据进行修改,将data1改成了data2,同时用户2访问系统B,拿到的是data2数据。
但是实际中,由于分布式系统具有八大谬论。
(1)网络相当可靠。
(2)延迟为零。
(3)传输带宽是无限的。
(4)网络相当安全。
(5)拓扑结构不会改变。
(6)必须要有一名管理员。
(7)传输成本为零。
(8)网络同质化。
只要有网络调用,网络总是不可靠的,来一一分析。
(1)当网络发生故障时,系统A和系统B没法进行数据同步,也就是不满足P,同时两个系统依然可以访问,那么此时其实相当于是两个单机系统,就不是分布式系统了,所以既然是分布式系统,P必须满足。
(2)当P满足时,如果用户1通过系统A对数据进行了修改将data1改成了data2,也要让用户2通过系统B正确的拿到data2,那么此时是满足C,就必须等待网络将系统A和系统B的数据同步好,并且在同步期间,任何人不能访问系统B(让系统不可用),否则数据就不是一致的。此时满足的是CP,牺牲的是可用性。
(3)当P满足时,如果用户1通过系统A对数据进行了修改将data1改成了data2,也要让系统B能继续提供服务,那么此时,只能接受系统A没有将data2同步给系统B(牺牲了一致性)。此时满足的就是AP,牺牲了数据的一致性。
注册中心Eureka就是满足 的AP,它并不保证C。而Zookeeper是保证CP,它不保证A。在生产中,A和C的选择,没有标准的规定,是取决于自己的业务的。例如12306,是满足CP,因为买票必须满足数据的一致性,不然一个座位多卖了,对铁路运输都是不可以接受的。
四、什么是BASE理论?
由于CAP中一致性C和可用性A无法兼得,eBay的架构师,提出了BASE理论,它是通过牺牲数据的强一致性,来获得可用性。它由于如下3种特征。
-
l Basically Available(基本可用):分布式系统在出现不可预知故障的时候,允许损失部分可用性,保证核心功能的可用。
-
l Soft state(软状态):软状态也称为弱状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
-
l Eventually consistent(最终一致性):最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
BASE理论并没有要求数据的强一致性,而是允许数据在一定的时间段内是不一致的,但在最终某个状态会达到一致。
在生产环境中,很多公司,会采用BASE理论来实现数据的一致,因为产品的可用性相比强一致性来说,更加重要。例如在电商平台中,当用户对一个订单发起支付时,往往会调用第三方支付平台,例如支付宝支付或者微信支付,调用第三方成功后,第三方并不能及时通知我方系统,在第三方没有通知我方系统的这段时间内,给用户的订单状态显示支付中,等到第三方回调之后,再将状态改成已支付。虽然订单状态在短期内存在不一致,但是用户却获得了更好的产品体验。
五、什么是两阶段提交?
两阶段提交2PC是分布式事务中最强大的事务类型之一
流程:两段提交就是分两个阶段提交:
(1)第一阶段询问各个事务数据源是否准备好,投票阶段。
(2)第二阶段才真正将数据提交给事务数据源。
为了保证该事务可以满足ACID,就要引入一个协调者(Cooradinator)。其他的节点被称为参与者(Participant)。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务进行提交。
处理流程如下:
阶段一
a) 协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待答复。
b) 各参与者执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)。
c) 如参与者执行成功,给协调者反馈 yes,否则反馈 no。
阶段二
如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(rollback)消息;否则,发送提交(commit)消息。
两种情况处理如下:
情况1:当所有参与者均反馈 yes,提交事务
a) 协调者向所有参与者发出正式提交事务的请求(即 commit 请求)。
b) 参与者执行 commit 请求,并释放整个事务期间占用的资源。
c) 各参与者向协调者反馈 ack(应答)完成的消息。
d) 协调者收到所有参与者反馈的 ack 消息后,即完成事务提交。
情况2:当有一个参与者反馈 no,回滚事务
a) 协调者向所有参与者发出回滚请求(即 rollback 请求)。
b) 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。
c) 各参与者向协调者反馈 ack 完成的消息。
d) 协调者收到所有参与者反馈的 ack 消息后,即完成事务。
问题:
- 性能问题:所有参与者在事务提交阶段处于同步阻塞状态,占用系统资源,容易导致性能瓶颈。
- 可靠性问题:如果协调者存在单点故障问题,或出现故障,提供者将一直处于锁定状态。
- 数据一致性问题:在阶段 2 中,如果出现协调者和参与者都挂了的情况,有可能导致数据不一致。
优点:尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致)。
缺点:实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景。
六、什么是补偿性事务?
TCC (Try Confifirm Cancel)是服务化的二阶段编程模型,采用的补偿机制。
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
TCC 的三个步骤:
1。Try 阶段主要是对业务系统做检测及资源预留。
2。Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
3。Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
使用场景:
(1)业务需要
举个例子,假入你要向A 转账,思路大概是:
我们有一个本地方法,里面依次调用步骤:
1、首先在 Try 阶段,要先调用远程接口把 你 和 A 的钱给冻结起来。
2、在 Confifirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
3、如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
(2)技术需要:
不同组件,无法在一个事务中完成。
优点:
-
性能提升:具体业务来实现控制资源锁的粒度变小,不会锁定整个资源。
-
数据最终一致性:基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。
-
可靠性:解决了 XA 协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。
缺点:
TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。
七、消息队列和事件表实现分布式事务
八、分布式ID生成有几种方案?
分布式ID的特性:
-
唯一性:确保生成的ID是全网唯一的。
-
有序递增性:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。
-
高可用性:确保任何时候都能正确的生成ID。
-
带时间:ID里面包含时间,不容易重复。
方案:
- UUID:算法的核心思想是结合机器的网卡、当地时间、一个随记数来生成UUID。
优点:本地生成,生成简单,性能好,没有高可用风险
缺点:长度过长,存储冗余,且无序不可读,查询效率低
- 数据库自增ID:使用数据库的id自增策略,如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。
优点:数据库生成的ID绝对有序,高可用实现方式简单
缺点:需要独立部署数据库实例,成本高,有性能瓶颈
- 批量生成ID:一次按需批量生成多个ID,每次生成都需要访问数据库,将数据库修改为最大的ID值,并在内存中记录当前值及最大值。
优点:避免了每次生成ID都要访问数据库并带来压力,提高性能
缺点:属于本地生成策略,存在单点故障,服务重启造成ID不连续
- Redis生成ID
Redis的所有命令操作都是单线程的,本身提供像 incr 和 increby 这样的自增原子命令,所以能保证生成的 ID 肯定是唯一有序的。
优点:不依赖于数据库,灵活方便,且性能优于数据库;数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:如果系统中没有Redis,还需要引入新的组件,增加系统复杂度;需要编码和配置的工作量比较大。
考虑到单节点的性能瓶颈,可以使用 Redis 集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台 Redis 的值分别是1, 2, 3, 4, 5,然后步长都是 5。
-
Twitter的snowflflake算法(重点)
Twitter 利用 zookeeper 实现了一个全局ID生成的服务 Snowflflake。
如上图的所示,Twitter 的 Snowflflake 算法由下面几部分组成: -
1位符号位:
由于 long 类型在 java 中带符号的,最高位为符号位,正数为 0,负数为 1,且实际系统中所使用的ID一般都是正数,所以最高位为 0。 -
41位时间戳(毫秒级):
需要注意的是此处的 41 位时间戳并非存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - 起始时间戳),这里的起始时间戳一般是ID生成器开始使用的时间戳,由程序来指定,所以41位毫秒时间戳最多可以使用 (1 << 41) / (1000x60x60x24x365) = 69年 。 -
10位数据机器位:
包括5位数据标识位和5位机器标识位,这10位决定了分布式系统中最多可以部署 1 << 10 = 1024个节点。超过这个数量,生成的ID就有可能会冲突。 -
12位毫秒内的序列:
这 12 位计数支持每个节点每毫秒(同一台机器,同一时刻)最多生成 1 << 12 = 4096个ID加起来刚好64位,为一个Long型。
优点:高性能,低延迟,按时间有序,一般不会造成ID碰撞
缺点:需要独立的开发和部署,依赖于机器的时钟
-
百度UidGenerator
UidGenerator是百度开源的分布式ID生成器,基于于snowflflake算法的实现,看起来感觉还行。不过,国内开源的项目维护性真是担忧。 -
美团Leaf
Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,但也需要依赖关系数据库、Zookeeper等中间件。
7、常见的负载均衡算法有哪些?
(1) 轮询负载均衡算法(RR,Round Robin)
挨个发,适合于所有服务器硬件都相同的场景。
一个一个来,如果有两个服务器,第一次是你,第二次就是我。
代码实现:
用i 保存 取 服务器的 下标。第一次来 取0,第二次来取1 ,第三次来 取 0。
(2)加权轮询算法(wrr,weighted round robin)
按照权重不同来分发,基本上是基于配置。
代码实现:
比如:两个服务权重分别是6和4,我们的方法,在1-10之间取 随机数,比如取到 1-6,就走6的权重,取到7-10,就走4权重的服务。
(3)随机轮询算法(Random)
(4)最少链接(Least connections)
记录每个服务器正在处理的 连接数 (请求数),将新的请求 分发到最少连接的服务器上,这是最符合负载均衡的算法
(5)原地址散列(Source Hashing)
根据请求来源的ip地址 进行hash计算,只要原地址不变,每次请求映射来的后面提供服务的节点也不变。这有利于进行session信息的维护。
九、说说什么是计数器(固定窗口)算法
计数器算法,是指在指定的时间周期内,累加访问的次数,达到设定的阈值时,触发限流策略。下一个时间周期进行访问时,访问次数清零。此算法无论在单机还是分布式环境下实现都非常简单,使用redis的incr原子自增性,再结合key的过期时间,即可轻松实现,计算器算法如图所示。
从上图看出,设置一分钟的阈值是100,在0:00到1:00内请求数是60,当到1:00时,请求数清零,从0开始计算,这时在1:00到2:00之间能处理的最大的请求为100,超过100个的请求,系统都拒绝。
这个算法有一个临界问题 ,例如在图中,在0:00到1:00内,只在0:50有60个请求,而在1:00到2:00之间,只在1:10有60个请求,虽然在两个一分钟的时间内,都没有超过100个请求,但是在0:50到1:10这20秒内,确有120个请求,虽然在每个周期内,都没超过阈值,但是在这20秒内,已经远远超过了原来设置的1分钟内100个请求的阈值。
十、什么是滑动窗口算法
为了解决计数器算法的临界值的问题,发明了滑动窗口算法。在TCP网络通信协议中,就采用滑动时间窗口算法来解决网络拥堵问题。
滑动时间窗口是将计数器算法中的时间周期切分成多个小的时间窗口,分别在每个小的时间窗口中记录访问次数,然后根据时间将窗口往前滑动并删除过期的小时间窗口。最终只需要统计滑动窗口范围内的小时间窗口的总的请求数即可,如图所示。
在图中,假设设置一分钟的请求阈值是100,将一分钟拆分成4个小时间窗口,这样,在每个小的时间窗口内,只能处理25个请求,用虚线方框表示滑动时间窗口,当前窗口的大小是2,也就是在窗口内最多能处理50个请求。随着时间的推移,滑动窗口也随着时间往前移动,例如图开始时,窗口是0:00到0:30的这个范围,过了15秒后,窗口是0:15到0:45的这个范围,窗口中的请求重新清零,这样就很好的解决了计数器算法的临界值问题。
在滑动时间窗口算法中,小窗口划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。
十一 、什么是漏桶算法
漏桶算法的原理就像它的名字一样,维持一个漏斗,它有恒定的流出速度,不管水流流入的速度有多快,漏斗出水的速度始终保持不变,类似于消息中间件,不管消息的生产者请求量有多大,消息的处理能力取决于消费者。
漏桶的容量=漏桶的流出速度*可接受的等待时长。
在这个容量范围内的请求可以排队等待系统的处理,超过这个容量的请求,才会被抛弃。
在漏桶限流算法中,存在下面几种情况:
(1)当请求速度大于漏桶的流出速度时,也就是请求量大于当前服务所能处理的最大极限值时,触发限流策略。
(2)请求速度小于或等于漏桶的流出速度时,也就是服务的处理能力大于或等于请求量时,正常执行。
漏桶算法有一个缺点,当系统在短时间内有突发的大流量时,漏桶算法处理不了。
十二、令牌桶算法
令牌桶算法,是增加一个大小固定的容器,也就是令牌桶,系统以恒定的速率向令牌桶中放入令牌,如果有客户端来请求,先需要从令牌桶中拿一个令牌,拿到令牌,才有资格访问系统,这时令牌桶中少一个令牌。当令牌桶满的时候,再向令牌桶生成令牌时,令牌会被抛弃。
在令牌桶算法中,存在以下几种情况。
(1)请求速度大于令牌的生成速度:那么令牌桶中的令牌会被取完,后续再进来的请求,由于拿不到令牌,会被限流。
(2)请求速度等于令牌的生成速度:那么此时系统处于平稳状态。
(3)请求速度小于令牌的生成速度:那么此时系统的访问量远远低于系统的并发能力,请求可以被正常处理。
令牌桶算法,由于有一个桶的存在,可以处理短时间大流量的场景。
1十三 、数据库如何处理大数据量
- 分区:隔离数据访问。
- 分库分表
- 水平分库/表,各个库和表的结构一模一样,数据量不一样。
- 垂直分库/表,各个库和表的结构不一样,数据量一样。
- 主从架构(读写分离):主机负责写,从机负责读。