一、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
2
3
4
5
save 900 1           #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发bgsave命令创建快照。

save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发bgsave命令创建快照。

save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发bgsave命令创建快照。

Redis 提供了两个命令来生成 RDB 快照文件

  • save : 同步保存操作,会阻塞 Redis 主线程;
  • bgsave : fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项

什么是 AOF 持久化?

AOF 全称为 Append Only File(追加文件)。Redis 出来的每一个写命令都会记录在 AOF 文件,可以 看做是命令日志文件

6、过期的数据的删除策略了解么?

如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?

常用的过期数据的删除策略就两个(重要!自己造缓存轮子的时候需要格外考虑的东西):

  1. 惰性删除:只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
  2. 定期删除:每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 定期删除+惰性/懒汉式删除

但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memozry 了。

怎么解决这个问题呢?答案就是:Redis 内存淘汰机制。

7、Redis 内存淘汰机制了解么?

相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?

Redis 提供 6 种数据淘汰策略:

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
  4. allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
  6. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!

4.0 版本后增加以下两种:

  1. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰。
  2. 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 根据请求信息调用 HandlerMappingHandlerMapping 根据 URL 去匹配查找能处理的 Handler(也就是我们平常说的 Controller 控制器) ,并会将请求涉及到的拦截器和 Handler 一起封装。
  • DispatcherServlet 调用 HandlerAdapter适配器执行 Handler
  • Handler 完成对用户请求的处理后,会返回一个 ModelAndView 对象给DispatcherServletModelAndView 顾名思义,包含了数据模型以及相应的视图的信息。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:漏洞算法
  • 网关:令牌桶算法
  • 自定义拦截器