Java高频面试题(公众号:奔跑的码畜)
2021-07-06 11:26:36 268 举报
AI智能生成
“Java高频面试题”是公众号“奔跑的码畜”推出的一个专栏,专门针对Java开发者在求职过程中可能会遇到的各类面试问题进行深度解析。这个专栏旨在帮助Java开发者更好地备战面试,提升自己的专业技能和面试技巧。内容涵盖了Java基础知识、面向对象编程、数据结构和算法、JVM、多线程、数据库、网络、框架等方面的问题,以及一些实际工作中可能会遇到的问题。通过这个专栏,Java开发者可以系统地学习和复习Java知识,提高自己的竞争力,顺利通过面试。无论你是初入职场的新人,还是有一定工作经验的老手,都可以从中获得有用的信息和启示。
作者其他创作
大纲/内容
java基础
1.面向对象
封装
javaBean的属性私有,提供getset对外方法
orm框架,只需要引入mybatis,不需要理解技术细节
继承
多态
继承
方法的重写
子类的实现指向父类的引用
2.JDK、JRE、JVM区别和联系
jdk
包含jre,java开发包
jre
包含jvm,java环境
jvm
java虚拟机
3.==和equals
==
比较的是栈中的地址
equals
不重写,和【==】一样比较的是栈中的地址
String
重写后,比较的是堆中对象的值
源码:使用循环,比较每一个字符是否相等
String a = "123";会放进常量池中
例子
4.final
修饰类:表示类不可被继承
修饰方法:表示方法不可被子类覆盖,但是可以重载
修饰变量:表示变量一旦被赋值就不可以更改它的值
修饰成员变量
如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值
如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值
修饰局部变量
系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,
即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码
中对final变量赋初值(仅一次)
即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码
中对final变量赋初值(仅一次)
修饰基本类型数据
如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改
修饰引用类型数据
如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是引用的值是可变
的。
的。
为什么局部内部类和匿名内部类只能访问局部final变量?
5.String、StringBuffer、StringBulider
String
final修饰的
每次操作都会产生新的对象
StringBuffer
线程安全,每个方法synchronized修饰的
每次都是在原对象上操作的
StringBulider
线程不安全
每次都是在原对象上操作的
6.重载和重写的区别
重写
发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于
等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方
法。
等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方
法。
重载(@Override)
发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问
修饰符可以不同,发生在编译时。
修饰符可以不同,发生在编译时。
7.接口和抽象类的区别
基础区别
抽象类可以存在普通成员函数,而接口中只能存在public abstract 方法(1.8以后可以有默认方法)
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的
抽象类只能继承一个,接口可以实现多个
设计目的
接口:对类的行为进行约束。如SpringMVC架构中的service层
抽象类:代码复用。如:设计模式中的模板模式
使用场景
接口:当你关注一个操作的时候,用接口;抽象成本低;like a
抽象类:当你关注一个事物的本质的时候,用抽象类;抽象成本高;is a
8.List和Set
List
有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出
所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素
所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素
Set
无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元
素,在逐一遍历各个元素
素,在逐一遍历各个元素
9.hashCode与equals
hashCode
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是
确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,Java中的任何类都包含有
hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用
到了散列码!(可以快速找到所需要的对象)
确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,Java中的任何类都包含有
hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用
到了散列码!(可以快速找到所需要的对象)
如果两个对象相等,则hashcode一定也是相同的
两个对象相等,对两个对象分别调用equals方法都返回true
两个对象有相同的hashcode值,它们也不一定是相等的
因此,equals方法被覆盖过,则hashCode方法也必须被覆盖
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个
对象无论如何都不会相等(即使这两个对象指向相同的数据)
对象无论如何都不会相等(即使这两个对象指向相同的数据)
equals
参考3.0
10.ArrayList和LinkedList区别
ArrayList
动态数组
连续内存存储
适合下标访问(随机访问)
扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会
涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList(需要创建大量的node对象)
涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList(需要创建大量的node对象)
LinkedList
基于链表
可以存储在分散的内存中
适合做数据插入及删除操作,不适合查询:需要逐
一遍历
一遍历
遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需
要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexlOf对list进行了遍历,当结
果为空时会遍历整个列表。
要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexlOf对list进行了遍历,当结
果为空时会遍历整个列表。
11.HashMapTable区别及实现
HashMap
线程不安全
HashMap允许key和value为null
底层实现:数组+链表实现
jdk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在
计算key的hash值,二次hash然后对数组长度取模,对应到数组下标
如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组
如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链
表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
key为null,存在下标0的位置
数组扩容
1.8以下,并发时会有扩容成环的问题
HashTable
线程安全,每个方法都加了synchronized
1.8以后一般都用ConcurrentHashMap
HashTable不允许key和value为null
12.ConcurrentHashMap原理
1.7
数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个
HashEntry又是一个链表结构
HashEntry又是一个链表结构
元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部
锁:Segment分段锁 Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影
响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment
get方法无需加锁,volatile保证
锁:Segment分段锁 Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影
响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment
get方法无需加锁,volatile保证
1.8
数据结构:synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性
查找,替换,赋值操作都使用CAS
查找,替换,赋值操作都使用CAS
锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写
操作、并发扩容
读操作无锁:
Node的val和next使用volatile修饰,读写线程对该变量互相可见
数组用volatile修饰,保证扩容时被读线程感知
操作、并发扩容
读操作无锁:
Node的val和next使用volatile修饰,读写线程对该变量互相可见
数组用volatile修饰,保证扩容时被读线程感知
13.什么是字节码?采用字节码的好处是什么?
编译后,后缀为.class的文件
好处是基于jvm,一处编译,处处运行
14.Java类加载器
bootstrap ClassLoader
BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和
class文件
class文件
ExtClassLoader
ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件夹下的jar包和
class类
class类
AppClassLoader
AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。系统类加载器,线程上下
文加载器
文加载器
继承ClassLoader实现自定义类加载器
15.双亲委托模型
双亲委派模型图
好处
主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String
同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不
同的 ClassLoader加载就是不同的两个类
同的 ClassLoader加载就是不同的两个类
tomcat打破了双亲委派,自己加载自己响应加载器下的
16.Java中的异常体系
异常体系UML类图
Java中的所有异常都来自顶级父类Throwable。
Throwable下有两个子类Exception和Error。
Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。
Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检
查异常。
查异常。
RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常
常发生在程序编译过程中,会导致程序编译不通过。
常发生在程序编译过程中,会导致程序编译不通过。
全局统一异常处理
1、编写GloabExceptionHandler
2、类上增加注解@ControllerAdvice
3、方法上增加注解@ExceptionHandler(Exception.class)
17.GC如何判断对象可以被回收
引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计
数为0时可以回收
数为0时可以回收
引用计数法,可能会出现A 引用了 B,B 又引用了 A,这时候就算他们都不再使用了,但因为相互
引用 计数器=1 永远无法被回收。
引用 计数器=1 永远无法被回收。
可达性分析法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC
Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象
Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象
GC Roots的对象
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象
并发编程
1.线程的生命周期及状态
创建
新创建了一个线程对象
就绪
线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于
可运行线程池中,变得可运行,等待获取CPU的使用权。
可运行线程池中,变得可运行,等待获取CPU的使用权。
运行
就绪状态的线程获取了CPU,执行程序代码。
阻塞
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进
入就绪状态,才有机会转到运行状态。
入就绪状态,才有机会转到运行状态。
等待阻塞
运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待
池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤
醒,wait是object类的方法
池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤
醒,wait是object类的方法
同步阻塞
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放
入“锁池”中
入“锁池”中
其他阻塞
运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状
态。当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
sleep是Thread类的方法
态。当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
sleep是Thread类的方法
死亡
线程执行完了或者因异常退出了run方法,该线程结束生命周期。
2.sleep()、wait()、join()、yield()的区别
1.锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线
程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到
后会进入就绪队列进行等待cpu资源分配。
程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到
后会进入就绪队列进行等待cpu资源分配。
2.等待池
当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了
notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放
到锁池,而notifyAll()是将等待池的所有线程放到锁池当中
notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放
到锁池,而notifyAll()是将等待池的所有线程放到锁池当中
3.sleep和wait
1、sleep 是 Thread 类的静态本地方法,wait 则是 Object 类的本地方法。
2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
5、sleep 一般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。
6、sleep 会让出 CPU 执行时间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会重新竞
争到锁继续执行的。
争到锁继续执行的。
4.yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,
所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
5.join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队
列,直到线程A结束或中断线程
列,直到线程A结束或中断线程
3.对线程安全的理解
不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问
堆
堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分
配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了
要还给操作系统,要不然就是内存泄漏
配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了
要还给操作系统,要不然就是内存泄漏
栈(线程安全的)
栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈
互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语
言里面显式的分配和释放。
互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语
言里面显式的分配和释放。
4.Thread和Runnable
Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new
Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简
单的执行一个任务,那就实现runnable。
Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简
单的执行一个任务,那就实现runnable。
5.对守护线程的理解
守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保
姆;
姆;
守护线程的作用
GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就
不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线
程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线
程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
应用场景
1.为其它线程提供服务支持的情况
2.在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用
注意点
thread.setDaemon(true)
必须在thread.start()之前设置,否则会跑出一个
IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
必须在thread.start()之前设置,否则会跑出一个
IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
在Daemon线程中产生的新线程也是Daemon的
守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作
的中间发生中断。
的中间发生中断。
Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线
程就不能用Java的线程池。
程就不能用Java的线程池。
6.ThreadLocal的原理和使用场景
每一个 Thread 对象均含有一个 ThreadLocalMap 类型的成员变量 threadLocals ,它存储本线程中所
有ThreadLocal对象及其对应的值。
ThreadLocalMap 由一个个 Entry 对象构成。
Entry 继承自 WeakReference<ThreadLocal<?>> ,一个 Entry 由 ThreadLocal 对象和 Object 构
成。由此可见, Entry 的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该
key就会被垃圾收集器回收
有ThreadLocal对象及其对应的值。
ThreadLocalMap 由一个个 Entry 对象构成。
Entry 继承自 WeakReference<ThreadLocal<?>> ,一个 Entry 由 ThreadLocal 对象和 Object 构
成。由此可见, Entry 的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该
key就会被垃圾收集器回收
当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对
象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap
对象。再以当前ThreadLocal对象为key,获取对应的value。
对象。再以当前ThreadLocal对象为key,获取对应的value。
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在
线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。
线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。
使用场景
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
7.ThreadLocal内存泄露原因,如何避免
ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal
实例,value为线程变量的副本
实例,value为线程变量的副本
key 使用强引用
当hreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强
引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
key 使用弱引用
当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱
引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用
set(),get(),remove()方法的时候会被清除value值。
引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用
set(),get(),remove()方法的时候会被清除value值。
8.并发、并行、串行的区别
串行
串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着
并行
并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。
并发
并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行
9.并发的三大特性
原子性
原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要
不都不执行。
不都不执行。
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性
虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按
照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对
变量的值没有造成影响,但有可能会出现线程安全问题。
照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对
变量的值没有造成影响,但有可能会出现线程安全问题。
10.为什么使用线程池,参数解释
1、降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。
4、参数
corePoolSize 代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会
消除,而是一种常驻线程
消除,而是一种常驻线程
maxinumPoolSize 代表的是最大线程数,它与核心线程数相对应,表示最大允许被创建的线程
数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但
是线程池内线程总数不会超过最大线程数
数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但
是线程池内线程总数不会超过最大线程数
keepAliveTime 、 unit 表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会
消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过
setKeepAliveTime 来设置空闲时间
消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过
setKeepAliveTime 来设置空闲时间
workQueue 用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放
入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程
入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程
ThreadFactory 实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建
工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择
自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择
自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
Handler 任务拒绝策略,有两种情况,第一种是当我们调用 shutdown 等方法关闭线程池后,这
时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程
池提交任务就会遭到拒绝。另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提
交的任务时,这是也就拒绝
时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程
池提交任务就会遭到拒绝。另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提
交的任务时,这是也就拒绝
11.简述线程池处理流程
线程池处理流程
12.线程池中阻塞队列的作用?为什么是先添加列队而不是先
创建最大线程?
创建最大线程?
(1)存放任务
(2)线程开销比较大
13.线程池中线程复用原理
线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的
一个线程必须对应一个任务的限制。
一个线程必须对应一个任务的限制。
同一个线程可以从阻塞队列中不断获取新任务来执行
让每个线程去执行一个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则直接执行,也就
是调用任务中的 run 方法,并不是每次执行任务都会调用 Thread.start() 来创建新线程
是调用任务中的 run 方法,并不是每次执行任务都会调用 Thread.start() 来创建新线程
Spring
1.如何实现一个IOC容器
1、配置文件配置包扫描路径
2、递归包扫描获取.class文件
3、反射、确定需要交给IOC管理的类
4、对需要注入的类进行依赖注入
5.拓展:解决循环依赖(本地map缓存);方便拓展,引入后置处理器等等
2.spring是什么?
轻量级的开源的J2EE框架
IOC
DI
AOP
日志
3.谈谈你对AOP的理解
相对于OOP(面向对象)来说
AOP:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象
(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增
强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情
(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增
强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情
4.谈谈你对IOC的理解
ioc容器:实际上就是个map(key,value),里面存的是各种对象(在xml里配置的bean节点、
@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的
bean节点,根据全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创
建对象放到map里。
这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入
(autowired、resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性
根据id注入,也会扫描这些注解,根据类型或id注入;id就是对象名)。
@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的
bean节点,根据全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创
建对象放到map里。
这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入
(autowired、resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性
根据id注入,也会扫描这些注解,根据类型或id注入;id就是对象名)。
通俗的讲:比如你去洗脚城,一般是你自己点小姐姐,如果是洗脚城安排小姐姐,那么就是控制反转了,你把点小姐姐的权利给了洗脚城
5.BeanFactory和ApplicationContext有什么区别?
ApplicationContext是BeanFactory的子接口
ApplicationContext提供了更完整的功能
继承MessageSource,因此支持国际化
统一的资源文件访问方式
提供在监听器中注册bean的事件
同时加载多个配置文件
载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用
getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问
题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法
才会抛出异常。
getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问
题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法
才会抛出异常。
ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我
们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。
ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的
时候,你就不用等待,因为它们已经创建好了。
们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。
ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的
时候,你就不用等待,因为它们已经创建好了。
相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置
Bean较多时,程序启动较慢。
Bean较多时,程序启动较慢。
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用
ContextLoader。
ContextLoader。
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的
使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
相同点:他们都可以生产bean
6.描述一下Spring Bean的生命周期?
1、解析类得到BeanDefinition
2、如果有多个构造方法,则要推断构造方法
3、确定好构造方法后,进行实例化得到一个对象
4、对对象中的加了@Autowired注解的属性进行属性填充
5、回调Aware方法,比如BeanNameAware,BeanFactoryAware
6、调用BeanPostProcessor的初始化前的方法
7、调用初始化方法
8、调用BeanPostProcessor的初始化后的方法,在这里会进行AOP
9、如果当前创建的bean是单例的则会把bean放入单例池
10、使用bean
11、Spring容器关闭时调用DisposableBean中destory()方法
7.解释下Spring支持的几种bean的作用域。
singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。该
对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。
对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。
prototype:为每一个bean请求提供一个实例。在每次注入时都会创建一个新的对象
request:bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用
这一个单例对象。
这一个单例对象。
session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean
会随之失效。
会随之失效。
application:bean被定义为在ServletContext的生命周期中复用一个单例对象。
websocket:bean被定义为在websocket的生命周期中复用一个单例对象。
8.Spring框架中的单例Bean是线程安全的么?
无状态的情况
controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法,
而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。
而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。
有状态的情况
Dao会操作数据库Connection,Connection是带有状态的,比如说数据库事务,Spring的事务管理器
使用Threadlocal为不同线程维护了一套独立的connection副本,保证线程之间不会互相影响(Spring
是如何保证事务获取同一个Connection的)
使用Threadlocal为不同线程维护了一套独立的connection副本,保证线程之间不会互相影响(Spring
是如何保证事务获取同一个Connection的)
拓展:不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变
为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用
synchronized、lock、CAS等这些实现线程同步的方法了。
为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用
synchronized、lock、CAS等这些实现线程同步的方法了。
9.Spring 框架中都用到了哪些设计模式?
简单工厂:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。
Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是 在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
工厂方法
实现了FactoryBean接口的bean是一类叫做factory的bean。其特点是,spring会在使用getBean()调 用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个 bean.getOjbect()方法的返回值。
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
spring对单例的实现: spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没 有从构造器级别去控制单例,这是因为spring管理的是任意的java对象。
适配器模式:
Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替 controller执行相应的方法。这样在扩展Controller时,只需要增加一个适配器类就完成了SpringMVC 的扩展了
装饰器模式:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类
更为灵活。
更为灵活。
Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有 Decorator。
动态代理:
切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象创建动态的创建一个代理 对象。SpringAOP就是以这种方式织入切面的。
观察者模式
spring的事件驱动模型使用的是 观察者模式 ,Spring中Observer模式常用的地方是listener的实现。
策略模式
Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
模板方法:父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现。
refresh方法
10.Spring事务的实现方式和原理以及隔离级别?
在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是申明式的,
@Transactional注解就是申明式的。
@Transactional注解就是申明式的。
在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象
作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻
辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有
出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务
进行回滚。
作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻
辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有
出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务
进行回滚。
spring事务隔离级别就是数据库的隔离级别
read uncommitted(未提交读)
read committed(提交读、不可重复读)
repeatable read(可重复读)
serializable(可串行化)
11.spring事务传播机制
多个事务方法相互调用时,事务如何在这些方法间传播
REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存在事
务,则加入这个事务
务,则加入这个事务
SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。
NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务
NEVER:不使用事务,如果当前事务存在,则抛出异常
NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)
12.spring事务什么时候会失效?
spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了
1、发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而
是UserService对象本身!
是UserService对象本身!
2、方法不是public的
3、数据库不支持事务
4、没有被spring管理
5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)
13.什么是bean的自动装配,有哪些方式?
@Autowired自动装配bean,可以在字段、setter方法、构造函数上使用。
装配的方式
no – 缺省情况下,自动配置是通过“ref”属性手动设定 。
byName-根据bean的属性名称进行自动装配。
byType-根据bean的类型进行自动装配。
constructor-类似byType,不过是应用于构造器的参数。如果一个bean与构造器参数的类型形
同,则进行自动装配,否则导致异常
同,则进行自动装配,否则导致异常
autodetect-如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式
进行自动装配。
进行自动装配。
springmvc、springBoot
1.Spring Boot、Spring MVC 和 Spring 有什么区别
spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提
供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给
方法执行,比如日志、异常等
供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给
方法执行,比如日志、异常等
springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,
然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术
生成视图展现给前端
然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术
生成视图展现给前端
springboot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc
应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis、
mongodb、es,可以开箱即用
应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis、
mongodb、es,可以开箱即用
2.SpringMVC 工作流程
1、用户发送请求至前端控制器 DispatcherServlet。
2、DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
3、处理器映射器找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器及处理器拦截器
(如果有则生成)一并返回给 DispatcherServlet。
(如果有则生成)一并返回给 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 响应用户。
3.Spring MVC的主要组件
1、HandlerMapping
initHandlerMappings(context),处理器映射器,根据用户请求的资源uri来查找Handler的。在
SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个
Handler进行,这就是HandlerMapping需要做的事。
SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个
Handler进行,这就是HandlerMapping需要做的事。
2、HandlerAdapter
initHandlerAdapters(context),适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处
理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方
法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的
事情。
理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方
法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的
事情。
3、HandlerExceptionResolver
initHandlerExceptionResolvers(context), 其它组件都是用来干活的。在干活的过程中难免会出现问
题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是
HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给
render方法进行渲染。
题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是
HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给
render方法进行渲染。
4、ViewResolver
initViewResolvers(context),ViewResolver用来将String类型的视图名和Locale解析为View类型的视
图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)
文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver
主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲
染,具体的渲染过程则交由不同的视图自己完成。
图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)
文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver
主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲
染,具体的渲染过程则交由不同的视图自己完成。
5、RequestToViewNameTranslator
initRequestToViewNameTranslator(context),ViewResolver是根据ViewName查找View,但有的
Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,
如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。
RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到
ViewName的转换规则都要在一个Translator里面全部实现。
Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,
如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。
RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到
ViewName的转换规则都要在一个Translator里面全部实现。
6、LocaleResolver
initLocaleResolver(context), 解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理
器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request
解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的
结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化
资源或者主题的时候。
器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request
解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的
结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化
资源或者主题的时候。
7、ThemeResolver
initThemeResolver(context),用于解析主题。SpringMVC中一个主题对应一个properties文件,里面
存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主
题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource
和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这
是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是
ThemeSource的工作。最后从主题中获取资源就可以了。
存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主
题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource
和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这
是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是
ThemeSource的工作。最后从主题中获取资源就可以了。
8、MultipartResolver
initMultipartResolver(context),用于处理上传请求。处理方法是将普通的request包装成
MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调
用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传
请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。
MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调
用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传
请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。
9、FlashMapManager
initFlashMapManager(context),用来管理FlashMap的,FlashMap主要用在redirect中传递参数。
4.Spring Boot 自动配置原理
自动装配原理图
@Import + @Configuration + Spring spi
自动配置类由各个starter提供,使用@Configuration + @Bean定义配置类,放到METAINF/spring.factories下
使用Spring spi扫描META-INF/spring.factories下的配置类
使用@Import导入自动配置类
5.如何理解 Spring Boot 中的 Starter
使用spring + springmvc使用,如果需要引入mybatis等框架,需要到xml中定义mybatis需要的bean
starter就是定义一个starter的jar包,写一个@Configuration配置类、将这些bean定义在里面,然后在
starter包的META-INF/spring.factories中写入该配置类,springboot会按照约定来加载该配置类
开发人员只需要将相应的starter包依赖进应用,进行相应的属性配置(使用默认配置时,不需要配
置),就可以直接进行代码开发,使用对应的功能了,比如mybatis-spring-boot--starter,springboot-starter-redis
starter就是定义一个starter的jar包,写一个@Configuration配置类、将这些bean定义在里面,然后在
starter包的META-INF/spring.factories中写入该配置类,springboot会按照约定来加载该配置类
开发人员只需要将相应的starter包依赖进应用,进行相应的属性配置(使用默认配置时,不需要配
置),就可以直接进行代码开发,使用对应的功能了,比如mybatis-spring-boot--starter,springboot-starter-redis
6.什么是嵌入式服务器?为什么要使用嵌入式服务器
节省了下载安装tomcat,应用也不需要再打war包,然后放到webapp目录下再运行
只需要一个安装了 Java 的虚拟机,就可以直接在上面部署应用程序了
springboot已经内置了tomcat.jar,运行main方法时会去启动tomcat,并利用tomcat的spi机制加载
springmvc
只需要一个安装了 Java 的虚拟机,就可以直接在上面部署应用程序了
springboot已经内置了tomcat.jar,运行main方法时会去启动tomcat,并利用tomcat的spi机制加载
springmvc
Mysql
1.索引的基本原理
索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。
索引的原理:就是把无序的数据变成有序的查询
1. 把创建了索引的列的内容进行排序
2. 对排序结果生成倒排表
3. 在倒排表内容上拼上数据地址链
4. 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据
2.mysql聚簇和非聚簇索引的区别
聚簇索引
将数据存储与索引放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数
据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是
相邻地存放在磁盘上的
据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是
相邻地存放在磁盘上的
InnoDB中一定有主键,主键一定是聚簇索引,不手动设置、则会使用unique索引,没有unique索引,
则会使用数据库内部的一个行的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之为辅助索引,
辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,
辅助索引叶子节点存储的不再是行的物理位置,而是主键值
则会使用数据库内部的一个行的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之为辅助索引,
辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,
辅助索引叶子节点存储的不再是行的物理位置,而是主键值
非聚簇索引
叶子节点不存储数据、存储的是数据行地址,也就是说根据索引查找到数据行的位置
再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节,那我们先在这个
目录里面找,找到对应的页码后再去对应的页码看文章。
再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节,那我们先在这个
目录里面找,找到对应的页码后再去对应的页码看文章。
MyISM使用的是非聚簇索引,没有聚簇索引,非聚簇索引的两棵B+树看上去没什么不同,节点的结构
完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助
键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据
来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。
完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助
键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据
来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。
3.mysql索引的数据结构,各自优劣
B+树
默认索引数据结构
hash
哈希碰撞
4.索引设计的原则
1. 适合索引的列是出现在where子句中的列,或者连接子句中指定的列
2. 基数较小的表,索引效果较差,没有必要在此列建立索引
3. 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间,
如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。
如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。
4.不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进
行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
5. 定义有外键的数据列一定要建立索引。
6. 更新频繁字段不适合创建索引
7. 若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
8. 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修
改原来的索引即可。
改原来的索引即可。
9. 对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
10. 对于定义为text、image和bit的数据类型的列不要建立索引。
5.什么是最左前缀原则
6.锁的类型有哪些
共享锁(Share Lock)
排他锁(eXclusive Lock)
表锁
行锁
记录锁(Record Lock)
页锁
间隙锁(Gap Lock)
临建锁(Next-Key Lock)
意向共享锁
意向排他锁
7.InnoDB存储引擎的锁的算法
Record lock:单个行记录上的锁
Gap lock:间隙锁,锁定一个范围,不包括记录本身
Next-key lock:record+gap 锁定一个范围,包含记录本身
8.关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢
查询都怎么优化过?
查询都怎么优化过?
在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运
维在做,会定期将业务中的慢查询反馈给我们。
维在做,会定期将业务中的慢查询反馈给我们。
mysql可以开启慢查询统计、连接池也可以
优化
首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载
了许多结果中并不需要的列,对语句进行分析以及重写。
了许多结果中并不需要的列,对语句进行分析以及重写。
分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽
可能的命中索引。
可能的命中索引。
如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者
纵向的分表。
纵向的分表。
9.事务的基本特性和隔离级别
事务基本特性ACID
原子性指的是一个事务中的操作要么全部成功,要么全部失败。
一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,
假设A只有90块,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库
数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证
假设A只有90块,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库
数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证
隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。
持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。
隔离性有4个隔离级别
read uncommit 读未提交,可能会读到其他事务未提交的数据,也叫做脏读。
用户本来应该读取到id=1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读
取结果age=20,这就是脏读。
用户本来应该读取到id=1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读
取结果age=20,这就是脏读。
read commit 读已提交,两次读取结果不一致,叫做不可重复读。
不可重复读解决了脏读的问题,他只会读取已经提交的事务。
用户开启事务读取id=1用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查
询读取到不同的结果叫做不可重复读。
不可重复读解决了脏读的问题,他只会读取已经提交的事务。
用户开启事务读取id=1用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查
询读取到不同的结果叫做不可重复读。
repeatable read 可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产
生幻读。
生幻读。
serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争
的问题。
的问题。
10.ACID靠什么保证的
A原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql
C一致性由其他三大特性保证、程序代码要保证业务上的一致性
I隔离性由MVCC来保证
D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,宕机的时候可
以从redo log恢复
以从redo log恢复
11.什么是MVCC
多版本并发控制:读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,
不同的事务session会看到自己特定版本的数据,版本链
不同的事务session会看到自己特定版本的数据,版本链
12.分表后非sharding_key的查询怎么处理,分表后的排序
1. 可以做一个mapping表,比如这时候商家要查询订单列表怎么办呢?不带user_id查询的话你总不
能扫全表吧?所以我们可以做一个映射关系表,保存商家和用户的关系,查询的时候先通过商家查
询到用户列表,再通过user_id去查询。
能扫全表吧?所以我们可以做一个映射关系表,保存商家和用户的关系,查询的时候先通过商家查
询到用户列表,再通过user_id去查询。
2. 宽表,对数据实时性要求不是很高的场景,比如查询订单列表,可以把订单表同步到离线(实时)
数仓,再基于数仓去做成一张宽表,再基于其他如es提供查询服务。
数仓,再基于数仓去做成一张宽表,再基于其他如es提供查询服务。
3. 数据量不是很大的话,比如后台的一些查询之类的,也可以通过多线程扫表,然后再聚合结果的方
式来做。或者异步的形式也是可以的。
式来做。或者异步的形式也是可以的。
13.mysql主从同步原理
Mysql的主从复制中主要有三个线程: master(binlog dump thread)、slave(I/O thread 、SQL thread) ,Master一条线程和Slave中的两条线程。
主节点 binlog,主从复制的基础是主库记录数据库的所有变更记录到 binlog。binlog 是数据库服
务器启动的那一刻起,保存所有修改数据库结构或内容的一个文件。
务器启动的那一刻起,保存所有修改数据库结构或内容的一个文件。
主节点 log dump 线程,当 binlog 有变动时,log dump 线程读取其内容并发送给从节点。
从节点 I/O线程接收 binlog 内容,并将其写入到 relay log 文件中。
从节点的SQL 线程读取 relay log 文件内容对数据更新进行重放,最终保证主从数据库的一致性。
注:主从节点使用 binglog 文件 + position 偏移量来定位主从同步的位置,从节点会保存其已接收到的
偏移量,如果从节点发生宕机重启,则会自动从 position 的位置发起同步。
偏移量,如果从节点发生宕机重启,则会自动从 position 的位置发起同步。
由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生
一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个
概念。
一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个
概念。
全同步复制
主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方
式的话性能会受到严重影响。
式的话性能会受到严重影响。
半同步复制
和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至
少一个从库的确认就认为写操作完成。
少一个从库的确认就认为写操作完成。
14.简述MyISAM和InnoDB的区别
MyISAM
不支持事务,但是每次查询都是原子的;
支持表级锁,即每次操作是对整个表加锁;
存储表的总行数;
一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;
采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引
不用保证唯一性。
不用保证唯一性。
InnoDb
支持ACID的事务,支持事务的四种隔离级别;
支持行级锁及外键约束:因此可以支持写并发;
不存储总行数;
15.简述mysql中索引类型及对数据库的性能的影响
索引类型
普通索引:允许被索引的数据列包含重复的值。
唯一索引:可以保证数据记录的唯一性。
主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用
关键字 PRIMARY KEY 来创建。
关键字 PRIMARY KEY 来创建。
联合索引:索引可以覆盖多个数据列,如像INDEX(columnA, columnB)索引。
全文索引:通过建立 倒排索引 ,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引
擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引
擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引
性能影响
索引可以极大的提高数据的查询速度
通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件
索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚
簇索引,那么需要的空间就会更大,如果非聚集索引很多,一旦聚集索引改变,那么所有非聚集索引都
会跟着变
簇索引,那么需要的空间就会更大,如果非聚集索引很多,一旦聚集索引改变,那么所有非聚集索引都
会跟着变
16.mysql执行计划怎么看
EXPLAIN SELECT * from A where X=? and Y=?
1。id :是一个有顺序的编号,是查询的顺序号,有几个 select 就显示几行。id的顺序是按 select 出现
的顺序增长的。id列的值越大执行优先级越高越先执行,id列的值相同则从上往下执行,id列的值为
NULL最后执行。
的顺序增长的。id列的值越大执行优先级越高越先执行,id列的值相同则从上往下执行,id列的值为
NULL最后执行。
2。selectType 表示查询中每个select子句的类型
SIMPLE: 表示此查询不包含 UNION 查询或子查询
PRIMARY: 表示此查询是最外层的查询(包含子查询)
SUBQUERY: 子查询中的第一个 SELECT
UNION: 表示此查询是 UNION 的第二或随后的查询
DEPENDENT UNION: UNION 中的第二个或后面的查询语句, 取决于外面的查询
UNION RESULT, UNION 的结果
DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查
询的结果.
询的结果.
DERIVED:衍生,表示导出表的SELECT(FROM子句的子查询)
3.table:表示该语句查询的表
4.type:优化sql的重要字段,也是我们判断sql性能和优化程度重要指标。他的取值类型范围:
const:通过索引一次命中,匹配一行数据
system: 表中只有一行记录,相当于系统表;
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配
ref: 非唯一性索引扫描,返回匹配某个值的所有
range: 只检索给定范围的行,使用一个索引来选择行,一般用于between、<、>;
index: 只遍历索引树;
ALL: 表示全表扫描,这个类型的查询是性能最差的查询之一。 那么基本就是随着表的数量增多,
执行效率越慢。
执行效率越慢。
5.possible_keys:它表示Mysql在执行该sql语句的时候,可能用到的索引信息,仅仅是可能,实际不一
定会用到。
定会用到。
6.key:此字段是 mysql 在当前查询时所真正使用到的索引。 他是possible_keys的子集
7.key_len:表示查询优化器使用了索引的字节数,这个字段可以评估组合索引是否完全被使用,这也是
我们优化sql时,评估索引的重要指标
我们优化sql时,评估索引的重要指标
9.rows:mysql 查询优化器根据统计信息,估算该sql返回结果集需要扫描读取的行数,这个值相关重
要,索引优化之后,扫描读取的行数越多,说明索引设置不对,或者字段传入的类型之类的问题,说明
要优化空间越大
要,索引优化之后,扫描读取的行数越多,说明索引设置不对,或者字段传入的类型之类的问题,说明
要优化空间越大
10.filtered:返回结果的行占需要读到的行(rows列的值)的百分比,就是百分比越高,说明需要查询到
数据越准确, 百分比越小,说明查询到的数据量大,而结果集很少
数据越准确, 百分比越小,说明查询到的数据量大,而结果集很少
11.extra
using filesort :表示 mysql 对结果集进行外部排序,不能通过索引顺序达到排序效果。一般有
using filesort都建议优化去掉,因为这样的查询 cpu 资源消耗大,延时大。
using filesort都建议优化去掉,因为这样的查询 cpu 资源消耗大,延时大。
using index:覆盖索引扫描,表示查询在索引树中就可查找所需数据,不用扫描表数据文件,往
往说明性能不错。
往说明性能不错。
using temporary:查询有使用临时表, 一般出现于排序, 分组和多表 join 的情况, 查询效率不
高,建议优化。
高,建议优化。
using where :sql使用了where过滤,效率较高。
redis
1.RDB 和 AOF 机制
RDB:Redis DataBase
优点
1、整个Redis数据库将只包含一个文件 dump.rdb,方便持久化。
2、容灾性好,方便备份。
3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进
程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
4.相对于数据集大时,比 AOF 的启动效率更高。
缺点
1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢
失。所以这种方式更适合数据要求不严谨的时候)
失。所以这种方式更适合数据要求不严谨的时候)
2、由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导
致整个服务器停止服务几百毫秒,甚至是1秒钟。
致整个服务器停止服务几百毫秒,甚至是1秒钟。
AOF:Append Only File
优点
1、数据安全,Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也
是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据
将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁
盘中。
是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据
将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁
盘中。
2、通过 append 模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过 redischeck-aof 工具解决数据一致性问题。
3、AOF 机制的 rewrite 模式。定期对AOF文件进行重写,以达到压缩的目的
缺点
1、AOF 文件比 RDB 文件大,且恢复速度慢。
2、数据集大的时候,比 rdb 启动效率低。
3、运行效率没有RDB高
2.Redis的过期键的删除策略
惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化
地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而
不会被清除,占用大量内存。
地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而
不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其
中已过期的key。该策略是一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,
可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
中已过期的key。该策略是一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,
可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
3.Redis线程模型、单线程快的原因
子主题
单线程快的原因
纯内存操作
核心是基于非阻塞的IO多路复用机制
单线程反而避免了多线程的频繁上下文切换带来的性能问题
4.简述Redis事务实现
5.redis集群方案
哨兵模式
sentinel,哨兵是 redis 集群中非常重要的一个组件,主要有以下功能
集群监控:负责监控 redis master 和 slave 进程是否正常工作。
消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布
式选举
式选举
即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
哨兵通常需要 3 个实例,来保证自己的健壮性。
哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演
练。
练。
Redis Cluster
方案说明
通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了
16384 个槽位
16384 个槽位
每份数据分片会存储在多个互为主从的多节点上
数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
同一分片多个节点间的数据不保持强一致性
读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
扩容时需要需要把旧节点的数据迁移一部分到新节点
在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端
口号,比如 16379。
16379 端口号是用来进行节点间通信的,也就是 cluster bus 的通信,用来进行故障检测、配置更新、
故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交
换,占用更少的网络带宽和处理时间。
口号,比如 16379。
16379 端口号是用来进行节点间通信的,也就是 cluster bus 的通信,用来进行故障检测、配置更新、
故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交
换,占用更少的网络带宽和处理时间。
优点
无中心架构,支持动态扩容,对业务透明
具备Sentinel的监控和自动Failover(故障转移)能力
客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
高性能,客户端直连redis服务,免去了proxy代理的损耗
缺点
运维也很复杂,数据迁移需要人工干预
只能使用0号数据库
不支持批量操作(pipeline管道操作)
分布式逻辑和存储模块耦合等
Redis Sharding
优点
优势在于非常简单,服务端的Redis实例彼此独立,相互无关联,每个Redis实例像单服务器一样运行,
非常容易线性扩展,系统的灵活性很强
非常容易线性扩展,系统的灵活性很强
缺点
由于sharding处理放到客户端,规模进一步扩大时给运维带来挑战。
客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新
调整。连接不能共享,当应用规模增大时,资源浪费制约优化
客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新
调整。连接不能共享,当应用规模增大时,资源浪费制约优化
6.redis 主从复制的核心原理
全量复制
主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬
盘IO的
盘IO的
主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行
bgrewriteaof,也会带来额外的消耗
bgrewriteaof,也会带来额外的消耗
部分复制
1. 复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset
2. 复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区,
当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
3. 服务器运行ID(runid):每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成,主节点
会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。 从节点Redis断开重连的时
候,就是根据运行ID来判断同步的进度:
会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。 从节点Redis断开重连的时
候,就是根据运行ID来判断同步的进度:
如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继
续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并
不是当前的主节点,只能进行全量复制
不是当前的主节点,只能进行全量复制
7.缓存雪崩、缓存穿透、缓存击穿
缓存雪崩
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓
存。
存。
缓存预热
互斥锁
缓存穿透
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有
效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户
反复用同一个id暴力攻击
效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户
反复用同一个id暴力攻击
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据
会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
缓存击穿
设置热点数据永远不过期。
加互斥锁
分布式/微服务
1.CAP理论,BASE理论
CAP
Consistency (一致性)
即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致。
对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。
从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。
Availability (可用性)
即服务一直可用,而且是正常响应时间。系统能够很好的为用户服务,不出现用户操作失败或者访问超
时等用户体验不好的情况。
时等用户体验不好的情况。
Partition Tolerance (分区容错性)
即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。分
区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如
现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对
于用户而言并没有什么体验上的影响。
区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如
现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对
于用户而言并没有什么体验上的影响。
BASE
基本可用
响应时间上的损失: 正常情况下,处理用户请求需要 0.5s 返回结果,但是由于系统出现故障,处理
用户请求的时间变为 3 s。
用户请求的时间变为 3 s。
系统功能上的损失:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,
系统的部分非核心功能无法使用。
系统的部分非核心功能无法使用。
软状态
数据同步允许一定的延迟
最终一致性
系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,不要
求实时
求实时
2.负载均衡算法、类型
算法
1、轮询法
2、随机法
3、源地址哈希法
4、加权轮询法
5、加权随机法
6、最小连接数法
类型
DNS 方式实现负载均衡
硬件负载均衡:F5 和 A10
软件负载均衡
Nginx
七层负载均衡,支持 HTTP、E-mail 协议,同时也支持 4 层负载均衡
HAproxy
支持七层规则的,性能也很不错。OpenStack 默认使用的负载均衡软件就是
HAproxy
HAproxy
LVS
运行在内核态,性能是软件负载均衡中最高的,严格来说工作在三层,所以更通用一些,
适用各种应用服务
适用各种应用服务
】
3.分布式架构下,Session 共享有什么方案
1、采用无状态服务,抛弃session
2、存入cookie(有安全风险)
3、服务器之间进行 Session 同步,这样可以保证每个服务器上都有全部的 Session 信息,不过当服务
器数量比较多的时候,同步是会有延迟甚至同步失败;
器数量比较多的时候,同步是会有延迟甚至同步失败;
4、 IP 绑定策略
5、使用 Redis 存储
实现了 Session 共享;
可以水平扩展(增加 Redis 服务器);
服务器重启 Session 不丢失(不过也要注意 Session 在 Redis 中的刷新/失效机制);
不仅可以跨服务器 Session 共享,甚至可以跨平台(例如网页端和 APP 端)。
4.简述你对RPC、RMI的理解
RPC
RMI
5.分布式id生成方案
uuid
如何生成
当前日期和时间 时间戳
时钟序列。 计数器
全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
优点
代码简单,性能好(本地生成,没有网络消耗),保证唯一(相对而言,重复概率极低可以忽 略)
缺点
每次生成的ID都是无序的,而且不是全数字,且无法保证趋势递增。
UUID生成的是字符串,字符串存储性能差,查询效率慢,写的时候由于不能产生顺序的append 操作,需要进 行insert操作,导致频繁的页分裂,这种操作在记录占用空间比较大的情况下,性 能下降比较大,还会增加读 取磁盘次数
UUID长度过长,不适用于存储,耗费数据库性能。
ID无一定业务含义,可读性差。
有信息安全问题,有可能泄露mac地址
数据库自增序列
单机模式
优点
实现简单,依靠数据库即可,成本小
ID数字化,单调自增,满足数据库存储和查询性能
具有一定的业务可读性
缺点
强依赖DB,存在单点问题,如果数据库宕机,则业务不可用
DB生成ID性能有限,单点数据库压力大,无法扛高并发场景
信息安全问题,比如暴露订单量,url查询改一下id查到别人的订单
数据库高可用
优点
解决了ID生成的单点问题,同时平衡了负载。
缺点
系统扩容困难:系统定义好步长之后,增加机器之后调整步长困难。
数据库压力大:每次获取一个ID都必须读写一次数据库。
主从同步的时候:电商下单->支付insert master db select数据 ,因为数据同步延迟导致 查不到这个数 据。加cache(不是最好的解决方式)数据要求比较严谨的话查master主库。
Leaf-segment
基于redis、mongodb、zk等中间件生成
雪花算法
优点
每个毫秒值包含的ID值很多,不够可以变动位数来增加,性能佳(依赖workId的实现)
时间戳值在高位,中间是固定的机器码,自增的序列在低位,整个ID是趋势递增的
能够根据业务场景数据库节点布置灵活挑战bit位划分,灵活度高
缺点
强依赖于机器时钟,如果时钟回拨,会导致重复的ID生成,所以一般基于此的算法发现时钟回拨, 都会抛异常处 理,阻止ID生成,这可能导致服务不可用。
6.分布式锁解决方案
数据库
利用主键冲突控制一次只有一个线程能获取锁,非阻塞、不可重入、单点、失效时间
Zookeeper分布式锁
zk通过临时节点,解决了死锁的问题,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临 时节点就会自动删除掉,其他客户端自动获取锁。临时顺序节点解决惊群效应
Redis分布式锁
setNX,单线程处理网络请求,不需要考虑并发安全性
7.分布式事务解决方案
XA规范
分布式事务规范,定义了分布式事务模型
四个角色
事务管理器(协调者TM)
资源管理器(参与者RM)
应用程序AP
通信资源管理器CRM
全局事务
一个横跨多个数据库的事务,要么全部提交、要么全部回滚
JTA事务时java对XA规范的实现,对应JDBC的单库事务
协议
两阶段协议
第一阶段( prepare ) :每个参与者执行本地事务但不提交,进入 ready 状态,并通知协调者已经准
备就绪。
备就绪。
第二阶段( commit ) 当协调者确认每个参与者都 ready 后,通知参与者进行 commit 操作;如果有
参与者 fail ,则发送 rollback 命令,各参与者做回滚。
参与者 fail ,则发送 rollback 命令,各参与者做回滚。
问题
单点故障:一旦事务管理器出现故障,整个系统不可用(参与者都会阻塞住)
数据不一致:在阶段二,如果事务管理器只发送了部分 commit 消息,此时网络发生异常,那么
只有部分参与者接收到 commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一
致。
只有部分参与者接收到 commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一
致。
响应时间较长:参与者和协调者资源都被锁住,提交或者回滚之后才能释放
不确定性:当协事务管理器发送 commit 之后,并且此时只有一个参与者收到了 commit,那么当
该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。
该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。
三阶段协议
第一阶段:CanCommit阶段,协调者询问事务参与者,是否有能力完成此次事务。
如果都返回yes,则进入第二阶段
有一个返回no或等待响应超时,则中断事务,并向所有参与者发送abort请求
第二阶段:PreCommit阶段,此时协调者会向所有的参与者发送PreCommit请求,参与者收到后
开始执行事务操作。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈
“Ack”表示我已经准备好提交了,并等待协调者的下一步指令。
开始执行事务操作。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈
“Ack”表示我已经准备好提交了,并等待协调者的下一步指令。
第三阶段:DoCommit阶段, 在阶段二中如果所有的参与者节点都返回了Ack,那么协调者就会从
“预提交状态”转变为“提交状态”。然后向所有的参与者节点发送"doCommit"请求,参与者节点在
收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参
与者的Ack消息后完成事务。 相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超
时,那么协调者都会向所有的参与者节点发送abort请求,从而中断事务。
“预提交状态”转变为“提交状态”。然后向所有的参与者节点发送"doCommit"请求,参与者节点在
收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参
与者的Ack消息后完成事务。 相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超
时,那么协调者都会向所有的参与者节点发送abort请求,从而中断事务。
TCC(补偿事务):Try、Confirm、Cancel
Try操作做业务检查及资源预留,Confirm做业务确认操作,Cancel实现一个与Try相反的操作既回滚操
作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有
分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中
Confirm/Cancel操作若执行失败,TM会进行重试。
作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有
分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中
Confirm/Cancel操作若执行失败,TM会进行重试。
TCC模型对业务的侵入性较强,改造的难度较大,每个操作都需要有 try 、 confirm 、 cancel 三个接
口实现
口实现
confirm 和 cancel 接口还必须实现幂等性。
消息队列的事务消息
发送prepare消息到消息中间件
发送成功后,执行本地事务
如果事务执行成功,则commit,消息中间件将消息下发至消费端(commit前,消息不会被
消费)
消费)
如果事务执行失败,则回滚,消息中间件将这条prepare消息删除
消费端接收到消息进行消费,如果消费失败,则不断重试
8.如何实现接口的幂等性
唯一id。每次操作,都根据操作和内容生成唯一的id,在执行之前先判断id是否存在,如果不存在
则执行后续操作,并且保存到数据库或者redis等。
则执行后续操作,并且保存到数据库或者redis等。
服务端提供发送token的接口,业务调用接口前先获取token,然后调用业务接口请求时,把token
携带过去,务器判断token是否存在redis中,存在表示第一次请求,可以继续执行业务,执行业务
完成后,最后需要把redis中的token删除
携带过去,务器判断token是否存在redis中,存在表示第一次请求,可以继续执行业务,执行业务
完成后,最后需要把redis中的token删除
建去重表。将业务中有唯一标识的字段保存到去重表,如果表中存在,则表示已经处理过了
版本控制。增加版本号,当版本号符合时,才能更新数据
状态控制。例如订单有状态已支付 未支付 支付中 支付失败,当处于未支付的时候才允许修改为支
付中等
付中等
9.简述ZAB 协议
10.zk的数据模型和节点类型
11.简述zk的命名服务、配置管理、集群管理
12.讲下Zookeeper watch机制
13.zk和eureka的区别
14.Spring Cloud和Dubbo的区别
底层协议
springcloud基于http协议
dubbo基于Tcp协议
注册中心
Spring Cloud 使用的 eureka、nacos
dubbo推荐使用zookeeper
模型定义
dubbo 将一个接口定义为一个服务
SpringCloud 则是将一个应用定义为一个服务
15.什么是Hystrix?简述实现机制
分布式容错框架
阻止故障的连锁反应,实现熔断
快速失败,实现优雅降级
提供实时的监控和告警
资源隔离
线程隔离:Hystrix会给每一个Command分配一个单独的线程池,这样在进行单个服务调用的时
候,就可以在独立的线程池里面进行,而不会对其他线程池造成影响
候,就可以在独立的线程池里面进行,而不会对其他线程池造成影响
信号量隔离:客户端需向依赖服务发起请求时,首先要获取一个信号量才能真正发起调用,由于信
号量的数量有限,当并发请求量超过信号量个数时,后续的请求都会直接拒绝,进入fallback流
程。信号量隔离主要是通过控制并发请求量,防止请求线程大面积阻塞,从而达到限流和防止雪崩
的目的。
号量的数量有限,当并发请求量超过信号量个数时,后续的请求都会直接拒绝,进入fallback流
程。信号量隔离主要是通过控制并发请求量,防止请求线程大面积阻塞,从而达到限流和防止雪崩
的目的。
熔断和降级:调用服务失败后快速失败
熔断是为了防止异常不扩散,保证系统的稳定性
降级:编写好调用失败的补救逻辑,然后对服务直接停止运行,这样这些接口就无法正常调用,但又不
至于直接报错,只是服务水平下降
至于直接报错,只是服务水平下降
总结
通过HystrixCommand 或者HystrixObservableCommand 将所有的外部系统(或者称为依赖)
包装起来,整个包装对象是单独运行在一个线程之中(这是典型的命令模式)。
包装起来,整个包装对象是单独运行在一个线程之中(这是典型的命令模式)。
超时请求应该超过你定义的阈值
为每个依赖关系维护一个小的线程池(或信号量); 如果它变满了,那么依赖关系的请求将立即被
拒绝,而不是排队等待。
拒绝,而不是排队等待。
统计成功,失败(由客户端抛出的异常),超时和线程拒绝。
打开断路器可以在一段时间内停止对特定服务的所有请求,如果服务的错误百分比通过阈值,手动
或自动的关闭断路器。
或自动的关闭断路器。
当请求被拒绝、连接超时或者断路器打开,直接执行fallback逻辑。
近乎实时监控指标和配置变化。
16.springcloud核心组件及其作用
“xxx系统”包括xx子系统、xx子系统、xx子系统、xx子 系统、xx子系统等。项目是基于 Spring Boot2.x快速集成开发,选择Spring Cloud进 行分布式微服务架构,主体采用Spring + SpringMVC + Mybatis 框架,使用nacos作为注册 和发现中心,使用gateway作为网关,使用rabbitMQ作为队列实现,使用Fegin进行微服务 之间的通信,使用Redis作为缓存服务,使用xxljob作为定时任务框架,集成Prometheus监 控。项目采用前后台分离的开 发模式,前端使用ant-design-pro-vue框架,集成Echats实现 了数据大屏,后端通过Swagger2框架生成接口描述文档, 并通过ElasticSearch进行日志收集 查询。
17.Dubbo 的整体架构设计及分层
五个角色
注册中心registry:服务注册与发现
服务提供者provider:暴露服务
服务消费者consumer:调用远程服务
监控中心monitor:统计服务的调用次数和调用时间
容器container:服务允许容器
调用流程
1:container容器负责启动、加载、运行provider
2:provider在启动时,向regisitry中心注册自己提供的服务
3:consumer在启动时,向regisitry中心订阅自己所需的服务
4:regisitry返回服务提供者列表给consumer,如果有变更,registry将基于长连接推送变更数据给
consumer
consumer
5:consumer调用provider服务,基于负载均衡算法进行调用
6:consumer调用provider的统计,基于短链接定时每分钟一次统计到monitor
分层
接口服务层( Service):面向开发者,业务代码、接口、实现等
配置层( Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig 为中心
服务代理层( Proxy):对生产者和消费者、dubbo都会产生一个代理类封装调用细节,业务层对远程
调用无感
调用无感
服务注册层( Registry) : 封装服务地址的注册和发现, 以服务 URL 为中心
路由层( Cluster) : 封装多个提供者的路由和负载均衡, 并桥接注册中心
监控层( Monitor) : RPC 调用次数和调用时间监控
远程调用层( Protocal):封装 RPC 调用
信息交换层( Exchange): 封装请求响应模式, 同步转异步
网络传输层( Transport):抽象 mina 和 netty 为统一接口,统一网络传输接口
数据序列化层( Serialize) : 数据传输的序列化和反序列化
MQ
1.简述RabbitMQ的架构设计
Broker:rabbitmq的服务节点
Queue:队列,是RabbitMQ的内部对象,用于存储消息。RabbitMQ中消息只能存储在队列中。生产
者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的
消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费者都收到所有的消息进行消费。(注
意:RabbitMQ不支持队列层面的广播消费,如果需要广播消费,可以采用一个交换器通过路由Key绑
定多个队列,由多个消费者来订阅这些队列的方式。
者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的
消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费者都收到所有的消息进行消费。(注
意:RabbitMQ不支持队列层面的广播消费,如果需要广播消费,可以采用一个交换器通过路由Key绑
定多个队列,由多个消费者来订阅这些队列的方式。
Exchange:交换器。生产者将消息发送到Exchange,由交换器将消息路由到一个或多个队列中。如果
路由不到,或返回给生产者,或直接丢弃,或做其它处理。
路由不到,或返回给生产者,或直接丢弃,或做其它处理。
RoutingKey:路由Key。生产者将消息发送给交换器的时候,一般会指定一个RoutingKey,用来指定
这个消息的路由规则。这个路由Key需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。
在交换器类型和绑定键固定的情况下,生产者可以在发送消息给交换器时通过指定RoutingKey来决定消
息流向哪里。
这个消息的路由规则。这个路由Key需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。
在交换器类型和绑定键固定的情况下,生产者可以在发送消息给交换器时通过指定RoutingKey来决定消
息流向哪里。
Binding:通过绑定将交换器和队列关联起来,在绑定的时候一般会指定一个绑定键,这样RabbitMQ
就可以指定如何正确的路由到队列了。
就可以指定如何正确的路由到队列了。
信道:信道是建立在Connection 之上的虚拟连接。当应用程序与Rabbit Broker建立TCP连接的时候,
客户端紧接着可以创建一个AMQP 信道(Channel) ,每个信道都会被指派一个唯一的D。RabbitMQ 处
理的每条AMQP 指令都是通过信道完成的。信道就像电缆里的光纤束。一条电缆内含有许多光纤束,允
许所有的连接通过多条光线束进行传输和接收。
客户端紧接着可以创建一个AMQP 信道(Channel) ,每个信道都会被指派一个唯一的D。RabbitMQ 处
理的每条AMQP 指令都是通过信道完成的。信道就像电缆里的光纤束。一条电缆内含有许多光纤束,允
许所有的连接通过多条光线束进行传输和接收。
2.RabbitMQ如何确保消息发送 ? 消息接收?
3.RabbitMQ事务消息
4.RabbitMQ死信队列、延时队列
5.RabbitMQ镜像队列机制
6.简述kafka架构设计
7.kafka怎么处理消息顺序、重复发送、重复消费、消息丢失
8.Kafka在什么情况下会出现消息丢失及解决方案
9.Kafka是pull?push?优劣势分析
10.Kafka中zk的作用
11.简述kafka的rebalance机制
12.Kafka的性能好在什么地方
0 条评论
下一页