一、Java基础面试题
1.2 面向对象的三大特性?
- 封装:核心思想就是“隐藏细节”,对外提供访问属性的方法。保证了数据的安全和程序的稳定。
- 继承:将子类共性的属性或者方法抽取出来成为父类,子类继承后就不用重复定义父类的属性和方法,只需要扩展自己的个性化。强制继承父类非私有的属性和方法,破坏了封装性原则。
- 多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。
继承,方法重写,父类指向子类对象。无法调用子类特有的功能;
父类类型 变量名=new 子类对象
变量名.方法名()
笔者在看设计模式和spring源码时,对面向对象有了进一步的认识。如果说封装和继承是面向对象的基石,那么多态则是面向对象的具体使用。编译看左边,运行看右边。 在日常书写代码时,尽量使用List list=new ArrayList();这种形式,左边为抽象父类,右边为具体的实现子类。这样的书写方式,不仅代码兼容性更好而且具体的实现细节由具体的对象去执行完成,这才是面向对象的初衷和设计核心!
2、HashMap底层原理
2.1 什么是哈希表?哈希冲突?如何处理哈希冲突?
1)、什么是哈希表?
如何在一个无序的线性表中查找一个数据元素?答:对着线性表遍历!但这样时间复杂度会很高,查找耗费性能。那有没有这样一种数据结构,数据结构中的元素和它所在位置有一个对应关系,这样就可以通过元素找到它的位置,时间复杂度就降低了。这种数据结构就是哈希表!
哈希表又叫散列表,通过映射函数f(key)将一组数据散列存储在数组中的一种数据结构。在哈希表中,每个key和它所在位置都有一个映射关系,可以通过f(key)来轻易找到。比如映射关系f(key)=key/11;
2)、什么是哈希冲突(哈希碰撞)?
比如f(key)=key/11,当值为key为6和72时,余数都是6,72该怎么存呢?这就是哈希冲突!
3)、如何减少哈希冲突?
哈希冲突不可避免,但能减少!这里提供几种最容易理解的几种解决方案:
1)再哈希法:选取若干个不同的哈希函数,当发生哈希冲突时,执行另外的哈希函数,直至不冲突为止。
2)链地址法(最常用!!!):这个就是与红黑树相关了!当我们出现[48,15,26,4,70,82,59,28,6,17]。我们这组数据仍然散列存储到长度为11的数组中,出现多个能被11整除的元素时(6,28,17),就会在第一个余数为6的元素下依次挂上,即形成所谓的链表!但链表太长查询效率又会降低,为了优化时间复杂度,当链表长度大于8时,会形成红黑树(可自平衡的二叉红黑树)
4)、哈希表的扩容与Rehash
在链表长度不变的情况下,插入元素越多,越容易哈希冲突。这时就进行扩容,当装载因子填充到一定数值(一般选0.75),就进行2倍扩容,然后将原来的数据重新装到新的扩容好的数组中(该过程即Rehash)
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
2.2 hashmap为什么是线程不安全的?
在并发下put数据,会发生数据覆盖、数据错乱。hashtable用的是synchronized关键字,而ConcurrentHashMap在jdk1.7之前是用了粒度更小的分段锁,1.8之后用的是synchronized+cas锁。
cas锁:没有获取到锁的线程不会阻塞,而是会循环控制不断获取锁。
2.3 HashMap扩容机制
jdk1.7,是数组+链表,扩容时会新生成一个数组,遍历老数组的每个链表上的元素,取每个元素的key,基于新数组的长度,计算出在新数组中的位置,将元素添加进去;
jdk1.8,是数组+链表+红黑树,扩容与jdk1.7时大致相同,不同的是,一棵红黑树进行计算时可能在新的数组中形成2个链表或是1个链表+1个红黑树。
2.4 说一下HashMap的Put()方法?
在jdk1.7和jdk1.8,都是先根据key哈希计算出在数组中的位置。
- 1、如果数组下标为空,则将key和value封装成一个Entry对象(jdk1.7是Entry对象,jdk1.8是Node对象),直接添加。
- 2、数组下标不为空,jdk1.7先判断是否需要扩容,不需要则通过头插法插入到链表中;jdk1.8,先判断当前Node节点上是红黑树还是链表,先生成一个Node节点通过尾插法进行插入,然后在进行扩容判断,不需要扩容则结束Put()。
2.5 CorrentHashMap()如何保证线程安全?
-
并发工具类-ConcurrentHashMap1.7原理
-
并发工具类-ConcurrentHashMap1.8原理
jdk1.8时的总结 :
1)如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。在第一次添加元素的时候创建哈希表,计算当前元素应存入的索引;
2)如果该索引位置为null,则利用cas算法,将本结点添加到数组中;
3)如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表;
4)当链表的长度大于等于8时,自动转换成红黑树;以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性
3、 ==和equals(常见于笔试)
== : 对于基本数据类型的变量,比较的是值。引用类型比较的地址值。
equals:equals方法不能作用于基本数据类型。对于引用类型的变量,没有重写equals比较的是内存地址;重写equals比较的是变量所指的对象内容。
4、 ArrayList 和LinkedList区别
- ArrayList:
基于动态数组,内存空间连续,所以查询快。但如果使用尾插法,并且制定好初始容量,会使性能极大提高,甚至超过LinkedList的插入速度。 - LinkedList:
基于链表,可以存储在分散的内存中,适合做增删,不适合查询。
两者都实现了List接口,但LinkedList额外实现了Deque接口,所以LinkedList还可以当队列来使用。
二、Java并发面试题
1、 ThreadLocal
1.1 谈谈你对ThreadLocal的理解?
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的。它不是针对程序的全局变量,只是针对当前线程的全局变量。
1.2 ThreadLocal底层实现原理?
Threadlocal内部有一个非常关键的内部类ThreadlocalMap,里面定义了一个由key - value组成的Entry数组,key存放的就是当前的线程,value为我们所需的数据。 而key是弱引用会被gc清除,value是强引用不会被清除,所以会造成内存泄漏。如果我们在线程池中使用了Threadlocal,一定要记得调用remove()方法,避免ThreadLocal 保存的数据被泄漏或污染!!!
public void set(T value) {
//进行set方法时,先获取当前线程对象,根据当前线程获取ThreadLocalMap,
//然后以当前Threadlocal为key进行存储
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
-
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
2、synchronized相关问题
2.1 synchronized与Reentrantlock的区别
- synchronized是一个关键字,属于JVM层面的,Reentrantlock是一个类,是API层面的;
- synchronized是自动加锁、释放锁,Reentrantlock则需要手动加锁和释放;
- synchronized底层有一个锁升级的过程(偏向锁—轻量级锁----重量级锁)
当一个线程获取一个琐时,此时该锁为偏向锁,当第二个线程尝试获取锁时,该锁会升级为轻量级锁,底层通过自旋来实现(不会造成线程阻塞),当自旋次数过多,会升级为重量级锁,会造成线程阻塞。
2.2 volatile关键字如何保证可见性和有序性
- 可见性:对加了volatile的成员变量进行修改时,会直接将cpu高级缓存区的数据放到主内存中,对这个数据的读取,会直接从主内存中取。
- 有序性:对加了volatile的成员变量进行读写时,会插入内存屏障,防止指令重排,保障了有序性。
volatile只有写操作是原子性的,也就是数据操作完成后会立刻刷新到主内存中。但是被volatile修饰的变量在读的时候可能会被多个线程读,所以它不能保证原子性。
2.3 Java如何避免死锁
- 注意加锁的顺序,保证每个线程按顺序进行加锁;
- 加锁时限,可以设置一个超时时间;
- 注意死锁检查,这是一种预防机制,可以确保发生死锁的第一时间进行处理。
3、 多线程(线程池)
3.1 线程有哪些状态(生命周期)
新建、就绪、运行、阻塞、死亡
3.2 如何获取多线程的返回值?
深坑!如果问多线程的创建方式,你一定知道是继承Thread类,实现runnable,callable接口。这里就是拐了个弯,变相的了解有返回值的callable接口。通过中间媒介FutureTask,将实现callable接口的类对象传递进去,调用FutureTask里面的get()方法,即可获取多线程的返回值。
3.3 为什么使用线程池,几个参数?
降低资源消耗(创建、销毁耗资源),提高响应速度(任务来了,可以有线程直接使用);
- 核心线程数、 最大线程数量、最大空闲时间、 空闲时间单位、任务队列、 线程工厂、拒绝策略;
4、并发相关问题
4.1、 并行、串行、并发?
- 并行: 同一时刻,多个任务互不干扰的同时进行;
- 串行:任务排队,一个一个执行;
- 并发:同一时刻,只有一个任务,多个任务交替执行;
4.2、 谈谈对AQS的理解,AQS如何实现可重入锁?
- AQS是一个Java线程同步的框架,是JDK很多锁的核心实现框架。
- 在AQS中,维护了一个信号量state和一个由线程组成的双向链表队列。其中,这个线程队列就是给线程排队的,而state就像是红绿灯,用来控制线程排队或者放行的。不同场景下,有不同的意义。
- 在可重入锁(锁了还可以再锁)场景下,state表示加锁次数,0表示无锁,每加一次锁,state就加1,释放锁state就减1。点击阅读 并发编程小结;
三、mysql相关问题
笔者总结了一篇Mysql高级,涉及内容较深些,也是常问的面试题,点击链接查阅
1、Mysql索引
1.1 索引的类型可以是String类型吗?
聚簇索引----数据和索引放一块,像主键索引,具有唯一性(Innodb就是)
数据库第一范式:必须要有id,这个id是自带索引的。
一般用自增id,字符串可以做id,但是不好,像uuid做的id是随机的,都没有排序!!!不像自增id维护索引的成本会很低
1.2 什么是索引?什么情况下用索引?什么时候不用?
1)就是一种数据结构,目的就是为了快速查找数据。
2)对查询频率高(索引就是为了提高查询效率),像where后的字段
数据量特别大, 索引不是越多越好,会影响增删的效率,典型的用空间换时间。
分组字段可以建立索引,因为分组的前提是排序(覆盖索引)
3)频繁更新的字段、查询少的、参与计算的不适合建索引。
1.3 索引失效?
在MySQL中,查询不走索引可能有以下几种情况:
数据量太小:如果表中的数据量比较小,MySQL会选择进行全表扫描而不使用索引,因为全表扫描的开销可能比使用索引更小。
对索引列进行了函数操作:如果在查询语句中对索引列进行了函数操作或表达式运算,MySQL无法使用索引进行优化,因此可能不走索引。
范围查询:如果查询语句中包含范围查询(例如大于、小于、区间查询等),MySQL可能无法使用索引进行优化,导致不走索引(模糊查询同理)。
数据分布不均匀:如果索引列的数据分布不均匀,索引的选择性较低,MySQL可能选择进行全表扫描而不使用索引。
在进行MySQL索引优化时,需要考虑以上情况,并通过分析查询语句、优化索引设计、适时更新统计信息等方法,提高MySQL查询性能,尽可能减少不走索引的情况。
1.4 查看索引使用和查看索引信息?
索引使用: explain 结果 (只要数字大于1)1 row in set 即生效了
查看索引信息: show indexs from 表名
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
1.5 复合索引?最左匹配原则?
最核心的是 等值比较
复合索引就是多个字段放一块(企业最常用的是符合索引!!!唯一索引,普通索引我们也用)
mysql会一直向右查询,直到遇到范围查询(> < like),比如用 a b c d四个字段创建了一个复合索引, a=3 b=5 c>7 d=9 只会用到前三个,因为b c d 是根据a 的后面进行规则的排序,即a是有序的,后面的bcd是无序的。 (hash索引用的不多,因为无序,但是定位快,了解即可)
1.6 Btree和B+tree?为什么mysql用的是B+ tree?**
B+tree是Btree的升级版。
Btree:多路平衡树,当增删数据时,会自动的将数据进行这个平衡(旋转)
为什么从Btree转到B+tree? 因为:索引也是一个文件,存档在磁盘,在使用时读入到内存。内存是有限的,如果索引文件过大,无法一次性全部加载,需要分批加载。在有限磁盘的限制下,B+tree可以减少磁盘的I/O。
对于B+tree,所有的数据都存在叶子节点,根和非叶子节点只是存储的指针,指向下一个数据的地址,由叶子节点再去查找到关联的数据信息。对于查询的数据,都要从root节点走到叶子节点,所以查询相比于Btree更加稳定。
对于mysql,是在B+tree的基础上,在相邻节点间增加了一个链表指针,形成了带有顺序的B+tree,提高了区间的访问性能。
1.7 覆盖索引与回表
如果只需要在一棵索引树上就可以获取Sql所需要的所有列,就不需要回表查询,这样查询速度会更快。而实现覆盖索引最快的方式就是将所需要的字段放在一起建一个联合索引。
1.8 Mysql的锁有哪些?什么是间隙锁?
从锁的粒度来分
1、行锁:加锁粒度小,但是加锁的资源开销比较大。Innodb支持。
1)共享锁:多个事务可以共享一把锁,但是只能读不能修改
2)排他锁:只有一个事务可以获得排他锁,其他事务不能获取该行的锁。Innodb会自行对增删改操作添加排他锁。
2、表锁:加锁粒度大,加锁的资源消耗小,Mysalm和Innodb都支持。
3、全局锁:加锁后全库都处于只读状态,用于全库数据备份。
2、数据库分库分表
2.1 数据库的分库分表?什么时候分?怎么分?
当单表数据超过1000W时,很多操作的性能会下降,所以需要切分,以减少数据库的压力,缩短查询时间。
垂直切分:将关系联系不紧密的表进行分库,将一张表中不常用的字段进行抽取新建一张表。优点类似于微服务。
水平切分:当一个应用难以再细粒度的垂直切分,根据数据间的逻辑进行划分,比如客户、存款、支付;
2.2 数据库的优化?
1)sql优化以及索引的优化,索引建立要合理,过多会影响增删性能
2)数据可设计要满足他的三大范式、五大约束
3)硬件优化
2.3 表设计的时候注意哪些,字段?
在设计数据库表时,需要注意以下几点,特别是在定义字段的时候:
数据类型选择:选择最适合的数据类型可以减小数据库表的存储空间,提高检索效率。对于整数、浮点数、字符串等不同类型的数据,选择合适的数据类型是设计表结构时的关键之一。
索引设计:根据实际查询需求设计合适的索引,可以提高查询效率。通常在主键、外键、经常用于查询的字段上创建索引。
主键设计:选择一个唯一、稳定、易于理解的字段作为主键,以确保每条记录都有唯一标识,便于数据访问和管理。
字段命名规范:字段命名应具有描述性,易于理解和记忆,建议使用下划线分隔(如:first_name),避免使用含糊不清或缩写的字段名。
字段约束:定义字段的约束条件,如NOT NULL、UNIQUE、DEFAULT值等,可以保证数据的完整性和一致性。
数据冗余:避免数据冗余,尽可能将重复的数据抽取成单独的表,以减小数据存储量,同时避免数据更新异常。
参照完整性:在设计外键关系时,保证参照完整性,确保相关数据的一致性,避免数据关联异常。
考虑数据增长:预估数据表的增长情况,选择适当的存储引擎和分区方案,以支持未来数据量的增长。
综上所述,在设计数据库表结构时,字段的数据类型、索引设计、主键选择、命名规范、约束条件、数据冗余、参照完整性、数据增长等方面都需要认真考虑,以保证数据库表的稳健性、性能和灵活性。
3、数据库事务
spring里面的事务和mysql里面的事务是一个概念,如果mysql不支持事务,加上@transation也是无效的。但是spring里面的@transation不能用于分布式环境下,分布式多线程下用的是senta+@globalTransation注解。
3.1 @transation用于类和方法有什么区别?
@transation只能修饰public方法。
类上:相当于在所有的public方法上加上了@transation注解
方法:会覆盖类上的配置。
3.2 事务的4个条件ACID
原子性:所有的操作是一个整体,要么全部成功,要么全部失败(回滚)
一致性:事务开始前后,数据库的完整性没有被破坏,也就是写入的资料完全符合我们的预设。
隔离性:允许多个并发事务对数据进行读写操作,它可以有效的防止多个事务并发执行时由于交叉执行而导致数据的不一致。(隔离性里面有4个隔离级别:读未提交、读以提交、可重复读、串行化)
持久性:事务完成,对数据的操作就是永久的,即使系统故障也不会丢失。
3.3 mysql事物隔离级别?
MySQL 提供了四种隔离级别,用于控制事务之间的隔离程度,以确保事务操作的一致性和并发性。这四种隔离级别分别是:
读未提交(Read Uncommitted):最低的隔离级别,允许一个事务读取另一个事务未提交的数据。可能会出现脏读(Dirty Read)问题。
读提交(Read Committed):其他事务提交后才能读取数据,避免脏读问题。但可能会出现不可重复读(指的是在同一个事务中,某个查询操作在不同时间点内多次执行,但由于其他事务的并发操作,在多次执行过程中返回的数据结果不一致的情况)问题。
可重复读(Repeatable Read):保证在同一个事务中多次读取同一行数据时,结果始终一致。避免了脏读和不可重复读问题。但依然可能存在幻读(在相同的查询条件下,不同的事务在不同时间点执行查询操作时,可能会看到不同数量的行,从而产生"幻像"的错觉)问题。
串行化(Serializable):最高的隔离级别,通过确保事务串行执行来避免脏读、不可重复读和幻读问题。确保所有事务的并发执行不会导致数据不一致。
在 MySQL 中,可以通过设置事务的隔离级别来控制事务的隔离程度,从而灵活地平衡并发性能和数据的一致性。开发者可以根据实际需求选择合适的隔离级别来确保数据操作的正确性和安全性。
4、Mysql其他问题
4.1 mysql中的null和空值有什么不一样?
1)空值是不占空间的,null值占用空间。两者就像:空值是真空状态的杯子,而null值是装满空气的杯子。
2)查寻上:null 值是用is null/is not null来查询,而空值( ’ ’ )则可以用 = > !=等。 在使用聚合函数count时,会过滤掉null,而不会过滤掉 ( ’ ’ )值。
在实际开发中,没有特定需求,可以直接使用空值!!!
4.2 mysql主从数据库延时的原因?
MySQL主从数据库延时可能有多种原因,以下是一些常见的原因:
网络延迟:主从数据库之间的网络连接如果不稳定或者网络质量差,会导致数据同步的延迟。
主从数据库负载:主数据库的负载过高,或者从数据库的性能不足,都可能导致数据同步延迟。
数据量过大:如果要同步的数据量过大,或者有大量的写操作,都会增加同步的时间。
复制方式设置不当:MySQL复制方式(如异步复制、半同步复制等)的配置不合理也会导致延时。
主从数据库版本不兼容:如果主从数据库的版本不一致或不兼容,也可能导致数据同步延迟。
在排查MySQL主从数据库延时问题时,可以逐一检查上述可能的原因,逐步定位并解决问题。
4.3 mysql主从复制的过程?
MySQL主从复制是一种常见的数据库复制技术,用于将主数据库中的数据实时同步复制到从数据库。以下是MySQL主从复制的基本过程:
配置主数据库:首先需要在主数据库上开启二进制日志(Binary Log),并配置主机的唯一标识(Server ID)。
配置从数据库:在从数据库上配置连接主数据库的信息,包括主数据库的IP地址、端口号、用户名密码等。同时设置从数据库的唯一标识(Server ID)。
启动主从复制过程:在从数据库上执行CHANGE MASTER TO命令,指定主数据库的连接信息,并启动从数据库与主数据库的连接。
数据传输与同步:当主从数据库连接成功后,主数据库会将变更写入二进制日志,并通过主从复制线程将这些变更传输给从数据库,从数据库接收到变更后应用于自身数据库,实现数据同步。
监控与维护:定期检查主从数据库的状态,确保主从复制正常运行。如果发现延迟或同步失败,需要及时排查并解决问题。
总的来说,MySQL主从复制的过程包括配置主从数据库、启动复制、数据传输与同步以及监控与维护等步骤。通过主从复制,可以实现数据的备份、负载均衡以及容灾等功能。
四、JVM虚拟机
笔者此前在其他博客上整理过,不在重复,链接如下:
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
五、Redis面试题
1 、缓存雪崩、缓存穿透、缓存击穿
- 缓存雪崩: 缓存同一时间大面积失效,大量请求打到数据库上,数据库承受不住崩掉;
解决方案:过期时间设置成随机、缓存预热(系统出初启动,先存数据到缓存里) - 缓存穿透: 数据库和redis中都没有数据,导致大量数据查询数据库,导致数据库崩掉;
- 可以将不存在的请求key的value 设置成一个字符串返回,这样就避免了黑君攻击到数据库。
解决方案: 接口层进行校验(id<0的直接拒绝)、采用布隆过滤器 - 缓存击穿: redis的一个key失效,请求全部打到数据库(缓存没有数据库有);
解决方案: 热点数据永不过期,设置成空字符串返回
2、 如何保证数据库和Redis的数据一致性
当我们遇到这个问题时,要考虑是先删缓存,还是先写数据库。
延时双删: 先删redis缓存数据,再更新数据库,然后再删redis缓存,这样就避免了删除redis和更新数据库这期间,其他的线程读不到redis里的值,去读数据库里的旧数据。
将操作可串行化(先写在读就可以了)
1)先删缓存,将更新数据库操作放到一个有序队列中
2)从缓存中查不到的查询操作,都进入有序队列(MQ)
问题:大量读请求积压--------> 将队列水平拆分
3、Redis的数据结构及使用场景
String :字符串 ----------原子计数器
List :列表 ---------微博热搜Top10
Set :无序集合 ---------好友推荐
Zset :有序集合 -----------排行榜
Hash :哈希表 ----------适合存储对象
----------------------------------前五种为常见,后四种为面试加分项-------------------------------------------
bitmap:布隆过滤器
GeoHash:坐标,(存储坐标的)基于Zset的score进行排序就可以得到坐标附近的值
HyperLoglog: 统计不重复数据,用于大数据基础使用
Streams:内存版的Kafka,消息的订阅发布
4、Redis持久化机制有哪些
基于内存,宕机数据会消失,所以需要持久化。有两种方式
- RDB:Redis DateBase
在指定的时间间隔内将内存中的数据以快照的形式写入磁盘,实际是fork(分支)一个子进程,先将数据复制写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
优点:
- 整个redis数据库将只有一个dump.rdb文件,方便持久化;
- 容灾性好,方便备份;
- 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不进行任何的IO操作,保证了Redis的高性能;
- 相对于数据集大时,比AOF的启动效率更高;
缺点:
- 数据安全性低。持久化期间Redis发生故障,会有数据丢失;
AOF: Append Only File
以日志的形式记录服务器的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录
优点:
- 数据安全,每次修改都会被记录到磁盘中;
- 通过append模式写文件,即使Redis宕机,也不会破坏已经存在的内容,可以通过redis-check-aof工具解决数据一致性问题;
缺点:
- 效率没有RDB高;
- AOF文件比RDB大,且恢复速度慢;
5、Redis其他常见问题
5.1 Redis线程模型,单线程为什么快?
单线程快的原因有以下几点:
- 纯内存操作
- 核心是基于非阻塞的IO多路复用机制(单个线程可以同时处理多个请求)
- 单线程避免了多线程来回切换的上下文环境切换问题
5.2 Redis分布式锁的实现原理
set(key,value,setnx,setpx)
setnx----------->加锁的命令
setpx----------->锁的过期时间
setnx+setp在高版本中已经整合为一个原子命令。
当任务超时,锁自动释放怎么办?可以使用redission的看门狗,会自动续期。
获取锁:
- 客户端使用SET命令尝试在Redis中设置一个特定的键作为锁,可以设置一个固定的值作为锁持有者的标识,同时设置一个过期时间(防止锁无法释放导致死锁)。
- 设置键时使用NX参数(即只在键不存在时才能设置成功),这样可以确保只有一个客户端能够成功设置该键,获得了锁。
释放锁:
- 当客户端要释放锁时,可以通过DEL命令来删除这个键,确保该锁被释放。
防止死锁:
- 设置锁时可以为键设置一个合理的过期时间,以防止锁无法释放导致死锁。即使持有锁的客户端在处理任务时发生异常退出,保证锁在一定时间后会自动释放。
避免误删除:
- 尽量不要使用DEL命令直接删除锁,因为可能会误删其他客户端的锁。可以使用Lua脚本来确保原子性操作,只有持有锁的客户端才能成功释放锁。
总的来说,Redis分布式锁的实现原理基于对Redis的SET命令设置NX参数进行加锁和DEL命令释放锁,配合合理设置过期时间来保证分布式锁的正确性和稳定性。通过合理设计和管理分布式锁,可以帮助多个客户端在分布式环境中协调访问共享资源,避免竞争条件和数据不一致问题。
5.3 Redis主从复制的原理
简述好下面的流程图即可
5.4、Redis 到底是单线程还是多线程?
Redis6.0 版本之前的单线程指的是网络 I/O 和键值对读写是由一个命令完成的;
Reds6.0 版本之前只有网络请求模块和数据操作模块是单线程的,而其他的特久化,集群数
据同步,其实是由额外的线程完成的
Redis6.0 版本之后的多线程指的是网络请求过程采用了多线程,而键值对的读写命令依然是单线程处理,所以Redis 是线程安全的。命令的执行排队执行
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取