Redis数据淘汰策略

  1. Redis提供了8种不同的数据淘汰策略,默认是noeviction不删除任务数据,内存不足直接报错

  2. LRU:最少最近使用,用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高

  3. LFU:最少频率使用,会统计每个key的访问频率,值越小淘汰优先级就越高

Redis分布式锁,是如何实现的呢?

Redis实现分布式锁主要利用Redis的setnx,setnx是SET If not exists(如果不存在则SET)

or 通过Redisson实现分布式锁,底层是setnx和lura脚本(保证原子性)

获取锁
# 添加锁,NX是互斥,EX是设置超时时间
SET lock value NX EX 10
释放锁
# 释放锁,删除即可
DEL key
Redisson实现分布式锁如何合理的控制锁的有效t时长?

在Redisson分布式锁中,提供了WatchDog(看门狗),一个锁获取锁成功以后,WatchDog会给持有锁的线程续期(默认是每隔10秒续期一次)

Redisson这个锁可以重入吗?

可以重入,多个锁重入需要判断是否是当前线程,在Redis进行存储的时候使用Hash结构,来存储线程信息和重入次数

Redisson锁能解决主从数据一致的问题吗?

不能解决,但是可以使用Redisson提供红锁来解决,但是这样的话,性能就太低了,如果业主中非要保证业务的强一致性,可以采用Zookeerper实现分布式锁。

介绍一下Redis的住从同步

单点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离,一般都是一主多从,主节点负责写数据,从节点负责读数据

能说一下,主从同步数据的流程吗?

全量同步:

  1. 从主节点请求主节点同步数据(replication id、offset)

  2. 主节点判断是否是第一次请求,是第一次就与从节点同步版本信息(replication id 和 offset)

  3. 主节点执行bgsave,生成rdp文件后,发送给从节点去执行

  4. 在rdb生成执行期间,主节点会以命令的方式记录到缓冲区(一个日志文件)

  5. 把生成之后的命令日志文件发送给从节点进行同步

增量同步:

  1. 从节点请求主节点同步数据,主节点判断是不是第一次请求,不是第一次就获取从节点的offset值

  2. 主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步

怎么保证Redis高并发高可用?

哨兵模式:实现主从集群的自动故障修复(监控、自动故障修复、通知)

你们使用Redis单点还是集群?哪种集群?

主从(1主1从)+哨兵模式就可以了,单节点不超过10G内存,如果Redis内存不足则可以给不同的服务分配独立的Redis主从节点

Redis集群脑裂,该怎么解决呢?

集群脑裂是由于主节点和从节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到主节点,所以通过选举的方式提升了一个从节点为主,这样就存在了两个master, 就像大脑分裂了一样,这样会导致客户端还在老的主节点那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将老的主节点降为从节点,这时再从新的master同步数据,就会导致数据丢失。

解决:

我们可以修改Redis配置,可以设置最少的从节点数量以及缩短主从数据同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失

Redis是单线程的,单是为什么还那么快?

  • Redis是纯内存操作,执行速度非常快

  • 采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑线程安全的问题

  • 使用I/O多路复用模型,非阻塞IO

能解释一下I/O多路复用模型吗?

Redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度,I/O多路复用模型主要就是实现了高效的网络请求

  • 用户空间和内核空间

  • 常见的IO模型

    • 阻塞IO(Blocking IO)

    • 非阻塞IO (Nonblocking IO)

    • IO多路复用(IO Multiplexing)

  • Redis网络模型

1、I/O多路复用

是指利用单个线程同时监听多个Socket,并在某个Socket可读,可写时得到通知,从而避免无效的等待,充分利用CPU资源,目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能

2、Redis网络模型

就是使用I/O多路复用的结合事件的处理器来应对多个Socket请求

  • 连接应答处理器

  • 命令回复处理器,在Redis6.0之后,为了提升更好的性能,使用了多线程来处理回复事件

  • 命令请求处理器,在Redis6.0之后,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程

如何定位慢查询?

  1. 我们系统中当时采用了运维工具(Skywalking),可以监测出哪个接口,最终是因为是sql的问题

  2. 在mysql中开启了慢日志查询,我们设置的值就是2秒,一旦sql执行超过2秒就会记录到日志中(调试阶段)在我们调试阶段才会开启慢日志查询的功能

那这条SQL语句执行很慢,如何分析呢?

  • 聚合查询(新增临时表解决)

  • 多表查询(试着优化SQL结构)

  • 表数据量过大查询(添加索引、分析SQL语句)

  • 深度分页查询(覆盖索引.....TODO)

理解Explain的列参数

  • possible_keys 当前sql可能会使用到的索引

  • key 当前sql实际命中的索引

  • key_len 索引占用的大小

  • Extra 额外的优化建议

    • Using where; Using Index 查找使用了索引,需要的数据在索引列中能找到,不需要回表查询数据

    • Using index condition 查找使用了索引,但是需要回表查询数据

  • type 这条sql的连接类型,性能由好到差为Null、System、const、eq_ref、ref、range、index、all

    • system 查询系统中的表

    • const 根据主键查询

    • eq_ref 主键索引查询或者唯一索引查询

    • ref 索引查询

    • range 范围查询

    • index 索引树扫描

    • all 全盘扫描

这条SQL语句执行的很慢,如何分析优化呢?

可以采用MySQL自带的分析工具 EXPLAIN

  • 通过key和key_len检查是否命中了索引(索引本身存在是否有失效的情况)

  • 通过type字段查看sql是否进一步的优化空间,是否存在全索引扫描或全盘扫描

  • 通过extra建议判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改返回的字段来修复

了解过索引吗?(什么是索引)

  • 索引(index)是帮助Mysql高效的获取数据的数据结构(有序)

  • 提高数据检索的效率,降低数据库的IO成本(不走全表扫描)

  • 通过索引对数据进行排序,降低数据库的排序成本,降低了CPU的消耗

索引底层的数据机构了解过吗?

Mysql的innoDB引擎采用的B+树的数据结构来存储索引

  • 阶数更多,路径更短

  • 磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据

  • B+树便于扫库和区间查询,叶子节点是一个双向链表

什么是聚簇索引什么是非聚簇索引?

  • 聚簇索引(聚集索引):数据与索引放到一块,B+树的叶子节点保存了整行数据,有且只有一个

  • 非聚簇索引(二级索引):数据与索引分开存储,B+树的叶子节点保存对应主键,可以有多个(单独给字段创建的索引,大部分都是二级索引)

知道什么是回表查询吗?

通过二级索引找到对应的主键值,到聚集索引中查找整行数据,这个过程就是回表

知道什么是覆盖索引吗?

覆盖索引是指查询使用了索引,返回的列,必须在索引中全部能够找到

  • 使用ID查询,直接走到聚集索引查询,一次索引扫描,直接返回数据,性能高。

  • 如果返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select *

Mysql超大分页怎么处理?

解决方案:覆盖索引+子查询

select * from tb_sku t,
(select id from tb_sku order by id limit 9000000,10) a 
where t.id = a.id;

索引创建原则有哪些?

  1. 针对数据量较大,且查询比较频繁的表建立索引。(单表超过10万数据(增加用户体验))

  2. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。

  3. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度较高,使用索引效率高。

  4. 如果是字符串类型的字段,字段的长度比较长,可以针对字段的特点,建立前缀索引。

  5. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。

  6. 要控制索引的数量,索引不是多多益善,索引越多,维护索引结构代价就越大,会影响增删改的效率。

什么情况下索引会失效?

  • 违反最左前缀法则(在使用复合索引时,跳过某一列就会导致索引失效)

  • 范围查询右边的列,不能使用索引

  • 不要在索引上进行运算操作,不然会失效

  • 字符串不加单引号,会导致索引失效

  • 以%开头like模糊查询,都会导致索引失效

谈谈你对Sql的优化经验

  • 表的设计优化,数据类型的选择

  • 索引优化,索引创建原则

  • sql语句优化,避免索引失效,避免使用Select *

  • 主从复制,读写分离,不让数据的写入,影响读的操作

  • 分库分表

ACID是什么?可以详细说一下吗?(事务的特性是什么?可以说说吗?)

  • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败

  • 一致性(Consistency):事务完成时,必须使所有的数据保持一致的状态。

  • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务不在受外部并发操作影响的独立环境下运行。

  • 持久性(Durability):事务的一旦提交或回滚,它对数据库中的数据的改变就是永久的。

并发事务带来哪些问题?怎么解决这些问题呢?Mysql的默认隔离级别是?

并发事务的问题:

  • 脏读:一个事务读到另外一个事务还没提交的数据。

  • 不可重复读:一个事务先读取同一条记录,但两次数据读取不同。

  • 幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了“幻觉”。

隔离级别:

  • READ UNCOMMITTED 未提交读

  • READ COMMITTED 读已提交

  • REPEATABLE READ 可重复读

  • SERIALIZABLE 串行化

undo log和redo log的区别

  • redo log:记录的是数据页的物理变化,服务宕机可用来同步数据

  • undo log:记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据

  • redo log保证了事务的持久性,undo log保证了事务的原子性和一致性

特性

Redo Log(重做日志)

Undo Log(回滚日志)

作用

记录"做了什么"

记录"如何撤销"

用途

崩溃恢复

事务回滚

写入时机

事务提交时

数据修改前

记录内容

数据页的物理修改

修改前的旧值

保证特性

持久性(Durability)

原子性(Atomicity)+ 一致性(Consistency)

记录格式

在数据页X偏移量Y处改为Z

表X记录Y字段Z原值为A

使用场景

宕机重启恢复已提交事务

事务回滚、MVCC

方向

向前恢复

向后回滚

事务的隔离性是怎么实现的?(解释一下MVCC)

MVCC是Mysql中的多版本控制,指维护一个数据的多个版本,使得读写操作没有冲突

  • 隐藏字段

    • trx_id(事务ID),记录每一次的操作的事务ID,是自增的

    • roll_pointer(回滚指针),指向上一个版本的版本记录地址

    • row_id(隐藏主键)如果表结构没有指定主键,会生成一个隐藏的主键

  • undo log

    • 回滚日志,存储老版本的日志

    • 版本链:多个事务并行操作某一记录,记录不同事务修改数据的版本,通过roll_pointer指针形成一个链表

  • readView解决的是一个事务查询选择版本的问题

    • 根据readView的匹配规则和当前的一些事务ID判断访问哪个版本的数据

    • 不同的隔离级别快照读是不一样的,最终的访问结果不一样

      • RC:每一次执行快照读生成ReadView

      • RR:仅在事务中第一次执行快照读时生成ReadView,后续复用

主从同步的原理

Mysql主从复制的核心是二进制日志binglog(DDL (数据定义语言)语句和DML(数据操纵语言)语句)

  • 主库在事务提交时,会把数据变更记录在二进制日志文件binlog中。

  • 从库读取主库的二进制文件Binlog,写入到从库的中继日志Relay Log。

  • 从库重做中继日志中的事件,将改变反映它自己的数据。

你们项目中用过分库分表吗?

  • 水平分库,将一个库的数据拆分到多个库中,解决海量数据存储和高并发的问题

  • 水平分表,解决单表存储和性能的问题

  • 垂直分库,根据业务进行拆分,高并发下提高磁盘IO和网络连接数

  • 垂直分表,冷热数据分离,多表互不影响

Spring框架中得单例bean是线程安全的吗?

不是线程安全的

Spring框架中有一个@Scope注解,默认的值是Sinleton,单例的。

因为一般在Spring bean中都是注入的无状态对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例或者加锁来解决。

什么是AOP?

面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合

你们项目中有没有用到AOP?

记录操作日志、缓存、spring实现的事务

核心是:使用aop的环绕通知+切点表达式(找到要记录日志的方法),通过环绕通知的参数获取请求方法的参数(类、方法、注解、请求方式等),获取到这些参数以后,保存到数据库

Spring事务是如何实现的?

基本是通过AOP的功能,对方法前后进行拦截,在方法执行之前开启事务,在执行目标方法之后根据执行情况提交或者回滚事务。

Spring中事务失效的场景有哪些?

  1. 异常捕获处理,自己处理了异常,没有抛出,解决:手动抛出

  2. 抛出检查异常,配置RollbackFor属性为Exception

  3. 非public方法导致的事务失效,改为public

Spring的Beam的生命周期

  1. 通过BeanDefinition获取bean的定义信息

  1. 调用构造函数实例化bean

  2. bean的依赖注入

  3. 处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware)

  4. Bean的后置处理器BeanPostProcessor-前置

  5. 初始化方法(initializingBean、init-method)

  6. Bean的后置处理器BeanPostProcessor-后置

  7. 销毁Bean

Spring中的循环依赖/引用

  • 循环依赖:循环依赖其实就是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环,比如A依赖于B,B依赖于A

  • 循环依赖在Spring中是允许存在的,Spring框架依据三级缓存解决了大部分循环依赖的问题

    • 一级缓存(singletonObjects):单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象

    • 二级缓存(earlySingletonObjects):缓存早期的bean对象(生命周期还没走完)

    • 三级缓存(singletonFactories):缓存的是ObjectFactory,表示对象工厂,用来创建某个对象

构造方法出现了循环依赖怎么解决呢?

A依赖于B,B依赖于A,注入的方式是构造函数

原因:由于Bean的生命周期中构造函数是第一个执行的,Spring框架并不能解决构造函数的依赖注入

解决方案:实用@Lazy进行懒加载,什么时候需要对象再进行bean对象的创建

Spring MVC的执行流程知道吗?

JSP视图版本:

  1. 用户发送请求到前端控制器DispatcherServelet

  2. DispatcherServelet收到请求后调用HandlerMapping(处理器映射器)

  3. HandlerMapping找到具体处理器,生成处理对象及处理拦截器(如果有),再一次返回给DispacherServelet

  4. DispatcherServelet调用HandlerAdaper(处理器适配器)

  5. HandlerAdapter经过适配调用具体的处理器(Handler/Controller)

  6. Controller执行完成返回ModelAndView对象

  7. HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServelet

  8. DispatcherServelet将ModelAndView传给ViewReslover(视图解析器)

  9. ViewReslover解析后返回具体View(视图)

  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)

  11. DispatcherServlet响应用户

前后端开发:

  1. 用户发送请求到前端控制器DispatcherServelet

  2. DispatcherServelet收到请求后调用HandlerMapping(处理器映射器)

  3. HandlerMapping找到具体处理器,生成处理对象及处理拦截器(如果有),再一次返回给DispacherServelet

  4. DispatcherServelet调用HandlerAdaper(处理器适配器)

  5. HandlerAdapter经过适配调用具体的处理器(Handler/Controller)

  6. 方法上添加了@ReponseBody

  7. 通过HttpMessageConverter来返回结果转换为JSON响应

Spring boot自动配置原理

1、在Spring boot项目中引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:

  1. @SpringBootConfiguration

  2. @EnableAutoConfiguration

  3. @ComponentScan

2、其中@EnableAutoConfiguration是实现自动化配置的核心注解,该注解通过@Import注解导入对应的配置选择器。

内部就是读取了该项目和该项目引用的Jar包的classpath路径下的META-INF/spring.factories文件中的所配置的类的全类名,在这些配置类中定义的Bean会根据条件注解所指定的条件来判定是否需要将其引入到Spring容器中。

3、条件判断会有像@ConditionalOnClass这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有Bean放入Spring容器中使用。

Spring框架常见注解(Spring、Spring Boot、Spring MVC)

Spring常见注解:
  • @Component、@Controller、@Service、@Repository 使用在类上用于实例化Bean

  • @Autowired 使用在字段上用于根据类型依赖注入

  • @Qualifier 结合@Autowired一起使用用于根据名称依赖注入

  • @Scope 标注Bean的作用范围

  • @Cofiguration 指定当前类是一个Spring配置类,当创建容器时会从该类上加载注解

  • @ComponentScan 用于指定Spring容器时要扫描的包

  • @Bean 使用该方法上,标注将该方法的返回值存储到Spring容器中

  • @Import 使用@Import导入类会被Spring加载到IOC容器中

  • @Aspect、@Before、@After、@Around、@Pointcut 用于切面编程(AOP)

Spring MVC 常见注解:
  • @RequestMapping 用于映射请求路径,可以定义在类上和方法上,用于类上,则表示类中的所有的方法都是以该地址作为父路径

  • @RequestBody 注解实现接收http请求的json数据,将JSON转为Java对象

  • @RequestParam 指定请求参数的名称

  • @PathViriable 从请求路径下中获取请求参数/usr/{id},传递给方法的形式参数

  • @ResponseBody 注解实现将Controller方法返回对象转化为JSON对象响应给客户端

  • @RequestHeader 获取指定的请求头数据

  • @RestController @Controller + @ResponseBody

Spring Boot 常见注解:
  • @SpringBootConfiguration 组合了 - @Configuration;注解,实现配置文件功能

  • @EnableAutoConfiguration 打开自动配置的功能

  • @ComponentScan Spring组件扫描

Mybatis 执行流程

  1. 加载Mybatis配置文件:mybatis-config.xml加载运行环境和映射文件

  2. 构造会话工厂SqlSessionFactory

  3. 会话工厂创建SqlSession对象(包含执行SQL语句的所有方法)

  4. 操作数据库的接口,Executor执行器,同时负责查询缓存的维护

  5. Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息

  6. 输入参数映射(把Java类型转换为数据库支持的类型)

  7. 输出结果映射 (从数据库类型转换为Java的类型)

Mybatis是否支持延迟加载?

  • 延迟加载的意思是:就是需要用到数据时才进行加载,不需要用数据时就不加载数据

  • Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载

  • 在Mybatis配置中,可以配置是否启用延迟加载LazyLoadingEnabled=true/false,默认是关闭的

延迟加载的底层原理知道吗?

  • 使用CGLIB创建目标对象的代理对象

  • 当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,执行sql查询

  • 获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了

Mybatis的一级、二级缓存用过吗?

  • 一级缓存:基于PerpetualCache 的 HashMap 本地缓存,其存储作用域为Session,当Session进行flush或close之后,

  • 二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于Sql session,默认也是采用PerpetualCache,HashMap存储。需要单独开启,一个是核心配置,一个是Mapper映射文件。

Mybatis的二级缓存什么时候会清理缓存中的数据

当某一个作用域(一级缓存 Session/二级缓存 namespace)的进行了新增、修改、删除操作后,默认该作用域下所有select中的缓存被clear。

Spring Cloud 5大组件有哪些?

功能

组件

说明

注册中心 / 配置中心

Nacos

阿里巴巴开源,支持服务注册、发现和动态配置管理

负载均衡

Ribbon(已被弃用)/ Spring Cloud LoadBalancer

Ribbon 曾广泛使用,现在官方推荐使用 Spring Cloud LoadBalancer

服务调用

Feign

声明式服务调用,集成 Ribbon/LoadBalancer 实现负载均衡

服务保护

Sentinel

阿里巴巴开源,用于服务限流、熔断、降级

服务网关

Spring Cloud Gateway

基于 WebFlux 的网关,代替旧的 Zuul

说下Nacos和eureka的区别?

  • Nacos和eureka的共同点(注册中心)

    • 都支持服务注册和服务拉取

    • 都支持服务提供者心跳方式做健康检测

  • Nacos与Eureka的区别(注册中心)

    • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式

    • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除

    • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时

    • Nacos集群默认采用AP的方式,当集群中存在非临时实例时,采用CP模式,Eureka采用AP方式

  • Nacos还支持了配置中心,eureka则只有注册中心,也是选择使用nacos的一个重要原因

Ribbon负载均衡策略有哪些?

  • RoundRobinRule:简单轮询服务列表来选择服务器

  • WeightedReponseTime:按照权重重新来选择服务器,响应时间越长,权重越小

  • RadomRule:随机选择一个可用的服务器

  • BestAvailableRule:忽哟那些短路的服务器,并选择并发数较低的服务器

  • RetryRule:重试机制的选择逻辑

  • AvailabilityFilteringRule:可用性敏感策略,先过滤非健康的,再选择连接数较小的实例

  • ZoneAvoidanceRule(Ribbon默认策略):以区域可用的服务器为基础进行服务器的选择,使用Zone对服务器进行分类,这个Zone可以理解为一个机房,一个机架等,而后再对Zone内的多个服务做轮询

如何自定义负载均衡策略?如何实现?

提供了两种方式:

  1. 创建类实现IRule接口,可以指定负载均衡策略(全局)

  2. 在客户端的配置文件中,可以配置某一个服务调用的负载均衡策略(局部)

什么是服务雪崩?怎么解决这个问题?

  • 服务雪崩:一个服务失败,导致整条链路的服务都失败的情形

  • 服务降级:服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃,一般实际开发中与Feign接口整合,编写降级逻辑。

  • 服务熔断:默认关闭,需要手动打开,如果检测到10秒内请求的失败率超过百分之50,就触发熔断降级,之后每隔5秒重新尝试请求微服务,如果服务不能响应,继续走熔断机制,如果服务可达,则关闭熔断机制,恢复正常请求