2020年11月 1-3年工作经验最新面试题汇总

java基础

1、java 中==和 equals 和 hashCode 的区别

解答:
1、==若是基本数据类型比较,是比较值,若是引用类型,则比较的是他们在内存中的存 放地址。

2、equals 是 Object 类中的方法,Object 类的 equals 方法用于判断对象的内存地址引用 是不是同一个地址(是不是同一个对象)。若是类中覆盖了 equals 方法,就要根据具体代 码来确定,一般覆盖后都是通过对象的内容是否相等来判断对象是否相等。

3、hashCode()计算出对象实例的哈希码,在对象进行散列时作为 key 存入。之所以有 hashCode 方法,因为在批量的对象比较中,hashCode 比较要比 equals 快。

所有对于需要大量并且快速的对比的话如果都用equals()去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equals()去再对比了),如果hashCode()相同,此时再对比他们的equals(),如果equals()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!
因为在散列表中,hashCode()相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。

2、int与Integer区别?

  1. Ingeter是int的包装类,int的初值为0,Ingeter的初值为null。

  2. Integer变量必须实例化后才能使用;int变量不需要;

  3. Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;

  4. Integer是int的包装类;int是基本数据类型;

  5. 举例说明

public class IntAndIntegerTest{
	public static void main(String[] args){
/* 		  new出来的对象存放在堆,而非new的Integer常量则在常量池(在方法区),
		  他们的内存地址不一样,所以为false
*/		
		Integer i = 100 ;
		Integer i1 = new Integer(100) ;
		System.out.println("i == i1 ?" + (i==i1)) ; // false
		System.out.println("*********************") ;
/* 
	两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false。
	因为java在编译Integer i2 = 128的时候,被翻译成:Integer i2 = Integer.valueOf(128);而valueOf()函数会对-128到127之间的数进行缓存。
*/
		Integer i3 = 127 ;
		Integer i2 = 127 ;
		Integer i4 = 129 ;
		Integer i5 = 129 ;
		System.out.println("i3 == i2 ? "+(i3==i2)) ;// true
		System.out.println("i5 == i4 ? "+(i5==i4)) ;// false
		System.out.println("*********************") ;
		Integer i6 = new Integer(129) ;
		Integer i7 = new Integer(129) ;
		System.out.println("i7 == i6 ? "+(i7==i6)) ; 
		System.out.println("*********************") ;
/* 
	int和Integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比。
*/
		int i8 = 100 ;
		Integer i9 = 100 ;
		Integer i10 = new Integer(100) ;
		System.out.println("i8 == i9 ? "+(i8==i9)) ; 
		System.out.println("i8 == i10 ? "+(i8==i10)) ; 		
	}
}

注意:
1,无论如何,Integer与new Integer不会相等。不会经历拆箱过程,new出来的对象存放在堆,而非new的Integer常量则在常量池(在方法区),他们的内存地址不一样,所以为false。

2,两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false。因为java在编译Integer i2 = 128的时候,被翻译成:Integer i2 = Integer.valueOf(128);而valueOf()函数会对-128到127之间的数进行缓存。

3,两个都是new出来的,都为false。还是内存地址不一样。

4,int和Integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比。

3、谈谈对 java 多态的理解

从两方面来考虑首先如何理解定义其次就是如何实现多态
1、定义概念
多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编 译时不确定,在运行期间才确定,一个引用变量到底会指向哪个类的实例。这样就可以不用 修改源程序,就可以让引用变量绑定到各种不同的类实现上。
2、如何实现
继承、重定、向上转型,在多态中需要将子类的引用赋值给父类对象,只有这样该引用才能 够具备调用父类方法和子类的方法。

4、String、StringBuffer、StringBuilder 区别

都是操作字符串

​ String 类中使用字符数组保存字符串,因有 final 修饰符,String 对象是不 可变的,每次对 String 操作都会生成新的 String 对象,这样效率低,且浪费内存空间。但 线程安全。

​ StringBuilder 和 StringBuffer 也是使用字符数组保存字符,但这两种对象都是可变的,即 对字符串进行 append 操作,不会产生新的对象。它们的区别是:StringBuffer 对方法加 了同步锁,是线程安全的,StringBuilder 非线程安全。

5、抽象类和接口区别,应用场景

1)抽象类可以提供成员方法实现的细节,而接口只能存在抽象方法;

2)抽象类的成员变量可以是各种类型,而接口中成员变量只能是 public static final 类型;

3)接口中不能含有静态方法及静态代码块,而抽象类可以有静态方法和静态代码块;

4)一个类只能继承一个抽象类,用 extends 来继承,却可以实现多个接口,用 implements 来实现接口。

抽象类的应用场景:1)规范了一组公共的方法,与状态无关,可以共享的,无需子类分别 实现;而另一些方法却需要各个子类根据自己特定状态来实现特定功能;

接口的应用场景: 2)定义一组接口,但不强迫每个实现类都必须实现所有的方法,可用抽象类定义一组方法 体可以是空方法体,由子类选择自己感兴趣的方法来覆盖;

6、final,finally,finalize 的区别

final: 修饰类,此类不能继承;修饰方法,此方法不能重写;修饰变量,此变量不可修改即常量。

finally: 异常处理关键字try…catch…finally 一直会执行的代码

finalize: Object类中的一个方法,垃圾回收的方法

7、说说你对 Java 反射的理解

在运行状态中,对任意一个类,都能知道这个类的所有属性和方法,

对任意一个对象,都能 调用它的任意一个方法和属性。

反射的作用:开发过程中,经常会遇到某个类的某个成员变量、方法或属性是私有的,或只 对系统应用开放,这里就可以利用 java 的反射机制通过反射来获取所需的私有成员或是方 法

8、String 为什么要设计成不可变的?

1)字符串常量池需要 String 不可变。因为 String 设计成不可变,当创建一个 String 对象时, 若此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。 如果字符串变量允许必变,会导致各种逻辑错误,如改变一个对象会影响到另一个独立对象。

2)String 对象可以缓存 hashCode。字符串的不可变性保证了 hash 码的唯一性,因此可以缓 存 String 的 hashCode,这样不用每次去重新计算哈希码。在进行字符串比较时,可以直接 比较 hashCode,提高了比较性能;

3)安全性。String 被许多 java 类用来当作参数,如 url 地址,文件 path 路径,反射机制所 需的 Strign 参数等,若 String 可变,将会引起各种安全隐患。

9、arraylist扩容机制

ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10 (如下图一);之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,Arraylist继续扩容变为10*1.5=15。

10、hashmap数据结构,put流程

数组+链表+红黑树

  1. 判断数组是否为空,为空进行初始化;
  2. 不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index;
  3. 查看 table[index] 是否存在数据,没有数据就构造一个Node节点存放在 table[index] 中;
  4. 存在数据,说明发生了hash冲突(存在二个节点key的hash值一样), 继续判断key是否相等,相等,用新的value替换原数据(onlyIfAbsent为false);
  5. 如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;(如果当前节点是树型节点证明当前已经是红黑树了)
  6. 如果不是树型节点,创建普通Node加入链表中;判断链表长度是否大于 8并且数组长度大于64, 大于的话链表转换为红黑树;
  7. 插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。

spring全家桶

1、如何理解spring
2、了解spring中IOC,aop
3、spring框架中使用了哪些设计模式
4、beanFactory和applicationFactory区别
5、你怎样定义类的作用域
6、解释spring支持的集中bean的作用域
7、spring框架中的单例bean是线程安全的吗,如何解决
8、Spring支持的事务管理类型
9、说一下Spring的事务传播行为
10、解释一下Spring AOP里面的几个名词
11、springmvc常见组件
12、springmvc常用注解
13、springmvc流程图
14、@PathVariable和@RequestParam的区别
15、Beanfactory与factorybean区别
16、Spring解决循环依赖
17、springboot优点
18、springboot自动装配
19、springboot的核心注解有哪些? 它主要有哪些注解组成的
20、springboot常用注解
21、springboot的核心配置文件是什么,Bootstrap.properties和application.proeprties有什么区别
22、springboot跨域问题
23、运行springboot有几种方式
23、微服务中如何实现session共享
24、springboot如何实现定时任务
25、实现springboot接口等幂性校验

MySQL数据库

1、Mysql三大范式

第一范式:每个列都不可以再拆分。

第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。

第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。

在设计数据库结构的时候,要尽量遵守三范式,如果不遵守,必须有足够的理由。比如性能。事实上我们经常会为了性能而妥协数据库的设计。

2、innodb与myisam存储引擎区别?

MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。

Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。

3、什么是聚簇索引?何时使用聚簇索引与非聚簇索引?

聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据。

非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因。

4、事务出现问题?

l 脏读:指一个线程中的事务读取到了另外一个线程中未提交的数据。

l 不可重复读:指一个线程中的事务读取到了另外一个线程中提交的update的数据。(修改强调)

l 虚读(幻读):指一个线程中的事务读取到了另外一个线程中提交的insert的数据。(强调添加删除)

5、索引有哪几种

主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。

唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。

  • 可以通过 ALTER TABLE table_name ADD UNIQUE (column); 创建唯一索引
  • 可以通过 ALTER TABLE table_name ADD UNIQUE (column1,column2); 创建唯一组合索引

普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。

组合索引:多个字段组合一起使用起效

6、索引创建原则

1) 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

2)较频繁作为查询条件的字段才去创建索引

3)更新频繁字段不适合创建索引

4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)

5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

6)定义有外键的数据列一定要建立索引。

7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。

7、索引创建过多劣势

索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。

8、B树与b+树区别?

在B树中,你可以将键和值存放在内部节点和叶子节点;但在B+树中,内部节点都是键,没有值,叶子节点同时存放键和值。

B+树的叶子节点有一条链相连,而B树的叶子节点各自独立。

9、了解数据库事务

数据库的事务是指一组sql语句组成的数据库逻辑处理单元,在这组的sql操作中,要么全部执行成功,要么全部执行失败。
例子就是转账了,事务A中要进行转账,那么转出的账号要扣钱,转入的账号要加钱,这两个操作都必须同时执行成功,为了确保数据的一致性。

在Mysql中事务的四大特性主要包含:原子性(Atomicity)、一致性(Consistent)、隔离性(Isalotion)、持久性(Durable),简称为ACID。
原子性是指事务的原子性操作,对数据的修改要么全部执行成功,要么全部失败,实现事务的原子性,是基于日志的Redo/Undo机制。
持久性则是指在一个事务提交后,这个事务的状态会被持久化到数据库中,也就是事务提交,对数据的新增、更新将会持久化到书库中。
在我的理解中,原子性、隔离性、持久性都是为了保障一致性而存在的,一致性也是最终的目的。

原子性是基于日志的Redo/Undo机制,你能说一说Redo/Undo机制吗?
Redo/Undo机制比较简单,它们将所有对数据的更新操作都写到日志中。
Redo log用来记录某数据块被修改后的值,可以用来恢复未写入 data file 的已成功事务更新的数据;Undo log是用来记录数据更新前的值,保证数据更新失败能够回滚。
假如数据库在执行的过程中,不小心崩了,可以通过该日志的方式,回滚之前已经执行成功的操作,实现事务的一致性。

可以举一个场景,说一下具体的实现流程吗?

可以的,假如某个时刻数据库崩溃,在崩溃之前有事务A和事务B在执行,事务A已经提交,而事务B还未提交。当数据库重启进行 crash-recovery 时,就会通过Redo log将已经提交事务的更改写到数据文件,而还没有提交的就通过Undo log进行roll back。

10、什么是事务的隔离级别?MySQL的默认隔离级别是什么?

为了达到事务的四大特性,数据库定义了4种不同的事务隔离级别,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。

隔离级别脏读不可重复读幻影读
READ-UNCOMMITTED
READ-COMMITTED×
REPEATABLE-READ××
SERIALIZABLE×××

11、了解锁吗,项目中用到了?

乐观锁
顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量

悲观锁
对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,然后再操作资源。
如何实现乐观锁
第一种方案
通过 数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据

第二种方案
在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

12、关联查询了解吗?

外连接,内连接,自连接
内连接:组合两个表中的记录,返回关联字段相符的记录,也就是两个表中的交集部分。
外连接:左连接,右连接 left outer join/right outer join
左:左表记录全部显示,然而右表只会显示符合条件的记录,右表记录不足的用null显示
右:右表记录全部显示,左表记录只是显示符合条件的记录,左表不符合的用null显示

自连接:自连接查询就是当前表和自身的连接查询,关键点在于虚拟化一张表给一个表名
自连接查询一般用于表中的某个字段的值引用另一个字段的值,比如权限,父权限也属于权限

union union all区别
union 用的比较多去重处理 获取唯一值 union all 不去重,可能重复

13、数据库优化用过吗?

1、SQL语句优化+添加索引
2、添加缓存redis或者memcached
3、主从复制,读写分离
4、垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统

14、mysql聚合函数有哪些?

avg() 平均数

​ count() 计数
​ max() 最大值
​ min() 最小值
​ sum() 求和

15、mysql执行计划用过吗 都有哪些重要字段

链接:https://blog.csdn.net/wuseyukui/article/details/71512793
id , type , key , rows , extra
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
1、id相同:执行顺序由上至下

2、id不同:如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
3、id相同又不同(两种情况同时存在):id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行

type

访问类型,sql查询优化中一个很重要的指标,结果值从好到坏依次是:

**system** > **const** > **eq_ref** > **ref** > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > **range** > **index** > **ALL**

一般来说,好的sql查询至少达到range级别,最好能达到ref

1、system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,可以忽略不计

2、const:表示通过索引一次就找到了,const用于比较primary key 或者 unique索引。因为只需匹配一行数据,所有很快。如果将主键置于where列表中,mysql就能将该查询转换为一个const

3、eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键 或 唯一索引扫描。

4、ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质是也是一种索引访问,它返回所有匹配某个单独值的行,然而他可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体

5、range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了那个索引。一般就是在where语句中出现了bettween、<、>、in等的查询。这种索引列上的范围扫描比全索引扫描要好。只需要开始于某个点,结束于另一个点,不用扫描全部索引

6、index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常为ALL块,应为索引文件通常比数据文件小。(Index与ALL虽然都是读全表,但index是从索引中读取,而ALL是从硬盘读取)

7、ALL:Full Table Scan,遍历全表以找到匹配的行

key

实际使用的索引,如果为NULL,则没有使用索引。

查询中如果使用了覆盖索引,则该索引仅出现在key列表中

rows

​ 根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数

extra

不适合在其他字段中显示,但是十分重要的额外信息

1、Using filesort
mysql对数据使用一个外部的索引排序,而不是按照表内的索引进行排序读取。也就是说mysql无法利用索引完成的排序操作成为“文件排序”

2、Using temporary
使用临时表保存中间结果,也就是说mysql在对查询结果排序时使用了临时表,常见于order by 和 group by

3、Using index
表示相应的select操作中使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高
如果同时出现Using where,表明索引被用来执行索引键值的查找(参考上图)
如果没用同时出现Using where,表明索引用来读取数据而非执行查找动作

4、Using where :
使用了where过滤

5、Using join buffer :
使用了链接缓存

6、Impossible WHERE:
where子句的值总是false,不能用来获取任何元祖

7、select tables optimized away:
在没有group by子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段在进行计算,查询执行计划生成的阶段即可完成优化

8、distinct:
优化distinct操作,在找到第一个匹配的元祖后即停止找同样值得动作

img

执行顺序
1(id = 4)、【select id, name from t2】:select_type 为union,说明id=4的select是union里面的第二个select。

2(id = 3)、【select id, name from t1 where address = ‘11’】:因为是在from语句中包含的子查询所以被标记为DERIVED(衍生),where address = ‘11’ 通过复合索引idx_name_email_address就能检索到,所以type为index。

3(id = 2)、【select id from t3】:因为是在select中包含的子查询所以被标记为SUBQUERY。

4(id = 1)、【select d1.name, … d2 from … d1】:select_type为PRIMARY表示该查询为最外层查询,table列被标记为 “derived3”表示查询结果来自于一个衍生表(id = 3 的select结果)。

5(id = NULL)、【 … union … 】:代表从union的临时表中读取行的阶段,table列的 “union 1, 4”表示用id=1 和 id=4 的select结果进行union操作。

16、mysql获取当前时间哪些函数

current_timestamp、now() 年月日时分秒

current_time ,curtime 时分秒

current_data , curdate 年月日

17、SQL语句执行流程?

先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。

通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id=‘1’。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。

接下来就是优化器进行确定执行方案,上面的 sql 语句,可以有两种执行方案:

a.先查询学生表中姓名为“张三”的学生,然后判断是否年龄是 18。
b.先找出学生中年龄 18 岁的学生,然后再查询姓名为“张三”的学生。
那么优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)。那么确认了执行计划后就准备开始执行了。

进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果。
select * from tb_student A where A.age=‘18’ and A.name=’ 张三 ';

18、重要题目 查询重复数据

select * from student s where s.stuName in (select s2.stuName from student s2 having count(s.stuName) > 1) ;

-- 查询某个字段重复
select s.stuName name1 , count(s.stuName) count1 from student s group by s.stuName having count(s.stuName) > 1 order by count1 desc ;

-- 查询多个字段都重复
select s.stuName , s.stuSex , count(*) from student s group by s.stuName , s.stuSex having count(*) > 1

19、SQL实现某个字段分组第一条数据

select a_id , a_name , a_part from a_table group by a_part ;

在这里插入图片描述

redis非关系数据库

1、Redis五种数据类型及应用场景

STRING字符串、整数或者浮点数对整个字符串或者字符串的其中一部分执行操作 对整数和浮点数执行自增或者自减操作1、分布式锁:SETNX(Key, Value),释放锁:DEL(Key),2、复杂计数功能缓存(用户量,视频播放量)
LIST列表从两端压入或者弹出元素 对单个或者多个元素进行修剪, 只保留一个范围内的元素存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据,简单的消息队列的功能
SET无序集合添加、获取、移除单个元素 检查一个元素是否存在于集合中 计算交集、并集、差集 从集合里面随机获取元素交集、并集、差集的操作,比如交集,可以把两个人的粉丝列表整一个交集,做全局去重的功能,点赞,转发,收藏;
HASH包含键值对的无序散列表添加、获取、移除单个键值对 获取所有键值对 检查某个键是否存在结构化的数据,比如一个对象,单点登录
ZSET有序集合添加、获取、删除元素 根据分值范围或者成员来获取元素 计算一个键的排名去重但可以排序,如获取排名前几名的用户,做排行榜应用,取TOPN操作;延时任务;做范围查找。周榜,月榜,年榜

基本操作

String
SET			--存入一个字符串键
SETNX		--存入一个字符串键,若Key存在则操作失败
GET			--获取指定Key的字符串
MSET		--批量存入字符串键
MGET		--批量获取指定Key的字符串
DEL			--删除指定Key(所有类型都可以使用此命令)

hash
HSET			--存入一个key filed 散列结构
HSETNX			--存入一个key field,若key中filed已经存在则操作失败
HGET			--获取指定key field
HMSET			--批量存入key filed
HMGET			--批量获取key filed
HDEL			--删除指定Key filed
HINCRBY			--对key field的数值进行加减操作

list
LPUSH Key value [value...]			--往key的列表键中左边放入一个元素,key不存在则新建
RPUSH Key value [value...]			--往key的列表键中右边放入一个元素,key不存在则新建
LPOP Key							--从key的列表键最左端弹出一个元素
RPOP Key							--从key的列表键最右端弹出一个元素
LRANGE Key start stop				--获取列表键从start下标到stop下标的元素
BLPOP Key [Key...] timeout			--阻塞的从key的列表键最左端弹出一个元素,若列表键中不存在元素,阻塞等待{timeout}秒,若{timeout}=0,一直阻塞

BRPOP Key [Key...] timeout			--阻塞的从key的列表键最右端弹出一个元素,若列表键中不存在元素,阻塞等待{timeout}秒,若{timeout}=0,一直阻塞

set
SADD Key member [member...]			--往集合键key中存放元素,若key不存在则新建
SREM Key member [member...]			--从集合键key中删除元素
SMEMBERS Key						--获取集合键key中的所有元素
SCARD Key							--获取集合键key中的元素个数
SISMEMBER Key member				--判断{member}元素是否存在于集合键key中
SRANDMEMBER Key [count]				--从集合键key中选出{count}个元素,不从集合键key中删除
SPOP Key [count]					--从集合键key中选出{count}个元素,并且从集合键key中删除

zset
ZADD Key score element [...]			--往有序集合键key中存放元素,若key不存在则新建
ZREM Key element [element...]			--从有序集合键key中删除元素
ZSCORE Key element						--获取有序集合键key中{element}元素的score值
ZINCRBY Key increment element			--给有序集合键key中{element}元素进行score值操作,若key不存在则新建,{element}元素不存在则新增后进行score值操作
ZCARD Key								--获取有序集合键key中元素个数
ZRANGE Key start stop [WITHSCORES]		--正序获取有序集合键key中从start下标到end下标的元素
ZREVRANGE Key start stop [WITHSCORES]	--倒序获取有序集合键key中从start下标到end下标的元素

2、为什么使用redis或者缓存

主要从“高性能”和“高并发”这两点来看待这个问题。
高性能
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
高并发:

直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
3、持久化机制
1、持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

2、rdb:rdb:快照方式,按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。

aop:aof:将数据每隔一秒追加到文件中。

3、两者区别联系

  • AOF文件比RDB更新频率高,优先使用AOF还原数据。
  • AOF比RDB更安全也更大
  • RDB性能比AOF好
  • 如果两个都配了优先加载AOF

4、Redis的过期键的删除策略

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。
  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

Redis中同时使用了惰性过期和定期过期两种过期策略。

5、redis淘汰策略

Redis官方给的警告,当内存不足时,Redis会根据配置的缓存策略淘汰部分keys,以保证写入成功。当无淘汰策略时或没有找到适合淘汰的key时,Redis直接返回out of memory错误。

1、volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

2、volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

3、volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

4、allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

5、allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

6、no-enviction(驱逐):禁止驱逐数据

6、缓存雪崩,缓存穿透,缓存击穿现象如何解决

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

  2. 一般并发量****不是特别多的时候,使用最多的解决方案是加锁排队

  3. redis高可用**

    1. redis有可能挂掉,多增加几台redis实例,(一主多从或者多主多从),这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。
  4. 限流降级

    1. 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  5. 数据预热

    1. 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key。

举例说明:

​ 比如我们基本上都经历过购物狂欢节,假设商家举办 23:00-24:00 商品打骨折促销活动。程序小哥哥在设计的时候,在 23:00 把商家打骨折的商品放到缓存中,并通过redis的expire设置了过期时间为1小时。这个时间段许多用户访问这些商品信息、购买等等。但是刚好到了24:00点的时候,恰好还有许多用户在访问这些商品,这时候对这些商品的访问都会落到数据库上,导致数据库要抗住巨大的压力,稍有不慎会导致,数据库直接宕机(over)
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
  3. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力

** 缓存击穿**是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  1. 设置热点数据永远不过期。
  2. 加互斥锁,互斥锁

归纳起来:造成缓存击穿的原因有两个。

(1)一个“冷门”key,突然被大量用户请求访问。

(2)一个“热门”key,在缓存中时间恰好过期,这时有大量用户来进行访问。

7、redis实现分布式锁

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。

当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

返回值:设置成功,返回 1 。设置失败,返回 0 。

使用SETNX完成同步锁的流程及事项如下:

使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功

为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间

释放锁,使用DEL命令将锁数据删除

举例说明:例如:某客户端要获得一个名字youzhi的锁,客户端使用下面的命令进行获取:
SETNX lock.youzhi
如返回1,则该客户端获得锁,把lock.youzhi的键值设置为时间值表示该键已被锁定,该客户端最后可以通过DEL lock.foo来释放该锁。
**如返回0,表明该锁已被其他客户端取得,这时我们可以先返回或进行重试等对方完成或等待锁超时。

8、如何保证Redis与数据库的数据一致性

方案一 涉及业务代码不合适
(1)更新数据库数据;
(2)缓存因为种种问题删除失败
(3)将需要删除的key发送至消息队列
(4)自己消费消息,获得需要删除的key
(5)继续重试删除操作,直到成功
方案二
(1)更新数据库数据
(2)数据库会将操作信息写入binlog日志当中
(3)订阅程序提取出所需要的数据以及key
(4)另起一段非业务代码,获得该信息
(5)尝试删除缓存操作,发现删除失败
(6)将这些信息发送至消息队列
(7)重新从消息队列中获得该数据,重试操作

9、redis主从模式,哨兵模式,集群模式

主从模式

1.一个Master可以有多个Slaves,可以是1主N从。

2.默认配置下,master节点可以进行读和写,slave节点只能进行读操作,写操作被禁止(readonly)。

3.不要修改配置让slave节点支持写操作,没有意义,原因一,写入的数据不会被同步到其他节点;原因二,当master节点修改同一条数据后,slave节点的数据会被覆盖掉。

4.slave节点挂了不影响其他slave节点的读和master节点的读和写,重新启动后会将数据从master节点同步过来。

5.master节点挂了以后,不影响slave节点的读,Redis将不再提供写服务,master节点启动后Redis将重新对外提供写服务。

6.特别说明:该种模式下,master节点挂了以后,slave不会竞选成为master。

哨兵模式

​ 宕机的主节点下线了,

​ 找到一个slave作为master,

​ 通知所有slave连接新的master,

​ 启动新的master和slave,

​ 全量复制N + 部分复制N

问题?

​ 谁来确认master宕机了

​ 找一个主节点,怎么找?

​ 修改配置后,原来主节点恢复了怎么办?

​ 哨兵sentinel是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时候通过投票选择新的master并将所有slave连接新的master;

​ 作用

​ 监控:

​ 不断检查master和slave是否正常运行

​ master存活检测,master和slave运行情况检测

​ 通知

​ 当被监控的服务器出现问题时,向其他哨兵或者客户端进行通知

自动故障转移

​ 断开master和slave连接,选取一个slave作为master,将其他slave连接到新的master上,并且告知客户端新的服务器地址。

注意:哨兵也是一台服务器,只是不提供数据服务,通常哨兵配置单数个

集群模式

cluster的出现是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器。对cluster的一些理解:

一个 Redis 集群包含 16384 个哈希槽(hash slot),数据库中的每个键都属于这 16384 个哈希槽的其中一个,集群中的每个节点负责处理一部分哈希槽。

例如一个集群有三个主节点,其中:

节点 A 负责处理 0 号至 5500 号哈希槽。

节点 B 负责处理 5501 号至 11000 号哈希槽。

节点 C 负责处理 11001 号至 16384 号哈希槽。

这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。例如:如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了。

如果用户要从集群中移除节点 A , 那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C , 然后再移除空白(不包含任何哈希槽)的节点 A 就可以了。

这里需要注意的是,集群如果是5主5从,主节点也是16384个hash slot,而不会因为主节点的增多slot也增多。我们在分槽的时候,尽量把槽平均分给主节点。因为一个key落在哪个槽里面,是根据key的CRC16值模上16384得出的值来计算的。

2.Redis 集群对节点使用了主从复制功能: 集群中的每个节点都有 1 个至 N 个复制品(replica), 其中一个复制品为主节点(master), 而其余的 N-1 个复制品为从节点(slave)。

我们知道集群模式下,1主N从时,当主节点挂掉时,从节点通过心跳监听机制,会竞选成为主节点(这时设置的readonly会失效),所以在部署的时候,主从节点应该部署在不同的机器上,这个时候如果主节点的服务器宕机,从节点竞选成功后会继续承担读写的任务。

10、redis主从复制,读写分离

当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。

如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,

同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,

接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。

slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。

11、如何解决redis并发竞争的key问题

第一种方案:分布式锁+时间戳
1.整体技术方案
这种情况,主要是准备一个分布式锁,大家去抢锁,抢到锁就做set操作。
加锁的目的实际上就是把并行读写改成串行读写的方式,从而来避免资源竞争。
2.Redis分布式锁的实现
主要用到的redis函数是setnx()
用SETNX实现分布式锁
利用SETNX非常简单地实现分布式锁。例如:某客户端要获得一个名字youzhi的锁,客户端使用下面的命令进行获取:
SETNX lock.youzhi
如返回1,则该客户端获得锁,把lock.youzhi的键值设置为时间值表示该键已被锁定,该客户端最后可以通过DEL lock.foo来释放该锁。
如返回0,表明该锁已被其他客户端取得,这时我们可以先返回或进行重试等对方完成或等待锁超时。
2.时间戳
由于上面举的例子,要求key的操作需要顺序执行,所以需要保存一个时间戳判断set顺序。系统A key 1 {ValueA 7:00}系统B key 1 { ValueB 7:05}
假设系统B先抢到锁,将key1设置为{ValueB 7:05}。接下来系统A抢到锁,发现自己的key1的时间戳早于缓存中的时间戳(7:00<7:05),那就不做set操作了。
3.什么是分布式锁
因为传统的加锁的做法(如java的synchronized和Lock)这里没用,只适合单点。因为这是分布式环境,需要的是分布式锁。
当然,分布式锁可以基于很多种方式实现,比如zookeeper、redis等,不管哪种方式实现,基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

—第二种方案:利用消息队列
在并发量过大的情况下,可以通过消息中间件进行处理,把并行读写进行串行化。
把Redis.set操作放在队列中使其串行化,必须的一个一个执行。
这种方式在一些高并发的场景中算是一种通用的解决方案。

12、redis实现异步队列?

​ 使用list类型保存数据信息,rpush生产消息,lpop消费消息,当lpop没有消息时,可以sleep一段时间,然后再检查有没有信息,如果不想sleep的话,可以使用blpop, 在没有信息的时候,会一直阻塞,直到信息的到来。

​ redis可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。

13、redis使用特点

(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

(2) 支持丰富数据类型,支持string,list,set,sorted set,hash

(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

并发编程

1、线程池创建理解
2、创建线程方式
3、说说线程的生命周期及五种基本状态?
4、ynchronized 的作用
5、ThreadLocal 是什么?有哪些使用场景?
6、并发编程三大特性
7、如何保证多线程顺序执行
8、多线程应用场景,项目用到了?

JVM

1、说一下 JVM 运行时数据区
2、类加载流程
3、创建对象方式
3、垃圾回收算法
4、双亲委派机制
5、堆内存划分
6、JVM调优从哪几方面考虑

总结:这是最新的11月份找工作一些经验,也是根据自己情况汇总了网上相关资源,或许不是那么严谨或者完整,但起码面试官问到了,你知道从哪里回答,也请各位大佬多多指教!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值