Java基础
1. ==和equals的区别?
==解释
基本类型和引用类型 == 的作用效果是不同的,如下
基本类型:比较的是值是否相同;
引用类型:比较的是引用是否相同;
代码示例:
1 | String x = "string"; |
注:因为 x 和 y 指向的是同一个引用,所以 == 也是 true,而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。
equals解释
equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。看下面的代码就明白了。
首先来看默认情况下 equals 比较一个有相同值的对象,代码如下:
1 | class Student { |
1 | //equals源码 |
注:原来是 String 重写了 Object 的 equals 方法,把引用比较改成了值比较。
总结 :== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
2.重载和重写的区别?
重载(Overload)
发生在同一个类中,方法名必须相同,参数类型不同、个数不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写(Override)
发生在父子类中,方法名、参数列表必须相同,个数相同,返回值相同,如果父类方法访问修饰符为private则子类就不能重写该方法。
3.什么是多态?
①指允许不同类的对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式。
②接口的不同表现方式
是同一个行为具有多个不同表现形式或形态的能力,多态就是同一接口,使用不同的实例而执行不同操作。
必要条件:继承、重写、向上转型
4.String、StringBuffer、StringBuilder的区别?
一、字符修改上的区别
String: 不可变字符序列(String类中使用final关键字修饰字符数组来保存字符串,所以String对象是不可变的)、线程安全
StringBuffer: 可变字符序列、效率低、线程安全
StringBuilder: 可变字符序列、效率高、线程不安全
二、初始化上的区别
String可以空赋值,后者不行,会报错
小结:(1)如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据 StringBuilder。
5.final、finally、finalize关键字使用区别?
final
final修饰类,方法,基本类型变量,引用的时候分别有不同的意思
修饰类 表示该类不能被继承
修饰方法 表示该方法不能被重写
修饰基本类型变量 表示该变量只能被赋值一次
修饰引用 表示该引用只有一次指向对象的机会
finally
finally 是用于异常处理的场面,无论是否有异常抛出,都会执行
finalize
finalize是Object的方法,所有类都继承了该方法。 当一个对象满足垃圾回收的条件,并且被回收的时候,其 finalize()方法就会被调用。
6.抽象类和接口的区别?
接口和抽象类从使用上看非常的相似,那么下面通过以下的表格对两者进行区分:
No | 比较 | 抽象类 | 接口 |
---|---|---|---|
1 | 关键字 | 使用 abstract class 声明 | 使用 interface 声明 |
2 | 定义 | 包含一个抽象方法的类 | 抽象方法和全局常量的集合 |
3 | 组成 | 属性、方法、构造、常量、抽象方法 | 全局常量、抽象方法 |
4 | 权限 | 抽象方法的权限可以任意 | 只能是 public 权限 |
5 | 使用 | 通过 extends 关键字继承抽象类 | 通过 implements 关键字实现接口 |
6 | 局限 | 抽象类存在单继承局限 | 没有此局限,一个子类可以实现多个接口 |
7 | 顺序 | 一个子类只能先继承抽象类再实现多个接口 | 一个子类只能先继承抽象类再实现多个接口 |
8 | 设计模式 | 模板设计 | 工厂设计、代理设计 |
9 | 实际作用 | 只能做一个模板使用 | 作为标准、表示能力 |
10 | 使用 | 两者没有什么本质的区别,但是从实际上来看,如果一个程序中抽象类和接口都可以 使用的话,则一定要优先考虑接口,因为接口可以避免单继承所带来的局限。 |
两者没有什么本质的区别,但是从实际上来看,如果一个程序中抽象类和接口都可以 使用的话,则一定要优先考虑接口,因为接口可以避免单继承所带来的局限。 |
11 | 实例化 | 都是依靠对象多态性,通过子类进行对象实例化的 | 都是依靠对象多态性,通过子类进行对象实例化的 |
7.|与||, &与&&的区别?
一、||和&&定义为逻辑运算符,I 和 & 定义为位运算符。
&&如果两个操作数都非零,则条件为真;
|| 如果两个操作数中有任意一个非零,则条件为真。
二、&是长位与
&&是短位与
&&表示前面如果false, 就会中断。
三、||表示或者的意思,&&表示并且的意思。
8.static关键字的使用?
static变量也称作静态变量
static方法一般称作静态方法,通常在一个类中定义一个方法
static关键字在类中可以声明属性和方法
声明的属性将称为全局属性,声明的方法将称为类方法
概念:方便在设创建对象的情况下来进行调用,static关键字修饰类、方法、变量、代码块
特点,static是一个修饰符,用于修饰成员
注:
使用static声明的方法,不能访问非static的操作
非static声明的方法,可以访问static声明的属性和方法。
- static修饰的成员变量和方法,从属于类
- 普通变量和方法从属于对象
- 静态方法不能调用非静态成员,编译会报错
static变量也称为静态变量,静态变量和非静态变量的区别:
- 静态变量被所有对象共享,在内存中只有一个副本,在类初次加载的时候才会初始化
- 非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响
9.Java四个修饰符 private ,default,protected,private区别,修饰类,方法,属性?
作用域 | 当前类 | 同包 | 子类 | 其他 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
default:即不加任何访问修饰符,通常称为“默认访问权限“或者“包访问权限”。该模式下,只允许在同一个包中进行访问。
protected: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护访问权限”。被其修饰的属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包访问。
10.String是基本数据类型吗?
String 不属于基础类型,基础类型有 8 种:byte、boolean、char、short、int、float、long、double,而 String 属于对象。
11.String str=”i”与 String str=new String(“i”)一样吗?
不一样,因为内存的分配方式不一样。String str=”i”的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
12.String str = new String(“abc”)创建了几个对象?
创建了两个,”abc”本身创建在常量池,通过new又创建在堆中引用。
13.String类的常用方法有哪些?
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空白。
- split():分割字符串,返回一个分割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串长度。
- toLowerCase():将字符串转成小写字母。
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比较。
14.Object类的常见方法有哪些?
1 | public final native Class getClass()//native方法,用于返回当前运行时对象的Class对象,使用了 final关键字修饰,故不允许子类重写。 |
15.错误(Error)和异常(Exception)的区别?
常见的错误:
Error:是程序无法处理的错误
Java虚拟机运行错误(VirtualMachineError)
StackOverFlowError、 OutOfMemoryError
这些错误是不可查的。
Exception: 是程序本身可以处理的异常
常见的异常:NullPointerException(空指针异常)、ArithmeticException(算术运算异常)、ArrayIndexOutBoundsException(数组下标越界异常)等等。
注:异常能被程序本身可以处理,错误无法处理。
16.Int和 Integer区别,什么是自动拆箱和装箱?
int: 基本数据类型, Integer:是int的包装类
装箱:将基本数据类型用它们对应的引用类型包装起来。
拆箱:将包装类型转化为基本数据类型。
17.什么是Java序列化?什么情况下需要序列化?
简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。
什么情况下需要序列化:
- 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
- 当你想用套接字在网络上传送对象的时候;
- 当你想通过RMI传输对象的时候;
18.枚举在哪些场景使用?
常用场景
1、作为普通的常量使用,如星期、季节、颜色、城市等等;通常配合switch语句使用。
例1:
定义
1 | //周一到周天枚举 |
使用
jdk1.7开始switch语句开始支持String类型,而jdk1.6之前只支持int,char,enum类型,使用枚举的话,能使代码的可读性大大增强
1 | public class TestEnum { |
2、往枚举中添加变量、构造函数、以达到灵活获取指定值的目的(注: Java 要求必须先定义 enum 实例,否则编译会报错);通常用于一些业务系统中定义一些固定值,如用于匹配db中的字段值等。
例2:
1 | //定义 利用构造函数将变量赋值,然后通过get方法获取指定值 |
总结
1、所有枚举类都是继承java.lang.Enum类,可以把 enum 看成是一个普通的 class,它们都可以定义一些属性和方法,不过,因为java是单继承,所以枚举不能再继承其它类,但枚举可以实现接口。
2、不使用枚举也可以用其它方式代替实现类似的效果,而灵活地运用枚举可以带来许多便利,但也切忌为了用枚举而用枚举,这样反而可能带来不利的影响。
19.什么io, 举例各io分别使用什么场景?
IO流分类:输入流和输出流,字节流和字符流
基类:字节流InputStream和OutputStream, 字符流Reader和wirter
![](C:\Users\潘潘\Desktop\面试资料\Java io.jpg)
20.Java8新特性,Lambda、Stream分别怎么使用?
lambda表达式
表示的是匿名函数
语法: 参数、箭头、然后是方法体
代码示例:
1 | //匿名内部类 |
Stream转化指定对象
// 1、数组
Object[] ints = stream.toArray(String[]::new);
// 2、字符串
String collect1 = stream.collect(Collectors.joining()).toString();
// 3、集合
List
// 4、map
Map<String, String> map1 = (Map<String, String>) stream.collect(Collectors.toMap(x -> x, y -> “value” + y));
21.什么是泛型
JDK5引入的新特性,泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数
泛型的好处
1 | 1.类型安全。泛型的主要目的就是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设只能我们自己记或者代码注释; |
泛型方法
1 | 泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型。 |
注:
无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。
22.反射
反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力
java反射:
在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法
Java反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
Java集合
List、Set、Map的区别?
List是有序集合,元素可以重复
set是无序集合,元素不可重复
map是键值对,key是无需不可重复的
说下set中的元素为什么不允许重复
因为map中的key是不允许重复的,所以set中的元素不能重复
Collection和Collections的区别?
java.util.Collection 是一个 集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
java.util.Collections 是一个包装类。它包含有各种有关集合操作的 静态多态方法。此类 不能实例化,就像一 个工具类,服务于Java的Collection框架。
LinkList和ArrayList的区别?
ArrayList如果需要随机访问列表中的元素,特别是列表规模很大时,应该使用ArrayList.
ArrayList是数组结构,所以定位很快,但是插入和删除很慢。
LinkedList需要对列表进行大量插入和删除操作,特别是插入的元素在列表头部和中部时,则使用LinkedList
LInkedList是双向链表结构,所以插入和删除很快,但是定位慢。
HashMap和HashTable、TreeMap的区别?
TreeMap是有序的,HashMap和HashTable是无序的。
HashTable的key和value都不允许为null,HashMap的key和value都允许为null。
HashTable是线程安全的,HashMap是非线程安全的。
初始容量的不同
HashTable的默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
解释为什么set的元素是不允许重复?
因为map中的key是不允许重复的,所以set中的元素不能重复
Hash冲突处理?
一种可能冲突的解决方法是采用二次散列函数
另一种方法是在每一个表项中存储一个条目的列表,所有的散列得到同一个表索引的表链的条目都会添加到这个索引值对应的列表中,在执行键检索时,只需要遍历这个列表即可,检查每一个元素是否相等,直到找到匹配。
为什么要重写equals和hashcode方法?
需要将对象放入hashmap、hashset等集合中的类,就需要重写
equals方法没有重写的话,用于判断对象的内存地址引用是否用一个地址,重写之后一般用来比较对象的内容是否相等
HashMap的底层原理?
一:HashMap的节点:HashMap是一个集合,键值对的集合,源码中每个节点用Node<K,V>表示
1 | static class Node<K,V> implements Map.Entry<K,V> { |
Node是一个内部类,这里的key为键,value为值,next指向下一个元素,可以看出HashMap中的元素不是一个单纯的键值对,还包含下一个元素的引用。
二:HashMap的数据结构:HashMap的数据结构为 **数组+(链表或红黑树)**,上图:
![](C:\Users\潘潘\Pictures\Saved Pictures\1656677875696.jpg)
注:红黑树,是一种自平衡的二叉查找数,是一种高效的查找数根节点是黑色的。
为什么采用这种结构来存储元素呢?
数组的特点:查询效率高,插入,删除效率低。
链表的特点:查询效率低,插入删除效率高。
在HashMap底层使用数组加(链表或红黑树)的结构完美的解决了数组和链表的问题,使得查询和插入,删除的效率都很高。
三:HashMap存储元素的过程:
有这样一段代码:
1 | HashMap<String,String> map = new HashMap<String,String>(); |
现在我要把键值对 “刘德华”,”张惠妹”存入map:
第一步:计算出键“刘德华”的hashcode,该值用来定位要将这个元素存放到数组中的什么位置.
数组是何如转成list?
Map中如何取key, value值?
JVM
什么是类加载机制?
· 加载:根据查找路径找到相应的 class 文件然后导入;
· 检查:检查加载的 class 文件的正确性;
· 准备:给类中的静态变量分配内存空间;
· 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
· 初始化:对静态变量和静态代码块执行初始化工作。
加载、连接(验证、准备、解析)、初始化、使用、卸载
加载:编译后的.class静态文件转换到内存中(方法区)
连接:将Java类的二进制代码合并到JVM的运行状态之中的过程
初始化:执行初始化方法()的过程,是类加载的最后一步,这一步真正执行初始化的代码逻辑。
卸载:执行了System.exit()方法
程序正常执行结束
程序在执行过程中遇到了异常或错误而异常终止
由于操作系统出现错误而导致Java虚拟机进程终止
什么是双亲委派?
一个类加载器需要加载类,那么首先它会把这个类加载请求委派给父类加载器去完成,如果
父类还有父类则委托, 一直递归到顶层。
JVM的原理,谈谈你对JVM的理解?
· 类加载器(ClassLoader)
· 运行时数据区(Runtime Data Area)
· 执行引擎(Execution Engine)
· 本地库接口(Native Interface)
组件的作用: 首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
JVM内存模型有哪些?
程序计数器:当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有。
Java虚拟栈:存放基本数据类型、对象的引用、方法出口等,线程私有。
Native方法栈:和虚拟栈相似,只不过它服务于Native方法,线程私有。
Java堆:java内存最大的一块,所有对象实例、数组都存放在java堆,GC回收的地方,线程共享。
方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。(即永久带),回收目标主要是常量池的回收和类型的卸载,各线程共享
GC垃圾回收机制?
不同的对象生命周期不同。把不同生命周期的对象放在不同代上,不同代上采用最合适它的垃圾回收方式进行回收。
JVM中共划分为三个代:年轻代、年老代和持久代,
年轻代:存放所有新生成的对象;
年老代:在年轻代中经历了N次垃圾回收仍然存活的对象,将被放到年老代中,故都是一些生命周期较长的对象;
持久代:用于存放静态文件,如Java类、方法等。
判断对象是否需要回收的方法有两种:
1.引用计数
当某对象的引用数为0时,便可以进行垃圾收集。
2.对象引用遍历
果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,gc必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。
jvm有哪几种垃圾回收器?
· Serial:最早的单线程串行垃圾回收器。
· Serial Old:Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选预案。
· ParNew:是 Serial 的多线程版本。
· Parallel 和 ParNew 收集器类似是多线程的,但 Parallel 是吞吐量优先的收集器,可以牺牲等待时间换取系统的吞吐量。
· Parallel Old 是 Parallel 老生代版本,Parallel 使用的是复制的内存回收算法,Parallel Old 使用的是标记-整理的内存回收算法。
· CMS:一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统。
· G1:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项。
多线程
进程、线程、和携程的区别?
- 进程:拥有自己独立的堆和栈,既不共享堆,也不共享栈,进程由操作系统调度;
- 线程:拥有自己独立的栈和共享的堆,共享堆,不共享栈,标准线程由操作系统调度;
- 协程:拥有自己独立的栈和共享的堆,共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
进程: 进程是一个具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统资源分配和独立运行的最小单位;
线程: 线程是进程的一个执行单元,是任务调度和系统执行的最小单位;
协程: 协程是一种用户态的轻量级线程,协程的调度完全由用户控制。
并发和并行的区别?
并发:多个线程在同一个cpu下运行
并行:各个线程分别在多个cpu下运行
线程有哪几种创建方式?
创建线程有4种方式:
1.继承java.lang.Thread类:extends
2.实现Runnable接口: implements
3.实现callable接口
4.线程池创建
run()和start()的区别?
run() 仅仅是封装被线程执行的代码,直接调用是普通方法
start() 启动了线程,由JVM去调用该线程run()方法
多线程的有哪几种状态?
新建:当一个Thread类或者其子类被调用时,新生的线程对象处于新建状态,
此时它已经有了相应的内存空间和其他资源,在新建状态下的线程不会被执行;
就绪:当线程被创建,并调用了start方法,该线程就进入了就绪状态,
该状态下的线程位于可运行池(线程池)等待获得cpu的使用权;
运行:处于该状态的线程专用cpu
只有处于就绪状态的线程才有机会转为运行状态;
阻塞:放弃cpu资源,让其他资源获取,
五种阻塞原因:
1.位于等待池:执行wait方法,jvm就会将该线程放于等待池;
2.位于锁池:试图获得某个对象同步锁时,如果该对象的同步锁已经被其他线程占用,
jvm会将这个线程放在这个对象的锁池中;
3.执行了sleep方法;
4.调用其他线程join方法;
5.发出IO请求时;
死亡:run方法结束,线程结束;
什么是线程死锁?如何避免死锁?
多个线程同时被阻塞,它们中的一个或者等待某个资源释放
由于线程被无限期地阻塞,因为程序不可能正常能不能终止
如何避免线程死锁?
我们只要破坏产生死锁的四个条件中的其中一个就可以了
破坏互斥条件
这个条件我们办法破坏,因为我们用锁本来就是想让他们互斥的
破坏请求与保持条件
一次性申请所有的资源
破坏不剥夺条件
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源
破坏循环等待条件
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
多线程锁的使用场景?
1、常见的浏览器、Web服务(现在写的web是中间件帮你完成了线程的控制),web处理请求,各种专用服务器(如游戏服务器)
2、servlet多线程
3、FTP下载,多线程操作文件
4、数据库用到的多线程
5、分布式计算
6、tomcat,tomcat内部采用多线程,上百个客户端访问同一个WEB应用,tomcat接入后就是把后续的处理扔给一个新的线程来处理,这个新的线程最后调用我们的servlet程序,比如doGet或者dpPost方法
7、后台任务:如定时向大量(100W以上)的用户发送邮件;定期更新配置文件、任务调度(如quartz),一些监控用于定期信息采集
8、自动作业处理:比如定期备份日志、定期备份数据库
9、异步处理:如发微博、记录日志
10、页面异步处理:比如大批量数据的核对工作(有10万个手机号码,核对哪些是已有用户)
11、数据库的数据分析(待分析的数据太多),数据迁移
12、多步骤的任务处理,可根据步骤特征选用不同个数和特征的线程来协作处理,多任务的分割,由一个主线程分割给多个线程完成
13、desktop应用开发,一个费时的计算开个线程,前台加个进度条显示
14、swing编程
假如执行100个任务,用多线程怎么如何保证全部执行?
MyBatis
#{}和${}的区别?
#{}预编译处理,防止sql注入
${}字符串替换,不能防止sql注入
什么是sql注入,是怎么产生的?何如防止sql注入?
SQL注入的原理
SQL 注入的原理主要有以下 4 点:
1)恶意拼接查询
我们知道,SQL 语句可以查询、插入、更新和删除数据,且使用分号来分隔不同的命令。例如:
1 | SELECT * FROM users WHERE user_id = $user_id |
其中,user_id 是传入的参数,如果传入的参数值为“1234; DELETE FROM users”,那么最终的查询语句会变为:
1 | SELECT * FROM users WHERE user_id = 1234; DELETE FROM users |
如果以上语句执行,则会删除 users 表中的所有数据。
2)利用注释执行非法命令。
SQL 语句中可以插入注释。例如:
1 | SELECT COUNT(*) AS 'num' FROM game_score WHERE game_id=24411 AND version=$version |
如果 version 包含了恶意的字符串'-1' OR 3 AND SLEEP(500)--
,那么最终查询语句会变为:
1 | SELECT COUNT(*) AS 'num' FROM game_score WHERE game_id=24411 AND version='-1' OR 3 AND SLEEP(500)-- |
以上恶意查询只是想耗尽系统资源,SLEEP(500) 将导致 SQL 语句一直运行。如果其中添加了修改、删除数据的恶意指令,那么将会造成更大的破坏。
3)传入非法参数
SQL 语句中传入的字符串参数是用单引号引起来的,如果字符串本身包含单引号而没有被处理,那么可能会篡改原本 SQL 语句的作用。 例如:
1 | SELECT * FROM user_name WHERE user_name = $user_name |
如果 user_name 传入参数值为 G’chen,那么最终的查询语句会变为:
1 | SELECT * FROM user_name WHERE user_name ='G'chen' |
一般情况下,以上语句会执行出错,这样的语句风险比较小。虽然没有语法错误,但可能会恶意产生 SQL 语句,并且以一种你不期望的方式运行。
4)添加额外条件
在 SQL 语句中添加一些额外条件,以此来改变执行行为。条件一般为真值表达式。例如:
1 | UPDATE users SET userpass='$userpass' WHERE user_id=$user_id; |
如果 user_id 被传入恶意的字符串“1234 OR TRUE”,那么最终的 SQL 语句会变为:
1 | UPDATE users SET userpass= '123456' WHERE user_id=1234 OR TRUE; |
这将更改所有用户的密码。
避免SQL注入
对于 SQL 注入,我们可以采取适当的预防措施来保护数据安全。下面是避免 SQL 注入的一些方法。
1. 过滤输入内容,校验字符串
过滤输入内容就是在数据提交到数据库之前,就把用户输入中的不合法字符剔除掉。可以使用编程语言提供的处理函数或自己的处理函数来进行过滤,还可以使用正则表达式匹配安全的字符串。
如果值属于特定的类型或有具体的格式,那么在拼接 SQL 语句之前就要进行校验,验证其有效性。比如对于某个传入的值,如果可以确定是整型,则要判断它是否为整型,在浏览器端(客户端)和服务器端都需要进行验证。
2. 参数化查询
参数化查询目前被视作是预防 SQL 注入攻击最有效的方法。参数化查询是指在设计与数据库连接并访问数据时,在需要填入数值或数据的地方,使用参数(Parameter)来给值。
MySQL 的参数格式是以“?”字符加上参数名称而成,如下所示:
1 | UPDATE myTable SET c1 = ?c1, c2 = ?c2, c3 = ?c3 WHERE c4 = ?c4 |
在使用参数化查询的情况下,数据库服务器不会将参数的内容视为 SQL 语句的一部分来进行处理,而是在数据库完成 SQL 语句的编译之后,才套用参数运行。因此就算参数中含有破坏性的指令,也不会被数据库所运行。
3. 安全测试、安全审计
除了开发规范,还需要合适的工具来确保代码的安全。我们应该在开发过程中应对代码进行审查,在测试环节使用工具进行扫描,上线后定期扫描安全漏洞。通过多个环节的检查,一般是可以避免 SQL 注入的。
有些人认为存储过程可以避免 SQL 注入,存储过程在传统行业里用得比较多,对于权限的控制是有一定用处的,但如果存储过程用到了动态查询,拼接 SQL,一样会存在安全隐患。
下面是在开发过程中可以避免 SQL 注入的一些方法。
1. 避免使用动态SQL
避免将用户的输入数据直接放入 SQL 语句中,最好使用准备好的语句和参数化查询,这样更安全。
2. 不要将敏感数据保留在纯文本中
加密存储在数据库中的私有/机密数据,这样可以提供了另一级保护,以防攻击者成功地排出敏感数据。
3. 限制数据库权限和特权
将数据库用户的功能设置为最低要求;这将限制攻击者在设法获取访问权限时可以执行的操作。
4. 避免直接向用户显示数据库错误
攻击者可以使用这些错误消息来获取有关数据库的信息。
mybatis有哪几种分页方式?
分页方式:逻辑分页和物理分页。
逻辑分页: 使用 MyBatis 自带的 RowBounds 进行分页,它是一次性查询很多数据,然后在数据中再进行检索。
物理分页: 自己手写 SQL 分页或使用分页插件 PageHelper,去数据库查询指定条数的分页数据的形式。
mybatis的一级缓存和二级缓存?
· 一级缓存:基于 PerpetualCache 的 HashMap 本地缓存,它的声明周期是和 SQLSession 一致的,有多个 SQLSession 或者分布式的环境中数据库操作,可能会出现脏数据。当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认一级缓存是开启的。
· 二级缓存:也是基于 PerpetualCache 的 HashMap 本地缓存,不同在于其存储作用域为 Mapper 级别的,如果多个SQLSession之间需要共享缓存,则需要使用到二级缓存,并且二级缓存可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)。
开启二级缓存数据查询流程:二级缓存 -> 一级缓存 -> 数据库。
缓存更新机制:当某一个作用域(一级缓存 Session/二级缓存 Mapper)进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
mybatis的实现原理?
mybatis应用程序通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件(也可以用Java文件配置的方式,需要添加@Configuration)来构建SqlSessionFactory(SqlSessionFactory是线程安全的);
然后,SqlSessionFactory的实例直接开启一个SqlSession,再通过SqlSession实例获得Mapper对象并运行Mapper映射的SQL语句,完成对数据库的CRUD和事务提交,之后关闭SqlSession。
————————————————
版权声明:本文为CSDN博主「圣斗士Morty」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014745069/article/details/80788127
Spring
什么是spring?
Spring是一种轻量级的开发框架,为了提高开发效率以及系统的可维护性。
为什么用spring?
方便解耦,简化开发
核心技术:AOP、IOC
IOC: 控制反转 (高内聚低耦合)
我们平时创建对象需要自己手动创建,现在对象都是通过spring容器ApplicationContext创建
也就是ioc容器来控制对象
AOP: 面向切面编程,并且把应用业务逻辑和系统服务分开
是通过预编译方式和运行期动态代理实现程序功能的统一维护的技术
比如当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日志、权限验证、事务等功能时,
只能在在每个对象里引用公共行为。这样做不便于维护,而且有大量重复代码。
AOP的出现弥补了OOP的这点不足。
SpringBoot自动装配原理?
1.SpringBoot启动会加载大量的自动配置类
2.我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;
3.我们再来看这个自动配置类中到底配置了那些组件;(只要我们要用的组件有,我们就不需要再来配置了)
4.给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们就可以在配置文件中指定这些属性的值。
Springboot自动装配核心注解@EnableAutoConfiguration xxxxAutoConfiguration:自动配置类
给容器中添加组件 @ComponentScan注解
配置 spring.properties xxxxProperties:封装配置文件中相关属性
AutoConfigurationImportSelector<DeferredImportSeletor<ImportSeletor
spring的相关注解?
@controller:用于定义控制器类,在spring项目中由控制器负责将用户发来的URL请求转发到对应的服务接口(service层),一般这个注解在类中,通常方法需要配合注解@RequestMapping。
对应Spring MVC的控制层,主要用户接受用户请求并调用Service层返回数据给前端页面。
1 | 1 @Autowired:它可以对类成员变量、方法及构造函数进行标注,让 spring 完成 bean 自动装配的工作。 |
@RequestMapping:提供路由信息,负责URL到Controller中的具体函数的映射。
@Import:用来导入其他配置类。
@Value:注入Spring boot application.properties配置的属性的值。
@Bean:用@Bean标注方法等价于XML中配置的bean。
@Service:一般用于修饰service层的组件
@ComponentScan :组件扫描,可自动发现和装配一些Bean。
@Component:泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
通用的注解,可标记任意类为Spring组件。
@Repository : 对应持久层即Dao层,主要用于数据库相关操作。
@Configuration:相当于传统的xml配置文件,如果有些第三方库需要用到xml文件,建议仍然通过@Configuration类作为项目的配置主类——可以使用@ImportResource注解加载xml配置文件。
1 | @Configuration: 指明当前类是一个配置类; 就是来替代之前的Spring配置文件 |
@SpringBootConfiguration: Spring Boot的配置类 @Configuration: 配置类上来标注这个注解
@EnableAutoConfiguration: 开启自动配置功能
@ImportResource:用来加载xml配置文件。
@AutoConfigurationPackage: 自动配置包
@Import(AutoConfigurationPackages.Registrar.class);
spring的底层注解@import, 给容器导入一个组件; 导入的组件由AutoConfigurationPackage.Regist
ar.class;
spring、springboot、springcloud的区别?
Spring 是一种生态。
spring boot 是一种快速的开发框架。完全采用注解化(使用注解启动SpringMVC),简化XML配置,内置HTTP服务器(Tomcat、Jetty)。作用是简化Spring应用的初始搭建及开发,解决各种jar包版本冲突问题。
Spring cloud 是一套完整的微服务框架。
什么是spring bean的生命周期?
Spring的生命周期主要指创建、初始化、销毁。Bean的生命周期主要由容器进行管理,我们可以自定义bean的初始化和销毁方法,容器在bean进行到生命周期的特定时间点,来调用自定义的初始化和销毁方法。
实例化Bean –> Bean属性填充 –> 初始化Bean –>销毁Bean
spring 支持 5 种作用域,如下:
· singleton:spring ioc 容器中只存在一个 bean 实例,bean 以单例模式存在,是系统默认值;
· prototype:每次从容器调用 bean 时都会创建一个新的示例,既每次 getBean()相当于执行 new Bean()操作;
· Web 环境下的作用域:
· request:每次 http 请求都会创建一个 bean;
· session:同一个 http session 共享一个 bean 实例;
· global-session:用于 portlet 容器,因为每个 portlet 有单独的 session,globalsession 提供一个全局性的 http session。
注意: 使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。
SpringMVC的原理?
1.用户发送请求至前端控制器DispatcherServlet(也叫中央处理器).
2.DispatcherServlet收到请求调用HandlerMappering处理器映射器
3.处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet.
4.DispatcherServlet调用HandlerAdapter处理器适配器。
5.HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6.Controller执行完成返回ModelAndView.
7.HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet.
8.DisPatcherServlet将ModelAndView传给ViewReslover视图解析器。
9.ViewReslover解析后返回具体View.
10.DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11.DispatcherServlet响应用户。
什么是spring事务,spring事务的传播方式有哪些?
spring事务主要是声明式事务和编程式事务
PROPAGATION_SUPPORTS :支持(使用)当前事务,如果不存在事务就不使用事务
PROPAGATION_MANDATORY :支持(使用)当前事务,如果不存在就抛出异常
PROPAGATION_REQUIRES_NEW :如果有事务存在,则挂起当前事务,创建一个新事务。(不在同一事务)
PROPAGATION_NOT_SUPPORTED :已非事务的方式运行,如果有事务存在,则挂起当前事务。
PROPAGATION_NEVER :已非事务的方式运行,如果有事务存在,则抛出异常。
PROPAGATION_NESTED : 如果当前事务存在,则嵌套事务执行
多个事务方法相互调用时,事务如何在这些方法间传播?
在方法a中调用方法b :如果方法a具有事务,则b方法使用方法a的事务。 如果a没有事务,请创建新事务。(A,b在同一事务中)
SpringCloud五大组件?
Eureka: 注册中心,用于服务的注册和发现
Ribbon/Feign: 负载均衡/服务调用
Hystrix: 断路器,提高分布式系统的弹性
GateWay/Zuul: 网关管理,由GateWay网关转发请求给对应的服务,服务同一的转发,以及权限管理和过滤
SpringConfig: 分布式配置中心
数据库
数据库的三范式是什么?
第一范式:每个列都不可以在拆分。
第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。
第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。
Oracle和MySQL的区别?
1、Oracle是大型数据库,而MySQL是中小型数据库。但是MySQL是开源的,Oracle是收费的
2、Oracle的内存占有量非常大,而mysql非常小
3、MySQL支持主键自增长,插入时会自动增长。Oracle主键一般使用序列。
4、MySQL分页用limit关键字,而Oracle使用rownum字段表明位置
5、MySQL中0、1判断真假,Oracle中true false
6、MySQL中命令默认commit,但是Oracle需要手动提交
7、MySQL在windows环境下大小写不敏感,在linux环境下区分大小写,Oracle不区分
varchar和varchar2的区别?
varchar是标准sql里面的。 varchar2是oracle提供的独有的数据类型。
varchar对于汉字占两个字节,对于数字,英文等是一个字节,占的内存小,varchar2都是占两个字节。
varchar对空串不处理,varchar2将空串当做null来处理。
varchar存放固定长度的字符串,最大长度是2000,varchar2是存放可变长度的字符串,最大长度是4000。
ACID是什么?
原子性,一致性,隔离性,持久性
MySQL事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)
· Atomicity(原子性):一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
· Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
· Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
· Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
多个并发事务可能引发的问题
脏读:脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。
幻读:幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。
不可重复读:对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。
MySQL数据库有哪些函数?
MySQL 中的函数主要分为以下四类: 字符串函数、数值函数、日期函数、流程函数。
MySQL有哪些索引,它们分别在那种场景下使用?
MySQL主要有五种索引类型,分别是:
- 普通索引(NORMAL)
- 唯一索引(UNIQUE)
- 主键索引 (PRIMARY)
- 组合索引
- 全文索引(FULLTEXT)
按数据结构分类可分为:B+tree索引、Hash索引、Full-text索引。 按物理存储分类可分为:聚簇索引、二级索引(辅助索引)。 按字段特性分类可分为:主键索引、普通索引、前缀索引。 按字段个数分类可分为:单列索引、联合索引(复合索引、组合索引)。
假如有a表数据,b表数据,如何保证两个表的数据库一致性?还有什么是一致性?
使用唯一索引或者主键索引可以保证数据的唯一性
从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中, C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手 段。数据库必须要实现AID三大特性,才有可能实现一致性。例如,原子性无法保证,显然一致性也 无法保证。
从应用层面,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据
Oracle和MySQL的分页查询?
oracle数据库分页
select * from
(select a.*,rownum rc from 表名 where rownum<=endrow) a
where a.rc>=startrow;
MySQL数据库分页
Select * from 表名 limit startrow,pagesize; (Pagesize为每页显示的记录条数)
优化写法 Select a.* from 表名 a,(Select id from 表名 order by id limit startrow,pagesize) b where a.id=b.id;
MySQL性能优化有哪些?
1.对查询进行优化,建立索引,加快检索速度
2.避免在where子句中使用 != 和 <>操作符 or来连接条件
3.尽量避免使用游标,效率较差
任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
应尽量避免在where子句中对字段进行null值判断
否则将导致引擎放弃使用索引而进行全表扫描,如:
1 | Select id from t where num is null |
应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。
应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描,如:
1 | Select id fromt where num=10 or Name ='admin' |
· 为搜索字段创建索引。
· 避免使用 select *,列出需要查询的字段。
· 垂直分割分表。
· 选择正确的存储引擎。
MySQL的内连接、左连接、右连接有什么区别?
内连接关键字:inner join;左连接:left join;右连接:right join。
内连接是把匹配的关联数据显示出来;左连接是左边的表全部显示出来,右边的表显示出符合条件的数据;右连接正好相反。
Redis
什么是redis?
redis是一种nosql的以键值对数据库,它的基本数据结构String, set, list, hash, zset(有序列表)
redis将所有数据放到内存中的做法让它读写性能十分惊人
它的持久化机制使得在断电,机器故障等情况发生数据不丢失
redis的数据放在内存中,
redis还提供了键过期、发布订阅、事务、流水线、Lua脚本等多个附加功能
运用场景:
- 热点数据的缓存:redis访问速度块、支持的数据类型丰富,很适合用来存储热点数据。
- 限时业务:redis中可以使用expire命令设置一个键的生存时间,到时间后redis会删除它。因此,Redis在限时业务中的表现很亮眼。
- 计数器:incrby命令可以实现原子性的递增,所以可以运用于高并发的秒杀活动、分布式序列号的生成。
- 排行榜:关系型数据库在排行榜方面查询速度普遍偏慢,所以可以借助redis的SortedSet进行热点数据的排序。
- 分布式锁:这个主要利用redis的setnx命令进行,在后面的如何用Redis实现一个分布式锁中会进行详解。 - 延时操作:redis自2.8.0之后版本提供Keyspace Notifications功能,允许客户订阅Pub/Sub频道,以便以某种方式接收影响Redis数据集的事件。
- 分页查询、模糊查询:edis的set集合中提供了一个zrangebylex方法,通过ZRANGEBYLEX zset - + LIMIT 0 10 可以进行分页数据查询,其中- +表示获取全部数据;rangebylex key min max 这个就可以返回字典区间的数据可以利用这个特性可以进行模糊查询功能。
- 点赞,好友等相互关系的存储:Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,我们可以通过这一点实现类似共同好友等功能。
- 队列:由于redis有list push和list pop这样的命令,所以能够很方便的执行队列操作。
redis常见的错误是什么?
Could not get a resource from the pool
意思就是不能获取Redis连接池实例。
All sentinels down, cannot determine where is mymaster_9487_6389 master is running…
翻译:哨兵宕机, 不能确定主机位置
原因1:redis主从机未正常启动
原因2:写的哨兵地址不对
什么是缓存穿透?
缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决方案:最简单粗暴的方法如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们就把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
什么是缓存雪崩?
缓存雪崩就是指缓存由于某些原因(比如 宕机、cache服务挂了或者不响应)整体crash掉了,导致大量请求到达后端数据库,从而导致数据库崩溃,整个系统崩溃,发生灾难。
导致这种现象可能的原因:
1、例如 “缓存并发”,“缓存穿透”,“缓存颠簸” 等问题,这些问题也可能会被恶意攻击者所利用。
2、例如 某个时间点内,系统预加载的缓存周期性集中失效了。解决方法:可以通过设置不同的过期时间,来错开缓存过期,从而避免缓存集中失效。
redis如何保证数据持久性
有两种方式
1RDB持久化():RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
2AOF持久化():一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。然后在服务重启以后,会执行这些命令来恢复数据。
redis是单线程还是多线程
Redis4.0之前是单线程运行的;Redis4.0后开始支持多线程。Redis4.0之前使用单线程的原因:
1、单线程模式方便开发和调试;
2、Redis内部使用了基于epoll的多路复用;
3、Redis主要的性能瓶颈是内存或网络带宽
6.0版本引用多线程
redis为什么引入多线程
Redis基于内存操作,CPU并不是性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽。在6.0的版本
中引入了多线程。
Redis 常见的性能问题都有哪些?如何解决?
1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。
2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内
redis如何搭建主从复制
拷贝多个redis.conf文件include(写绝对路径)
开启daemonize yes
Pid文件名字pidfile
指定端口port
Log文件名字
dump.rdb名字dbfilename
Appendonly 关掉或者换名字
简述缓存穿透
缓存穿透指缓存和数据库均没有需要查询的数据,攻击者不断发送这种请求,使数据库压力过大。
简述缓存穿透的解决方法
- 在数据库操作访问前进行校验,对不合法请求直接返回。
- 对于经常被访问的,并且数据库没有的键,缓存层记录键=null。
简述缓存击穿
缓存击穿指缓存中没有数据,但数据库中有该数据。一般这种情况指特定数据的缓存时间到期,但由于并发用户访问该数据特别多,因此去数据库去取数据,引起数据库访问压力过大
简述缓存穿透的解决方法
- 设置热点数据永远不过期。
- 对并发读数据设置并发锁,降低并发性
简述缓存雪崩
缓存雪崩指缓存中一大批数据到过期时间,而从缓存中删除。但该批数据查询数据量巨大,查询全部走数据库,造成数据库压力过大。
简述缓存雪崩的解决方法
- 缓存数据设置随机过期时间,防止同一时间大量数据过期。
- 设置热点数据永远不过期。
- 对于集群部署的情况,将热点数据均与分布在不同缓存中。