1、Spring中的事务是如何实现的
Spring支持编程式事务管理和声明式事务管理两种方式。
- 编程式事务控制:需使用TransactionTemplate来进行实现,对业务代码有侵入性,项目中很少使用
- 声明式事务管理:声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
//开启事务
//执行业务代码
Object proceed = joinPoint.proceed();
//提交事务
return proceed;
}catch (Exception e){
e.printStackTrace();
//回滚事务
}
}
2、Spring中事务失效的场景有哪些
异常捕获处理
原因
- 事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
解決
- 在catch块添加throw new RuntimeException(e)抛出
抛出检查异常
原因
- Spring 默认只会回滚非检查异常
解決
- 配置rollbackFor属性
@Transactional(rollbackFor=Exception.class)
非public方法导致的事务失效
原因
- Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的
解決
- 改为 public 方法
3、Spring的bean的生命周期
- 通过BeanDefinition获取bean的定义信息
- 调用构造函数实例化bean
- bean的依赖注入
- 处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware)
- Bean的后置处理器BeanPostProcessor-前置
- 初始化方法(InitializingBean、init-method)
- Bean的后置处理器BeanPostProcessor-后置
- 销毁bean
4、Spring中的循环引用
循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A
循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
缓存名称 | 源码名称 | 作用 |
---|---|---|
一级缓存 | singletonObjects | 单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象 |
二级缓存 | earlySingletonObjects | 缓存早期的bean对象(生命周期还没走完) |
三级缓存 | singletonFactories | 缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的 |
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可/免费获取】
https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho
5、具体解决流程
- 先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories
- A在初始化的时候需要B对象,这个走B的创建的逻辑
- B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories
- B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键
- B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects
- 回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects
- 二级缓存中的临时对象A清除
6、构造方法出现了循环依赖怎么解决
由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入,可以使用@Lazy懒加载,什么时候需要对象再进行bean对象的创建。
public A(@Lazy B b){
System.out.println("A的构造方法执行了...");
this.b = b ;
}
7、SpringMVC的执行流程
3.10.1、视图阶段(JSP)
1、用户发送出请求到前端控制器DispatcherServlet,这是一个调度中心
2、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。
3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter(处理器适配器)。
5、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。
6、Controller执行完成返回ModelAndView对象。
7、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
8、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。
9、ViewReslover解析后返回具体View(视图)。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户。
8、前后端分离阶段(接口开发,异步请求)
1、用户发送出请求到前端控制器DispatcherServlet
2、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)
3、HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter(处理器适配器)
5、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)
6、方法上添加了@ResponseBody
7、通过HttpMessageConverter来返回结果转换为JSON并响应
9、Springboot自动配置原理
1、在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
2、其中@EnableAutoConfiguration
是实现自动化配置的核心注解。 该注解通过@Import
注解导入对应的配置选择器。
内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。
在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。
3、一般条件判断会有像@ConditionalOnClass
这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。
10、Spring 的常见注解
注解 | 说明 |
---|---|
@Component、@Controller、@Service、@Repository | 使用在类上用于实例化Bean |
@Autowired | 使用在字段上用于根据类型依赖注入 |
@Qualifier | 结合@Autowired一起使用用于根据名称进行依赖注入 |
@Scope | 标注Bean的作用范围 |
@Configuration | 指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解 |
@ComponentScan | 用于指定 Spring 在初始化容器时要扫描的包 |
@Bean | 使用在方法上,标注将该方法的返回值存储到Spring容器中 |
@Import | 使用@Import导入的类会被Spring加载到IOC容器中 |
@Aspect、@Before、@After、@Around、@Pointcut | 用于切面编程(AOP) |
11、SpringMVC常见注解
注解 | 说明 |
---|---|
@RequestMapping | 用于映射请求路径,可以定义在类上和方法上。用于类上,则表示类中的所有的方法都是以该地址作为父路径 |
@RequestBody | 注解实现接收http请求的json数据,将json转换为java对象 |
@RequestParam | 指定请求参数的名称 |
@PathViriable | 从请求路径下中获取请求参数(/user/{id}),传递给方法的形式参数 |
@ResponseBody | 注解实现将controller方法返回对象转化为json对象响应给客户端 |
@RequestHeader | 获取指定的请求头数据 |
@RestController | @Controller + @ResponseBody |
12、Springboot常见注解
Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 :
注解 | 说明 |
---|---|
@SpringBootConfiguration | 组合了- @Configuration注解,实现配置文件的功能 |
@EnableAutoConfiguration | 打开自动配置的功能,也可以关闭某个自动配置的选 |
@ComponentScan | Spring组件扫描 |
13、MyBatis执行流程
- 读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件
- 构造会话工厂SqlSessionFactory
- 会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)
- 操作数据库的接口,Executor执行器,同时负责查询缓存的维护
- Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息
- 输入参数映射
- 输出结果映射
14、Mybatis是否支持延迟加载?
支持
延迟加载的意思是:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。
Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载
在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false
,默认是关闭的
15、延迟加载的底层原理
- 使用CGLIB创建目标对象的代理对象
- 当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,执行sql查询
- 获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了
16、Mybatis的一级、二级缓存
mybatis的一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存
关于二级缓存需要单独开启
二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储。
如果想要开启二级缓存需要在全局配置文件和映射文件中开启配置才行。
开启方式,两步:
1,全局配置文件
<settings>
<setting name="cacheEnabled" value="true
</settings>
2、映射文件
使用<cache/>
标签让当前mapper生效二级缓存
注意事项
1,对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear
2,二级缓存需要缓存的数据实现Serializable接口
3,只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
3.19、Mybatis的二级缓存什么时候会清理缓存中的数据
当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除
操作后,默认该作用域下所有 select 中的缓存将被 clear。
四、微服务
4.1、Spring Cloud 5大组件
早期我们一般认为的Spring Cloud五大组件是
- Eureka : 注册中心
- Ribbon : 负载均衡
- Feign : 远程调用
- Hystrix : 服务熔断
- Zuul/Gateway : 网关
随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件
- 注册中心/配置中心 Nacos
- 负载均衡 Ribbon
- 服务调用 Feign
- 服务保护 sentinel
- 服务网关 Gateway
4.2、服务注册和发现是什么意思?Spring Cloud 如何实现服务注册发现?
主要三块大功能,分别是服务注册 、服务发现、服务状态监控
我们当时项目采用的eureka作为注册中心,这个也是spring cloud体系中的一个核心组件
服务注册:服务提供者需要把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、ip、端口等等
服务发现:消费者向eureka拉取服务列表信息,如果服务提供者有集群,则消费者会利用负载均衡算法,选择一个发起调用
服务监控:服务提供者会每隔30秒向eureka发送心跳,报告健康状态,如果eureka服务90秒没接收到心跳,从eureka中剔除
4.3、nacos与eureka的区别
Nacos与eureka的共同点(注册中心)
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
Nacos与Eureka的区别(注册中心)
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
Nacos还支持了配置中心,eureka则只有注册中心,也是选择使用nacos的一个重要原因
4.4、项目负载均衡如何实现
在服务调用过程中的负载均衡一般使用SpringCloud的Ribbon 组件实现 , Feign的底层已经自动集成了Ribbon , 使用起来非常简单
当发起远程调用时,ribbon先从注册中心拉取服务地址列表,然后按照一定的路由策略选择一个发起远程调用,一般的调用策略是轮询
4.5、Ribbon负载均衡策略
RoundRobinRule
:简单轮询服务列表来选择服务器WeightedResponseTimeRule
:按照权重来选择服务器,响应时间越长,权重越小RandomRule
:随机选择一个可用的服务器- BestAvailableRule:忽略那些短路的服务器,并选择并发数较低的服务器
- RetryRule:重试机制的选择逻辑
- AvailabilityFilteringRule:可用性敏感策略,先过滤非健康的,再选择连接数较小的实例
ZoneAvoidanceRule
:以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。(默认方式,如果没有区域的概念,使用的就和简单轮询一致)
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可/免费获取】
https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho
4.6、自定义负载均衡策略如何实现
提供了两种方式:
1,创建类实现IRule接口,可以指定负载均衡策略(全局)
2,在客户端的配置文件中,可以配置某一个服务调用的负载均衡策略(局部)
4.7、什么是服务雪崩,怎么解决这个问题?
服务雪崩是指一个服务失败,导致整条链路的服务都失败的情形,一般我们在项目解决的话就是两种方案:
第一个是服务降级,第二个是服务熔断,如果流量太大的话,可以考虑限流。
4.7.1、服务降级
服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃,一般在实际开发中与feign接口整合,编写降级逻辑
4.7.2、服务熔断
默认关闭,如果需要开启需要在引导类上添加注解@EnableCircuitBreaker
如果检测到 10 秒内请求的失败率超过 50%
,就触发熔断机制。之后每隔 5 秒
重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常请求。
4.8、微服务是怎么监控的
- Springboot-admin
- prometheus+Grafana
- zipkin
- skywalking
项目中采用的skywalking进行监控的
1,skywalking主要可以监控接口、服务、物理实例的一些状态。特别是在压测的时候可以看到众多服务中哪些服务和接口比较慢,我们可以针对性的分析和优化。
2,我们还在skywalking设置了告警规则,特别是在项目上线以后,如果报错,我们分别设置了可以给相关负责人发短信和发邮件,第一时间知道项目的bug情况,第一时间修复
4.9、项目中如何限流
为什么要限流?
1,并发的确大(突发流量)
2,防止用户恶意刷接口
限流的实现方式:
- Tomcat:可以设置最大连接数
- Nginx,漏桶算法
- 网关,令牌桶算法
- 自定义拦截器
4.9.1、Nginx限流
控制速率(突发流量)
limit_req_zone key zone rate
- 1
- key:定义限流对象,binary_remote_addr就是一种key,基于客户端ip限流
- Zone:定义共享存储区来存储访问信息,10m可以存储16wip地址访问信息
- Rate:最大访问速率,rate=10r/s 表示每秒最多请求10个请求
- burst=20:相当于桶的大小
- Nodelay:快速处理
控制并发连接数
- limit_conn perip 20:对应的key是 $binary_remote_addr,表示限制单个IP同时最多能持有20个连接。
- limit_conn perserver 100:对应的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。
4.9.2、网关限流
yml配置文件中,微服务路由设置添加局部过滤器RequestRateLimiter
- key-resolver :定义限流对象( ip 、路径、参数),需代码实现,使用spel表达式获取
- replenishRate :令牌桶每秒填充平均速率。
- urstCapacity :令牌桶总容量。
4.9.3、回答
1、先来介绍业务,什么情况下去做限流,需要说明QPS具体多少
-
我们当时有一个活动,到了假期就会抢购优惠券,QPS最高可以达到2000,平时10-50之间,为了应对突发流量,需要做限流
-
常规限流,为了防止恶意攻击,保护系统正常运行,我们当时系统能够承受最大的QPS是多少(压测结果)
2、nginx限流
- 控制速率(突发流量),使用的漏桶算法来实现过滤,让请求以固定的速率处理请求,可以应对突发流量
- 控制并发数,限制单个ip的链接数和并发链接的总数
3、网关限流
- 在spring cloud gateway中支持局部过滤器RequestRateLimiter来做限流,使用的是令牌桶算法
- 可以根据ip或路径进行限流,可以设置每秒填充平均速率,和令牌桶总容量
4.10、限流常见的算法有哪些呢?
比较常见的限流算法有漏桶算法和令牌桶算法
漏桶算法是把请求存入到桶中,以固定速率从桶中流出,可以让我们的服务做到绝对的平均,起到很好的限流效果
令牌桶算法在桶中存储的是令牌,按照一定的速率生成令牌,每个请求都要先申请令牌,申请到令牌以后才能正常请求,也可以起到很好的限流作用
它们的区别是,漏桶和令牌桶都可以处理突发流量,其中漏桶可以做到绝对的平滑,令牌桶有可能会产生突发大量请求的情况,一般nginx限流采用的漏桶,spring cloud gateway中可以支持令牌桶算法
4.11、什么是CAP理论
CAP主要是在分布式项目下的一个理论。包含了三项,一致性、可用性、分区容错性
- 一致性(Consistency)是指更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致(强一致性),不能存在中间状态。
- 可用性(Availability) 是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
- 分区容错性(Partition tolerance) 是指分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
结论
- 分布式系统节点之间肯定是需要网络连接的,分区(P)是必然存在的
- 如果保证访问的高可用性(A),可以持续对外提供服务,但不能保证数据的强一致性–> AP
- 如果保证访问的数据强一致性(C),就要放弃高可用性 --> CP
4.12、为什么分布式系统中无法同时保证一致性和可用性
首先一个前提,对于分布式系统而言,分区容错性是一个最基本的要求,因此基本上我们在设计分布式系统的时候只能从一致性(C)和可用性(A)之间进行取舍。
如果保证了一致性(C):对于节点N1和N2,当往N1里写数据时,N2上的操作必须被暂停,只有当N1同步数据到N2时才能对N2进行读写请求,在N2被暂停操作期间客户端提交的请求会收到失败或超时。显然,这与可用性是相悖的。
如果保证了可用性(A):那就不能暂停N2的读写操作,但同时N1在写数据的话,这就违背了一致性的要求。
4.13、什么是BASE理论
BASE是CAP理论中AP方案的延伸,核心思想是即使无法做到强一致性(StrongConsistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。它的思想包含三方面:
- Basically Available(基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
- Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。
- Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。
4.14、分布式事务解决方案
4.14.1、Seata事务管理中有三个重要的角色
- TC (Transaction Coordinator) -事务协调者: 维护全局和分支事务的状态,协调全局事务提交或回滚。
- TM (Transaction Manager) -事务管理器: 定义全局事务的范围、开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) -资源管理器: 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
4.14.2、seata的XA模式
RM一阶段的工作:
- 注册分支事务到TC
- 执行分支业务sql但不提交
- 报告执行状态到TC
TC二阶段的工作:
- TC检测各分支事务执行状态
- 如果都成功,通知所有RM提交事务
- 如果有失败,通知所有RM回滚事务
RM二阶段的工作:
- 接收TC指令,提交或回滚事务
4.14.3、AT模式原理
AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。
阶段一RM的工作:
- 注册分支事务
- 记录undo-log(数据快照)
- 执行业务sql并提交
- 报告事务状态
阶段二提交时RM的工作:
- 删除undo-log即可
阶段二回滚时RM的工作:
- 根据undo-log恢复数据到更新前
4.14.4、TCC模式原理
1、Try:资源的检测和预留;
2、Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
3、Cancel:预留资源释放,可以理解为try的反向操作。
4.14.5、MQ分布式事务
4.14.6、描述项目中采用的哪种方案(seata | MQ)
1、seata的XA模式,CP,需要互相等待各个分支事务提交,可以保证强一致性,性能差。
2、seata的AT模式,AP,底层使用undo log 实现,性能好。
3、seata的TCC模式,AP,性能较好,不过需要人工编码实现。
4、MQ模式实现分布式事务,在A服务写数据的时候,需要在同一个事务内发送消息到另外一个事务,异步,性能最好。
1、3适合银行业务
2、4适合互联网业务
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可/免费获取】
https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho
4.15、分布式服务的接口幂等性如何设计
- 幂等: 多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致
- 如果是新增数据,可以使用数据库的唯一索引
- 如果是新增或修改数据
- 分布式锁,性能较低
- 使用token+redis来实现,性能较好
- 第一次请求,生成一个唯一token存入redis,返回给前端
- 第二次请求,业务处理,携带之前的token,到redis进行验证,如果存在,可以执行业务,删除token;如果不存在,则直接返回,不处理业务
4.16、你们项目中使用了什么分布式任务调度
4.16.1、xxl-job解决的问题
- 解决集群任务的重复执行问题
- cron表达式定义灵活
- 定时任务失败了,重试和统计
- 任务量大,分片执行
4.16.2、xxl-job路由策略有哪些?
xxl-job提供了很多的路由策略,我们平时用的较多就是:轮询、故障转移、分片广播…
- FIRST(第一个):固定选择第一个机器;
- LAST(最后一个):固定选择最后一个机器;
- ROUND(轮询)
- RANDOM(随机):随机选择在线的机器;
- CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
- LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的机器优先被选举;
- LEAST_RECENTLY_USED(最近最久未使用):最久未使用的机器优先被选举;
- FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;
- BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
- SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;
4.16.3、xxl-job任务执行失败怎么解决?
-
路由策略选择故障转移,优先使用健康的实例来执行任务
-
如果还有失败的,我们在创建任务时,可以设置重试次数
-
如果还有失败的,就可以查看日志或者配置邮件告警来通知相关负责人解决
4.16.4、如果有大数据量的任务同时都需要执行,怎么解决?
我们会让部署多个实例,共同去执行这些批量的任务,其中任务的路由策略是分片广播
- BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
- SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;
五、消息中间件
5.1、RabbitMQ如何保证消息不丢失
5.1.1、生产者确认机制
RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功
消息失败之后如何处理呢?
- 回调方法即时重发
- 记录日志
- 保存到数据库然后定时重发,成功发送后即刻删除表中的数据
5.1.2、消息持久化
MQ默认是内存存储消息,开启持久化功能可以确保缓存在MQ中的消息不丢失。
- 交换机持久化
@Bean public DirectExchange simpleExchange(){ // 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除 return new DirectExchange("simple.direct", true, false); }
- 队列持久化
@Bean public Queue simpleQueue(){ // 使用QueueBuilder构建队列,durable就是持久化的 return QueueBuilder.durable("simple.queue").build(); }
- 消息持久化,SpringAMQP中的的消息默认是持久的,可以通过MessageProperties中的DeliveryMode来指定
Message msg = MessageBuilder .withBody(message.getBytes(StandardCharsets.UTF_8)) // 消息体 .setDeliveryMode(MessageDeliveryMode.PERSISTENT) // 持久化 .build();
5.1.3、消费者确认
RabbitMQ支持消费者确认机制,即:消费者处理消息后可以向MQ发送ack回执,MQ收到ack回执后才会删除该消息。而SpringAMQP则允许配置三种确认模式:
- manual:手动ack,需要在业务代码结束后,调用api发送ack。
- auto:自动ack,由spring监测listener代码是否出现异常,没有异常则返回ack;抛出异常则返回nack
- none:关闭ack,MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除
我们可以利用Spring的retry机制,在消费者出现异常时利用本地重试,设置重试次数,当次数达到了以后,如果消息依然失败,将消息投递到异常交换机,交由人工处理
5.2、RabbitMQ消息重复消费
消费者是设置了自动确认机制,当服务还没来得及给MQ确认的时候,服务宕机了,导致服务重启之后,又消费了一次消息。这样就重复消费了
解决方案
- 每条消息设置一个唯一的标识id
- 幂等方案:分布式锁、数据库锁(悲观锁、乐观锁)
5.3、RabbitMQ中死信交换机 (延迟队列)
延迟队列就是用到了死信交换机 + TTL(消息存活时间)实现的。
- 延迟队列:进入队列的消息会被延迟消费的队列
- 场景:超时订单、限时优惠、定时发布
如果消息超时未消费就会变成死信,在RabbitMQ中如果消息成为死信,队列可以绑定一个死信交换机,在死信交换机上可以绑定其他队列,在我们发消息的时候可以按照需求指定TTL的时间,这样就实现了延迟队列的功能了。
我记得RabbitMQ还有一种方式可以实现延迟队列,在RabbitMQ中安装一个死信插件,这样更方便一些,我们只需要在声明交互机的时候,指定这个就是死信交换机,然后在发送消息的时候直接指定超时时间就行了,相对于死信交换机+TTL要省略了一些步骤
5.4、100万消息堆积在MQ , 如何解决
解决消息堆积有三种种思路:
- 提高消费者的消费能力 ,可以使用多线程消费任务
- 增加更多消费者,提高消费速度
- 使用工作队列模式, 设置多个消费者消费消费同一个队列中的消息
- 扩大队列容积,提高堆积上限
- 可以使用RabbitMQ惰性队列,惰性队列的好处主要是
- 接收到消息后直接存入磁盘而非内存
- 消费者要消费消息时才会从磁盘中读取并加载到内存
- 支持数百万条的消息存储
- 可以使用RabbitMQ惰性队列,惰性队列的好处主要是