目录
5 String, StringBuilder,StringBuffer三者的区别
2 Mybatis 和 Mybatis Plus 的区别和优化
1 SpringBoot自动装配原理(@AutoWired)
1 elasticsearch 了解多少,说说你们公司 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段
面试前言:
《葵花宝典》奉上!-!,最近浏览了以下网上的面试题,很多都是长篇大论,甚至上百上千到题,看到这些面试题,让人直打退堂鼓,我结合了市面上常见的面试题,总结了一下内容,可以说再面试过程中百分之70的问题,都在这了,剩下30问道就是不会,谁也不能问啥会啥吧,大家一起卷起来吧!!
个人介绍:省略 ...
项目介绍:省略 ...
Java基础篇
1 抽象类与接口区别
抽象类可以有构造方法,接口中不能有构造方法、 抽象类中可以有普通成员变量,接口中没有普通成员变量、 一个类可以实现多个接口,但只能继承一个抽象类。 抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型 普通类和抽象类的区别总结 1、抽象类的存在时为了被继承,不能实例化,而普通类存在是为了实例化一个对象 2、抽象类的子类必须重写抽象类中的抽象方法,而普通类可以选择重写父类的方法,也可以直接调用父类的方法 3、抽象类必须用abstract来修饰,普通类则不用 4、普通类和抽象类都可以含有普通成员属性和普通方法 5、普通类和抽象类都可以继承别的类或者被别的类继承 6、普通类和抽象类的属性和方法都可以通过子类对象来调用
2 == 和 equals 的区别是什么?
== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用; equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。 当两个对象equals相等,hashcode值一定相等,反之equals不相等,hashcode值有可能会相等,所以重写equals.
3 Stream常用方法
map: 用作类型转换 如把集合里面的字符串转为大写,或者一个对象的集合取几个字段转为新的对象集合 filter: 过滤 符合条件的集合元素保存下来,不符合条件的去掉 flatMap:合并集合,比如List<Album> Album里面有一LIst<Track> 对象,这个时候就能不通过循环的方式把 List<Album> 里的每一个元素的 trasks 对象组装成一个新的集合 reduce: reduce可以做累加运算, .reduce(0, (a,b)-> a+b); count: count和size一样返回的是元素的个数 max,min: 求最大值和最小值,这两个方法需要传入一个comparator比较器,Comparator比较器有一个comparing() 方法 anyMatch表示,判断的条件里,任意一个元素成功,返回true allMatch表示,判断条件里的元素,所有的都是,返回true
4 final 在 Java 中有什么作用
final 修饰的类叫最终类,该类不能被继承。 final 修饰的方法不能被重写。 final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。
5 String, StringBuilder,StringBuffer三者的区别
String是一个不可改变的字符序列. StringBuilder是一个可以改变的字符序列. 常见的字符拼接,该选择谁 推荐使用StringBuilder,因为拼接的效率高 StringBuffer是jdk1.0出现的,线程安全(同步):效率低. ------主要因为StringBuffer很多方法都是synchronized 修饰的 StringBuilder是jdk1.5出现的,线程不安全(不同步):效率高.
6 反射常见API
获取类的实例 类名.Class 类对象.getClass() class.forName(全类名) --静态的 getDeclaredField() getFields() --获取属性 getMethod() ---获取方法 newInstance() ---创建实例对象(依赖无参构造创建) getClassLoader() --获取类加载器 getName() ---返回该类对象的全限定名称
7 Java中finally和return执行顺序
finally语句在return语句执行之后 return返回之前执行的 finally块中的return语句会覆盖try块中的return返回 如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变 try块里的return语句在异常的情况下不会被执行,这样具体返回哪个看情况 当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样
8 基本排序
冒泡排序
public void bubbleSort(int[] arr) { //从小到大
int temp = 0;
for(int i = 0; i < arr.length -1; i++){ //控制趟数,到倒数第二个为止
for(int j = arr.length-1; j>i; j--){ //从最后一个值开始冒泡,将后面的小值与前面的大值进行交换,并且保证循环到前面已经排序完的索引为止
if(arr[j-1] > arr[j]){
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}
}
}
}
选择排序:
public void selectionSort(int[] arr){
int temp = 0;
int k = 0; //存储最小值的索引
for(int i = 0; i<arr.lengrh - 1; i++){ //控制趟数,到倒数第二个为止
k = i;
for(int j = i; j<arr.length;j++){ //将第一个数默认为最小值,将其索引赋值给k,从k索引开始,将后面每个数与k索引对应的值比较,如果值小了,就将其索引赋值给k
if(arr[j] < arr[k]){
k = j;
}
}
//遍历完后,k就指向了最小的值,将其与i对应的值交换(也可 以先做个判断,判断k的索引是否有变化,无变化可以不交换)
temp = arr[k];
arr[k] = arr[i];
arr[i] = temp;
}
}
9 JDK 1.8新特性
1.default关键字 jdk1.8引⼊了新的关键字default,通过使⽤default修饰⽅法,可以让我们在接⼝⾥⾯定义具体的⽅法实现 2.Lambda表达式 函数式编程 3.Date Api更新 LocalDate/LocalTime/LocalDateTime TemporalAdjusters DateTimeFormatter 前⽇期格式化 4.流 5.Objects⽅法新特性 isNull() 判断传⼊的obj是否为null nonNull() 判断传⼊的obj是否不为null requireNonNull() 如果传⼊的obj为null抛出NullPointerException并且使⽤参数messageSupplier指定错误信息,否者返回obj
JVM篇
1 JVM内存结构
JVM主要有三个核心组成部分,分别是类加载的一个系统,运行时数据区和执行引擎 1. 类加载系统,它的主要功能是去查找和验证.class文件并且去完成内存空间的分配和对象的赋值 2. 运行时数据区 包括 方法区,堆, 本地方法栈,虚拟机栈和程序计数器 方法区 用来存储 静态变量+常量+类信息 JDK1.8 使用元空间 MetaSpace 替代方法区,元空间并不在 JVM中,而是使用本地内存。 堆 主要用来存储Java对象的实例 而堆又分为新生代和老年代,新生代又有伊甸园区,survivor0区和survivor1区 默认情况下按照8:1:1的比例进行分配。 //survivor区域是将在Eden中未被清理的对象存放到该区域中,采用的是标记复制算法 本地方法栈 负责加载并运行native方法 虚拟机栈 为java执行Java方法服务的 程序计数器 保存每个线程执行方法的地址 方法区和堆是线程共享的,而栈和程序计数器是线程私有的 3. 执行引擎包含即时编译器和垃圾回收器 即时编译器负责将字节码翻译成CPU指令,可以通过JVM参数来设置它的执行方式 垃圾回收器 负责对运行时数据区的数据进行回收和管理,本质是对各种垃圾回收算法的实现
2 JVM如何确定对象要被回收
JVM在进行垃圾回收时会通过一个 可达性分析 的算法来确定对象是否可达,如果可达就不能回收 可达性分析的主要逻辑: JVM会从GC ROOT开始,顺着对象的引用去查找对象,能被查找到的对象就是可达的对象
3 垃圾回收算法
1 标记清除算法 核心逻辑: 是会先标记出存活的对象,然后没有标记的对象就是需要回收的垃圾对象 缺点: 会产生很多内存碎片,导致GC频率增加 2 标记复制算法(年轻代) 核心逻辑: 是会把内存分为两个相等的部分,每次只使用其中一部分,在使用的这部分内存满了之后, 会先标记处存活的对象,把存活的对象拷贝到另一半闲置的内存中,剩下的对象就会被回收 缺点: 只有50%的空间使用,存活对象较多时,复制会非常耗时 适用于存活对象少,垃圾对象多的场景 3 标记整理法(老年代) 核心逻辑: 会先标记出存活的对象,把所有存活的对象整理到内存空间的一端,内有被标记的就会被覆盖或者释放
4 JVM参数
Xms: 主要作用是用来分配内存的初始化大小 Xmx: 用来配置最大堆内存 //一般 Xmx和Xms设置为相同大小,可以避免内存的自动扩展 Xmn: 设置新生代的内存大小 Xss: 每个线程的虚拟机栈大小
5 类加载过程
加载: 获取类的二进制字节流,在堆中生成class字节码对象 验证: 确保被加载类的正确性 准备: 为类的静态变量分配内存并初始化为默认值 解析: 虚拟机将常量池中的符号引用替换成直接引用 初始化: 为类的静态变量赋予正确的初始值 类实例化方法调用顺序 父类静态变量--父类静态代码块--子类静态变量--子类静态代码块 父类成员变量--父类构造函数--子类成员变量--子类构造函数
6 双亲委派
指优先委派上级类加载器进行加载, 如果上级类加载器能找到这个类,就由上级加载,加载后的类对下级类加载器可见, 如果找不到,则下级类加载器才有资格加载 目的: 1 让上级类加载器中的类对下级共享,反之不行;即让你的类能依赖到jdk的核心类 2 让类的加载有优先次序,保证核心类优先加载
7 对象创建过程
1. JVM会先去方法区下检查类是否已经被加载,有就可以创建对象了,没有则把该类加载到方法区 2. 在堆内存中为该对象分配内存空间 3. 加载对象中所有的非静态成员变量到该空间下 4. 所有的非静态成员变量加载完成之后,对所有的非静态成员进行默认初始化 5. 所有的非静态成员默认初始化完成之后,调用相应的构造方法到栈中 6. 在栈中执行构造函数时,先执行隐式,再执行构造方法中书写的代码 7. 执行顺序:静态代码库,构造代码块,构造方法 8. 当整个构造方法全部执行完,此对象创建完成,并把堆内存中分配的空间地址赋给对象名(此时对象名就指向了该空间)
8 Java内存溢出(泄露)
1. Java堆溢出 Java堆用于存储对象实例,只要不断地创建对象,当对象数量到达最大堆的容量限制后就会产生内存溢出异常。最常见的内存溢出就是存在大 的容器,而没法回收,比如:Map,List等。 内存溢出:内存空间不足导致,新对象无法分配到足够的内存; 内存泄漏:应该释放的对象没有被释放,多见于自己使用容器保存元素的情况下。 2. 虚拟机栈和本地方法栈溢出 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,请见情况:递归调用,无法退出。 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常,线程创建过多导致,每个线程需要申请栈空间。
多线程篇
1 创建线程方式
继承Thread类 //线程启动 start() 实现Runnable接口 //区别 实现Callable接口 //Callabe可以有返回值 线程池方式
2 线程的状态
新建 就绪 //可以分到CPU时间 运行 //已经分到CPU时间 阻塞 //分不到CPU时间 终结
3 sleep()和wait()区别
1. sleep()是Thread类的静态方法,wait()是Object中的成员方法 2. sleep()可以在任意地方使用,而wait()必须获取锁对象才能使用 3. sleep()会设置一个时间,时间到了会自动释放,而wait()必须通过notify()或notifyAll()进行释放 * sleep的线程不占用CPU,wait会占用CPU
4 线程池种类
newCachedThreadPool 可缓存线程池 newFixedThreadPool 固定线程数的线程池 newSingleThreadExecutor 单线程的线程池 newScheduledThreadPool 支持定时及周期性任务执行 //都是通过jdk工具类Executors来构建的 1.通过 ThreadPoolExecutor 手动创建线程池 (有1种实现方法)。 2.使用 Executors 自动创建线程池 (共有6种实现方法)。
5 线程池核心参数
核心线程数 最大线程数 存活时间 //针对救急线程 时间单位 任务队列 线程工厂 拒绝策略 //丢弃并抛出异常 丢弃不抛异常 丢弃最早加入任务队列的 交给调用者线程自己处理 线程池创建后,任务提交进来,首先会创建核心线程数,核心线程数满了以后,会往任务队列里塞,此时有两种情况 1 如果是无界队列,最终有可能会导致OOM 2 如果是有界队列,队列满了以后,会继续创建线程,知道达到最大线程数,然后开始触发拒绝策略 最终如果流量降下来,当临时线程到达存活时间,会进行销毁 核心线程数默认是不会销毁的,但是它有一个超时设置,手动设置为true或执行run方法抛出异常,核心线程都会被销毁
6 volatile关键字的理解
volatile能保证数据的一致性和可见性,但是不能保证原子性 因为我们都是多核CPU,多核CPU说白了就是线程对共享变量进行修改,其他线程可以马上感知到, 在实现MESI协议(CPU缓存一致性)的基础上,它底层是用内存屏障,就是一个store和load指令,在防止指令的重排序,来保证数据一致性, 但是不能保证原子性,想要保证原子性,我们通常的做法就是再去加把锁 //volatile和synchronized区别 volatile只能作用于变量,synchronized可以使用在变量和方法上 volatile能保证可见性和一致性,但是不能保证原子性,synchronized可以 volatile不会造成线程阻塞,synchronized会造成线程阻塞
7 Lock锁和synchronized区别
1. synchronized是java内置的关键字,而Lock是J.U.C包下的接口,实现类ReentrantLock(悲观锁),ReentrantReadWriteLock(乐观锁) 2. synchronized可以写在需要同步的对象、方法或特定代码块中 Lock则是用lock()和unLock()方法进行添加锁和释放锁,可以保证lock()和unLock()之间的代码是线程安全的 3. synchronized是自动释放锁的,而Lock锁需要手动调用unLock()释放锁 而为了避免死锁,一般将unLock()方法写在finally块中,Lock锁还提供了tryLock()方法,通过返回true或false判断锁是否被占用 4. synchronized使用的是悲观锁机制,在JDK1.6对synchronized进行了优化
8 synchronized优化 JDK1.6
synchronized默认采用的是偏向锁,在程序运行的过程中,有一个线程去获得锁,在java对象中会记录下这个线程ID,在下次需要获取锁的时候,只需要去比较这个线程ID就行 如果出现第二个线程去请求获取锁的时候,这里有两种情况 1. 在没有发生并发争抢锁的情况下,这个synchronized就会自动升级为轻量级锁,此时第二个线程就会尝试通过自旋锁的方式来获取锁,因为很快就能拿到锁,所以第二个线程也不会阻塞. 2. 在发生并发争抢锁的情况下,synchronized会自动升级为重量级锁,此时只有一个线程能获得锁,其他线程就会阻塞等待锁的释放,才能拿到锁
9 死锁
概念: 多个并发进程因争夺系统资源而产生相互等待的现象。 //死锁产生的4个必要条件 互斥 占有且等待 不可抢占 循环等待 //防止死锁 尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。 尽量使用 Java.util.concurrent 并发类代替自己手写锁。 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。 尽量减少同步的代码块。
10 ThreadLocal内存泄露
理解: ThreadLocal可以实现 线程间互相隔离,线程内资源共享 采用"空间换时间"的策略,本身是有两层Map嵌套在里面,每个线程内都有一个ThreadLocalMap集合,key是ThreadLocal,value是资源对象 内存泄露原因: 如果ThreadLocal没有外部强引用,在GC时,ThreadLocal就会被回收,而ThreadLocal作为key,被回收就会导致一个key为null的Entry 外部则无法来访问这个entry,无法回收,造成内存泄漏 解决方法: 1. 每次用完ThreadLocal,调用remove()方法清除数据 2. 将ThreadLocal变量定义成static,将它变为外部强引用
IO模型篇
IO对比总结
IO 的方式通常分为几种:同步阻塞的 BIO、同步非阻塞的 NIO、异步非阻塞的 AIO。 BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序直观简单易理解。 NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4 开始支持。 AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。 举个例子: 同步阻塞:你到饭馆点餐,然后在那等着,啥都干不了,饭馆没做好,你就必须等着! 同步非阻塞:你在饭馆点完餐,就去玩儿了。不过玩一会儿,就回饭馆问一声:好了没 啊! 异步非阻塞:饭馆打电话说,我们知道您的位置,一会给你送过来,安心玩儿就可以了, 类似于现在的外卖。
集合篇
1 ArrayList
ArrayListi属于线性表结构,是JDK封装的动态数组 当且仅当第一个元素添加进来时,会将数组长度设置为10 (若使用addAll()方法添加,则初始化大小为10 和 添加集合长度的 较大值) 后面再添加元素,每次都会检查数组容量大小,如果超出容量,底层会调用C++的arraycopy()方法,将数组长度扩容为原来的1.5倍 //删除元素? 比如说删除一个中间元素,会采用一个覆盖的形式,将后面的元素依次往前挪,最后一个位置置为null,等待GC回收
2 ArrayList和LinkedList区别
1. ArrayList基于动态数组,LinkedList基于链表 2. 对于随机访问,ArrayList的时间复杂度是O(1),效率要比LinkedList高 3. 对于新增和删除,LinkedList要优于ArrayList,因为ArrayList会涉及到数据的移动
3 数组和链表的区别
1. 数组在内存中连续,且长度固定;链表在内存中不要求连续,每个元素都指向了下一个元素的地址 2. 数组从栈上分配内存,链表从堆上分配内存 3. 数组插入数据,如果内存空间不连续,需要数据迁移,还可能会出现索引越界;链表则不需要移动数据,只需要更改地址指向,所以速度快 4. 数组在内存中顺序存储,随机访问效率高;而链表因为不连续,随机访问慢
4 HashMap工作原理
HashMap在jdk1.7采用数组加链表,1.8以后引入了红黑树. 在数组的每一个元素都是链表结构,而链表中每个节点又是一个entry对象,用来存储k-v键值对;hashmap中有两个比较重要的方法,get()和put() put(): 在存储k-v键值对的时候,首先会调用一个hash方法,计算出key的hash值,再和数组的长度进行按位与计算,得到对应的桶下标,然后遍历该桶下标下链表上的每个节点的key,和put的key进行equals比较;如果相等,就更新这个key对应的value,如果不相等,就将这个k-v键值对put到链表上 //扩容 1.在put()过程中,当数组元素超过了数组长度乘负载因子(默认为0.75)的时候,会将数组扩容为两倍 //树化 2.在插入链表时,如果链表长度超过了默认树化阈值8 且 数组长度大于等于64的时候,链表节点的数据结构就会变成红黑树 get(): get()方法再调用时,和put()方法类似,会计算出key的hash值和数组长度进行按位与计算得到桶下标,然后遍历该桶下标下链表上所有节点的key,和查询的key进行equals比较,如果相等就返回value给用户. 总结: HashMap 最核心的原理就是利用hash值来计算出桶下标,再进行equals比较 equals比较主要是为了解决hash冲突的问题
5 为什么要引入红黑树
红黑树是二叉查找树的一种,它的查找算法就相当于二分的查找, 红黑树的时间复杂度O(log n)在数据较多的时候会比链表的时间复杂度O(n)要好很多
6 为什么不一上来就用红黑树
我认为是时间和空间的折中考虑,在hash冲突比较小的时候,即使转化为红黑树,在时间复杂度上所产生的效果也不是特别大 而且在put的时候,效率可能会降低,毕竟每次put都要进行非常复杂的红黑树的旋转算法操作,就显得有点得不偿失了
7 红黑树何时退化为链表
1. 扩容时,如果进行了拆分树,且树的长度小于等于6 2. remove树节点时,如果根节点,左右孩子,左孙子有一个为null
8 HashMap的长度为什么是2的幂次方
因为在hashmap长度为2的幂次方时,才会有hash%length==hash&(length-1)
9 HashMap和HashTable区别
HashMap允许存储null键和null值,线程不安全,效率高 HashTable不允许存储null键和null值,线程安全,效率低
10 CurrentHashMap底层实现
1. 在JDK1.7,它的底层是使用数组加链表来实现的,使用了分段锁来保证线程安全,它是将数组分成16段, 给每个segment来配一把锁,在读每个segment的时候都要先获取锁,所以它最多能支持16个线程去并发操作 2. 到了JDK1.8以后,它跟HashMap一样,也引入了红黑树,同时在并发处理方面,不再使用分段锁, 而是采用CAS和synchronized关键字的方式来实现一种更加细粒度的锁, 在写入键值对时,可以锁住链表的头节点,就不会影响其他哈希桶的写入,从而提高并发处理能力
WEB篇
1 cookie和session的区别与联系
一、cookie数据存放在客户的浏览器上,session数据存放在服务器上. 二、很多浏览器限制站点最多保存20个cookie,单个cookie保存的数据不能超过4k. 三、cookie不是很安全,考虑安全应当使用session. 四、可以考虑将登录信息等重要信息存放为session,其它信息如果需要保留,可以放在cookie中. 五、session会在一定时间内保存在服务器上. 六、session会在浏览器关闭或者一段时间内销毁,也可以通过setMaxInactiveInterval(int)方法进行设置,或是通过invalidate()方法强制结束当前会话.cookie可以通过setMaxAge(int)方法设置缓存在客户端的时间. 七、一般情况下,session生成的sessionid都是保存在cookie中. 总结: cookie:在客户端保存数据,不安全.只能保存字符串,且是少量数据. session:在服务器端保存数据,安全.可以保存对象数据,数据无限制. Session是基于cookie的,session的id是存在cookie中的
2 Get方法和Post方法区别
1. post请求放在body中,没有长度限制,且不会被缓存更加安全 get请求参数放在url中,且有长度限制,安全性较差 2. post请求用于修改、写入数据,get一般用于获取数据 3. post能发送更多的数据类型,get只能发送ASCLL码字符
3 servlet的生命周期及常用方法
init()方法:在servlet的生命周期中,仅执行一次init()方法. service()方法:它是servlet的核心,每当客户请求一个httpservlet对象,该对象的service()方法就要调用,而且传递给这个方法一个”请求”对象和一个”响应”对象作为参数. destory()方法:仅执行一次,在服务器端停止且卸载servlet时执行该方法. 解决servlet线程安全 一、继承SingleThreadModel,消耗服务器内存,降低性能.并且过时,不推荐. 二、尽量避免使用全局变量,推荐. 三、通过使用ThreadLocal.
4 转发和重定向的区别
一、重定向是浏览器发送请求并收到响应以后再次向一个新地址发请求;转发是服务器收到请求后为了完成响应转到另一个资源. 二、重定向中有两次请求对象,不共享数据;转发只产生一次请求对象且在组件间共享数据. 三、重定向后地址栏地址改变,而转发不会. 四、重定向的新地址可以是任意地址;转发必须是同一个应用内的某个资源. 获取servlet的转发和响应重定向的方式 转发的方法: 通过HttpServletRequest的getRequestDispatcher()方法获得 通过ServletContext的getRequestDispatcher()方法获得 重定向的方法: HttpServletResponse的sendRedirect()方法.
5 TCP和UDP的区别,HTTP协议
TCP协议提供安全可靠的网络传输服务,它是一种面向连接的服务.类似于打电话,必须先拨号.双方建立一个传递信息的通道传输. UDP协议是一种数据报协议,它传输的数据是分组报文,它是无连接的,不需要和目标通信方建立连接,类似于写信,所以它的传输不保证安全可靠.但适合大数据量的传输. HTTP协议是超文本传输协议,是一种相对于TCP来说更细致的协议,TCP以及UDP协议规范的是网络设备之间的通信规范,HTTP实在TCP协议的基础上针对用户的协议,用户服务具体体现在应用程序之间的交互,比如javaweb中客户端服务端体系就要用http协议来规范通信. TCP和UDP在开发中很少见到,但是网络底层都有他们的影子,正常的会话级别的服务:如客户端服务器体系底层就说基于TCP协议.而邮件发送,短信发送等底层使用的是UDP协议. HTTP协议,客户端/服务器体系的程序都使用HTTP协议来规范通信.
6 三次握手和四次挥手
1)第一次握手:建立连接。客户端发送连接请求报文段,将 SYN 位置为 1,Sequence Number 为 x;然后,客户端进入 SYN_SEND 状态,等待服务器的确认; 2)第二次握手:服务器收到 SYN 报文段。服务器收到客户端的 SYN 报文段,需要对这个 SYN 报文段进行确认,设置 Acknowledgment Number 为x+1(Sequence Number+1);同时,自己自己还要发送 SYN 请求信息,将 SYN 位置为 1,Sequence Number 为y;服务器端将上述所有信息放到一个报文段(即 SYN+ACK 报文段)中,一并发送给客户端,此时服务器进入 SYN_RECV 状态; 3)第三次握手:客户端收到服务器的 SYN+ACK 报文段。然后将 Acknowledgment Number设置为 y+1,向服务器发送 ACK 报文段,这个报文段发送完毕以后,客户端和服务器端都进入 ESTABLISHED 状态,完成 TCP 三次握手。 1 客户端发送一个信号用来关闭客户端到服务端的数据传输 2 服务端接受这个信号,向客户端回发一个信号表示收到 3 服务端关闭与客户端的连接,然后再发一个信号给客户端 4 客户端接受信号之后确认, 再向服务端发信息验证是否已经断开
数据库篇
1 事务的特性和隔离级别
事务的特性: 原子性(Atomicity)指事务不可分割,要么都成功,要么都失败 一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态 隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的 隔离级别: 未提交读read uncommitted 会发生 脏读、不可重复读、虚读 已提交读read committed //Oracle SQL Server(系统事务) 解决脏读,但是不可重复读和虚读有可能发生 重复读repeatable read //Mysql 解决脏读和不可重复读,但是虚读有可能发生. 串行化serializable (只有读读可以并发,读写和写写都会阻塞) 避免脏读,不可重复读,虚读的发生 脏读:读到了别的事务回滚前的脏数据 不可重复读:在一个事务的两次查询中数据不一致,可能是两次查询过程中,数据被更新了 幻读:事务两次查询得到的数据个数不一致,中途被其他事务插入了几行数据 rr读到的是可重复的,为什么update能更新到最新? update会加锁,会首先做一个当前读
2 谈谈你对MySQL索引的理解
MySQL索引的底层数据结构是通过B+树或hash表来实现的,对于不同类型的数据结构是和存储引擎相关的。如果使用的是InnoDB或MyISAM存储引擎,对应的底层数据结构是B+树,如果使用的是Memory存储引擎,对应的底层数据结构是hash表。 一般,我们是根据某个key去找到value,key就是我们索引的某个列的值,value就是这一行的数据。当我们选择用k-v格式存储的时候,可以选择用hash表、二叉树、AVL树、红黑树。但是这最终都会导致树变高,树变高会影响磁盘I/O次数增加,从而导致访问的效率变低。所以选择使用B+树的结构,可以在某一个数据节点里面尽可能多的去存储数据,让树变低,从而减少磁盘I/O的次数,提高访问效率。 同时,在MySQL中有普通索引、唯一索引、主键索引、组合索引、全文索引等各种分类,我们用到的最多的是主键索引和组合索引,但在使用时,会存在回表、覆盖索引、最左匹配、索引下推等细节问题。 我们还可以通过索引来对SQL语句进行优化,提高我们对数据的访问效率 回表: 从某一个索引的叶子节点中获取聚簇索引的id值,根据id再去聚簇索引中获取全量数据 索引覆盖: 从索引的叶子节点中能获取到全量查询列的过程 最左原则: 最左优先,以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配。 索引下推: 索引下推是数据库检索数据过程中为减少回表次数而做的优化
3 MySQL为什么采用B+树而不是B树?
1.从磁盘I/O效率来看: B+树的非叶子节点不存储数据,所以树的每一层能存储更多的索引数据,间接地减少了磁盘I/O的次数,提高查询效率 2.从范围查询效率来看: MySQL对B+数的优化,使B+树存储在叶子节点的数据使用双向链表来关联,因此B+树查询只需要查询两个叶子节点进行遍历就行,而B树要查询 所有节点进行遍历,因此B+树范围查询效率高 3.从全表扫描来看: 因为B+树叶子节点存储了所有数据,所以只需要扫描所有的叶子节点进行遍历,而B树要遍历整个树
4 聚簇索引和非聚簇索引
聚簇索引的叶子节点存储的是行数据,索引和数据紧密联系,可以通过聚簇索引直接查询到数据 非聚簇索引的叶子节点存储的是主键信息,因此需要回表查询,所以聚簇索引的查询效率高 在InnoDB存储引擎中,既存在聚簇索引,又存在非聚簇索引 在MyISAM存储引擎中,只有非聚簇索引,因为它的叶子节点存储的是地址
5 MySQL日志
1.binlog binlog 用于记录数据库执行的写入性操作[不包括查询信息],以二进制的形式保存在磁盘中。 //使用场景 主从复制 :在 Master 端开启 binlog ,然后将 binlog发送到各个 Slave 端, Slave 端重放 binlog 从而达到主从数据一致。 数据恢复 :通过使用 mysqlbinlog 工具来恢复数据。 2.redo log 实现ACID中的持久性,保证提交数据不丢失 事务提交时,首先将redo log记录变更,事务就会视为成功 3.undo log 回滚数据 多版本并发控制: 快照读(一致性)可重复读 每条数据修改(insert、update或delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上
6 sql优化
1. select子句中避免使用‘*’ 2. 避免在索引列上使用计算,not,in和<>等操作 3. 当只需要一行数据的时候使用limit 1 4. 保证表单数据不超过200w,适时分割表 5. 针对查询较慢的语句,可以使用explain来分析该语句具体的执行情况 6. 避免查询时判断null,否则可能会导致全表扫描,无法使用索引; 7. 避免like查询,否则可能导致全表扫描,可以考虑使用全文索引 8. 能用union all的时候就不用union,union过滤重复数据要耗费更多的CPU资源
7 索引失效
1. 如果条件中有or,即使其中有部分条件带索引也不会使用(这也是为什么尽量少用or的原因),例子中user_id无索引。注意:要想使用or,又 想让索引生效,只能将or条件中的每个列都加上索引 2. 对于复合索引,如果不使用前列,后续列也将无法使用,类电话簿。 3. like查询是以%开头 4. 存在索引列的数据类型隐形转换,则用不上索引,比如列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引 5. where 子句里对索引列上有数学运算,用不上索引 6. where 子句里对有索引列使用函数,用不上索引 7. 如果mysql估计使用全表扫描要比使用索引快,则不使用索引
8 三范式
第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项。 第二范式:要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。 第三范式:任何非主属性不依赖于其它非主属性。
9 乐观锁和悲观锁
乐观锁:无需加锁,每次只有一个线程能成功修改共享变量,其他失败的不停止,不断重试直至成功 悲观锁:线程只有占了锁,才能去操作变量,每次只有一个线程占锁成功,获取锁失败的线程,都得停下来等待
10 主键使用自增ID还是UUID?
推荐自增ID,因为在InnoDB存储引擎中,主键是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引及全部的数据(按照顺序),如果主键使用自增ID,那么只需要不断向后排列即可,如果是UUID,想要顺序存储,则需要数据移动,会产生很多内存碎片,造成插入性能下降
11 Mysql的体系结构
//连接层 最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于 TCP/IP的通 信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证 它所具有的操作权限。 //服务层 第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如 过程、函数等。在该层,服务器会解析查询并创建相应的内部 解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等, 最后生成相应的执行操作。如果是 select语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。 //引擎层 存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取, //存储层 服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。 数据存储层,主要是将数据存储在文件系统之上,并完成与存储引擎的交互。和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎上,插件式的存储引擎架构,将查询处理和其他的系统任务以及数据的存储提取分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
12 两表关联
1、INNER JOIN (内连接) -----只返回两个表中连接字段相等的行 2、LEFT OUTER JOIN (左连接) -----返回包括左表中的所有记录和右表中连接字段相等的记录 3、RIGHT OUTER JOIN (右连接)-----返回包括右表中的所有记录和左表中连接字段相等的记录 4、union ------作用很简单用来合并两条sql的结果集
13 mysql常用函数
CONCAT(s1,s2…sn)字符串 s1,s2 等多个字符串合并为一个字符串 case when 类似于编程语言中的if else判断、switch case语句。该语句执行时先对条件进行判断,然后根据判断结果做出相应的操作 FORMAT(x,n)函数 可以将数字x进行格式化,将x保留到小数点后n位
Spring篇
1 spring模块
spring core:框架的最基础部分,提供 ioc 和依赖注入特性。 spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。 spring dao:Data Access Object 提供了JDBC的抽象层。 spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。 spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。 spring Web mvc:spring 中的 mvc 封装包提供了 Web 应用的 Model-View-Controller(MVC)的实现。
2 对spring的理解
Spring是使用了分层思想的,针对Java 开发的轻量级开源框架。以IOC和AOP(面向切面)为内核,提供了展现层Spring MVC,持久层Spring JDBC及业务层事务管理等一站式企业级应用技术。并整合了很多的开源第三方框架。 好处: 方便解耦,简化开发。–通过IOC解耦。 AOP的编程的支持。–面向切面编程。 声明式事务的支持。 方便程序测试 集成各种优秀框架
3 bean的生命周期
①实例化bean 通过反射创建对象,在堆内存中开辟空间,属性是默认值 ②设置对象属性 populateBean方法-----这里会出现循环依赖问题----使用三级缓存解决 ③调用aware接口并设置相关依赖 完成beanName,beanFactory,beanClassLoader对象的属性设置 ④调用beanPostProcessor中的前置处理方法 使用较多的是:ApplicationContextPostProcessor来设置ApplicationContext,Environment,ResourceLoader等对象 ⑤调用initMethod方法,完成初始化 ⑥调用BeanPostProcessor的后置处理方法 spring的aop就是在这里实现的 ⑦通过getBean方法获取完整的对象 ⑧调用destroyMethod方法进行销毁
4 谈谈对IOC的理解
①控制反转: 原来的对象是由使用者进行创建和控制的,有了spring之后,可以把对象交给spring来进行管理 DI: 依赖注入,通过@AutoWired或者populateBean完成对象的属性注入 ②同时IOC还是一个容器,使用Map结构来存储对象,bean的整个生命周期,从创建到使用到销毁都是有容器来管理 容器在创建的时候,有一个最上层的根接口BeanFactory,我们在实际调用过程中,最多用到的是DefaultListableBeanFactory, 在使用时会先创建一个bean工厂,向工厂中设置一些参数,如BeanPostProcessor,Aware接口的子类等等 接着就是加载并解析bean对象,创建一个beanDefinition对象, 通过反射将benaDefinition对象实例化成具体对象,并进行初始化,执行BeanPostProcessor的一些前置后置处理方法 通过getBean获取bean对象使用,最后可以通过destoryMethod方法进行销毁 这些操作都是有IOC容器来进行管理的
3 IOC底层实现
1. 先通过createBeanFactory创建出一个bean工厂,使用较多的是DefaultListableBeanFactory 2. 开始循环创建对象,因为容器中bean默认是单例的,所以优先通过getBean,doGetBean去容器中查找 3. 找不到的话,通过createBean和doCreateBean方法,以反射的方式去创建对象,一般使用无参构造 4. 通过populateBean进行对象的属性填充 5. 进行其他初始化操作
5 循环依赖--三级缓存解决
a依赖b,b依赖c,而c又依赖a,最终形成闭环 这时候我们需要使用三级缓存来解决,核心是提前暴露引用 一级缓存 singletonObjects 放的是完整的bean 二级缓存 earlySingletonObjects 放的是实例化但未初始化的bean,是个半成品 三级缓存 singletonFatories 存储能够建立这个Bean的一个工厂,通过工厂来获取这个Bean,延迟Bean的生成 流程: 首先在一级缓存和二级缓存中找不到a对象,就会去三级缓存用工厂进行创建,并提前暴露引用.放入二级缓存,a对象属性注入, 会去找b,找不到就会去创建b对象,同样提前暴露引用,放入二级缓存, b对象开始属性注入,在二级缓存中找到a,完成属性注入,放入一级缓存 a对象同样的也会创建成功,放入一级缓存 理论上二级缓存就能解决,但是为了使用工厂来创建代理对象,引入了三级缓存 这三个 map 是如何配合的呢? 一级缓存找不到,找二级缓存,二级缓存找不到就去三级缓存用工厂来创建代理对象,提前暴露引用,放入二级缓存 多实例的bean(创建多个bean,死循环) 构造器注入的bean 不能解决循环依赖问题
6 BeanFactory和FactoryBean区别
同: 都是用来创建bean的 异: BeanFactory是IOC容器的顶级接口,能通过getBean去容器中获取指定的bean的实例 FactoryBean是一个工厂Bean,它的主要功能是创建bean,我们可以自定义一个bean加载到IOC容器中
7 spring中用到的设计模式
单例模式: spring中bean默认是单例的 原型模式: bean指定作用域 prototype (它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。) 工厂模式: BeanFactory 责任链模式: 使用AOP的时候,会生成一个拦截器链 代理模式: AOP动态代理 观察者模式: listener 在Spring中定义了一个ApplicationListener接口,用来监听Application的事件
8 AOP的底层实现原理
AOP:面向切面编程,它是IOC的一个扩展功能,能在不修改原代码的基础上,对方法进行增强 bean在创建的过程中,有一个步骤可以对bean进行扩展实现,AOP本身就具备扩展功能, 所以在BeanPostProcessor的后置处理方法中就是AOP来实现的 首先AOP底层会通过jdk或cglib动态代理来创建代理对象, 在执行方法调用时,会找到一个叫intercept方法,从此方法开始执行 根据之前定义好的通知生成一个拦截器链 在拦截器链中依次获取每一个通知 开始执行
9 AOP应用场景
使用AOP做统一的日志处理 自定义注解想要生效,也需要使用AOP来实现 权限控制....
10Bean的作用域
singleton(单例模式): IOC容器每次返回的是同一个Bean实例. prototype(原型模式): IOC容器每次返回的是一个新的实例. request(HTTP请求): 每次HTTP请求都会创建一个新的Bean session(会话): 同一个session共享一个Bean实例.不同session使用不同的实例.
11 singleton并发线程安全问题
不安全 对于无状态的bean,如DAO类,是安全的 对于有状态的bean,即有数据存储功能的,如VO视图对象,是不安全的 解决方案: 1. 设置bean的scope属性为prototype多例 2. 使用threadLocal,为每个线程提供一个独立的变量副本,从而隔离多个线程对数据的访问冲突
12 bean实例化和依赖注入
实例化: 一、构造器实例化Bean 二、静态工厂方式实例化Bean 三、实例工厂方式实例化Bean 依赖注入: 一、基于构造函数的注入 二、基于set方法的注入 三、基于自动装配的注入 四、基于注解的依赖注入
13 spring常用注解
@Component:用于标记在一个类上,表示当前类是spring的一个组件,是ioc的一个容器.他有三个衍生注解:@Controller、@Service、@Repository @Controller:用于标记在一个类上,代表这个类是控制层组件. @Service:用于标记在一个类上,代表这个类是业务层组件. @Repository:用于标记在一个类上,代表这个类是数据访问层组件.、 @Bean @Autowired @Value @Transactional:写在类上用于指定当前类中的方法支持事务,写在方法上表示当前的方法支持事务
14 spring事务传播行为
PROPAGATION_REQUIRED: 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。 PROPAGATION_SUPPORTS: 支持当前事务,如果当前没有事务,就以非事务方式执行。 PROPAGATION_MANDATORY: 支持当前事务,如果当前没有事务,就抛出异常。 PROPAGATION_REQUIRES_NEW: 新建事务,如果当前存在事务,把当前事务挂起。 PROPAGATION_NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 PROPAGATION_NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常。 PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
SpringMVC篇
1 SpringMVC执行流程
1) 用户发送请求至前端控制器DispatcherServlet 2) DispatcherServlet收到请求调用HandlerMapping处理器映射器。 3) 处理器映射器根据请求url找到具体的处理器,生成处理器对象返回给DispatcherServlet。 4) DispatcherServlet通过HandlerAdapter处理器适配器调用处理器 5) HandlerAdapter执行处理器(handler,也叫后端控制器)。 6) Controller执行完成返回ModelAndView 7) HandlerAdapter将handler执行结果ModelAndView返回给DispatcherServlet 8) DispatcherServlet将ModelAndView传给ViewReslover视图解析器 9) ViewReslover解析后返回具体View对象 10) DispatcherServlet对View进行渲染视图 11) DispatcherServlet响应用户
2 SpringMVC常用注解
@RequestMapping:是一个用于处理请求地址映射的注解,可用于类或方法上.用于类上,表示类中所有响应请求的方法都是以该地址作为父路径 @RequestParam:用于将指定的请求参数赋给方法中的形参. @PathVariable:可以获取URL中的动态参数. @RequestBody:用于读取request请求的body部分数据. @ResponseBody:用于将controller方法返回的对象,用流响应给客户端. @RestController:@Controller+@ResponseBody,用于标记在一个类上
3 SpringMVC异常处理
第1种. 编写异常处理类@ControllerAdvice/@RestControllerAdvice + 在类中编写异常处理方法@ExceptionHandler(Exception.class) 第2种. 编写异常处理类实现接口 HandlerExceptionResolver
4 SpringMVC文件上传
MultipartFile 接收文件 -> FileUpload提供api去做的 -> request的请求体
5 SpringMVC拦截器
实现HandlerInterceptor接口
MyBatis篇
1 MyBatis和JDBC区别,优化
1. MyBatis是对JDBC进行封装的一个持久层框架 2. jdbc的sql是在java代码中的,如果sql语句修改,就需要重新编译;而mybatis在xml文件中编写sql语句,不需要重新编译 3. jdbc 数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能. mybatis则在XML文件中配置数据链接池,使用连接池管理数据库链接。 4. 对于重复性高的sql,如select后面where条件不同,使用jdbc就要写多遍,而使用mybatis可以SQL片段模块化,将重复的SQL片段独立成一个 SQL块,然后在各个SQL语句引用重复的SQL块。
2 Mybatis 和 Mybatis Plus 的区别和优化
Mybatis-Plus是一个Mybatis的增强工具,只是在Mybatis的基础上做了增强却不做改变,MyBatis-Plus支持所有Mybatis原生的特性,所以引入Mybatis-Plus不会对现有的Mybatis构架产生任何影响。 MyBatis: 所有SQL语句全部自己写 手动解析实体关系映射转换为MyBatis内部对象注入容器 不支持Lambda形式调用 Mybatis Plus: 强大的条件构造器,满足各类使用需求 内置的Mapper,通用的Service,少量配置即可实现单表大部分CRUD操作 支持Lambda形式调用 提供了基本的CRUD功能,连SQL语句都不需要编写 自动解析实体关系映射转换为MyBatis内部对象注入容器
SpringBoot篇
1 SpringBoot自动装配原理(@AutoWired)
在启动IOC容器时,容器会自动加载一个叫AutoWiredAnnotationBeanPostProcessor后置处理器, 当容器扫描到@AutoWired注解时,会在IOC容器中查找需要加载的bean对象,并装配给该对象的属性 @AutoWired会先根据类型找,然后会根据名字找,如果没有找到会抛出异常
2 SpringBoot自动配置原理....(启动过程)
在启动类上加上@SpringBootApplication注解,他是一个复合注解,真正去实现自动配置的注解是@EnableAutoConfiguration注解 首先,会引入starter启动依赖组件,必须包含@Configuration配置类,配置类里需要通过@Bean去声明需要配置到IOC容器里的bean对象 然后,这个配置类是放在一个第三方的Jar包里,springBoot会通过SpringFactoriesLoader来读取META-INF下spring.properties文件中所有的自动配置类 最后,通过spring提供的ImportSlector接口来加载这些自动配置类,从而得到它们的BeanDefinition,完成实例化
3 SpringBoot常用注解
@SpringBootApplication: 包含@Configuration、@EnableAutoConfiguration、@ComponentScan通常用在主类上; @ComponentScan:组件扫描。个人理解相当于,如果扫描到有@Component @Controller @Service等这些注解的类,则把这些类注册为bean*; @Configuration:指出该类是 Bean 配置的信息源,相当于XML中的,一般加在主类上; @Bean:相当于XML中的,放在方法的上面,而不是类,意思是产生一个bean,并交给spring管理; @Import:用来导入其他配置类。 @ImportResource:用来加载xml配置文件。 @ControllerAdvice:包含@Component。可以被扫描到。统一处理异常。 @ExceptionHandler(Exception.class):用在方法上面表示遇到这个异常就执行以下方法。
3 SpringBoot读取文件配置方式
方式一: 通过@Value("${spring.datasource.url}")这样的方式读取 方式二:通过@ConfigurationProperties,建立配置文件和对象的映射关系,然后使用@AutoWired注入 方式三: 我们可以直接注入Environment对象示例并读取properties对象属性值,通过key-value方式读取
4 SpringBoot事务
首先使用注解EnableTransactionManagement开启事物之后, 然后在Service方法上添加注解Transactional便可。
5 SpringBoot Async异步调用
只需要在方法上使用@Async注解 在启动类加入@EnableAsync使异步调用@Async注解生效。
6 Spring Boot 全局异常处理
定义一个全局异常类,使用@RestControllerAdvice和@ExceptionHandler注解 @RestControllerAdvice是@ControllerAdvice的派生注解,能拦截相应的请求,将信息通过responsebody响应给前端 @ExceptionHandler的作用是拦截和处理具体的某个异常
7 SpringBoot 实现 session 共享
Spring Session + Redis 来实现 将所有微服务的session 统一保存在 Redis 上,当各个微服务对 session 有相关的读写操作时,都去操作 Redis 上的 session Spring Session 基于 Spring 中的代理过滤器实现,使得 session 的同步操作对开发人员而言是透明的,非常简便。
SpringCloud篇
1 常用注册中心,基本原理
Nacos,Eureka,zookpeer,Consoul 注册中心本质上就是存放接口地址,本质思想都差不多,都是使用服务注册,服务发现,服务续约,健康检查之类的 基本原理: 1. 我们在做RPC远程调用的过程中,项目启动完成后,生产者会将他的ip地址和端口号告诉注册中心,我们的注册中心就会来存放接口地址,它底层会有一个map集合,key就是服务名称,value就存放生产者的IP地址和端口号;当然,因为我们是集群部署,有多个地址,所以在我们的注册中心上,key是一个键值,value是一个数组类型,存放多个生产者接口地址 2. 然后我们消费者一方,它会根据服务名称去连接注册中心,获取接口地址,在本地使用负载均衡算法选择一个接口地址,作为我们RPC的远程调用 // 如果消费者从注册中心获取到一个宕机的地址,如何处理? 一般会先做重试,如果重试多次还是不行,则从本地缓存中选取其他地址
2 对SpringCloud理解
springcloud是一套分布式微服务的技术解决方案,他提供了快速去构建分布式系统的常用的一些组件,比如说配置管理、服务的注册与发现、服务调用的负载均衡、资源隔离、熔断降级等等.
3 Eureka工作流程
1. Eureka Server启动成功,等待Eureka Server注册,每个 Eureka Server 都存在独立完整的服务注册表信息 2. Eureka Client启动时根据配置的Eureka Server地址去注册中心注册服务 3. Eureka Client每30s向Eureka Server发送一次心跳续约请求,证明客户端服务正常 4. 当Eureka Server 90s内没有收到Eureka Client的心跳,注册中心则认为该节点失效,会注销该实例 5. 单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端 6. 当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式 7. Eureka Client 定时(每30秒钟)全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地 8. 服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存 9. Eureka Client 获取到目标服务器信息,发起服务调用 10. Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除
4 网关GateWay的作用
权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。 路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。 限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。
Redis篇
1 redis存储类型
存储类型--------数据结构 string 字符串 list 双向链表,压缩列表 hash 哈希表,压缩列表 set 哈希表,整数数组 SortedSet 压缩列表,跳表-------SortedSet中每个元素会关联一个double类型的分数,redis通过分数来排序
2 Redis持久化
RDB : 将Redis中全部数据生成快照,保存在磁盘上 原理: fork主进程得到一个子进程,共享内存空间,子进程读取内存数据并写入新的RDB文件,用新RDB文件替换旧的RDB文件。 缺点: RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。 AOF : 将每次写的命令追加写入日志,需要恢复数据时重新执行命令 优点: 1、数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。 2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof工具解决数据一致性问题。 3、AOF 机制的 rewrite 模式。文件过大时会对命令进行合并重写。 缺点: AOF 文件比 RDB 文件大,且恢复速度慢。 //混合持久化 结合了 RDB 与 AOF 的优点,恢复速度快,增量用 AOF 表示,数据更完整(取决于同步策略)、也无需 AOF 重写
3 Redis主从集群 数据同步
1. 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。 2. 增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave 同步流程: 1. slave节点请求增量同步 2. master节点判断replid,发现不一致,拒绝增量同步 3. master将完整内存数据生成RDB,发送RDB到slave 4. slave清空本地数据,加载master的RDB 5. master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave 6. slave执行接收到的命令,保持与master之间的同步 什么时候执行全量同步? slave节点第一次连接master节点时 slave节点断开时间太久,repl_baklog中的offset已经被覆盖时 什么时候执行增量同步? slave节点断开又恢复,并且在repl_baklog中能找到offset时
4 Redis哨兵
Redis Sentinel (哨兵)着眼于高可用,在 master 宕机时会自动将 slave 提升为master,继续提供服务。 哨兵的作用如下: - 监控:Sentinel 会不断检查您的master和slave是否按预期工作 - 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主 - 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端 //集群监控的原理(如何判断redis实例是否健康) 基于心跳机制,每隔1s发送一次ping命令,如果超过一定时间没有响应,主观下线 如果大多数sentinel都认为实例主观下线,则该实例客观下线 //故障转移步骤 - 首先选定一个slave作为新的master,执行slaveof no one - 然后让所有节点都执行slaveof 新master - 修改故障节点配置,添加slaveof 新master
5 Redis分片集群
Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行分片存储。 Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上 数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况: - key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分 - key中不包含“{}”,整个key都是有效部分 //Redis如何判断某个key应该在哪个实例? - 将16384个插槽分配到不同的实例 - 根据key的有效部分计算哈希值,对16384取余 - 余数作为插槽,寻找插槽所在实例即可
6 缓存穿透、击穿、雪崩
-- 缓存穿透 缓存穿透是指:如果一个 key 在缓存和数据库都不存在,那么访问这个 key 每次都会进入数据库 * 很可能被恶意请求利用 * 缓存雪崩与缓存击穿都是数据库中有,但缓存暂时缺失 * 缓存雪崩与缓存击穿都能自然恢复,但缓存穿透则不能 解决方案: * 设置一个key-null到redis中(不建议) * 布隆过滤器 ① 过滤器可以用来判定 key 不存在,发现这些不存在的 key,把它们过滤掉就好 ② 需要将所有的 key 都预先加载至布隆过滤器 ③ 布隆过滤器不能删除,因此查询删除的数据一定会发生穿透 -- 缓存击穿 某一热点 key 在缓存和数据库中都存在,它过期时,这时由于并发用户特别多,同时读缓存没读到,又同时去数据库去读,压垮数据库 - 解决方案 * 热点数据不过期 * 对【查询缓存没有,查询数据库,结果放入缓存】这三步进行加锁,这时只有一个客户端能获得锁,其它客户端会被阻塞,等锁 释放开,缓存已有了数据,其它客户端就不必访问数据库了。但会影响吞吐量(有损方案) -- 缓存雪崩 情况1:由于大量 key 设置了相同的过期时间(数据在缓存和数据库都存在),一旦到达过期时间点,这些 key 集体失效,造成访问这些 key 的请求全部进入数据库。 解决方案 * 错开过期时间:在过期时间上加上随机值(比如 1~5 分钟) * 服务降级:暂停非核心数据查询缓存,返回预定义信息(错误页面,空值等) 情况2:Redis 实例宕机,大量请求进入数据库 - 解决方案 * 事前预防:搭建高可用集群 * 多级缓存:缺点是实现复杂度高 * 熔断:通过监控一旦雪崩出现,暂停缓存访问待实例恢复,返回预定义信息(有损方案) * 限流:通过监控一旦发现数据库访问量超过阈值,限制访问数据库的请求数(有损方案)
7 redis 过期键的删除策略
1、定时删除:在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键的过期时间来临时,立即执行对键的删除操作。 2、惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回键。 3、定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
8 Redis 的回收(淘汰)策略
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 no-enviction(驱逐):禁止驱逐数据
9 系统的高并发问题怎么解决
数据层 集群 开启索引 分表分库 开启缓存 表设计优化 sql语句优化 缓存服务器 搜索服务器 项目层 采用分布式架构 做页面缓存 使用rabbitMQ解耦,异步处理 分布式文件系统存储海量文件 应用层 nginx做负载均衡
10 MySQL和Redis数据一致性
第一种,先更新数据库,再更新缓存,如果缓存更新失败,就会导致数据库和redis中的数据不一致 第二种,先删除缓存,再更新数据库,理想情况下,是下次访问redis的时候,发现数据是空的,就会去查询数据库并保存到redis中,但是删除redis和更新数据库并不是原子性操作,还是会存在数据不一致的问题 第三种,采用rabbitmq的可靠性消息通信达到数据的最终一致性, 还能通过canal组件,监控MySQL中的binlog日志,把更新后的数据同步到redis中. 因为是基于最终一致性来实现的, 延迟双删?第二次删除时间不好把控(不推荐) 在分布式领域做数据同步,必然会出现延迟,几十毫秒一般都是被允许的
11 Redis性能优化
1. 生产环境禁用keys命令 因为keys命令需要遍历存储的键值对,所以操作延时很高,在生产环境使用很可能导致Redis阻塞 2. keys需要设置过期时间 Redis作为内存数据库,一切的数据都是在内存中,一旦内存占用过大则会大大影响性能,因此要设置过期时间 3. 禁止批量的给keys设置相同的过期时间 防止出现缓存雪崩 4. 谨慎选择数据结构 5. 检查持久化策略 6. 增加机器内存或者使用Redis集群
RabbitMQ篇
1 RabbitMQ组件
vHost 虚拟主机 Channel 通道 Exchange 交换机 --作用就是根据路由规则,将消息转发到对应的队列上。 Queue 消息队列,具有缓存消息的能力,rabbitmq中,队列消息可以设置为持久化,临时或者自动删除 Routing key 路由key 来指定这个消息的路由规则。 Binding 作用就是把exchange和queue按照路由规则绑定起来。 Binding key 在绑定Exchange与Queue时,一般会指定一个binding key
2 中间件,交换机,优点
RabbitMQ(延迟低) RocketMQ ActiveMQ Kafka(吞吐量高) 交换机: fanout direct topic 优点: 解耦,异步,削峰 RabbitMQ 的使用场景有哪些? 抢购活动,削峰填谷,防止系统崩塌。 延迟信息处理,比如 10 分钟之后给下单未付款的用户发送邮件提醒 解耦系统,对于新增的功能可以单独写模块扩展,比如用户确认评价之后,新增了给用户返积分的功能
3 谈谈对MQ的理解
我们项目中主要使用MQ来做一些异步的操作,比如说我们的接口是属于http协议的接口,属于同步调用,同步调用过程中,如果我们接口响应慢的情况下,会导致客户端超时阻塞.此时客户端就会进行一些重试策略,在重试的过程中,就会导致我们的业务重复执行,所以我们就需要注意一些幂等性问题,所以我们会将一些执行业务逻辑比较耗时的接口,改成使用MQ异步去实现,能够提高我们http协议接口的响应速度 同时我们还会使用MQ去异步的发优惠券,异步的去扣减库存,异步的去发短信等
4 保证幂等性(不被重复消费)
可以在把数据先存在set或者redis中,消费前,先去里面查看,数据是否已存在,已存在就丢弃这数据 在数据库中设置唯一约束,就不会导致重复数据的多次插入
5 消息堆积问题
要分析是生产者生产过快还是消费者消费能力不足 1.临时启动多个消费者,并发处理消息; 2.每个消费者一般都是批量的形式来进行消费,以此来提高消费速率,如果说在以后我们消费跟不上的情况下 可以不断横向扩张,对消费者不断做集群,从而解决消息堆积问题
6 消息不丢失
消息丢失的情况: 生产者写的消息在到中间件的网络传输过程中就丢了,或者是消息到了中间件,但是内部出错,消息没保存下来 中间件(交换机)将消息保存下来,还没等到消费者消费完,就自己挂掉,导致消息丢失 消费者取到消息还没来得及消费就自己挂掉了 解决方案: 1.生产者消息丢失,可以通过开启事务功能,如果消息没有发送成功,发生异常就回滚,然后重试发送消息,发送成功就提交事务 2.关于中间件的数据丢失,可以开启中间件的持久化,将消息持久化磁盘中,中间件挂了恢复之后自动读取之前存储的数据。 3.消费者数据丢失,关闭rabbitMQ的autoACK机制,自己手动提交完成信息
7 消息的顺序性
rabbitMQ为多个消费者开辟多个queue队列(先进先出),根据队列特性来保证数据的顺序性。
8 MQ如何保证分布式事务的最终一致性
分布式事务: 业务相关的多个操作,保证他们同时成功同时失败 最终一致性: 与之对应的是强一致性(在任意时刻,所有节点中的数据是一样的) MQ要保证事务的最终一致性,就需要做到两点 1. 生产者要保证100%的消息投递 事务消息机制 2. 消费者端需要保证幂等消费 唯一id+业务自己实现幂等
Elasticsearch篇
1 elasticsearch 了解多少,说说你们公司 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段
ES 集群架构 13 个节点,索引根据通道不同共 20+索引,根据日期,每日递增 20+,索引:10分片,每日递增 1 亿+数据,每个通道每天索引大小控制:150GB 之内。
2 elasticsearch 倒排索引
传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置。 而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表即为倒排索引。 有了倒排索引,就能实现 o(1)时间复杂度的效率检索文章了,极大的提高了检索效率。
Linxu篇
1 常用命令
cd 切换目录 pwd 显示当前的绝对路径 cat 查看文件命令 more 分页查看文件命令(不能快速定位到最后一页) less 分页查看文件命令(可以快速定位到最后一页) tail -10 ;文件名 看最后10行 tar (解压 压缩 命令) 常用的组合命令: -z 是否需要用gzip压缩。 -c 建立一个压缩文件的参数指令(create) –压缩 -x 解开一个压缩文件的参数指令(extract) –解压 -v 压缩的过程中显示文件(verbose) -f 使用档名,在f之后要立即接档中(file) 常用解压参数组合:zxvf 常用压缩参数组合:zcvf 解压命令: tar -zxvf redis-3.2.8.tar.gz ;解压到当前文件夹 tar -zxvf redis-3.2.8.tar.gz -C /opt/java/ ; 解压到指定目录 压缩命令:(注意 语法有点反了,我反正每次都搞反) tar -zcvf redis-3.2.8.tar.gz redis-3.2.8/ ; 语法 tar -zcvf 压缩后的名称 要压缩的 文件tar -zcvf 压缩后的文件(可指定目录) 要压缩的文件(可指定目录)
2 linux查看日志命令
vi 文件名 #编辑方式查看,可修改 cat 文件名 #显示全部文件内容 more 文件名 #分页显示文件内容 less 文件名 #与 more 相似,更好的是可以往前翻页 tail 文件名 #仅查看尾部,还可以指定行数 head 文件名 #仅查看头部,还可以指定行数
建议:根据自身情况,适当改变回答内容逻辑,便于理解记忆,最后祝大家能找到一份心意的工作!