常见面试题
一 , ==和equals的区别
== 对于基本类型和引用类型的作用效果是不同的:
• 对于基本数据类型来说,== 比较的是值。
• 对于引用数据类型来说,== 比较的是对象的内存地址。
因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
equals()不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。
Object类equals()` 方法:
public boolean equals(Object obj) {
return (this == obj);
}
equals() 方法存在两种使用情况:
• 类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。
• 类重写了 equals()方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
举个例子(这里只是为了举例。实际上,你按照下面这种写法的话,像 IDEA 这种比较智能的 IDE 都会提示你将 == 换成 equals() ):
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa == bb);// true
System.out.println(a == b);// false
System.out.println(a.equals(b));// true
System.out.println(42 == 42.0);// true
String 中的 equals 方法是被重写过的,因为 Object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
String类equals()方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
二 什么是多态
多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。`
多态的特点:
• 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
• 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
• 多态不能调用“只在子类存在但在父类不存在”的方法;
• 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
三 接口和抽象类有什么共同点和区别?
共同点 :
• 都不能被实例化。
• 都可以包含抽象方法。
• 都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)。
区别 :
• 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
• 一个类只能继承一个类,但是可以实现多个接口。
• 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
四 为什么重写 equals() 时必须重写 hashCode() 方法?
因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。
思考 :重写 equals() 时没有重写 hashCode() 方法的话,使用 HashMap 可能会出现什么问题。
总结 :
• equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
• 两个对象有相同的 hashCode 值,他们也不一定是相等的(哈希碰撞)。
五 String、StringBuffer、StringBuilder 的区别?
可变性
String 是不可变的
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
//...
}
线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
操作少量的数据: 适用 String
单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
多线程操作字符串缓冲区下操作大量数据: 适用 `StringBuffer
六 char和varchar的区别是什么?
1.char类型的长度是固定的,varchar的长度是可变的。
这就表示,存储字符串'abc',使用char(10),表示存储的字符将占10个字节(包括7个空字符)
使用varchar(10),则表示只占3个字节,10是最大值,当存储的字符小于10时,按照实际的长度存储。
2.char类型的效率比varchar的效率稍高
3.varchar与varchar2的区别
varchar2是oracle开发的一个数据类型。
sql标准的varchar可以存储空字符串,oracle的varchar2还可以存储NULL值,如果想要有向后兼容的能力建议使用varchar2
4.varchar比char节省空间,但是在效率上比char稍差些。既要获得效率即必须牺牲一点空间,这就是设计上的""以空间换时间""
varchar虽然比char节省空间,但是一个varchar列经常被修改,而且每次修改的数据长度不同,这会引起“行迁移的现象”,
而这造成的多余的I/O,是数据库设计中尽量避免的,在这种情况下使用char代替varchar会更好些。
七 Java集合框架
Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接口。我们比较常用的是Set、List,Map接口不是collection的子接口。
Collection集合主要有List和Set两大接口
• List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
• Set:不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。
Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
• Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap
八 ArrayList 和 LinkedList 的区别是什么?
• 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
• 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
• 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
• 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
• 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
• 综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
• LinkedList 的双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
九 cookie和session的区别?
1.存储位置不同
cookie的数据信息存放在客户端浏览器上。
session的数据信息存放在服务器上。
2.存储容量不同
单个 cookie保存的数据<=4KB,一个站点最多保存 20个 Cookie。
对于 session来说并没有上限,但出于对服务器端的性能考虑,session内不要存放过多的东西,并且设置 session删除机制。
3.存储方式不同
cookie中只能保管 ASCII字符串,并需要通过编码方式存储为 Unicode字符或者二进制数据。
session中能够存储任何类型的数据,包括且不限于 string,integer,list,map等。
4.隐私策略不同
cookie对客户端是可见的,别有用心的人可以分析存放在本地的 cookie并进行 cookie 欺骗,所以它是不安全的。 session存储在服务器上,不存在敏感信息泄漏的风险。
5.有效期上不同
开发可以通过设置 cookie的属性,达到使 cookie 长期有效的效果。
session依赖于名为 JSESSIONID 的 cookie,而 cookieJSESSIONID的过期时间默认为-1,只需关闭窗口该 session就会失效,因而 session不能达到长期有效的效果。
6.服务器压力不同
cookie保管在客户端,不占用服务器资源。对于并发用户十分多的网站,cookie是很好的选择。
session是保管在服务器端的,每个用户都会产生一个 session。假如并发访问的用户十分多,会产生十分多的session,耗费大量的内存。
十 HashMap、HashTable、ConcurrentHashMap的区别
HashMap和Hashtable的区别。
HashMap线程不安全的,Hashtable是线程安全的(方法上使用了synchronized关键字)
HashMap允许键值对都可以为null, 而Hashtable不允许键值对为null
HashMap由数组+链表/红黑树,而Hashtable依然使用数组+链表
Hashtable与ConcurrentHashMap的区别。
Hashtable在进行元素操作的时候,是锁住整个table,而jdk1.7中的ConcurrentHashMap是采用分段的分式,锁住某个段,而jdk1.8之后去除了“分段锁”,而是改成了局部同步代码块,且是锁住的是某个Node节点,这样其他槽里的节点依然可以并发进行操作。
Hashtable和ConcurrentHashMap一样不允许键值对为null
十一 start和run的区别
1,run方法是Runnable接口中定义的,start方法是Thread类定义的。 所有实现Runnable的接口的类都需要重写run方法,run方法是线程默认要执行的方法,是绑定操作系统的,也是线程执行的入口。 start方法是Thread类的默认执行入口,Thread又是实现Runnable接口的。要使线程Thread启动起来,需要通过start方法,表示线程可执行状态,调用start方法后,则表示Thread开始执行,此时run变成了Thread的默认要执行普通方法。
2),通过start()方法,直接调用run()方法可以达到多线程的目的 通常,系统通过调用线程类的start()方法来启动一个线程,此时该线程处于就绪队列,而非运行状态,这也就意味着这个线程可以被JVM来调度执行。在调度过程中,JVM会通过调用线程类的run()方法来完成试机的操作,当run()方法结束之后,此线程就会终止。 如果直接调用线程类的run()方法,它就会被当做一个普通的函数调用,程序中任然只有主线程这一个线程。也就是说,star()方法可以异步地调用run()方法,但是直接调用run()方法确实同步的,因此也就不能达到多线程的目的。
注: run()和start()的区别可以用一句话概括:单独调用run()方法,是同步执行;通过start()调用run(),是异步执行。
十二 synchronized与lock的区别
• Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
• synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
• Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
• 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
• Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
• 性能上来说,在资源竞争不激烈的情形下,Lock性能稍微比synchronized差点(编译程序通常会尽可能的进行优化synchronized)。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
十三 线程有几种创建方式
一、继承Thread类创建线程类
二、通过Runnable接口创建线程类
三、通过Callable和Future创建线程
四.使用线程池创建(使用java.util.concurrent.Executor)
十四 线程有几种状态
线程共包括以下 5 种状态:
1. 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
• (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
• (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
• (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
十五 线程池七大参数及核心原理
public ThreadPoolExecutor(int corePoolSize, 5
int maximumPoolSize, 10
long keepAliveTime, 30
TimeUnit unit, TimeUnit.Seconds
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
几个核心参数的作用:
• corePoolSize: 线程池核心线程数最大值
• maximumPoolSize: 线程池最大线程数大小(核心线程数+临时的非核心线程数)
• keepAliveTime: 线程池中非核心线程空闲的存活时间大小
• unit: 线程空闲存活时间单位
• workQueue: 存放任务的阻塞队列
• threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。
• handler: 线城池的饱和策略事件,主要有四种类型。
工作原理
• 提交一个任务,线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务。
• 如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。
• 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。
• 如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。
四种拒绝策略
• AbortPolicy(抛出一个异常,默认的)
• DiscardPolicy(直接丢弃任务)
• DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
• CallerRunsPolicy(交给线程池调用所在的线程进行处理)
线程池都有哪几种工作队列?
• ArrayBlockingQueue
ArrayBlockingQueue(有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量。
• LinkedBlockingQueue
LinkedBlockingQueue(可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列
• DelayQueue
DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
• PriorityBlockingQueue
PriorityBlockingQueue(优先级队列)是具有优先级的无界阻塞队列;
• SynchronousQueue
SynchronousQueue(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。
针对面试题:线程池都有哪几种工作队列? 我觉得,回答以上几种ArrayBlockingQueue,LinkedBlockingQueue,Synchronou、sQueue等,说出它们的特点,并结合使用到对应队列的常用线程池(如newFixedThreadPool线程池使用LinkedBlockingQueue),进行展开阐述, 就可以啦
十六 sleep() 方法和 wait() 方法对比
共同点 :两者都可以暂停线程的执行。
区别 :
• sleep() 方法没有释放锁,而 wait() 方法释放了锁 。
• wait() 通常被用于线程间交互/通信,sleep()通常被用于暂停执行。
• wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法。sleep()方法执行完成后,线程会自动苏醒,或者也可以使用 wait(long timeout) 超时后线程会自动苏醒。
• sleep() 是 Thread 类的静态本地方法,wait() 则是 Object 类的本地方法。为什么这样设计呢?
十七 IOC与AOP
什么是IOC
IOC: Inversion of Control (控制反转/反转控制),它是一个技术思想而不是一个技术实现,在Java开发领域他所描述的事情是对象的创建和管理的问题。
与传统开发方式相比在IOC的思想开发方式下,当类A需要以来类B时,我们不要自己去new对象了,而是由IOC容器帮我们实例化对象并且去管理它,我们需要什么对象直接从IOC容器中获取即可,由此我们可以省去创建和管理对象的一系列事情,也丧失了创建、管理对象的权力。
控制反转解释:
控制:指的是对象创建(实例化、管理)的权利
反转:控制权交给外部环境了(spring框架、IoC容器)
-IOC解决了什么问题
IOC解决对象之间的耦合问题,例如当service层调用dao层时,传统方式下我们需要在service中new出dao层的具体实现类,这时当我们实现类需要改变时,service层也需要做相应的改变,这就造成了service层和dao层的强耦合。而使用IOC实例化对像时,我们只需要关注调用的dao层的接口,在service中声明接口属性,具体的实现类在IOC容器中进行切换,因此也不会产生对象中强耦合的情况。
IOC和DI的区别
IOC和DI(依赖注入)其实描述的都是对象实例化和依赖关系维护这同一件事情,只是角度不同。IOC是站在对象的角度,对象的实例化和管理交给了容器(反转);DI是站在容器的角度,容器会把对象所依赖的对象注入,例如A对象在实例化的过程中声明了B对象,那么容器就会把B对象注入给A。
什么是AOP
AOP: Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程,AOP是OOP的延续。
OOP的三大特征: 封装、继承和多态
opp思想是一种垂直纵向的继承体系,解决了代码开发中的大多数代码重复问题,例如当我们由三个类(pig、dog和cat),其中每个类中都有相同的方法(eat(); run();),那么未来避免代码重复,oop思想下我们就可以提取父类(animal),在父类中设置通用的属性(weight、height、eat(); run(); … …),让原有的类继承该类,这样子类中就可以不再重复写这些方法了,同样子类下的子类依然可以通过继承父类来避免代码的重复。
Aop底层原理
1、aop底层使用了动态代理
(1)有两种情况的动态代理:
1、有接口的情况,使用jdk动态代理
创建接口实现类对象,去增强类中的方法
2、没有接口的情况,使用CGLIB动态代理
创建子类的代理对象,去增强类中的方法
十八 spring事务的传播行为有哪几种?
spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置(默认)。
② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
④ PROPAGATIONREQUIRESNEW:创建新事务,无论当前存不存在事务,都创建新事务。
⑤ PROPAGATIONNOTSUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
十九 说说spring事务在哪些场景下会失效?
1、@Transactional 应用在非 public 修饰的方法上
注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。
2、@Transactional 注解属性 propagation 设置错误
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。 TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
3、@Transactional 注解属性 rollbackFor 设置错误
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。
4、同一个类中方法调用,导致@Transactional失效
比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
5、异常被你的 catch“吃了”导致@Transactional失效
这种情况是最常见的一种@Transactional注解失效场景,
@Transactional
private Integer A() throws Exception {
int insert = 0;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
cityInfoDict.setParentCityId(2);
/**
* A 插入字段为 2的数据
*/
insert = cityInfoDictMapper.insert(cityInfoDict);
/**
* B 插入字段为 3的数据
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?
答案:不能!
会抛出异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。
6、数据库引擎不支持事务
这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。
二十 @Autowired 和 @Resource 的区别是什么?
Autowired 属于 Spring 内置的注解,默认的注入方式为byType(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)
@Resource属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType。
二十一 SpringBean生命周期
spring bean 容器的生命周期流程如下:
实例化====》属性赋值====》初始化=====》销毁
(1)Spring 容器根据配置中的 bean 定义中实例化 bean。
(2)Spring 使用依赖注入填充所有属性,如 bean 中所定义的配置。
(3)如果 bean 实现BeanNameAware 接口,则工厂通过传递 bean 的 ID 来调用setBeanName()。
(4)如果 bean 实现 BeanFactoryAware 接口,工厂通过传递自身的实例来调用 setBeanFactory()。
(5)如果存在与 bean 关联的任何BeanPostProcessors,则调用preProcessBeforeInitialization() 方法。
(6)如果为 bean 指定了 init 方法( 的 init-method 属性),那么将调 用它。
(7)最后,如果存在与 bean 关联的任何 BeanPostProcessors,则将调用 postProcessAfterInitialization() 方法。
(8)如果 bean 实现DisposableBean 接口,当 spring 容器关闭时,会调用 destory()。
(9)如果为bean 指定了 destroy 方法( 的 destroy-method 属性),那么将 调用它。
① singleton
使用该属性定义Bean时,IOC容器仅创建一个Bean实例,IOC容器每次返回的是同一个Bean实例。
② prototype
使用该属性定义Bean时,IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例。
③ request
该属性仅对HTTP请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationContext环境。
④ session
该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例。
⑤ global-session
该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。
二十三 Spring循环依赖
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** 二级缓存:存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** 三级级缓存:存放 bean 工厂对象,用于解决循环依赖 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/**
bean 的获取过程:先从一级获取,失败再从二级、三级里面获取
创建中状态:是指对象已经 new 出来了但是所有的属性均为 null 等待被 init
*/
循环依赖的场景是有两个类对象 A 和 B。在初始化 A 的时候发现需要依赖 B,这个时候就会初始化 B,然后在 B 里面会依赖 A 的对象。这个时候就会发生循环依赖的情况。
初始化对象是根据构造函数初始化,那么循环依赖是没有办法解决的,spring 会抛出异常。
初始化对象是根据属性 set 注入的话,spring 这边会创建三级缓存,分别为:singletonObjects,earlySingletonObjects,singletonFactories。当创建 A 的对象的时候,发现依赖 B 的对象,这个时候创建 B 的对象,将 A 创建好的临时对象存放在 singletonFactories 中,在创建 B 对象的时候发现依赖 A 对象,则首先从 singletonObjects 获取,发现 A 对象不存在,就从 earlySingletonObjects 也不存在,就从 singletonFactories 获取。这个时候获取到,就会将 A 创建好的对象存放在 earlySingletonObjects 中,然后 B 创建完毕后再创建 A,A 创建完毕后从 earlySingletonObjects 移到 singletonObjects 中。
用三级缓存而不是二级缓存是因为在将对象从三级缓存放入二级缓存的时候,会有一个 SmartInstantiationAwareBeanPostProcessor 这样的后置处理器,给用户提供了扩展接口
二十四 Mybatis分页插件原理
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。(一般使用pagehelper插
select * from tbl_user
select * from tbl_user limit 0,10
select count(*) from tbl_userl
二十五 #{} 和 ${} 的区别是什么?
注:这道题是面试官面试我同事的。
答:
• ${}是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc. Driver。
• #{}是 sql 的参数占位符,MyBatis 会将 sql 中的#{}替换为? 号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的? 号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name} 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 param.getItem().getName()。
###
二十六 Spring boot自动配置原理
二十七 SpringBoot和SpringCloud,请你谈谈对他们的理解?
1)、SpringBoot专注于快速方便的开发单个个体微服务(对SSM的简化,它的思想约定大于配置)。
2)、SpringCloud是关注全局的微服务协调、整理、治理的框架,它将SpringBoot开发的单体整合并管理起来。SpringCloud常用组件:网关、注册中心、配置中心、feign、hystrix
3)、SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系。
二十八 SpringBoot配置文件的加载顺序
1、若application.yml 和 bootstrap.yml 在同一目录下,则bootStrap.yml 的加载顺序要高于application.yml,即bootStrap.yml 会优先被加载。
2、不同位置的配置文件的加载顺序:
在不指定要被加载文件时,默认的加载顺序:由里向外加载,所以最外层的最后被加载,会覆盖**里层**的属性
3、可以通过属性指定加载某一文件:最下层的优先加载,所以最上层的属性会覆盖下层的属性
二十九 SpringMVC的工作流程
前端控制器(DispatcherServlet)、处理器映射器(HandlerMapping)、处理器适配器(HandlerAdapter)、处理器(Handler)controller、视图解析器(viewResolver)
客户端(浏览器)发送请求, DispatcherServlet拦截请求。
DispatcherServlet 根据请求信息调用 HandlerMapping 。HandlerMapping 根据 uri 去匹配查找能处理的 Handler(也就是我们平常说的 Controller 控制器) ,并会将请求涉及到的拦截器和 Handler 一起封装。
DispatcherServlet 调用 HandlerAdapter适配执行 Handler 。
Handler 完成对用户请求的处理后,会返回一个 ModelAndView 对象给DispatcherServlet,ModelAndView 顾名思义,包含了数据模型以及相应的视图的信息。Model 是返回的数据对象,View 是个逻辑上的 View。
ViewResolver 会根据逻辑 View 查找实际的 View。
DispaterServlet 把返回的 Model 传给 View(视图渲染)。
把 View 返回给请求者(浏览器)
三十 SpringMVC的常用注解
@Controller
@RestController
@ReqeuestMapping
@GetMapping
@PostMapping
@DeleteMapping
@PutMapping
@PathVaiable
@RequestParameter
@ReqeustBody
@ControllerAdvice
三十一 Spring常用注解
@Component
@Service
@Mapper
@Controller
@Bean
@Autowired 按类型注入,如果有相同类型再按名称注入(spring)
@Resources 按名字注入,如果有名字相同的按类型
@Value
@Configuration
@Import
@ComponentScan
三十二 mq怎么避免重复消费(幂等)
幂等性就是重复消费造成结果不一致。为了保证幂等性,因此消费者消费消息只能消费一次消息。我么可以是用全局的消息id来控制幂等性。当消息被消费了之后我们可以选择缓存保存这个消息id,然后当再次消费的时候,我们可以查询缓存,如果存在这个消息id,我们就不错处理直接return即可。先改造生产者代码,在消息中添加消息id:
@RequestMapping("/send")
public void sendMessage(){
JSONObject jsonObject = new JSONObject();
jsonObject.put("email","11111111111");
jsonObject.put("timestamp",System.currentTimeMillis());
Map map = new HashM
String json = jsonObject.toJSONString();
System.out.println(json);
Message message = MessageBuilder.withBody(json.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON)
.setContentEncoding("UTF-8").setMessageId(UUID.randomUUID() "").build();
amqpTemplate.convertAndSend(EXCHANGE_NAME,QUEUE_NAME,message);
}
消费者代码改造:
@Component
public class Consumer {
public static final String QUEUE_NAME = "byte-zb";
@RabbitListener(queues = QUEUE_NAME)
public void receiveMessage(Message message) throws Exception {
Jedis jedis = new Jedis("localhost", 6379);
String messageId = message.getMessageProperties().getMessageId();
String msg = new String(message.getBody(),"UTF-8");
System.out.println("接收导的消息为:" msg "==消息id为:" messageId);
String messageIdRedis = jedis.get("messageId");
if(messageId == messageIdRedis){
return;
}
JSONObject jsonObject = JSONObject.parseObject(msg);
String email = jsonObject.getString("email");
String content = jsonObject.getString("timestamp");
String httpUrl = "http://127.0.0.1:8080/email?email" email "&content=" content;
// 如果发生异常则返回null
String body = HttpUtils.httpGet(httpUrl, "utf-8");
//
if(body == null){
throw new Exception();
}
jedis.set("messageId",messageId);
}
}
我们在消费者端使用redis存储消息id,只做演示,具体项目请根据实际情况选择相应的工具进行存储。
三十三 mq怎么避免消息丢失
一、生产方
开启生产者消息确认
publisher-confirm,发送者确认
publisher-return,发送者回执
二、rabbitmq
开启消息持久化机制。交换机持久化 队列持久化 消息持久化
三、消费者确认
将消息应答改为手机或自动。修改消息本地重试机制。如果本地重试失败可以让其投递到死信交换机。
三十四 mq怎么避免消息堆积
利用多线程或其他方式提高消费速度
增加更多消费者,提高消费速度
扩大队列容积,提高堆积上限(惰性队列)
三十五 redis怎么和数据库保持一致
缓存延时双删
解释:
- 先淘汰缓存
- 再写数据库
- 休眠1秒,再淘汰缓存(这么做,可以将1秒内所造成的缓存脏数据,再次删除。)
针对上面的情形,读者应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
删除缓存重试机制
不管是延时双删还是Cache-Aside的先操作数据库再删除缓存,都可能会存在第二步的删除缓存失败,导致的数据不一致问题。
(1)更新数据库数据;
(2)缓存因为种种问题删除失败
(3)将需要删除的key发送至消息队列
(4)自己消费消息,获得需要删除的key
(5)继续重试删除操作,直到成功
读取biglog异步删除缓存
流程如上图所示:
(1)更新数据库数据
(2)数据库会将操作信息写入binlog日志当中
(3)订阅程序提取出所需要的数据以及key
(4)另起一段非业务代码,获得该信息
(5)尝试删除缓存操作,发现删除失败
(6)将这些信息发送至消息队列
(7)重新从消息队列中获得该数据,重试操作。
备注说明:上述的订阅binlog程序有现成的中间件canal,可以完成订阅binlog日志的功能。
三十六 说说事务的四大特征?
原子性
事务的原子性是指事务必须是一个原子的操作序列单元。事务中包含的各项操作在一次执行过程中,只允许出现两种状态之一,要么都成功,要么都失败。
任何一项操作都会导致整个事务的失败,同时其它已经被执行的操作都将被撤销并回滚,只有所有的操作全部成功,整个事务才算是成功完成。
一致性
事务的一致性是指事务在执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处以一致性状态。
比如:张三给李四转钱,不可能张三被扣了钱,李四没有加钱。
隔离性
事务的隔离性是指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。也就是说,不同事物并非操作相同数据时,每个事务都有完整的数据空间。
一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务是不能互相干扰的。
持久性
事务的持久性是指事务一旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态。
三十七 说说事务的四大隔离级别
读未提交: 一个事务还没有提交时,它做的变更就能被别的事务看到。可能出现脏读。
读已提交: 一个事物提交之后,它做的变更才会被其他事务看到。可能出现不可重复读。
可重复读: 一个事物执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。未提交变更对其他事务也是不可见的。可能出现幻读。
串行化: 对于同一行记录,写会加“写锁”,读会加“读锁”,当出现锁冲突时,后访问的事务需要等前一个事务执行完成,才能继续执行。
可能导致的问题有
1.脏读: 一个事务读到另一个事务未提交的更新数据。
2.不可重复读: 一个事务两次读同一行数据,可是这两次读到的数据不一样。
3.幻读: 一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
三十八 Spring 框架中都用到了哪些设计模式?
1 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
2 单例模式:Bean默认为单例模式。
3 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
4 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
5 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的 对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener
三十九 MySql常见存储引擎及其区别?
A、InnoDB
1、支持事务。
2、使用的锁粒度默认为行级锁,可以支持更高的并发;当然,也支持表锁。
3、支持外键约束;外键约束其实降低了表的查询速度,但是增加了表之间的耦合度。
B、MyISAM
1、不提供事务支持
2、只支持表级锁
3、不支持外键
C、memory
数据存储在内存中
总结:
• MyISAM管理非事务表,提供高速存储和检索以及全文搜索能力,如果再应用中执行大量select操作,应该选择MyISAM
• InnoDB用于事务处理,具有ACID事务支持等特性,如果在应用中执行大量insert和update操作,应该选择InnoDB
四十 MySQL中的索引类型有以下几种
• 普通索引
CREATE INDEX index_name ON table(column(length))
• 唯一索引
CREATE UNIQUE INDEX indexName ON table(column(length))
• 主键索引(聚合索引)
是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引
• 组合索引
指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合
ALTER TABLE table ADD INDEX namecityage (name,city,age);
• 全文索引
主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配
CREATE FULLTEXT INDEX index_content ON article(conten
四十一 MyBatis 的一对一,一对多,多对多
MyBatis 的一对一、多对多,主要就是 resultMap 两个属性的使用,而一对多和多对一都是相互的,只是站的角度不同:
【一对一】association:一个复杂的类型关联。许多结果将包成这种类型
【一对多】collection:复杂类型的集合
【多对多】相互都是一对多
不管是一对多、多对一,还是多对多,只需要知道这两个属性哪个代表多哪个代表一,就可以很好的在实体的mapper文件中配置出来。
四十二 Mybatis 和 Mybatis Plus 的区别
MyBatis:
所有SQL语句全部自己写
手动解析实体关系映射转换为MyBatis内部对象注入容器
不支持Lambda形式调用
Mybatis Plus:
强大的条件构造器,满足各类使用需求
内置的Mapper,通用的Service,少量配置即可实现单表大部分CRUD操作
支持Lambda形式调用
提供了基本的CRUD功能,连SQL语句都不需要编写
自动解析实体关系映射转换为MyBatis内部对象注入容器
MyBatis的优缺点
优点
MyBatis封装了JBDC底层访问数据库的细节,使我们程序猿不需要与JDBC API打交道,就可以访问数据库
MyBatis简单易学,程序猿直接编写SQL语句,适合于对SQL语句性能要求比较高的项目
SQL语句封装在配置文件中,便于统一管理与维护,降低了程序的耦合度
SQL代码从程序代码中彻底分离出来,可重用
提供了动态SQL标签,支持编写动态SQL
提供映射标签,支持对象与数据库的ORM字段关系映射
缺点
过于依赖数据库SQL语句,导致数据库移植性差,更换数据库,如果SQL语句有差异,SQL语句工作量大
由于xml里标签id必须唯一,导致DAO中方法不支持方法重载
MyBatis-Plus 优点
1、依赖少:仅仅依赖 Mybatis 以及 Mybatis-Spring 。
2、损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 。
3、预防Sql注入:内置 Sql 注入剥离器,有效预防Sql注入攻击 。
4、通用CRUD操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求 。
5、多种主键策略:支持多达4种主键策略(内含分布式唯一ID生成器),可自由配置,完美解决主键问题 。
6、支持热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
7、支持ActiveRecord:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可实现基本 CRUD 操作
8、支持代码生成:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码(生成自定义文件,避免开发重复代码),支持模板引擎、有超多自定义配置等。
9、支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )。
10、支持关键词自动转义:支持数据库关键词(order、key…)自动转义,还可自定义关键词 。
11、内置分页插件:基于 Mybatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询。
12、置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能有效解决慢查询 。
13、内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,预防误操作。
14、默认将实体类的类名查找数据库中的表,使用@TableName(value=“table1”)注解指定表名,@TableId指定表主键,若字段与表中字段名保持一致可不加注解。
四十三 Redis的常用数据类型
• 5 种基础数据结构 :String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
• 3 种特殊数据结构 :HyperLogLogs(基数统计)、Bitmap (位存储)、Geospatial (地理位置)。
四十四 Redis的持久化机制
RDB 比 AOF 优秀的地方 :
• RDB 文件存储的内容是经过压缩的二进制数据, 保存着某个时间点的数据集,文件很小,适合做数据的备份,灾难恢复。AOF 文件存储的是每一次写命令,类似于 MySQL 的 binlog 日志,通常会比 RDB 文件大很多。当 AOF 变得太大时,Redis 能够在后台自动重写 AOF。新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。不过, Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。
• 使用 RDB 文件恢复数据,直接解析还原数据即可,不需要一条一条地执行命令,速度非常快。而 AOF 则需要依次执行每个写命令,速度非常慢。也就是说,与 AOF 相比,恢复大数据集的时候,RDB 速度更快。
AOF 比 RDB 优秀的地方 :
• RDB 的数据安全性不如 AOF,没有办法实时或者秒级持久化数据。生成 RDB 文件的过程是比繁重的, 虽然 BGSAVE 子进程写入 RDB 文件的工作不会阻塞主线程,但会对机器的 CPU 资源和内存资源产生影响,严重的情况下甚至会直接把 Redis 服务干宕机。AOF 支持秒级数据丢失(取决 fsync 策略,如果是 everysec,最多丢失 1 秒的数据),仅仅是追加命令到 AOF 文件,操作轻量。
• RDB 文件是以特定的二进制格式保存的,并且在 Redis 版本演进中有多个版本的 RDB,所以存在老版本的 Redis 服务不兼容新版本的 RDB 格式的问题。
• AOF 以一种易于理解和解析的格式包含所有操作的日志。你可以轻松地导出 AOF 文件进行分析,你也可以直接操作 AOF 文件来解决一些问题。比如,如果执行FLUSHALL命令意外地刷新了所有内容后,只要 AOF 文件没有被重写,删除最新命令并重启即可恢复之前的状态。
四十五 Rdis为什么会这么快?
Redis 内部做了非常多的性能优化,比较重要的主要有下面 3 点:
• Redis 基于内存,内存的访问速度是磁盘的上千倍;
• Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用(Redis 线程模式后面会详细介绍到);
• Redis 内置了多种优化过后的数据结构实现,性能非常高。
四十六 redis淘汰策略有哪几种?
1)noeviction: 不删除,直接返回报错信息。
2)volatile-lfu:在设置了过期时间的key中,移除最近最少使用的key。
4)volatile-lru:在设置了过期时间的key中,移除最久未使用的key。
3)volatile-ttl: 在设置了过期时间的key中,移除准备过期的key。
5)volatile-random:在设置了过期时间的key中,随机移除某个key。
6)allkeys-random:随机移除某个key。
7)allkeys-lru:移除最久未使用使用的key。
8)allkeys-lfu:移除最近最少使用的key。
四十七 redis删除策略有哪几种?
key到期了该如何进行处理?
1.定时删除
在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
优点:
保证内存被尽快释放
缺点:
1)若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key。
2)定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
2.惰性删除
key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
优点:
删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步.
缺点:
若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)
3.定期删除
如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历,随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key,判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除。(默认每个库检测20个key)
优点:
1)通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用–处理"定时删除"的缺点
2)定期删除过期key–处理"惰性删除"的缺点
缺点:
1)在内存友好方面,不如"定时删除"
2)在CPU时间友好方面,不如"惰性删除"
Redis采用的策略:定期删除+惰性删除
四十八 缓存雪崩、缓存穿透、缓存击穿?
1.缓存雪崩
描述:
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
设置数据永远不过期。
2.缓存击穿
描述:
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案:
设置数据永远不过期。
3.缓存穿透
当发生缓存雪崩或击穿时,数据库中还是保存了应用要访问的数据,一旦缓存恢复相对应的数据,就可以减轻数据库的压力,而缓存穿透就不一样了。
当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。
缓存穿透的发生一般有这两种情况:
业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据;
黑客恶意攻击,故意大量访问某些读取不存在数据的业务;
应对缓存穿透的方案,常见的方案有两种。
第一种方案,非法请求的限制;
第二种方案,设置缓存空值或者默认值;
四十九 说说对SQL语句优化有哪些方法?
1、 在 where 子句中使用 != 或 <> 操作符,会导致引擎放弃使用索引而进行全表扫描。SQL 中,不等于操作符会限制索引,造成全表扫描,即使比较的字段上有索引。MySQL 只有对以下操作符才使用索引:<,<=,=,>,>=,BETWEEN,IN,以及某些方式的 LIKE (‘a%’)。
select * from user where age!=20
select * from user where name like '张%'
select * from user where age>20 and name='张三'
2、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。如果排序字段没有用到索引,就尽量少排序。根据实际情况进行调整,因为有时索引太多也会降低性能。
3、在 where 子句中对字段进行 null 值判断,会导致引擎放弃使用索引而进行全表扫描
select * from user where age is null
4、在 where 子句中对字段进行函数、算术运算或其他表达式运算,会导致引擎放弃使用索引而进行全表扫描
5、如果限制条件中其他字段没有索引,尽量少用 or。or两边的字段中,如果有一个不是索引字段,而其他条件也不是索引字段,会造成该查询不走索引的情况。应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描
6、select语句务必指明字段名称,尽量避免使用"select * "。因为它会进行全表扫描,不能有效利用索引,增加很多不必要的消耗(CPU、IO、内存、网络带宽),增大了数据库服务器的负担,以及它与应用程序客户端之间的网络IO开销。当表结构发生改变时,前端也需要更新。所以要求直接在select后面接上字段名
7、SQL语句中 in 和 not in 也要慎用,否则会导致全表扫描MySQL对于in做了相应的优化,即将in中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。对于连续的数值,能用between就不要用in,再或者使用连接来替换。
8、索引并不是越多越好
• 索引并不是越多越好,索引虽然提高了查询的效率,但是也降低了插入和更新的效率。
• insert 或 update 时有可能会重建索引,所以建索引需要慎重考虑,视具体情况来定。
• 一个表的索引数最好不要超过 6 个,若太多需要考虑一些索引是否没有存在的必要。
9、尽可能的使用 varchar/nvarchar 代替 char/nchar 。
10、使用EXPLAIN分析 SQL 计划。EXPLAIN可以检查索引使用情况以及扫描的行。日常开发写 SQL 的时候,尽量养成用 explain 分析 SQL 的习惯,尤其是走不走索引这一块。
11、【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
五十 SpringCloud的五大组件有哪些?
1、注册中心
2、配置中心
3、网关
4、feign
5、hystrix熔断器