java面试题
一、redis
1、缓存穿透
查询一个不存在的数据,MySQL 查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库
解决方案:
- 缓存空数据——当 redis 中和数据库中无该数据,则对该数据进行 null 值缓存。弊端:数据量过多会导致内存损耗严重
- 布隆过滤器——在请求之前添加布隆过滤器拦截,如布隆过滤器没有,则直接返回。
2、缓存击穿
给某一个 key 设置了过期时间,当 key 过期的时候,恰好这时间点对这个 key 有大量的并发请过来,这些并发的请求可能会瞬间把 DB 压垮
解决方案:
- 互斥锁,强一致,性能差
- 逻辑过期,高可用,性能优,不能保证数据绝对一致
3、缓存雪崩
缓存雪崩是指在同一时段大量的缓存 key 同时失效或者 redis 服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:
- 给不同的 key 的 TTL(过期时间)添加随机值
- 利用 redis 集群提高服务的可用性
- 给缓存业务添加降级限流策略
- 给业务添加多级缓存
打油诗:来自黑马
《缓存三兄弟》
穿透无中生有 key,布隆过滤 null 隔离缓存击穿过期 key,锁与非期解难题
雪崩大量过期 key,过期时间要随机
面试必考三兄弟,可用限流来保底
4、如何保证双写一致性
双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保存一致
情况一:允许延迟一致的业务,采用异步通知
- 使用 MQ 中间件,更新数据之后,通知缓存删除
- 利用 canal 中间件,不需要修改业务代码,伪装为 mysql 的一个从节点,canal 通过读取 binlog 数据更新缓存
情况二:强一致性,采用 redisson 提供的读写锁
- 共享锁:读写 readLock,加锁之后,其他线程可以共享读操作
- 排他锁:独占锁 writeLock,枷锁之后,阻塞其他线程读写操作
5、持久化
- 快照(snapshotting,RDB)
- 只追加文件(append-only file, AOF)
- RDB 和 AOF 的混合持久化(Redis 4.0 新增)
官方文档地址:https://redis.io/topics/persistence
什么是 RDB 持久化?
Redis 可以通过创建快照来获得存储在内存里面的数据在 某个时间点 上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
快照持久化是 Redis 默认采用的持久化方式,在 redis.conf
配置文件中默认有此下配置:
1 | save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发bgsave命令创建快照。 |
Redis 提供了两个命令来生成 RDB 快照文件
save
: 同步保存操作,会阻塞 Redis 主线程;bgsave
: fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项
什么是 AOF 持久化?
AOF 全称为 Append Only File(追加文件)。Redis 出来的每一个写命令都会记录在 AOF 文件,可以 看做是命令日志文件
6、过期的数据的删除策略了解么?
如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?
常用的过期数据的删除策略就两个(重要!自己造缓存轮子的时候需要格外考虑的东西):
- 惰性删除:只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
- 定期删除:每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 定期删除+惰性/懒汉式删除 。
但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memozry 了。
怎么解决这个问题呢?答案就是:Redis 内存淘汰机制。
7、Redis 内存淘汰机制了解么?
相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?
Redis 提供 6 种数据淘汰策略:
- volatile-lru(least recently used):从已设置过期时间的数据集(
server.db[i].expires
)中挑选最近最少使用的数据淘汰。 - volatile-ttl:从已设置过期时间的数据集(
server.db[i].expires
)中挑选将要过期的数据淘汰。 - volatile-random:从已设置过期时间的数据集(
server.db[i].expires
)中任意选择数据淘汰。 - allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
- allkeys-random:从数据集(
server.db[i].dict
)中任意选择数据淘汰。 - no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
4.0 版本后增加以下两种:
- volatile-lfu(least frequently used):从已设置过期时间的数据集(
server.db[i].expires
)中挑选最不经常使用的数据淘汰。 - allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
8、Redis 集群方案
主从复制
哨兵模式
分片集群
主从复制
单节点 Redis 的并发能力是有上限的,哟啊进一步提高 Redis 的并发能力,就需要搭建主从集群,实现读写分离
—-有点吃不消,后期回来继续学
9、redis 是单线程,但是为什么还那么快
- redis 是纯内存操作,执行速度非常快
- Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用(Redis 线程模式后面会详细介绍到);
- Redis 内置了多种优化过后的数据类型/结构实现,性能非常高。
二、spring
1、单例 bean 是线程安全的吗
不是线程安全的
因为一般在 spring 的 bean 中都是注入无状态对象,没有线程安全问题。如果在 bean 中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例或者加锁来解决
2、aop 相关
aop 称为面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑(例如事务处理、日志管理、权限控制等),抽取并封装一个可重用的模块。这个模块被名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性
使用场景
- 记录操作日志
- 缓存处理
- Spring 中内置的事务处理
3、事务失效场景
- 异常捕获处理,自己处理了异常,没有抛出。解决:手动抛出
- 抛出检查异常(方法上抛出异常),配置 rollbackFor 属性为 Exception
- 非 public 方法导致的事务失效,改为 public
4、spring 中 bean 的生命周期
通过 BeanDefinition 获取 bean 的定义信息
调用构造函数实例化 bean
bean 的依赖注入
处理 Aware 接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware)
Bean 的后置处理器 BeanPostProcessor-前置
初始化方法(InitializingBean、init-method)
Bean 的后置处理器 BeanPostProcessor-后置
销毁 bean
5、Spring 中 bean 的循环依赖
- 循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的 bean 互相持有对方。最终形成闭环。简单来说就是,A 依赖于 B,B 依赖于 A
- 循环依赖在 spring 中是允许存在,spring 框架依据三级缓存已经解决了大部分的循环依赖
- 一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的 bean 对象
- 二级缓存:缓存早期的 bean 对象(生命周期还没走完)
- 三级缓存:缓存的是 ObjectFactory,表示对象工厂,用来创建某个对象的
ps:构造方法出现了循环依赖怎么解决?
使用@Lazy 进行懒加载,什么时候需要对象再进行 bean 对象的创建
三、Spring MVC
1、执行流程
Spring MVC 的核心组件有哪些?
记住了下面这些组件,也就记住了 Spring MVC 的工作原理。
DispatcherServlet
:核心的中央处理器,负责接收请求、分发,并给予客户端响应。HandlerMapping
:处理器映射器,根据 URL 去匹配查找能处理的Handler
,并会将请求涉及到的拦截器和Handler
一起封装。HandlerAdapter
:处理器适配器,根据HandlerMapping
找到的Handler
,适配执行对应的Handler
;Handler
:请求处理器,处理实际请求的处理器。ViewResolver
:视图解析器,根据Handler
返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给DispatcherServlet
响应客户端
Spring MVC 原理如下图所示:
- 客户端(浏览器)发送请求,
DispatcherServlet
拦截请求。 DispatcherServlet
根据请求信息调用HandlerMapping
。HandlerMapping
根据 URL 去匹配查找能处理的Handler
(也就是我们平常说的Controller
控制器) ,并会将请求涉及到的拦截器和Handler
一起封装。DispatcherServlet
调用HandlerAdapter
适配器执行Handler
。Handler
完成对用户请求的处理后,会返回一个ModelAndView
对象给DispatcherServlet
,ModelAndView
顾名思义,包含了数据模型以及相应的视图的信息。Model
是返回的数据对象,View
是个逻辑上的View
。ViewResolver
会根据逻辑View
查找实际的View
。DispaterServlet
把返回的Model
传给View
(视图渲染)。- 把
View
返回给请求者(浏览器)
四、SpringBoot
1、SpringBoot 自动装配原理
- 在 SpringBoot 项目中的引导类上有一个注解
@SpringBootApplication
,这个注解是对三个注解进行累封装,分别是@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
- 其中
@EnableAutoConfiguration
是实现自动化配置的核心注解。该注解通过@Import
注解导入对应的配置选择器。内部就是读取了该项目和该项目引用的 jar 包的 classpath 路径下META-INF/spring.factories
文件中的所配置的类的全类名。在这些配置类中所定义的 Bean 会根据条件注解所指定的条件来决定是否需要将其导入到 Spring 容器中 - 条件判断会有像
@ConditionnalOnClass
这样的注解,判断是否有对应的 class 文件,如果有则加载该类,把这个配置类的所有的 Bean 放入 spring 容器中使用
五、spring 框架常见注解
1、Spring
2、SpringMVC
3、SpringBoot
六、MyBatis
1、MyBatis 执行流程
- 读取 MyBatis 配置文件:mybatis-config.xml 加载运行环境和映射文件
- 构造会话工厂 SqlSessionFactory
- 会话工厂创建 SqlSession 对象(包括了执行 SQL 语句的所有方法)
- 操作数据库的接口,Executor 执行器,同时负责查询缓存的维护
- Executor 接口的执行方法中有一个 MappedStatement 类型的参数,封装了映射信息
- 输入参数映射
- 输出结果映射
2、MyBatis 延迟加载使用及原理
Mybatis 是否支持延迟加载?
MyBatis 支持延迟加载
- 延迟加载的意思是:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。
- MyBatis 支持一对一关联对象和一对多关联集合对象的延迟加载
- 在 MyBatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=ture|false,默认是关闭的
延迟加载的底层原理知道吗?
- 使用 CGLIB 创建目标对象的代理对象
- 当调用目标方法时,进入拦截器 invoke 方法,发现目标方法是 null 值,执行 sql 查询
- 获取数据以后,调用 set 方法设置属性值,再继续查询目标方法,就有值了
3、MyBatis 一级、二级缓存
- 一级缓存:基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session 进行 flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存
- 二级缓存:基于 namespace 和 mapper 的作用域起作用的,不是依赖于 SQL session,默认也是采用 PerpetualCache,HashMap 存储。需要单独开始,一个是核心配置,一个是 mapper 映射文件
MyBatis 的二级缓存什么时候辉清理缓存中的数据?
当某一个作用域(一级缓存 Session/二级缓存 Namespaces)进行了增、删、改操作后,默认该作用域下所有 select 中的缓存将被清除
七、微服务
1、SpringCloud 常见组件有哪些?
- Eureka:注册中心
- Ribbon:负载均衡
- Feign:远程调用
- Hystrix:服务熔断
- Zuul/Geteway:网关
如果是阿里巴巴组件,可以说一下组件
- 注册中心/配置中心:Nacos
- 负载均衡:Ribbon
- 服务调用:Feign
- 服务保护:sentinel
- 服务网关:Geteway
2、注册中心 eureka、Nacos
服务注册和发现是什么意思?Spring Cloud 如何实现服务注册发现?
- 微服务中心必须要使用的组件,考察我们使用微服务的程度
- 注册中心的核心作用是:服务注册和发现
- 常见的注册中心:eureka、nacos、zookeeper
- 我们当项目采用的是 eureka 作为服务中心时,这个也是 springcloud 体系中的一个核心组件
- 服务注册:服务提供者需要把自己的信息注册到 eureka,由 eureka 来保存这些信息,比如服务名称、ip、端口等等
- 服务发现:消费者向 eureka 拉去服务列表信息,如果服务提供者有集群,则消费者会利用负载均衡算法,选中一个发起调用
- 服务监控:服务提供则会每隔 30s 向 eureka 发送心跳,报告健康状态,如果 eureka 服务 90s 没接收到心跳,从 eureka 中剔除
Nacos 与 Eureka 的区别?
- Nacos 与 Eureka 的共同点(注册中心)
- 都支持服务注册和服务拉取
- 都支持服务提供则心跳方式做健康检测
- Nacos 与 Eureka 的区别(注册中心)
- Nacos 支持服务端主动检测提供者状态:临时示例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos 支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos 集权默认采用 AP 方式,当集群中存在非临时实例时,采用 CP 模式;Eureka 采用 AP 方式
- Nacos 还支持了配置中心,eureka 则只有注册中心,也是选择使用 Nacos 的一个重要原因
回忆一下 CAP 模型:CAP 模型
3、负载均衡 Ribbon
你们项目负载均衡如何实现的?
微服务的负载均衡主要使用了一个组件 Ribbon,比如,我们再使用 feign 远程调用的过程中,底层的负载均衡就是使用了 ribbon
Ribbon 负载均衡策略有哪些?
主要记前三个和最后一个,其中最后一个是默认策略
- RoundRobinRule:简单轮询服务列表来选择服务器
- WeightedResponseTimeRele:按照权重来选择服务器,响应时间越长,权重越小
- RandomRule:随机选择一个可用的服务器
- BestAvailableRule:忽略哪些短路的服务器,并选择并发数较低的服务器
- RetryRule:重试机制的选择逻辑
- AvailabilityFilteringRule:可用性敏感策略,先过滤非健康的,再选择连接数较小的实例
- ZoneAvoidanceRule:以区域可用的服务器为基础进行服务器的选择。使用 Zone 对服务器进行分类,这个 Zone 可以理解为一个机房、一个机架等。而后再对 Zone 内的多个服务做轮询
如果想自定义负载均衡策略如何实现?
有两种方式
- 创建类实现 IRule 接口,可以指定负载均衡策略(全局)
- 在客户端的配置文件中,可以配置某一个服务调用的负载均衡策略(局部)
4、服务雪崩、服务降级
服务雪崩
一个服务失败,导致整条链路的服务都失败的情形
服务降级
服务降级是服务自我保护的一种方式,或则保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃,一般在实际开发中与 feign 接口整合,编写降级逻辑
服务熔断
Hystrix 熔断积之,用于监控微服务调用情况,默认是关闭的,如果需要开启需要在引导类上添加注解:@EnableCircuitBreaker 如何检测到 10 秒内请求的失败率超过 50%,就出发熔断机制。之后每隔 5s 重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,回复正常请求
5、微服务的监控
采用 skywalking 进行监控的
1、skywalking 主要可以监控接口、服务、物理实例的一些状态。特别是在压测的时候可以看到众多服务中哪些服务和接口比较慢,我们可以针对性的分析和优化
2、我们还在 skywalking 设置了告警规则,特别实在项目上线以后,如果报错,我们分别设置了可以给相关负责人发短信和发邮件,第一时间知道项目的 bug 情况,第一时间修复
6、微服务限流
为什么要限流?
1、并发的确大(突发流量)
2、防止用户恶意刷接口
限流的实现方式
- Tomcat:可以设置最大连接数
- Nginx:漏洞算法
- 网关:令牌桶算法
- 自定义拦截器