1.说说你对Spring IOC的理解?
总体来说,构造方法注入和setter方法注入因为其侵入性较弱,且易于理解和使用,所以是现在使用最多的注入方式。而接口注入因为侵入性较强,近年来已经不流行了。
-
@ComponentScan用于声明扫描策略,通过它的声明,容器就知道要扫描哪些包下带有声明的类,也可以知道哪些特定的类是被排除在外的。
-
@Component、@Repository、@Service、@Controller用于声明Bean,它们的作用一样,但是语义不同。@Component用于声明通用的Bean,@Repository用于声明DAO层的Bean,@Service用于声明业务层的Bean,@Controller用于声明视图层的控制器Bean,被这些注解声明的类就可以被容器扫描并创建。
-
@Autowired、@Qualifier用于注入Bean,即告诉容器应该为当前属性注入哪个Bean。其中,@Autowired是按照Bean的类型进行匹配的,如果这个属性的类型具有多个Bean,就可以通过@Qualifier指定Bean的名称,以消除歧义。
-
@Scope用于声明Bean的作用域,默认情况下Bean是单例的,即在整个容器中这个类型只有一个实例。可以通过@Scope注解指定prototype值将其声明为多例的,也可以将Bean声明为session级作用域、request级作用域等等,但最常用的还是默认的单例模式。
-
@PostConstruct、@PreDestroy用于声明Bean的生命周期。其中,被@PostConstruct修饰的方法将在Bean实例化后被调用,@PreDestroy修饰的方法将在容器销毁前被调用。
2.说说你对spring AOP的理解
AOP的术语:
-
连接点(join point):对应的是具体被拦截的对象,因为Spring只能支持方法,所以被拦截的对象往往就是指特定的方法,AOP将通过动态代理技术把它织入对应的流程中。
-
切点(point cut):有时候,我们的切面不单单应用于单个方法,也可能是多个类的不同方法,这时,可以通过正则式和指示器的规则去定义,从而适配连接点。切点就是提供这样一个功能的概念。
-
通知(advice):就是按照约定的流程下的方法,分为前置通知、后置通知、环绕通知、事后返回通知和异常通知,它会根据约定织入流程中。
-
目标对象(target):即被代理对象。
-
引入(introduction):是指引入新的类和其方法,增强现有Bean的功能。
-
织入(weaving):它是一个通过动态代理技术,为原有服务对象生成代理对象,然后将与切点定义匹配的连接点拦截,并按约定将各类通知织入约定流程的过程。
-
切面(aspect):是一个可以定义切点、各类通知和引入的内容,SpringAOP将通过它的信息来增强Bean的功能或者将对应的方法织入流程。
3.怎么保证MQ中的消息不会丢失?
其中的每一步都可能导致消息丢失,常见的丢失原因包括:
-
发送时丢失:
-
生产者发送的消息未送达exchange
-
消息到达exchange后未到达queue
-
-
MQ宕机,queue将消息丢失
-
consumer接收到消息后未消费就宕机
针对这些问题,RabbitMQ分别给出了解决方案:
1.生产者确认机制
RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。这种机制必须给每个消息指定一个唯一ID。消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。
返回结果有两种方式:
-
publisher-confirm,发送者确认
-
消息成功投递到交换机,返回ack
-
消息未投递到交换机,返回nack
-
-
publisher-return,发送者回执
-
消息投递到交换机了,但是没有路由到队列。返回ACK,及路由失败原因
-
2. mq持久化
生产者确认可以确保消息投递到RabbitMQ的队列中,但是消息发送到RabbitMQ以后,如果突然宕机,也可能导致消息丢失。
要想确保消息在RabbitMQ中安全保存,必须开启消息持久化机制。
-
交换机持久化
事实上,默认情况下,由SpringAMQP声明的交换机都是持久化的。
-
队列持久化
RabbitMQ中队列默认(原生)是非持久化的,mq重启后就丢失。
SpringAMQP中可以通过代码指定交换机持久化:
事实上,默认情况下,由SpringAMQP声明的队列都是持久化的。
消息持久化
默认情况下,SpringAMQP发出的任何消息都是持久化的,不用特意指定。
交换机与队列都不能是自动删除
3.消费者确认机制
-
none模式下,消息投递是不可靠的,可能丢失
-
auto模式类似事务机制,出现异常时返回nack,消息回滚到mq;没有异常,返回ack
-
manual:自己根据业务情况,判断什么时候该ack
一般,我们都是使用默认的auto即可
4.失败重试机制
-
开启本地重试时,消息处理过程中抛出异常,不会requeue到队列,而是在消费者本地重试
-
重试达到最大次数后,Spring会返回ack,消息会被丢弃
开启消费者失败重试机制,并设置MessageRecoverer,多次重试失败后将消息投递到异常交换机,交由人工处理
4.什么是消息堆积?有哪些后果?怎么解决?-
当生产者发送消息的速度超过了消费者处理消息的速度,就会导致队列中的消息堆积,直到队列存储消息达到上限。之后发送的消息就会成为死信,可能会被丢弃,这就是消息堆积问题。
会导致大量的消息丢失问题 而且还会导致消息堆积过多影响消息的消费导致后续的消息不能及时消费 大量的进入死信队列
解决消息堆积有两种思路:
-
增加更多消费者,提高消费速度。也就是我们之前说的work queue模式
-
扩大队列容积,提高堆积上限
要提升队列容积,把消息保存在内存中显然是不行的。
-
队列过长的话会占用系统较多内存,RabbitMQ为了释放内存,会将队列消息转储到硬盘,称为 page out 。 如果队列很长,Page out 操作会消耗较长时间,且page out 过程中队列不能处理消息。因此会出现间歇性的暂停状态、并发时出现波浪性的忽高忽低现象
-
队列过长同时会加长RabbitMQ重启时间,因为启动时候需要重建索引。
-
队列过长还会导致集群之间节点同步消息时间变长。
从RabbitMQ的3.6.0版本开始,就增加了Lazy Queues的概念,也就是惰性队列。惰性队列的特征如下:
-
接收到消息后直接存入磁盘而非内存
-
消费者要消费消息时才会从磁盘中读取并加载到内存
-
支持数百万条的消息存储
惰性队列的缺点:
-
基于磁盘存储,消息时效性会降低
-
性能受限于磁盘的IO
5.es介绍, 什么是倒排索引(结构)?
elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容。 一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能 elasticsearch底层是基于lucene来实现的 但是相比lucene elasticsearch 有以下几点优势 1.支持分布式,可水平扩展 2.提供Restful接口,可被任何语言调用
6.说说spring事务有哪7种传播方式?
1、Propagation.REQUIRED,支持当前事务
事务传播特征:
- 当A有事务时,B会加入到A事务中,一起commit,这个时候A、B中发生异常,事务都会回滚。
- 当A没有事务时,B会新建事务单独执行,这个时候B发生异常,B回滚,A不回滚。
2、Propagation.SUPPORTS,支持当前事务
事务传播特征:
- 当A有事务时,B会加入到A事务中,一起commit,这个时候A、B中发生异常,事务都会回滚。
- 当A没有事务时,B会以非事务的方式执行。
3、Propagation.MANDATORY,支持当前事务,强制性的,要求调用方必须支持事务
事务传播特征:
当A有事务时,B会加入到A事务中,一起commit,这个时候A、B中发生异常,事务都会回滚。
当A没有事务时,会抛出异常,org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation ‘mandatory’
4、Propagation.REQUIRES_NEW,不支持当前事务
事务传播特征:
- 当A有事务时,B会将A事务挂起,新建事务,这个时候A、B中发生异常,是互不影响的。
- 当A没有事务时,B新建事务。
5、Propagation.NOT_SUPPORTED,不支持当前事务
事务传播特征:
- 当A有事务时,B会将A事务挂起,以非事务方式运行。
- 当A没有事务时,以非事务方式运行。
6、Propagation.NEVER,不支持当前事务,强制性的
事务传播特征:
- 当A有事务时,会抛出异常
- 当A没有事务时,以非事务方式运行。
7、Propagation.NESTED,嵌套事务
- 当A有事务时,A发生异常,A、B都会回滚;当B发生异常并被A捕获时,A可以正常提交事务。
- 当A没有事务时,B新建事务,这个时候和REQUIRES_NEW是一样的。
7.什么情况下spring的事务@Transactional会失效
类上没有加@Service
事务方法是final
方法private失效
service中调用另一个方法(事务失效)
捕获异常没有抛出
抛出异常(但不在指定的异常范围内 rollbackFor=异常类型)Throwabe Exception
@Transactional只能放到方法上
mybatis整合时,批量操作时有可能事务问题,使用编程事务
8.数据同步的方式有哪些
直连同步:业务层操作数据成功远程调用es微服保存数据到es中
直连同步是指通过定义好的规范接口API和基于动态链接库的方式直接连接业务库,比如ODBC/JDBC等规定了统一的标准接口,不同的数据库基于这套标准提供规范的驱动,从而支持完全相同的函数调用和SQL实现。比如经常使用的Sqoop就是采取这种方式进行批量数据同步的。
直连同步的方式配置十分简单,很容易上手操作,比较适合操作型业务系统的数据同步,但是会存在以下问题:
数据同步时间:随着业务规模的增长,数据同步花费的时间会越来越长,无法满足下游数仓生产的时间要求。
性能瓶颈(关键):直连数据库查询数据,对数据库影响非常大,容易造成慢查询,如果业务库没有采取主备策略,则会影响业务线上的正常服务,如果采取了主备策略,虽然可以避免对业务系统的性能影响,但当数据量较大时,性能依然会很差。
异步:RabbitMQ,数据库操作成功发消息给mq, 由es微服消费消息,把数据保存es里
canel: 监听mysql binlog触发create/update/delete,调用es微服进行数据同步
频繁的磁盘io, 使用定时任务(数据延迟), 通过记录上一次同步时间,数据一旦发生变更都是更新时间,定时扫描数据库时使用时间>上次更新时间,同步到es里
9.什么是微服务雪崩?有哪些处理方案?
微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。 如果服务提供者I发生了故障,当前的应用的部分业务因为依赖于服务I,因此也会被阻塞。此时,其它不依赖于服务I的业务似乎不受影响。 但是,依赖服务I的业务请求被阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:
服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,那么当前服务也就不可用了。
那么,依赖于当前服务的其它服务随着时间的推移,最终也都会变的不可用,形成级联失败,雪崩就发生了:
解决雪崩问题的常见方式有四种:
超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待
仓壁模式(线程池隔离):
仓壁模式来源于船舱的设计船舱都会被隔板分离为多个独立空间,当船体破损时,只会导致部分空间进入,将故障控制在一定范围内,避免整个船体都被淹没。
于此类似,我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
断路器模式:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
断路器会统计访问某个服务的请求数量,异常比例:当发现访问服务D的请求异常比例过高时,认为服务D有导致雪崩的风险,会拦截访问服务D的一切请求,形成熔断:
流量控制:限制业务访问的QPS(每秒处理请求的多少),避免服务因流量的突增而故障。
10.线程隔离有哪些方案有哪些?区别是什么?
线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。
信号量隔离的特点是?
-
基于计数器模式,简单,开销小
线程池隔离的特点是?
-
基于线程池模式,有额外开销,但隔离控制更强
区别:
1. 信号量(多线程下包下的工具类,主要通过计数器来统计共享的线程)、高扇出、低消耗。不支持主动超时、不支持异步调用
sentinel
2. 线程池 低扇出 高消耗、主动超时、异步调用
hystrics
11.说说熔断器的工作原理(有哪些状态,是怎么转换的)
熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。
状态机包括三个状态:
-
closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
-
open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
-
half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
-
请求成功:则切换到closed状态
-
请求失败:则切换到open状态
-
12.SpringBoot自动配置原理
1.测试代码,启动加载到启动类,则会加载启动类上的注解@SpringBootApplication
2.加载注解@SpringBootApplication,就会加载三个注解@SpringBootConfiguration, @EnableAutoConfiguration,@ComponentScan
3.@SpringBootConfiguration因为有@Configuration注解所以本身就是一个配置类,@ComponentScan是用于扫描包的作用 ,所以在只要;类在该类的包下及其子包下就可以被扫描到spring容器中.
4.@EnableAutoConfiguration自动配置 ,里面有AutoConfigurationImportSelector类,通过这个类中的ImportSelect的实现类可以读取META-INF/spring.factorys的配置文件到EnableAutoConfiguration的配置项得到spring自带的一百多项全限定字符串串数组和该配置类上的注解进行过滤 是通过@conditional注解中的condition接口实现类的matches方法进行判断,如果返回值是true则保留,如果是false则过滤掉 最后得到Autoconfiguration类的全限定字符串,然后在通过
反射获取类字节码就可以解析bean注解创建bean对象,把对应的bean的注册到spring容器中,使用的时候直接@Autowied注入即可 .这就springboot的自动配置原理.
13.讲讲你对CAP的理解
C: 分布式下,数据存储分主从集群,当对主写操作时,数据要及时同步给从,才可保证客户端读到的数据是一致的.
A: 分布式下,当网络分区发生时,各个分区能正常对外提供服务,而不是拒绝或阻塞
P: 分区容错性:网络故障时,会导致分区。分区发生也要保证对外可用。由于分布式下使用网络通讯,网络故障无法避免
cp 独立的分区要拒绝或阻塞请求,数据不一致,为了保证数据一致,等网络恢复
ap 失去了数据的一致,分区发生时,数据无法同步到另一个分区,分区不能拒绝或阻塞请求(保证可用性),数据不一致
-
Basically Available (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
-
Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。
-
Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。
分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴CAP定理和BASE理论,有两种解决思路:
-
AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
-
CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。
ES 分片集群,用的就是CP。 数据写时,先写给主,再同步给从,之后再响应客户端
Nacos: 临时实例 AP, 非临时 CP
14.seata的工作原理 哪三角色?分别做什么?怎么工作
TC: 事务协调者
TM: 事务管理器
RM: 资源管理器
1. TM @GlobalTransactional 实现AOP,业务方法,执行之前,进行全局事务的注册, xid
2. TM把xid传递下去 本地事务通过ThreadLocal,远程调用则通过请求头与mvc拦截器
3. RM通过Threadlocal 获取xid, 它就是这个xid分支事务,向TC注册自己为xid的分支事务,且执行业务代码
4. 随着远程调用,每个微服按RM流程的走
5. @GlobalTransactional 业务代码执行完了,TM向TC汇报,业务做完了,可以提交或回滚了
6. TC 通过xid检查各分支事务的状态,如果没有异常则通知所有的RM提交,有一个异常则通知所有RM回滚
15.什么是事务悬挂、空回滚、幂等(有哪些解决方案)
当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚。
执行cancel操作时,应当判断try是否已经执行,如果尚未执行,则应该空回滚。
业务悬挂: cancel先执行再执行try,预留的资源没有被释放
允许空回滚:cancel时判断xid是否存在预留资源,不存在则添加记录标识已经执行过cancel,而不执行补偿业务
业务悬挂:空回滚时添加标识已经执行过cancel,判断是否存在补偿业务标识,存在则不执行业务。
幂等:方法无论执行多少次,结果都一样
判断预留资源状态,如果不是预期的则不处理
每次处理时添加标记(xid 存入redis), 当再执行时,先判断redis是否存在,存在则代表重复 不执行
16. 说说你对redis持久化的理解?
Redis有两种持久化方案:
-
RDB持久化
-
AOF持久化
RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件称为RDB文件,默认是保存在当前运行目录。
RDB持久化在四种情况下会执行:
-
执行save命令
-
执行bgsave命令
-
Redis停机时
-
触发RDB条件时
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。
fork采用的是copy-on-write技术:
-
当主进程执行读操作时,访问共享内存;
-
当主进程执行写操作时,则会拷贝一份数据,执行写操作。
RDB的缺点?
-
RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
-
fork子进程、压缩、写出RDB文件都比较耗时
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
AOF的命令记录的频率也可以通过redis.conf文件来配:
# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
17.主从复制时,同步原理(redis)
全量同步
主从第一次建立连接时,会执行全量同步,将master节点的所有数据都拷贝给slave节点,
-
slave节点请求增量同步
-
master节点判断replid,发现不一致,拒绝增量同步
-
master将完整内存数据生成RDB,发送RDB到slave
-
slave清空本地数据,加载master的RDB
-
master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
-
slave执行接收到的命令,保持与master之间的同步
增量同步:
-
slave节点请求增量同步
-
master节点判断replid,发现一致,继续增量同步
-
master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave获取offsets后的数据
-
slave通过offsets获得后面的数据同步到slave中
-
repl_baklog大小有上限,写满后覆盖最早的数据,如果slave断开时间过久,导致尚未备份的数据覆盖,则无法基于log做增量同步,只能再次全量同步
18.redis使用中的问题
缓存雪崩:是指在同一时段大量的缓存key同时失效或者redis服务宕机,导致大量请求到达数据库,带来巨大的压力.
解决方案:
给不同的key的TTL添加随机值
利用redis集群提高服务的可用性
给缓存业务添加降级限流策略
给业务添加多级缓存
缓存穿透:是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会失效,这些请求都会打到数据库.
常见的解决方案有两种:
缓存空对象:优点:实现简单,维护方便 缺点:额外的内存消耗 可能造成短期的不一致性
布隆过滤:优点:内存占用较少,没有多余key 缺点:实现复杂 存在误判可能
缓存击穿:一个高并发key,在失效的瞬间,大量的请求找数据库
常见的解决方案有两种:
互斥锁:优点:没有额外的内存消耗 保证一致性 实现简单 缺点:线程需要等待,性能受影响 可能有死锁风险
逻辑过期:线程无需等待,性能较好, 缺点:不保证一致性 有额外内存消耗 实现复杂