分布式事务
CAP定理
分布式系统有三个指标:
-
Consistency(一致性)
-
Availability(可用性)
-
Partition tolerance (分区容错性)
它们的第一个字母分别是 C
、A
、P
。Eric Brewer认为任何分布式系统架构方案都不可能同时满足这3个目标,这个结论就叫做 CAP 定理。
一致性
Consistency
(一致性):用户访问分布式系统中的任意节点,得到的数据必须一致。
可用性
Availability (可用性):用户访问分布式系统时,读或写操作总能成功。
只能读不能写,或者只能写不能读,或者两者都不能执行,就说明系统弱可用或不可用。
分区容错
Partition
,就是分区,就是当分布式系统节点之间出现网络故障导致节点之间无法通信的情况:
如上图,node01和node02之间网关畅通,但是与node03之间网络断开。于是node03成为一个独立的网络分区;node01和node02在一个网络分区。
Tolerance
,就是容错,即便是系统出现网络分区,整个系统也要持续对外提供服务。
在分布式系统中,A
和C
之间只能满足一个。
BASE理论
人们在总结系统设计经验时,最终得到了一些心得:
-
B asicallyA vailable ( 基本可用**)**:分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
-
S oft State**(** 软状态**):**在一定时间内,允许出现中间状态,比如临时的不一致状态。
-
Ev entually Consistent**(** 最终一致性**)**:虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。
因此解决分布式事务的思想也是这样,有两个方向:
-
AP思想:各个子事务分别执行和提交,无需锁定数据。允许出现结果不一致,然后采用弥补措施恢复,实现最终一致即可。例如
AT
模式就是如此 -
CP思想:各个子事务执行后不要提交,而是等待彼此结果,然后同时提交或回滚。在这个过程中锁定资源,不允许其它人访问,数据处于不可用状态,但能保证一致性。例如
XA
模式
AT模式的脏写问题
AT模式也分为两个阶段:
第一阶段是记录数据快照,执行并提交事务:
第二阶段根据阶段一的结果来判断:
-
如果每一个分支事务都成功,则事务已经结束(因为阶段一已经提交),因此删除阶段一的快照即可
-
如果有任意分支事务失败,则需要根据快照恢复到更新前数据。然后删除快照
这种模式在大多数情况下(99%)并不会有什么问题,不过在极端情况下,特别是多线程并发访问AT模式的分布式事务时,有可能出现脏写问题,
解决思路就是引入了全局锁的概念。在释放DB锁之前,先拿到全局锁。避免同一时刻有另外一个事务来操作当前数据。
TCC模式
TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:
-
try
:资源的检测和预留; -
confirm
:完成资源操作业务;要求try
成功confirm
一定要能成功。 -
cancel
:预留资源释放,可以理解为try的反向操作。
XA模式用数据库的锁进行隔离,AT模式利用全局锁保证隔离性。AT模式由于在一阶段直接释放锁,故性能上优于XA,但是加入了全局锁,性能也会有所降低,但整体上性能仍优于XA模式。若追求极致性能,则应采用TCC模式,可以在保证一致性的同时,在隔离上无需加锁。
阶段一( Try ):检查余额是否充足,如果充足则冻结金额增加30元,可用余额扣除30
阶段二(Confirm):假如要提交(Confirm),之前可用金额已经扣减,并转移到冻结金额。因此可用金额不变,直接冻结金额扣减30即可
阶段二(Canncel):如果要回滚(Cancel),则释放之前冻结的金额,也就是冻结金额扣减30,可用余额增加30
最大努力通知
方案一 **:**本方案是利用 MQ的 ack机制由 MQ向接收通知方发送通知,流程如下:
【1】发起通知方将通知发给MQ。 使用普通消息机制将通知发给MQ。 注意:如果消息没有发出去可由接收通知方主动请求发起通知方查询业务执行结果;
【2】接收通知方监听 MQ;
【3】接收通知方接收消息,业务处理完成回应ack;
【4】接收通知方若没有回应ack 则 MQ会重复通知。 MQ会按照间隔1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知间隔 (如果MQ采用 RocketMq,在 broker中可进行配置),直到达到通知要求的时间窗口上限;
【5】接收通知方可通过消息校对接口来校对消息的一致性[幂等性];
方案二 : 本方案也是利用 MQ的 ack机制,与方案一不同的是应用程序向接收通知方发送通知,如下图:
【 1】发起通知方将通知发给MQ。 使用可靠消息一致方案中的事务消息保证本地事务与消息的原子性,最终将通知先发给MQ;
【2】通知程序监听 MQ,接收 MQ的消息。 方案一中接收通知方直接监听 MQ,方案二中由通知程序监听 MQ。通知程序若没有回应 ack则 MQ会重复通知。
【3】通知程序通过互联网接口协议(如Http、WebService)调用接收通知方案接口,完成通知。 通知程序调用接收通知方案接口成功就表示通知成功,即消费 MQ消息成功,MQ将不再向通知程序投递通知消息;
【4】接收通知方可通过消息校对接口来校对消息的一致性。
**方案一和方案二的不同点:**1、方案一中接收通知方与 MQ对接,即接收通知方监听 MQ,此方案主要应用与内部应用之间的通知。 2、方案二中由通知程序与 MQ对接,通知程序监听MQ,收到 MQ的消息后由通知程序通过互联网接口协议调用接收通知方。此方案主要应用于外部应用之间的通知,例如支付宝、微信的支付结果通知。
总结
TCC模式的每个阶段是做什么的?
-
Try:资源检查和预留
-
Confirm:业务执行和提交
-
Cancel:预留资源的释放
TCC的优点是什么?
-
一阶段完成直接提交事务,释放数据库资源,性能好
-
相比AT模型,无需生成快照,无需使用全局锁,性能最强
-
不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
TCC的缺点是什么?
-
有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
-
软状态,事务是最终一致
-
需要考虑Confirm和Cancel的失败情况,做好幂等处理、事务悬挂和空回滚处理
注册中心
环境隔离
Nacos提供了基于namespace
的环境隔离功能。具体的隔离层次如图所示:
说明:
-
Nacos中可以配置多个
namespace
,相互之间完全隔离。默认的namespace
名为public
-
namespace
下还可以继续分组,也就是group ,相互隔离。 默认的group是DEFAULT_GROUP
-
group
之下就是服务和配置了
分级模型
Nacos中提供了集群(cluster
)的概念,来对应不同机房。也就是说,一个服务(service
)下可以有很多集群(cluster
),而一个集群(cluster
)中下又可以包含很多实例(instance
)。
如图:
任何一个微服务的实例在注册到Nacos时,都会生成以下几个信息,用来确认当前实例的身份,从外到内依次是:
-
namespace:命名空间
-
group:分组
-
service:服务名
-
cluster:集群
-
instance:实例,包含ip和端口
这就是nacos中的服务分级模型。
在Nacos内部会有一个服务实例的注册表,是基于Map实现的,其结构与分级模型的对应关系如下:
Eureka和Nacos对比
Eureka和Nacos的相似点有:
-
都支持服务注册发现功能
-
都有基于心跳的健康监测功能
-
都支持集群,集群间数据同步默认是AP模式,即最全高可用性
Eureka和Nacos的区别有:
-
都支持服务注册发现功能
-
都有基于心跳的健康监测功能
-
都支持集群,集群间数据同步默认是AP模式,即最全高可用性
-
Eureka的心跳是30秒一次,Nacos则是5秒一次
-
Eureka如果90秒未收到心跳,则认为服务疑似故障,可能被剔除。Nacos中则是15秒超时,30秒剔除。
-
Eureka每隔60秒执行一次服务检测和清理任务;Nacos是每隔5秒执行一次。
-
Eureka只能等微服务自己每隔30秒更新一次服务列表;Nacos即有定时更新,也有在服务变更时的广播推送
-
Eureka仅有注册中心功能,而Nacos同时支持注册中心、配置管理
-
Eureka和Nacos都支持集群,而且默认都是AP模式
远程调用
负载均衡原理
我们会发现Spring在整合OpenFeign的时候,实现了org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient
类,其中定义了OpenFeign发起远程调用的核心流程。也就是四步:
-
获取请求中的
serviceId
-
根据
serviceId
负载均衡,找出一个可用的服务实例 -
利用服务实例的
ip
和port
信息重构url -
向真正的url发起请求
而具体的负载均衡则是不是由OpenFeign
组件负责。而是分成了负载均衡的接口规范 ,以及负载均衡的具体实现两部分。
负载均衡的接口规范是定义在Spring-Cloud-Common
模块中,包含下面的接口:
-
LoadBalancerClient
:负载均衡客户端,职责是根据serviceId最终负载均衡,选出一个服务实例 -
ReactiveLoadBalancer
:负载均衡器,负责具体的负载均衡算法
OpenFeign的负载均衡是基于Spring-Cloud-Common
模块中的负载均衡规则接口,并没有写死具体实现。这就意味着以后还可以拓展其它各种负载均衡的实现。
不过目前SpringCloud
中只有Spring-Cloud-Loadbalancer
这一种实现。
Spring-Cloud-Loadbalancer
模块中,实现了Spring-Cloud-Common
模块的相关接口,具体如下:
-
BlockingLoadBalancerClient
:实现了LoadBalancerClient
,会根据serviceId选出负载均衡器并调用其算法实现负载均衡。 -
RoundRobinLoadBalancer
:基于轮询算法实现了ReactiveLoadBalancer
-
RandomLoadBalancer
:基于随机算法实现了ReactiveLoadBalancer
,
这样一来,整体思路就非常清楚了,流程图如下:
修改负载均衡策略
模块中的添加一个配置类:
public class OpenFeignConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
Environment environment, NacosDiscoveryProperties properties,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new NacosLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name, properties);
}
}
注意:
这个配置类千万不要加@Configuration
注解,也不要被SpringBootApplication扫描到。
由于这个OpenFeignConfig没有加@Configuration
注解,也就没有被Spring加载,因此是不会生效的。接下来,我们要在启动类上通过注解来声明这个配置。
有两种做法:
-
全局配置:对所有服务生效
@LoadBalancerClients(defaultConfiguration = OpenFeignConfig.class)
-
局部配置:只对某个服务生效
@LoadBalancerClients({
@LoadBalancerClient(value = "item-service", configuration = OpenFeignConfig.class)
})
集群优先(NacosLoadBalancer
)
大概流程如下:
-
通过
ServiceInstanceListSupplier
获取服务实例列表 -
获取
NacosDiscoveryProperties
中的clusterName
,也就是yml文件中的配置,代表当前服务实例所在集群信息(参考2.2
小节,分级模型) -
然后利用stream的filter过滤找到被调用的服务实例中与当前服务实例
clusterName
一致的。简单来说就是服务调用者与服务提供者要在一个集群
找到同一集群的实例然后实施权重配置
服务保护
线程隔离
无论是Hystix还是Sentinel都支持线程隔离。不过其实现方式不同。
线程隔离有两种方式实现:
-
线程池 隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
-
信号量 隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求
Sentinel的线程隔离就是基于信号量隔离实现的,而Hystix两种都支持,但默认是基于线程池隔离。
滑动窗口算法
在熔断功能中,需要统计异常请求或慢请求比例,也就是计数。在限流的时候,要统计每秒钟的QPS,同样是计数。可见计数算法在熔断限流中的应用非常多。sentinel中采用的计数器算法就是滑动窗口计数算法。
固定窗口计数
-
将时间划分为多个窗口,窗口时间跨度称为
Interval
,本例中为1000ms; -
每个窗口维护1个计数器,每有1次请求就将计数器
+1
。限流就是设置计数器阈值,本例为3,图中红线标记 -
如果计数器超过了限流阈值,则超出阈值的请求都被丢弃。
但是我们考虑一种特殊场景
-
假如在第5、6秒,请求数量都为3,没有超过阈值,全部放行
-
但是,如果第5秒的三次请求都是在4.5~5秒之间进来;第6秒的请求是在5~5.5之间进来。那么从第4.5~5.之间就有6次请求!也就是说每秒的QPS达到了6,远超阈值。
这就是固定窗口计数算法的问题,它只能统计当前某1个时间窗的请求数量是否到达阈值,无法结合前后的时间窗的数据做综合统计。
因此,我们就需要滑动时间窗口算法来解决。
滑动窗口计数
具体规则如下:
-
窗口时间跨度
Interval
大小固定,例如1秒 -
时间区间跨度为
Interval / n
,例如n=2,则时间区间跨度为500ms -
窗口会随着当前请求所在时间
currentTime
移动,窗口范围从currentTime-Interval
时刻之后的第一个时区开始,到currentTime
所在时区结束。
而且滑动窗口内划分的时区越多,这种统计就越准确。
令牌桶算法
说明:
-
以固定的速率生成令牌,存入令牌桶中,如果令牌桶满了以后,多余令牌丢弃
-
请求进入后,必须先尝试从桶中获取令牌,获取到令牌后才可以被处理
-
如果令牌桶中没有令牌,则请求等待或丢弃
基于令牌桶算法,每秒产生的令牌数量基本就是QPS上限。
当然也有例外情况,例如:
-
某一秒令牌桶中产生了很多令牌,达到令牌桶上限N,缓存在令牌桶中,但是这一秒没有请求进入。
-
下一秒的前半秒涌入了超过2N个请求,之前缓存的令牌桶的令牌耗尽,同时这一秒又生成了N个令牌,于是总共放行了2N个请求。超出了我们设定的QPS阈值。
因此,在使用令牌桶算法时,尽量不要将令牌上限设定到服务能承受的QPS上限。而是预留一定的波动空间,这样我们才能应对突发流量。
优点:实现简单,令牌就是一个计数器
补充缺点:无法发挥到QPS上限因为要预留空间供请求大面积波动做容量储蓄用于缓解波动
漏桶算法
说明:
-
将每个请求视作"水滴"放入"漏桶"进行存储;
-
"漏桶"以固定速率向外"漏"出请求来执行,如果"漏桶"空了则停止"漏水";
-
如果"漏桶"满了则多余的"水滴"会被直接丢弃。
sentinel中的限流中的排队等待功能正是基于漏桶算法实现的。
总结:
SpringCloud有哪些常用组件?分别是什么作用?
答:Nacos, OpenFeign, Sentinel,Seata,RabbitMQ,Gateway
Nacos: 服务注册中心,提供服务注册和发现功能
OpenFeign: 实现远程调用
Sentinel: 提供服务容错保护
Seata: 实现分布式事务管理
RabbitMQ: 实现异步通知
Gateway:(API网关服务):作用:安全,路由,限流,监控
服务注册发现的基本流程是怎样的?答: 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心,调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
Eureka和Nacos有哪些区别?答: Nacos与Eureka的区别
1.Nacos支持服务端主动检测提供者状态: 临时实例采用心跳模式,非临时实例采用主动检测模式
2.临时实例心跳不正常会被剔除,非临时实例则不会被剔除
3.Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
4.Nacos集群默认采用AP方式,但也支持CP;Eureka采用AP方式
Nacos的分级存储模型是什么意思?
答:任何一个微服务的实例在注册到Nacos时,都会生成以下几个信息,用来确认当前实例的身份,从外到内依次是:
namespace:命名空间
group:分组
service:服务名
cluster:集群
instance:实例,包含ip和端口
这就是nacos中的服务分级模型。
OpenFeign是如何实现负载均衡的?
答:Spring在整合OpenFeign的时候,实现org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient类,其中定义了OpenFeign发起远程调用的核心流程。也就是四步:
- 获取请求中的serviceId
- 根据serviceId负载均衡,找出一个可用的服务实例
- 利用服务实例的ip和port信息重构url
- 向真正的url发起请求
而具体的负载均衡则是不是由OpenFeign组件负责。而是分成了负载均衡的接口规范,以及负载均衡的具体实现两部分。
负载均衡的接口规范是定义在Spring-Cloud-Common模块中,包含下面的接口:
LoadBalancerClient:负载均衡客户端,职责是根据serviceId最终负载均衡,选出一个服务实例
ReactiveLoadBalancer:负载均衡器,负责具体的负载均衡算法
OpenFeign的负载均衡是基于Spring-Cloud-Common模块中的负载均衡规则接口,并没有写死具体实现。这就意味着以后还可以拓展其它各种负载均衡的实现。
不过目前SpringCloud中只有Spring-Cloud-Loadbalancer这一种实现。
Spring-Cloud-Loadbalancer模块中,实现了Spring-Cloud-Common模块的相关接口,具体如下:
BlockingLoadBalancerClient:实现了LoadBalancerClient,会根据serviceId选出负载均衡器并调用其算法实现负载均衡。
RoundRobinLoadBalancer:基于轮询算法实现了ReactiveLoadBalancer
RandomLoadBalancer:基于随机算法实现了ReactiveLoadBalancer
什么是服务雪崩,常见的解决方案有哪些?答:
线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求
Hystix和Sentinel有什么区别和联系?答:
无论是Hystix还是Sentinel都支持线程隔离。不过其实现方式不同。
Hystix默认是基于线程池实现的线程隔离,每一个被隔离的业务都要创建一个独立的线程池,线程过多会带来额外的CPU开销,性能一般,但是隔离性更强.支持主动超时和异步调用,适用场景为低扇出
Sentinel则是基于信号量隔离的原理,这种方式不用创建线程池,性能较好,但是隔离性一般.不支持主动超时和异步调用,使用场景为高频调用和高扇出
Sentinel的线程隔离就是基于信号量隔离实现的,而Hystix两种都支持,但默认是基于线程池隔离。
限流的常见算法有哪些?答:
固定窗口计算器算法
固定窗口计数器算法概念如下
将时间划分为多个窗口,窗口时间跨度称为Interval,本例中为1000ms
每个窗口分别计数统计,每有一次请求就将计数器加一,限流就是设置计数器阈值,本例为3
如果计数器超过了限流阈值,则超出阈值的请求都被丢弃
滑动窗口算法(默认区间数量是2,区间数越多,计数器越多,压力越大)Sentinel
滑动窗口计数器算法会将一个窗口划分为n个更小的区间,例如
窗口时间跨度Interval为1秒;区间数量n=2,则每个小区间时间跨度为500ms,每个区间都有计数器
限流阈值依然为3,时间窗口(1秒)内请求超过阈值时,超出的请求被限流
窗口会根据当前请求所在时间(currentTime)移动,窗口范围是从(currentTime-Interval)之后的第一个时区开始,到currentTime
限流之后(滑动窗口算法后,默认方案是快速拒绝:抛出异常,失败)
其他方案:
漏桶算法
将每个请求视作"水滴"放入"漏桶"进行存储
"漏桶"以固定速率向外"漏"出请求来执行,如果"漏桶"空了则停止"漏水"
如果"漏桶"满了则多余的"水滴"会被直接丢弃
可以理解成请求在桶内排队等
令牌桶算法
以固定的速率生成令牌,存入令牌桶中,如果令牌桶满了以后,停止生成
请求进入后,必须先尝试从桶中获取令牌,获取到令牌后才可以被处理
如果令牌桶中没有令牌,则请求等待或丢弃
什么是CAP理论和BASE思想?答:
1998年,加州大学的计算机科学家Eric Brewer提出,分布式系统有三个指标:
Consistency(一致性): 用户访问分布式系统中的任意节点,得到的数据必须一致
Availability(可用性): 用户访问分布式系统时,读或写操作总能成功.只能读不能写,或者只能写不能读,或者两者都不执行,就说明系统弱可用或不可用
Partition tolerance(分区容错性): 因为网络故障或其他原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区, 系统要能容忍网络分区现象,出现分区时,整个系统也要持续对外提供服务
他说,分布式系统无法同时满足这三个指标
这个结论就叫做CAP定理
BASE理论是对CAP的一种解决思路,包含三个思想:
Basically Available(基本可用): 分布式系统在出现故障时,允许算是部分可用性,即保证核心可用
Soft State(软状态): 在一定时间内,允许出现中间状态,比如临时的不一致状态
Eventually Consistent(最终一致性): 虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致
项目中碰到过分布式事务问题吗?怎么解决的?答:
分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴CAP定理和BASE理论:
CP模式: 各个子事务执行后互相等待,同时提交,同时回滚,达成强一致.但事务等待过程中,处于弱可用状态
AP模式: 各个事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致.
AT模式如何解决脏读和脏写问题的?
答:
AT模式也分为两个阶段
第一阶段是记录数据快照,执行并提交事务
第二阶段根据阶段一的结果来判断:
如果每一个分支事务都成功,则事务已经结束(因为阶段一已经提交),因此删除阶段一的快照即可
如果有任意分支事务失败,则需要根据快照恢复到更新前数据。然后删除快照
这种模式在大多数情况下(99%)并不会有什么问题,不过在极端情况下,特别是多线程并发访问AT模式的分布式事务时,有可能出现脏写问题
解决思路就是引入了全局锁的概念。在释放DB锁之前,先拿到全局锁。避免同一时刻有另外一个事务来操作当前数据。
TCC模式与AT模式对比,有哪些优缺点答:
TCC模式的每个阶段是做什么的?
Try: 资源检查和预留
Confirm: 业务执行和提交
Cancel: 预留资源的释放
TCC的优点是什么?
一阶段完成直接提交事务,释放数据库资源,性能好
相比AT模型,无需生成快照,无需使用全局锁,性能最强
不依赖数据库,而是依赖补偿操作,可以用于非事务型数据库
TCC的缺点是什么?
有代码侵入,需要人为编写try,Confirm和Cancel接口,太麻烦
软状态,事务是最终一致
需要考虑Confirm和Cancel的失败情况,做好幂等处理
RabbitMQ是如何确保消息的可靠性的?
答:
1.首先第一种情况,就是生产者发送消息时,出现了网络故障,导致与MQ的连接中断。
生产者重试机制:
为了解决这个问题,SpringAMQP提供的消息发送时的重试机制。即:当RabbitTemplate与MQ连接超时后,多次重试。
2.在少数情况下,也会出现消息发送到MQ之后丢失的现象
生产者确认机制:
RabbitMQ提供了生产者消息确认机制,包括Publisher Confirm和Publisher Return两种。在开启确认机制的情况下,当生产者发送消息给MQ后,MQ会根据消息处理的情况返回不同的回执。
3.消息到达MQ以后,如果MQ不能及时保存,也会导致消息丢失,所以MQ的可靠性也非常重要。
数据持久化:
为了提升性能,默认情况下MQ的数据都是在内存存储的临时数据,重启后就会消失。为了保证数据的可靠性,必须配置数据持久化,包括:- 交换机持久化 - 队列持久化 - 消息持久化
4.为了确认消费者是否成功处理消息,RabbitMQ提供了消费者确认机制(Consumer Acknowledgement)。即:当消费者处理消息结束后,应该向RabbitMQ发送一个回执,告知RabbitMQ自己消息处理状态。回执有三种可选值:
ack:成功处理消息,RabbitMQ从队列中删除该消息
nack:消息处理失败,RabbitMQ需要再次投递消息
reject:消息处理失败并拒绝该消息,RabbitMQ从队列中删除该消息
5.当消费者出现异常后,消息会不断requeue(重入队)到队列,再重新发送给消费者。如果消费者再次执行依然出错,消息会再次requeue到队列,再次投递,直到消息处理成功为止.
失败重试机制:在消费者出现异常时利用本地重试,而不是无限制的requeue到mq队列。
在yml文件上配置retry:enabled: true
开启本地重试时,消息处理过程中抛出异常,不会requeue到队列,而是在消费者本地重试
重试达到最大次数后,Spring会返回reject,消息会被丢弃
6.本地测试达到最大重试次数后,消息会被丢弃。这在某些对于消息可靠性要求较高的业务场景下,显然不太合适了。
失败处理策略:
因此Spring允许我们自定义重试次数耗尽后的消息处理策略,这个策略是由MessageRecovery接口来定义的,它有3个不同 实现:
RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机
RabbitMQ是如何解决消息堆积问题的?答:
在默认情况下,RabbitMQ会将接收到的信息保存在内存中以降低消息收发的延迟。但在某些特殊情况下,这会导致消息积压,比如:
消费者宕机或出现网络故障
消息发送量激增,超过了消费者处理速度
消费者处理业务发生阻塞
一旦出现消息堆积问题,RabbitMQ的内存占用就会越来越高,直到触发内存预警上限。此时RabbitMQ会将内存消息刷到磁盘上,这个行为成为PageOut. PageOut会耗费一段时间,并且会阻塞队列进程。因此在这个过程中RabbitMQ不会再处理新的消息,生产者的所有请求都会被阻塞。
为了解决这个问题,从RabbitMQ的3.6.0版本开始,就增加了Lazy Queues的模式,也就是惰性队列。惰性队列的特征如下:
接收到消息后直接存入磁盘而非内存
消费者要消费消息时才会从磁盘中读取并加载到内存(也就是懒加载)
支持数百万条的消息存储