面试题
目录
Redis是一个内存数据库(Nosql),是基于键值对的一种存储形式。内部支持多种数据结构
Redis几种数据类型
String、hash、list、set、zset、bitmaps、hyperLoglog、GEO,还有5.0版本新增的Stream
Redis AOF和RDB持久化机制
AOF写回策略
AOF(Append Only File)Redis每执行一次写操作命令,就会把这个命令以追加的方式写入到一个文件,默认是不开启的,需要通过redis.conf 修改appendonly为yes
AOF有三种写回策略
AOF重写机制
当执行的写操作命令过多,文件越来越大,为了避免越写越大,提供了重写机制,当AOF文件大小超过阈值,Redis 就会启用 AOF 重写机制,来压缩 AOF 文件。
在重写时,读取当前数据库所有的键值对,然后将每一个键值对用一条命令记录到
新的AOF文件
,等全部记录完成后,就将新的AOF文件替换到现有的AOF文件
重写机制的好处就在于,某个键值对反复修改过的记录,将不再存在
重写AOF的过程由后台子进程去完成的,通过fork创建的子进程会只复制操作页表,也就是只复制内存地址,如果是多线程的话,只能通过加锁操作,而加锁会影响性能。
当主进程修改了某项数据,此时子进程的内存数据就跟主进程的内存数据不一致,为了解决这种数据不一致的情况,Redis设置了一个
AOF重写缓冲区
在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会同时将这个写命令写入到
AOF 缓冲区
和
AOF 重写缓冲区
子进程重写期间,主进程主要做三件事
当子进程完成AOF重写工作后,向主进程发送信号,主进程收到信号后,主要做两件事
RDB机制
RDB(Redis Database)持久化是把当前内存数据生成快照保存到硬盘的过程,触发RDB持久化过程分为 手动触发 和 自动触发
在redis.conf配置文件中可以配置:
save <seconds> <changes>
如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点,默认情况下执行shutdown,如果没有开启AOF,也会自动执行bgsave
混合持久化
Redis4.0以后提供了混合持久化方案,将RDB和AOF结合。如果想开启混合持久化功能,需要修改下面命令
aof-use-rdb-preamble yes
当开启混合持久化后,当
AOF日志进行重写时
,fork出来的重写子进程会先将与主进程共享数据以RDB形式方式写入AOF文件,然后主进程执行的操作命令被记录在重写缓冲区里,缓冲区的重写命令会以AOF方式写入AOF文件,写入完成后通知主进程,将含有RDB和AOF格式的AOF文件替换旧文件,使用了混合持久化,AOF的文件前半部分是RDB的全量数据,后半部分时AOF的增量数据
这样的好处在于,重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样 加载的时候速度会很快 。
加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得 数据更少的丢失
Redis 是否为单线程
Redis单线程,主要要考虑是从什么角度理解,如果从使用的角度来说,所有的客户端请求到数据读写操作,都是由一个线程,主线程去完成的,从这个角度来说,他确实是单线程
但从Redis架构来看,他并不是单线程的
关闭文件、AOF 刷盘、释放内存这三个任务都有各自的任务队列:
Redis 缓存雪崩、击穿、穿透,有什么不同?怎么解决
Redis 场景题
list push
和
list pop
这样的命令,所以能够很方便的执行队列操作。
Redis集群是AP还是CP?怎么切换?
集群是ap,因为是通过异步去进行同步的,尽管可以通过wait进行阻塞等待,但也无法实现cp
spring面试题
什么是Spring
Spring是一个容器框架,核心是IOC(控制反转)和AOP(依赖注入),实现了简化开发和解耦。有了AOP的支持,可以做到面向切面编程
Spring用到了什么设计模式
Autowired和Resource的区别
Autowired是Spring的注解,而Resource是jdk原生注解
2、@Resource有两个属性name和type。Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
@Autowired只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖@Qualifier或@Primary注解一起来修饰
Spring中常用的注解
@Component,@Controller,@Service,@Autowired,@Bean,@Transactional
谈谈你对循环依赖的理解
循环依赖指的是两个类,或者多个类相互依赖,形成了闭环,要解决循环依赖,一般是从设计上去避免类之间相互依赖。另外可以通过@Lazy进行延迟加载,或者使用Setter
https://juejin.cn/post/7146458376505917447
Bean的生命周期
Bean的生命周期可以分为四个阶段,分别是 实例化,属性赋值,初始化,销毁
1.调用构造方法实例化
2.设置属性,也就是属性赋值
3.在初始化前面会有一些扩展点,比如
BeanNameAware
、
BeanFactoryAware
、
BeanPostProcessor
以及
InitializingBean
4.自定义的init方法,也就是初始化阶段
5.初始化之后,仍然会有
BeanPostProcessor
6.正常使用
7.销毁Bean 执行
DispoableBean
接口或者自定义的destory 或者默认spring销毁
Spring支持几种作用域
六种作用域
注意: 后 3 种作用域,只适用于 Spring MVC 框架。
Spring事务的隔离级别
Spring有五种事务隔离级别,分别是:
读未提交
,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读;
读已提交
,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读;
可重复读
,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的 事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读 (Phantom Read);
串行化
,最高的事务隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。
Spring事务的传播行为
1) PROPAGATION_REQUIRED ,默认的spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。
2)PROPAGATION_SUPPORTS ,从字面意思就知道,supports,支持,该传播级别的特点是,如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作。应用场景较少。
3)PROPAGATION_MANDATORY , 该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。
4)PROPAGATION_REQUIRES_NEW ,从字面即可知道,new,每次都要一个新事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。 怎么处理整个业务需求呢?就是通过这个PROPAGATION_REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。
5)PROPAGATION_NOT_SUPPORTED ,这个也可以从字面得知,not supported ,不支持,当前级别的特点就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。
这个级别有什么好处?可以帮助你将事务极可能的缩小。我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了。用当前级别的事务模板抱起来就可以了。
6)PROPAGATION_NEVER ,该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行!这个级别上辈子跟事务有仇。
7)PROPAGATION_NESTED ,字面也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
那么什么是嵌套事务呢?很多人都不理解,我看过一些博客,都是有些理解偏差。
嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于那个save point。看几个问题就明了了:
如果子事务回滚,会发生什么?
父事务会回滚到进入子事务前建立的save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。
如果父事务回滚,会发生什么?
父事务回滚,子事务也会跟着回滚!为什么呢,因为父事务结束之前,子事务是不会提交的,我们说子事务是父事务的一部分,正是这个道理。那么:
事务的提交,是什么情况?
是父事务先提交,然后子事务提交,还是子事务先提交,父事务再提交?答案是第二种情况,还是那句话,子事务是父事务的一部分,由父事务统一提交。
BeanFactory和ApplicationContext的区别
BeanFactory和ApplicationContext都是Spring容器的核心接口,它们的区别主要在功能和扩展性上,BeanFactory提供了基本的IOC功能,如实例化Bean,管理Bean,还有生命周期的管理,而ApplicationContext继承了BeanFactory接口的所有功能。增加了AOP,事务管理,国际化支持等等
它们的初始化时间不 同:BeanFactory在容器启动时不会立即初始化所有Bean,而是在第一次获取时才会初始化,ApplicationContext在容器启动时就会立即初始化所有单例Bean
BeanFactory面向Spring本身,而ApplicationContext则面向开发者
@Transaction注解的失效场景
SpringMvc面试题
Springmvc的工作流程
Springmvc的注解
Mybatis面试题
Mybatis的一二级缓存机制
SqlSession
级别的缓存);
namespace
级别的缓存,需要我们手动进行开启和配置;
一级缓存失效场景:开启多个sqlsession或者查询结果为空
Mybatis #和$的区别
# 是预编译处理,$ 是字符串替换
SpringBoot面试题
为什么要用SpringBoot
快速开发,快速整合,配置简化、内嵌服务容器
Spring Boot 主要有如下优点:
容易上手,提升开发效率,为 Spring 开发提供一个更快、更简单的开发框架。 开箱即用,远离繁琐的配置。 提供了一系列大型项目通用的非业务性功能,例如:内嵌服务器、安全管理、运行数据监控、运行状况检查和外部化配置等。 SpringBoot总结就是使编码变简单、配置变简单、部署变简单、监控变简单等等
Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
java 如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
SpringBoot自动装配原理
1.首先在启动类上,加入
@SpringBootApplication
注解,里面有一个叫做
@EnableAutoConfiguration
注解,在这个注解上面,又包含了
@AutoConfigurationPackge
和
@Import(AutoConfigurationImportSelector)
2.
AutoConfigurationImportSelector
里面包含了
register
类,这个类实现了
ImportBeanDefinitionRegistrar
他用来动态加载bean
3.
register
内部就去加载
META_INF/spring.factories
中的自动装配类
4.通过一些
Conditional
注解,判断是否加入IOC
sleep和wait的区别?
sleep()
是
Thread
类的静态方法,用于让当前线程暂停执行一段时间。
wait()
是
Object
类的方法,用于在线程间进行协调通信。
sleep一般用于休眠当前线程,或者轮训暂停操作(自旋锁),wait一般用于多线程之间的通信
sleep 会让出 CPU 执行时间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会重新竞争到锁继续执行的。