目录
- Redis面试题
- spring面试题
- SpringMvc面试题
- Mybatis面试题
- SpringBoot面试题
- 多线程
- 分布式事务
- 分布式锁
- 网络
Redis面试题
什么是Redis
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有三种写回策略
- Always 每次写操作执行完成以后,同步将AOF日志数据写回硬盘
- Everysec 每次写操作执行完成以后,先将命令写入AOF内核缓冲区,每隔一秒钟将缓冲区数据写回硬盘
- No 交给操作系统去控制写回,每次写操作执行完成以后,将命令写入AOF文件内核区,再交给操作系统决定什么时候写回 硬盘
AOF重写机制
当执行的写操作命令过多,文件越来越大,为了避免越写越大,提供了重写机制,当AOF文件大小超过阈值,Redis 就会启用 AOF 重写机制,来压缩 AOF 文件。
在重写时,读取当前数据库所有的键值对,然后将每一个键值对用一条命令记录到新的AOF文件
,等全部记录完成后,就将新的AOF文件替换到现有的AOF文件
重写机制的好处就在于,某个键值对反复修改过的记录,将不再存在
重写AOF的过程由后台子进程去完成的,通过fork创建的子进程会只复制操作页表,也就是只复制内存地址,如果是多线程的话,只能通过加锁操作,而加锁会影响性能。
当主进程修改了某项数据,此时子进程的内存数据就跟主进程的内存数据不一致,为了解决这种数据不一致的情况,Redis设置了一个AOF重写缓冲区
在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会同时将这个写命令写入到 AOF 缓冲区
和 AOF 重写缓冲区
子进程重写期间,主进程主要做三件事
- 执行客户端发来的请求
- 将执行完后的命令追加到AOF缓冲区
- 将执行完后的命令追加到AOF重写缓冲区
当子进程完成AOF重写工作后,向主进程发送信号,主进程收到信号后,主要做两件事
- 将AOF重写缓冲区中的所有内容追加到新的AOF文件中,两个AOF文件状态保持一致
- 新文件替换旧文件
RDB机制
RDB(Redis Database)持久化是把当前内存数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发
- 手动触发
- 手动触发对应save命令,会阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用
- 自动触发
- 自动触发对应bgsave命令,Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短
在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架构来看,他并不是单线程的
- Redis 在 2.6 版本,会启动 2 个后台线程,分别处理关闭文件、AOF 刷盘这两个任务;
- Redis 在 4.0 版本之后,新增了一个新的后台线程,用来异步释放 Redis 内存,也就是 lazyfree 线程
关闭文件、AOF 刷盘、释放内存这三个任务都有各自的任务队列:
- BIO_CLOSE_FILE,关闭文件任务队列:当队列有任务后,后台线程会调用 close(fd) ,将文件关闭;
- BIO_AOF_FSYNC,AOF刷盘任务队列:当 AOF 日志配置成 everysec 选项后,主线程会把 AOF 写日志操作封装成一个任务,也放到队列中。当发现队列有任务后,后台线程会调用 fsync(fd),将 AOF 文件刷盘,
- BIO_LAZY_FREE,lazy free 任务队列:当队列有任务后,后台线程会 free(obj) 释放对象 / free(dict) 删除数据库所有对象 / free(skiplist) 释放跳表对象;
Redis 缓存雪崩、击穿、穿透,有什么不同?怎么解决
- 「缓存雪崩」缓存雪崩,大量热点同时过期
- 设置永不过期或者设置 随机过期时间/均匀设置过期时间
- 「缓存击穿」热点数据过期了,大量访问这个热点数据,就造成了击穿
- 互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
- 不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;
- 「缓存穿透」当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。
- 缓存空值或者默认值
- 使用布隆过滤器
Redis 场景题
- 对热点数据的缓存;因为 Redis 支持多种数据类型,数据存储在内存中,访问速度块,所以 Redis 很适合用来存储热点数据;
- 限时类业务的实现;可以使用 expire 命令设置 key 的生存时间,到时间后自动删除 key。例如使用在验证码验证、优惠活动等业务场景;
- 计数器的实现;因为 incrby 命令可以实现原子性的递增,所以可以运用于高并发的秒杀活动、分布式序列号的生成。例如限制一个手机号发多少条短信、一个接口一分钟限制多少请求、一个接口一天限制调用多少次等业务场景。
- 排行榜的实现;借助 Sorted Set 进行热点数据的排序。例如:下单量最多的用户排行榜,最热门的帖子(回复最多)等业务场景;
- 分布式锁实现;可以利用 Redis 的 setnx 命令进行。
- 队列机制实现;Redis 提供了
list push
和list pop
这样的命令,所以能够很方便的执行队列操作。
Redis集群是AP还是CP?怎么切换?
集群是ap,因为是通过异步去进行同步的,尽管可以通过wait进行阻塞等待,但也无法实现cp
spring面试题
什么是Spring
Spring是一个容器框架,核心是IOC(控制反转)和AOP(依赖注入),实现了简化开发和解耦。有了AOP的支持,可以做到面向切面编程
Spring用到了什么设计模式
- 工厂设计模式:BeanFactory就是简单工厂
- 单例模式:Bean默认就是单例
- 代理模式:AOP使用了JDK的动态代理和CGLIB
- 模板方法:用来解决代码重复问题,RestTemplate
- 观察者模式:当一个对象的状态发生变化,会通知所有观察者 ApplicationListener
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支持几种作用域
六种作用域
- singleton → (默认)Spring支持几种作用域
- 官方说明:(Default) Scopes a single bean definition to a single object instance for each Spring IoC container。
- 描述:该作用域下的 Bean 在 IoC 容器中只存在一个实例:获取 Bean(即通过 applicationContext.getBean等方法获取)及装配 Bean(即通过 @Autowired 注入)都是同一个对象。
- 场景:通常无状态的 Bean 使用该作用域。无状态表示 Bean 对象的属性状态不需要更新。
- 备注:Spring 默认选择该作用域。
- prototype → 原型作用域(多例作用域)
- 官方说明:Scopes a single bean definition to any number of object instances。
- 描述:每次对该作用域下的 Bean 的请求都会创建新的实例:获取 Bean(即通过 applicationContext.getBean 等方法获取)及装配 Bean(即通过 @Autowired 注入)都是新的对象实例。
- 场景:通常有状态的 Bean 使用该作用域。
- request → 请求作用域
- 官方说明:Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext。
- 描述:每次 Http 请求会创建新的 Bean 实例,类似于 prototype。
- 场景:一次 Http 的请求和响应的共享 Bean。
- 备注:限定 Spring MVC 框架中使用。
- session → 会话作用域
- 官方说明:Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext。
- 描述:在一个 Http Session 中,定义一个 Bean 实例。
- 场景:用户会话的共享 Bean, 比如:记录一个用户的登陆信息。
- 备注:限定 Spring MVC 框架中使用。
- application → 全局作用域
- 官方说明:Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext。
- 描述:在一个 Http Servlet Context 中,定义一个 Bean 实例。
- 场景:Web 应用的上下文信息,比如:记录一个应用的共享信息。
- 备注:限定 Spring MVC 框架中使用。
注意: 后 3 种作用域,只适用于 Spring MVC 框架。
Spring事务的隔离级别
Spring有五种事务隔离级别,分别是:
- DEFAULT:spring默认的事务隔离级别,以连接数据库的事务隔离级别为准
- READ_UNCOMMITTED:
读未提交
,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读; - READ_COMMITTED:
读已提交
,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读; - REPEATABLE_READ:
可重复读
,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的 事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读 (Phantom Read); - SERIALIZABLE:
串行化
,最高的事务隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。
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注解的失效场景
- 访问权限问题,必须为public,private会导致Transaction事务失效
- 方法用final修饰,代理类就无法重写该方法,static也一样
- 方法内部调用,在事务方法中,调用内部的另外一个方法会导致事务失效,可以考虑新增一个service去调用,或者在service类中自己注入自己,也可以通过AopContent.currentProxy()
- 未被spring管理
- 多线程调用
- 自己手动try catch
SpringMvc面试题
Springmvc的工作流程
- (1)用户发送请求至前端控制器DispatcherServlet;
- (2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
- (3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦 截器(如果有则生成)一并返回给DispatcherServlet;
- (4)DispatcherServlet 调用 HandlerAdapter处理器适配器;
- (5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
- (6)Handler执行完成返回ModelAndView;
- (7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
- (8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
- (9)ViewResolver解析后返回具体View;
- (10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
- (11)DispatcherServlet响应用户。
Springmvc的注解
- @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
- @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
- @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
- @Conntroller:控制器的注解,表示是表现层,不能用用别的注解代替
Mybatis面试题
Mybatis的一二级缓存机制
- 一级缓存:也叫 本地缓存,默认情况下开启的缓存(
SqlSession
级别的缓存); - 二级缓存:基于
namespace
级别的缓存,需要我们手动进行开启和配置;
一级缓存失效场景:开启多个sqlsession或者查询结果为空
Mybatis #和$的区别
#是预编译处理,$是字符串替换
- mybatis在处理#时,会将sql中的#替换为?号,调用PreparedStatement的set方法来赋值。
- mybatis在处理时,就是把时,就是把替换成变量的值。
- 使用#可以有效的防止SQL注入,提高系统安全性。原因在于:预编译机制。预编译完成之后,SQL的结构已经固定,即便用户输入非法参数,也不会对SQL的结构产生影响,从而避免了潜在的安全风险。
- 预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。
SpringBoot面试题
为什么要用SpringBoot
快速开发,快速整合,配置简化、内嵌服务容器
Spring Boot 主要有如下优点:
容易上手,提升开发效率,为 Spring 开发提供一个更快、更简单的开发框架。 开箱即用,远离繁琐的配置。 提供了一系列大型项目通用的非业务性功能,例如:内嵌服务器、安全管理、运行数据监控、运行状况检查和外部化配置等。 SpringBoot总结就是使编码变简单、配置变简单、部署变简单、监控变简单等等
Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
- 启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
- @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项, 例如:
java 如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
- @ComponentScan:Spring组件扫描。
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 后可能还是有机会重新竞争到锁继续执行的。
线程实现方式
- 继承Thread
- 实现Runnable
- 使用Callable和FutureTask创建线程
- 使用线程池创建线程