java面试汇总


//----------解析---------->


java
public static void main(String[] args) { 
    String s = new String("abc"); 
    // 在这中间可以添加N⾏代码,但必须保证s引⽤的指向不变,最终将输出变成abcd 
    Field value = s.getClass().getDeclaredField("value"); 
    value.setAccessible(true); 
    value.set(s, "abcd".toCharArray()); 
    System.out.println(s); 
}

public static void main(String[] args) { 
    String s1 = new String("abc"); 
    String s2 = "abc"; // s1 == s2? 
    String s3 = s1.intern(); // s2 == s3? 
}
String对象的intern⽅法,⾸先会检查字符串常量池中是否存在"abc",如果存在则返回该字符串引⽤, 如果不存在,则把"abc"添加到字符串常量池中,并返回该字符串常量的引⽤。

public static void main(String[] args) { 
    Integer i1 = 100; Integer i2 = 100; // i1 == i2? 
    Integer i3 = 128; 
    Integer i4 = 128; // i3 == i4? 
}

在Interger类中,存在⼀个静态内部类IntegerCache, 该类中存在⼀个Integer cache[], 并且存在⼀ 个static块,会在加载类的时候执⾏,
会将-128⾄127这些数字提前⽣成Integer对象,并缓存在cache数 组中,当我们在定义Integer数字时,会调⽤Integer的valueOf⽅法,valueOf⽅法会判断所定义的数字 是否在-128⾄127之间,如果存在则直接从cache数组中获取Integer对象,如果超过,则⽣成⼀个新的 Integer对象

String、StringBuffer、StringBuilder的区别
1. String是不可变的,如果尝试去修改,会新⽣成⼀个字符串对象,StringBuffer和StringBuilder是可变的 
2. StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更⾼

为什么局部内部类和匿名内部类只能访问局部final变量
首先需要知道的一点是: 内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。
这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以
访问它,实际访问的是局部变量的"copy"。这样就好像延长了局部变量的生命周期将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?
就将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量
和方法的局部变量的一致性。这实际上也是一种妥协。使得局部变量与内部类内建立的拷贝保持一致

java中的编译器和解释器
Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器
在任何平台上都提供给编译程序一个的共同的接口。
编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系
统的机器码执行。
在Java中,这种供虚拟机理解的代码叫做字节码(即扩展名为 .class的文件),它不面向任何特定的处理器,只面向虚拟机。
每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。
Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
采用字节码的好处:
Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解
释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,
因此,Java程序无须重新编译便可在多种不同的计算机上运行

GC如何判断对象可以被回收
引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收,
可达性分析法:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
引用计数法,可能会出现A引用了B,B又引用了 A,这时候就算他们都不再使用了,但因为相互
引用计数器=1 永远无法被回收。
GC Roots的对象有:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象

可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”
每个对象只能触发一次finalize()方法, 由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它

深拷贝浅拷贝
深拷⻉和浅拷⻉就是指对象的拷⻉,⼀个对象中存在两种类型的属性,⼀种是基本数据类型,⼀种是实例对象的引⽤。
1. 浅拷⻉是指,只会拷⻉基本数据类型的值,以及实例对象的引⽤地址,并不会复制⼀份引⽤地址所指向的对象,也就是浅拷⻉出来的对象,内部的类属性指向的是同⼀个对象
2. 深拷⻉是指,既会拷⻉基本数据类型的值,也会针对实例对象的引⽤地址所指向的对象进⾏复制,深拷⻉出来的对象,内部的属性指向的不是同⼀个对象

数据结构-线性结构
ArrayList和LinkedList区别
⾸先,他们的底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList底层是基于链表实现的
因此,由于底层数据结构不同,他们所适⽤的场景也不同,ArrayList更适合随机查找,LinkedList更适合删除和添加
另外ArrayList和LinkedList都实现了List接⼝,但是LinkedList还额外实现了Deque接⼝,所以LinkedList还可以当做队列来使⽤

hashmap put
先说HashMap的Put⽅法的⼤体流程: 
1. 根据Key通过哈希算法与与运算得出数组下标
2. 如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是 Node对象)并放⼊该位置 
3. 如果数组下标位置元素不为空,则要分情况讨论 
a. 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进⾏扩容,如果不⽤扩容就⽣成Entry对象,并使⽤头插法添加到当前位置的链表中 
b. 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红⿊树Node,还是链表Node 
    i. 如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去,在这个过程中会判断红⿊树中是否存在当前key,如果存在则更新value
    ii. 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插法插⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,
    在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插⼊到链表中,插⼊到链表后,会看当前链表的节点个数,如果⼤于等于8,那么则会将该链表转成红⿊树 
    iii. 将key和value封装为Node插⼊到链表或红⿊树中后,再判断是否需要进⾏扩容,如果需要就扩容,如果不需要就结束PUT⽅法

hashmap
Jdk1.7到Jdk1.8 HashMap 发⽣了什么变化(底层)
1. 1.7中底层是数组+链表,1.8中底层是数组+链表+红⿊树,加红⿊树的⽬的是提⾼HashMap插⼊和查询 整体效率
2. 1.7中链表插⼊使⽤的是头插法,1.8中链表插⼊使⽤的是尾插法,因为1.8中插⼊key和value时需要判断 链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使⽤尾插法
3. 1.7中哈希算法⽐较复杂,存在各种右移与异或运算,1.8中进⾏了简化,因为复杂的哈希算法的⽬的就 是提⾼散列性,来提供HashMap的整体效率,⽽1.8中新增了红⿊树,所以可以适当的简化哈希算法,节省CPU资源

concurrentHashMap
1.7版本 
1. 1.7版本的ConcurrentHashMap是基于Segment分段实现的
2. 每个Segment相对于⼀个⼩型的HashMap
3. 每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
4. 先⽣成新的数组,然后转移元素到新数组中
5. 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值

1.8版本
1. 1.8版本的ConcurrentHashMap不再基于Segment实现
2. 当某个线程进⾏put时,如果发现ConcurrentHashMap正在进⾏扩容那么该线程⼀起进⾏扩容
3. 如果某个线程put时,发现没有正在进⾏扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进⾏扩容
4. ConcurrentHashMap是⽀持多个线程同时扩容的
5. 扩容之前也先⽣成⼀个新的数组
6. 在转移元素时,先将原数组分组,将每组分给不同的线程来进⾏元素的转移,每个线程负责⼀组或多组的元素转移⼯作

数据结构-图
1. 图的深度优先遍历是指,从⼀个节点出发,⼀直沿着边向下深⼊去找节点,如果找不到了则返回上⼀层找其他节点
2. 图的⼴度优先遍历只是,从⼀个节点出发,向下先把第⼀层的节点遍历完,再去遍历第⼆层的节点,直到遍历到最后⼀层

数据结构-树
⼆叉搜索树和平衡⼆叉树有什么关系
平衡⼆叉树也叫做平衡⼆叉搜索树,是⼆叉搜索树的升级版,⼆叉搜索树是指节点左边的所有节点都⽐该 节点⼩,节点右边的节点都⽐该节点⼤,⽽平衡⼆叉搜索树是在⼆叉搜索的基础上还规定了节点左右两边的⼦树⾼度差的绝对值不能超过1

强平衡⼆叉树和弱平衡⼆叉树有什么区别
强平衡⼆叉树AVL树,弱平衡⼆叉树就是我们说的红⿊树。
1. AVL树⽐红⿊树对于平衡的程度更加严格,在相同节点的情况下,AVL树的⾼度低于红⿊树
2. 红⿊树中增加了⼀个节点颜⾊的概念
3. AVL树的旋转操作⽐红⿊树的旋转操作更耗时

B树和B+树的区别,为什么Mysql使⽤B+树
B树的特点:
1. 节点排序
2. ⼀个节点了可以存多个元素,多个元素也排序了
B+树的特点:
1. 拥有B树的特点
2. 叶⼦节点之间有指针
3. ⾮叶⼦节点上的元素在叶⼦节点上都冗余了,也就是叶⼦节点中存储了所有的元素,并且排好顺序
Mysql索引使⽤的是B+树,因为索引是⽤来加快查询的,⽽B+树通过对数据进⾏排序所以是可以提⾼查询速度的,
然后通过⼀个节点中可以存储多个元素,从⽽可以使得B+树的⾼度不会太⾼,
在Mysql中⼀个Innodb⻚就是⼀个B+树节点,⼀个Innodb⻚默认16kb,所以⼀般情况下⼀颗两层的B+树可以存2000万⾏左右的数据,然后通过利⽤B+树叶⼦节点存储了所有数据并且进⾏了排序,并且叶⼦节点之间有指针, 可以很好的⽀持全表扫描,范围查找等SQL语句

算法-快排
快速排序算法底层采⽤了分治法。 基本思想是: 
1. 先取出数列中的第⼀个数作为基准数
2. 将数列中⽐基准数⼤的数全部放在它的右边,⽐基准数⼩的数全部放在它的左边
3. 然后在对左右两部分重复第⼆步,直到各区间只有⼀个数


本地线程
threadlocal
1. ThreadLocal是Java中所提供的线程本地存储机制,可以利⽤该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意⽅法中获取缓存的数据
2. ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal 对象)中都存在⼀个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
3. 如果在线程池中使⽤ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使⽤完之后,应该 要把设置的key,value,也就是Entry对象进⾏回收,但线程池中的线程不会回收,⽽线程对象 是通过强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收,Entry对象也就不会被回收,从⽽出现内存泄漏,解决办法是,在使⽤了 ThreadLocal对象之后,⼿动调⽤ThreadLocal的remove⽅法,⼿动清楚Entry对象
4. ThreadLocal经典的应⽤场景就是连接管理(线程之间不共享同⼀个连接, ⼀个线程持有⼀个连接,该连接对象可以在不同的⽅法之间进⾏传递)

jvm
Java的内存结构,堆分为哪⼏部分,默认年龄多⼤进⼊⽼年代
1. 年轻代 a. Eden区(8) 
b. From Survivor区(1)
c. To Survivor区(1)
2. ⽼年代 默认对象的年龄达到15后,就会进⼊⽼年代

堆区和⽅法区是所有线程共享的,栈、本地⽅法栈、程序计数器是每个线程独有的

什么是gc root,JVM在进⾏垃圾回收时,需要找到“垃圾”对象,也就是没有被引⽤的对象,但是直接 找“垃圾”对象是⽐较耗时的,所以反过来,先找“⾮垃圾”对象,也就是正常对象,那么就需要从某 些“根”开始去找,根据这些“根”的引⽤路径找到正常对象,⽽这些“根”有⼀个特征,就是它只会引⽤其他 对象,⽽不会被其他对象引⽤,例如:栈中的本地变量、⽅法区中的静态变量、本地⽅法栈中的变量、正 在运⾏的线程等可以作为gc root

对于还在正常运⾏的系统: 
1. 可以使⽤jmap来查看JVM中各个区域的使⽤情况 
2. 可以通过jstack来查看线程的运⾏情况,⽐如哪些线程阻塞、是否出现了死锁 
3. 可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc⽐较频繁,那么就得进⾏调优了 
4. 通过各个命令的结果,或者jvisualvm等⼯具来进⾏分析
5. ⾸先,初步猜测频繁发送fullgc的原因,如果频繁发⽣fullgc但是⼜⼀直没有出现内存溢出,那么表示 fullgc实际上是回收了很多对象了,所以这些对象最好能在younggc过程中就直接回收掉,避免这些对 象进⼊到⽼年代,对于这种情况,就要考虑这些存活时间不⻓的对象是不是⽐较⼤,导致年轻代放不下,直接进⼊到了⽼年代,尝试加⼤年轻代的⼤⼩,如果改完之后,fullgc减少,则证明修改有效 
6. 同时,还可以找到占⽤CPU最多的线程,定位到具体的⽅法,优化这个⽅法的执⾏,看是否能避免某些对象的创建,从⽽节省内存

对于已经发⽣了OOM的系统: 
1. ⼀般⽣产系统中都会设置当系统发⽣了OOM时,⽣成当时的dump⽂件(-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base)
2. 我们可以利⽤jvisualvm等⼯具来分析dump⽂件
3. 根据dump⽂件找到异常的实例对象,和异常的线程(占⽤CPU⾼),定位到具体的代码
4. 然后再进⾏详细的分析和调试

Jdk1.7到Jdk1.8 java虚拟机发⽣了什么变化
1.7中存在永久代,1.8中没有永久代,替换它的是元空间,元空间所占的内存不是在虚拟机内部,⽽是本地内存空间,这么做的原因是,不管是永久代还是元空间,他们都是⽅法区的具体实现,之所以元空间所占 的内存改成本地内存,官⽅的说法是为了和JRockit统⼀,不过额外还有⼀些原因,⽐如⽅法区所存储的类 信息通常是⽐较难确定的,所以对于⽅法区的⼤⼩是⽐较难指定的,太⼩了容易出现⽅法区溢出,太⼤了 ⼜会占⽤了太多虚拟机的内存空间,⽽转移到本地内存后则不会影响虚拟机所占⽤的内存

jvm-双亲委派模型
1. BootstrapClassLoader
2. ExtClassLoader
3. AppClassLoader
AppClassLoader的⽗加载器是ExtClassLoader,
ExtClassLoader的⽗加载器是 BootstrapClassLoader。 
JVM在加载⼀个类时,会调⽤AppClassLoader的loadClass⽅法来加载这个类,
不过在这个⽅法中,会先使⽤ExtClassLoader的loadClass⽅法来加载类,同样ExtClassLoader的loadClass⽅法中会先使⽤BootstrapClassLoader来加载类,
如果BootstrapClassLoader加载到了就直接成功,如果BootstrapClassLoader没有加载到,那么ExtClassLoader就会⾃⼰尝试加载该类,
如果没有加载到,那么则会由AppClassLoader来加载这个类。
所以,双亲委派指得是,JVM在加载类时,会委派给Ext和Bootstrap进⾏加载,如果没加载到才由⾃⼰进⾏加载


sychronized和ReentrantLock的区别
1. sychronized是⼀个关键字,ReentrantLock是⼀个类
2. sychronized会⾃动的加锁与释放锁,ReentrantLock需要程序员⼿动加锁与释放锁
3. sychronized的底层是JVM层⾯的锁,ReentrantLock是API层⾯的锁
4. sychronized是⾮公平锁,ReentrantLock可以选择公平锁或⾮公平锁
5. sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识来标识锁的状态
6. sychronized底层有⼀个锁升级的过程

sychronized的⾃旋锁、偏向锁、轻量级锁、重量级锁,分别介绍和联系
1. 偏向锁:在锁对象的对象头中记录⼀下当前获取到该锁的线程ID,该线程下次如果⼜来获取该锁就可以直接获取到了
2. 轻量级锁:由偏向锁升级⽽来,当⼀个线程获取到锁后,此时这把锁是偏向锁,此时如果有第⼆个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层是通过⾃旋来实现的,并不会阻塞线程
3. 如果⾃旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞
4. ⾃旋锁:⾃旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就⽆所谓唤醒线程,阻塞和唤醒这两个步骤都是需要操作系统去进⾏的,⽐较消耗时间,
⾃旋锁是线程通过CAS获取预期的⼀个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程⼀直在运⾏中,相对⽽ ⾔没有使⽤太多的操作系统资源,⽐较轻量

ReentrantLock中的公平锁和⾮公平锁的底层实现
⾸先不管是公平锁和⾮公平锁,它们的底层实现都会使⽤AQS来进⾏排队,
它们的区别在于:线程在使⽤lock()⽅法加锁时,如果是公平锁,会先检查AQS队列中是否存在线程在排队,
如果有线程在排队, 则当前线程也进⾏排队,如果是⾮公平锁,则不会去检查是否有线程在排队,⽽是直接竞争锁。 
不管是公平锁还是⾮公平锁,⼀旦没竞争到锁,都会进⾏排队,当锁释放时,都是唤醒排在最前⾯的线程,所以⾮公平锁只是体现在了线程加锁阶段,⽽没有体现在线程被唤醒阶段。 另外,ReentrantLock是可重⼊锁,不管是公平锁还是⾮公平锁都是可重⼊的。

ReentrantLock中tryLock()和lock()⽅法的区别
1. tryLock()表示尝试加锁,可能加到,也可能加不到,该⽅法不会阻塞线程,如果加到锁则返回 true,没有加到则返回false
2. lock()表示阻塞加锁,线程会阻塞直到加到锁,⽅法也没有返回值

CountDownLatch和Semaphore的区别和底层原理
CountDownLatch表示计数器,可以给CountDownLatch设置⼀个数字,
⼀个线程调⽤CountDownLatch的await()将会阻塞,其他线程可以调⽤CountDownLatch的countDown()⽅法来对 CountDownLatch中的数字减⼀,当数字被减成0后,所有await的线程都将被唤醒。 对应的底层原理就是,调⽤await()⽅法的线程会利⽤AQS排队,⼀旦数字被减为0,则会将AQS中 排队的线程依次唤醒。 Semaphore表示信号量,可以设置许可的个数,表示同时允许最多多少个线程使⽤该信号量,
通过acquire()来获取许可,如果没有许可可⽤则线程阻塞,并通过AQS来排队,
可以通过release()⽅法来释放许可,当某个线程释放了某个许可后,会从AQS中正在排队的第⼀个线程开始依次唤醒,直到没有空闲许可

死锁
1. 可以通过jstack命令来进⾏查看,jstack命令中会显示发⽣了死锁的线程 
2. 或者两个线程去操作数据库时,数据库发⽣了死锁,这是可以查询数据库的死锁情况

1、查询数据库是否锁表 
show OPEN TABLES where In_use > 0; 
2、查询进程 
show processlist; 
3、查看正在锁的事务 
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; 
4、查看等待锁的事务 
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

innodb_locks表在8.0.13版本中
innodb_locks由performance_schema.data_locks表所代替
innodb_lock_waits表由performance_schema.data_lock_waits表代替

造成死锁的⼏个原因:
1. ⼀个资源每次只能被⼀个线程使⽤
2. ⼀个线程在阻塞等待某个资源时,不释放已占有资源
3. ⼀个线程已经获得的资源,在未使⽤完之前,不能被强⾏剥夺
4. 若⼲线程形成头尾相接的循环等待资源关系
这是造成死锁必须要达到的4个条件,如果要避免死锁,只需要不满⾜其中某⼀个条件即可。⽽其中前3个条件是作为锁要符合的条件,所以要避免死锁就需要打破第4个条件,不出现循环等待锁的关系。
在开发过程中:
1. 要注意加锁顺序,保证每个线程按同样的顺序进⾏加锁
2. 要注意加锁时限,可以针对锁设置⼀个超时时间
3. 要注意死锁检查,这是⼀种预防机制,确保在第⼀时间发现死锁并进⾏解决

线程
1. 线程之间可以通过共享内存或基于⽹络来进⾏通信
2. 如果是通过共享内存来进⾏通信,则需要考虑并发问题,什么时候阻塞,什么时候唤醒
3. 像Java中的wait()、notify()就是阻塞和唤醒
4. 通过⽹络就⽐较简单了,通过⽹络连接将通信数据发送给对⽅,当然也要考虑到并发问题,处理⽅式就是加锁等⽅式

线程池
1. 如果使⽤的⽆界队列,那么可以继续提交任务时没关系的
2. 如果使⽤的有界队列,提交任务时,如果队列满了,如果核⼼线程数没有达到上限,那么则增加线程, 如果线程数已经达到了最⼤值,则使⽤拒绝策略进⾏拒绝

线程池-原理
FixedThreadPool⽤的阻塞队列是什么
1. 如果此时线程池中的数量⼩于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。 
2. 如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放⼊缓冲队列。 
3. 如果此时线程池中的数量⼤于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量⼩于maximumPoolSize,建新的线程来处理被添加的任务。
4. 如果此时线程池中的数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于 maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
5. 当线程池中的线程数量⼤于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终⽌。这样,线程池可以动态的调整池中的线程数 FixedThreadPool代表定⻓线程池,底层⽤的LinkedBlockingQueue,表示⽆界的阻塞队列

多线程-并发三要素
1. 原⼦性:不可分割的操作,多个步骤要保证同时成功或同时失败
2. 有序性:程序执⾏的顺序和代码的顺序保持⼀致
3. 可见性:⼀个线程对共享变量的修改,另⼀个线程能⽴⻢看到

多线程
volatile关键字,他是如何保证可⻅性,有序性
1. 对于加了volatile关键字的成员变量,在对这个变量进⾏修改时,会直接将CPU⾼级缓存中的数据写回到主内存,对这个变量的读取也会直接从主内存中读取,从⽽保证了可⻅性
2. 在对volatile修饰的成员变量进⾏读写时,会插⼊内存屏障,⽽内存屏障可以达到禁⽌重排序的效果,从⽽可以保证有序性

spring-源码
1. Spring是⼀个快速开发框架,Spring帮助程序员来管理对象 
2. Spring的源码实现的是⾮常优秀的,设计模式的应⽤、并发安全的实现、⾯向接⼝的设计等 
3. 在创建Spring容器,也就是启动Spring时:
a. ⾸先会进⾏扫描,扫描得到所有的BeanDefinition对象,并存在⼀个Map中
b. 然后筛选出⾮懒加载的单例BeanDefinition进⾏创建Bean,对于多例Bean不需要在启动过程中去 进⾏创建,对于多例Bean会在每次获取Bean时利⽤BeanDefinition去创建
c. 利⽤BeanDefinition创建Bean就是Bean的创建⽣命周期,这期间包括了合并BeanDefinition、推断构造⽅法、实例化、属性填充、初始化前、初始化、初始化后等步骤,其中AOP就是发⽣在初始化后这⼀步骤中
4. 单例Bean创建完了之后,Spring会发布⼀个容器启动事件
5. Spring启动结束
6. 在源码中会更复杂,⽐如源码中会提供⼀些模板⽅法,让⼦类来实现,⽐如源码中还涉及到⼀些BeanFactoryPostProcessor和BeanPostProcessor的注册,Spring的扫描就是通过 BenaFactoryPostProcessor来实现的,依赖注⼊就是通过BeanPostProcessor来实现的
7. 在Spring启动过程中还会去处理@Import等注解
beanFactory->DefaultListableBeanFactory
AnnocatedBeanDefinitionReader
dependencyComparator 处理@Priority @Order
autowireCandidateResolver 自动注入候选者解析器
ConfigurationClassPostProcessor 解析配置类
AutowireAnnotationBeanPostProcessor 处理@Autowire @Value
EventListenerMethodProcessor @EventListener 封装为ApplicationListener添加到ApplicationContext
DefaultEventListenerFactory 如何把@EventListener封装为ApplicationListener
ClassPathBeanDefinitionScanner @Component到includeFilter
AppConfig->AnnotatedGenericBeanDefinition
refresh
prepareRefresh
Enironment是否包括必须要有的属性
obtainFreshBeanFactory
prepareBeanFactory
设置beanFactory的类加载器
StandardBeanExpressionResolver
ResourceEditorRegistrar
ApplicationContextAwareProcessor
ignoredDependencyInterface
resolvableDependencies
ApplicationListenerDetector
添加一些单例bean到单例池
postProcessBeanFactory
invokeBeanFactoryPostProcessors
BeanDefinitionRegistryPostProcessor ConfigurationClassPostProcessor getBean
postProcessBeanDefinitionRegistry
appConfig
BeanDefinition 并注册
@Import @Bean -> BeanDefinition 并注册
postProcessBeanDefinitionRegistry
BeanDefinitionRefistryPostProcessor -> postProcessBeanFactory
BeanFactory找BeanFactoryPostProcessor的beanName
BeanFactoryPostProcessor -> postProcessBeanFactory
registerBeanPostProcessors
initMessageSource
initApplicationEventMulticaster
onRefresh
registerListeners
finishBeanFactoryInitialization 实例化非懒加载的单例bean
finishRefresh
ApplicationContext -> lifecycleProcessor
LifecycleBean
ContectRefreshedEvent

Spring解决循环依赖
什么是循环依赖?
在创建A的时候发现A中的属性需要B对象,那就先去创建B对象,又发现B中的属性需要A对象,那又去创建A,形成死循环,这就是循环依赖。

什么是单例池,什么是一级缓存?
一级缓存:singletonObjects,存放初始化后的单例对象

什么是二级缓存,它的作用是什么?
二级缓存:earlySingletonObjects,存放实例化,未完成初始化的单例对象(未完成属性注入的对象)

什么是三级缓存,它的作用是什么?
三级缓存:singletonFactories,存放ObjectFactory对象

为什么Spring一定要用三级缓存来解决循环依赖?
https://www.cnblogs.com/semi-sub/p/13548479.html
https://www.jianshu.com/p/2f08c524f12e

三级缓存解决循环依赖的底层源码分析
假设A依赖B,B依赖A,Spring创建A实例的过程如下:
A依次执行doGetBean方法、依次查询三个缓存是否存在该bean、没有就createBean,实例化完成(早期引用,未完成属性装配),放入三级缓存中,接着执行populateBean方法装配属性,但是发现装配的属性是B对象,走下面步骤。
创建B实例,依次执行doGetBean、查询三个缓存、createBean创建实例,接着执行populateBean发现属性中需要A对象。
再次调用doGetBean创建A实例,查询三个缓存,在三级缓存singletonFactories得到了A的早期引用(在第一步的时候创建出来了),将它放到二级缓存并移除3级缓存并返回,B完成属性装配,一个完整的对象放到一级缓存singletonObjects中。
B完成之后就回到A了,A得到完整的B,肯定也完成全部初始化,也存入一级缓存中。
解决了循环依赖问题。这里可能很多初学者很蒙,什么是早期引用,其实学过C语言的指针的话就比较好理解了,这里的引用就是地址,所以先开辟内存而不封装属性,我后面再给它封装属性,那引用得到的永远是这个地址的最新值,所以就可以先给引用地址后面再封装属性值,这个一定要与传值区分开来,单纯的传值和传地址是不一样的

Spring AOP的底层原理分析

有哪些情况下的循环依赖是Spring解决不了的?
如果是构造方法去注入,那循环依赖问题没有方法解决,如果是Set方法注入,那就可以使用三级缓存来解决,Spring中就是这样的解决的。

为什么@Lazy注解可以用来解决循环依赖?

10.为什么不使用二级缓存?
如果仅仅是解决循环依赖问题,二级缓存也可以,但是如果注入的对象实现了AOP,那么注入到其他bean的时候,不是最终的代理对象,而是原始的。通过三级缓存的ObjectFactory才能实现类最终的代理对象。

11.Spring在创建Bean的过程
实例化,对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法
属性注入,对应方法:AbstractAutowireCapableBeanFactory的populateBean方法
初始化,对应方法:AbstractAutowireCapableBeanFactory的initializeBean

实例化,简单理解就是new了一个对象
属性注入,为实例化中new出来的对象填充属性
初始化,执行aware接口中的方法,初始化方法,完成AOP代理

singletonObjects,一级缓存,存储的是所有创建好了的单例Bean
earlySingletonObjects,完成实例化,但是还未进行属性注入及初始化的对象
singletonFactories,提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象


1. AService首先实例化,实例化通过ObjectFactory半成品暴露在三级缓存中
2. 填充属性BService,发现BService还未进行过加载,就会先去加载BService
3. 再加载BService的过程中,实例化,也通过ObjectFactory半成品暴露在三级缓存
4. 填充属性AService的时候,这时候能够从三级缓存中拿到半成品的ObjectFactory

到ObjectFactory对象后,调用ObjectFactory.getObject()方法最终会调用getEarlyBeanReference()方法,getEarlyBeanReference这个方法主要逻辑大概描述下如果bean被AOP切面代理则返回的是beanProxy对象,如果未被代理则返回的是原bean实例,这时我们会发现能够拿到bean实例(属性未填充),然后从三级缓存移除,放到二级缓存earlySingletonObjects中,而此时B注入的是一个半成品的实例A对象,不过随着B初始化完成后,A会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象

我们会发现再执行一遍singleFactory.getObject()方法又是一个新的代理对象,这就会有问题了,因为AService是单例的,每次执行singleFactory.getObject()方法又会产生新的代理对象,假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象,执行getObject()方法又会产生新的代理对象,这是不行的,因为AService是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了singleFactory.getObject()产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象

spring-bean线程安全
Spring本身并没有针对Bean做线程安全的处理,所以: 
1. 如果Bean是⽆状态的,那么Bean则是线程安全的
2. 如果Bean是有状态的,那么Bean则不是线程安全的 
另外,Bean是不是线程安全,跟Bean的作⽤域没有关系,Bean的作⽤域只是表示Bean的⽣命周期范围, 对于任何⽣命周期的Bean都是⼀个对象,这个对象是不是线程安全的,还是得看这个Bean对象本身

spring-后置处理器
Spring中的后置处理器分为BeanFactory后置处理器和Bean后置处理器,它们是Spring底层源码架构设计 中⾮常重要的⼀种机制,同时开发者也可以利⽤这两种后置处理器来进⾏扩展。BeanFactory后置处理器 表示针对BeanFactory的处理器,Spring启动过程中,会先创建出BeanFactory实例,然后利⽤ BeanFactory处理器来加⼯BeanFactory,⽐如Spring的扫描就是基于BeanFactory后置处理器来实现 的,⽽Bean后置处理器也类似,Spring在创建⼀个Bean的过程中,⾸先会实例化得到⼀个对象,然后再 利⽤Bean后置处理器来对该实例对象进⾏加⼯,⽐如我们常说的依赖注⼊就是基于⼀个Bean后置处理器 来实现的,通过该Bean后置处理器来给实例对象中加了@Autowired注解的属性⾃动赋值,还⽐如我们常 说的AOP,也是利⽤⼀个Bean后置处理器来实现的,基于原实例对象,判断是否需要进⾏AOP,如果需 要,那么就基于原实例对象进⾏动态代理,⽣成⼀个代理对象

Spring中的Bean创建的⽣命周期有哪些步骤
Spring中⼀个Bean的创建⼤概分为以下⼏个步骤:
1. 推断构造⽅法
2. 实例化
3. 填充属性,也就是依赖注⼊
4. 处理Aware回调
5. 初始化前,处理@PostConstruct注解
6. 初始化,处理InitializingBean接⼝
7. 初始化后,进⾏AOP

ApplicationContext和BeanFactory有什么区别
BeanFactory是Spring中⾮常核⼼的组件,表示Bean⼯⼚,可以⽣成Bean,维护Bean,
⽽ApplicationContext继承了BeanFactory,所以ApplicationContext拥有BeanFactory所有的特点,
也是⼀个Bean⼯⼚,但是ApplicationContext除继承了BeanFactory之外,
还继承了诸如 EnvironmentCapable、MessageSource、ApplicationEventPublisher等接⼝,
从⽽ApplicationContext还有获取系统环境变量、国际化、事件发布等功能,这是BeanFactory所不具备的

springmvc-工作流程
1. ⽤户发送请求⾄前端控制器DispatcherServlet。
2. DispatcherServlet 收到请求调⽤ HandlerMapping 处理器映射器。
3. 处理器映射器找到具体的处理器(可以根据 xml 配置、注解进⾏查找),⽣成处理器及处理器拦截器 (如果有则⽣成)⼀并返回给 DispatcherServlet。
4. DispatcherServlet 调⽤ HandlerAdapter 处理器适配器。
5. HandlerAdapter 经过适配调⽤具体的处理器(Controller,也叫后端控制器)
6. Controller 执⾏完成返回 ModelAndView。
7. HandlerAdapter 将 controller 执⾏结果 ModelAndView 返回给 DispatcherServlet。
8. DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。
9. ViewReslover 解析后返回具体 View。
10. DispatcherServlet 根据 View 进⾏渲染视图(即将模型数据填充⾄视图中)。
11. DispatcherServlet 响应⽤户

springboot
SpringBoot是如何启动Tomcat的
1. ⾸先,SpringBoot在启动时会先创建⼀个Spring容器
2. 在创建Spring容器过程中,会利⽤@ConditionalOnClass技术来判断当前classpath中是否存在Tomcat依赖,如果存在则会⽣成⼀个启动Tomcat的Bean 3. Spring容器创建完之后,就会获取启动Tomcat的Bean,并创建Tomcat对象,并绑定端⼝等,然后启动Tomcat

SpringBoot中配置⽂件的加载顺序是怎样的
优先级从⾼到低,⾼优先级的配置覆盖低优先级的配置,所有配置会形成互补配置。
1. 命令⾏参数。所有的配置都可以在命令⾏上进⾏指定;
2. Java系统属性(System.getProperties());
3. 操作系统环境变量;
4. jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置⽂件
5. jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置⽂件再来加载不带profile
6. jar包外部的application.properties或application.yml(不带spring.profile)配置⽂件
7. jar包内部的application.properties或application.yml(不带spring.profile)配置⽂件
8. @Configuration注解类上的@PropertySource

springboot-注解
1. @SpringBootApplication注解:这个注解标识了⼀个SpringBoot⼯程,它实际上是另外三个注解的组 合,这三个注解是: 
a. @SpringBootConfiguration:这个注解实际就是⼀个@Configuration,表示启动类也是⼀个配 置类 
b. @EnableAutoConfiguration:向Spring容器中导⼊了⼀个Selector,⽤来加载ClassPath下 SpringFactories中所定义的⾃动配置类,将这些⾃动加载为配置Bean 
c. @ComponentScan:标识扫描路径,因为默认是没有配置实际扫描路径,所以SpringBoot扫描的 路径是启动类所在的当前⽬录 
2. @Bean注解:⽤来定义Bean,类似于XML中的<bean>标签,Spring在启动时,会对加了@Bean注解 的⽅法进⾏解析,将⽅法的名字做为beanName,并通过执⾏⽅法得到bean对象 
3. @Controller、@Service、@ResponseBody、@Autowired都可以说

spring-事务
1. Spring事务底层是基于数据库事务和AOP机制的
2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean
3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解
4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接
5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮常重要的⼀步
6. 然后执⾏当前⽅法,⽅法中会执⾏sql
7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务
8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务
9. Spring事务的隔离级别对应的就是数据库的隔离级别
10. Spring事务的传播机制是Spring事务⾃⼰实现的,也是Spring事务中最复杂的
11. Spring事务的传播机制是基于数据库连接来做的,⼀个数据库连接⼀个事务,如果传播机制配置为需要新开⼀个事务,那么实际上就是先建⽴⼀个数据库连接,在此新数据库连接上执⾏sql

@Transactional失效
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时,那么这个注解才会⽣效,所以如果是被代理对象来调⽤这个⽅法,那么@Transactional是不会⽣效的。 同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效

aop
利⽤动态代理技术来实现AOP,⽐如JDK动态代理或Cglib动态代理,利⽤动态代理技术,可以针对某个类 ⽣成代理对象,当调⽤代理对象的某个⽅法时,可以任意控制该⽅法的执⾏,⽐如可以先打印执⾏时间, 再执⾏该⽅法,并且该⽅法执⾏完成后,再次打印执⾏时间。 项⽬中,⽐如事务、权限控制、⽅法执⾏时⻓⽇志都是通过AOP技术来实现的,凡是需要对某些⽅法做统 ⼀处理的都可以⽤AOP来实现,利⽤AOP可以做到业务⽆侵⼊

spring-设计模式
工厂模式-BeanFactory/FactoryBean
适配器模式-AdvisorAdapter
访问者模式-PropertyAccessor
装饰器模式-BeanWrapper
代理模式-AOP
观察者模式-事件监听机制
策略模式-jdbcTemplate
委派模式-BeanDefinitionParserDelegate
责任链模式-BeanPostProcessor

什么是服务雪崩?什么是服务限流
1. 当服务A调⽤服务B,服务B调⽤C,此时⼤量请求突然请求服务A,假如服务A本身能抗住这些请求,但是如果服务C抗不住,导致服务C请求堆积,从⽽服务B请求堆积,从⽽服务A不可⽤,这就是服务雪崩,解决⽅式就是服务降级和服务熔断。
2. 服务限流是指在⾼并发请求下,为了保护系统,可以对访问服务的请求进⾏数量上的限制,从⽽防⽌系统不被⼤量请求压垮,在秒杀中,限流是⾮常重要的

什么是服务熔断?什么是服务降级?区别是什么
1. 服务熔断是指,当服务A调⽤的某个服务B不可⽤时,上游服务A为了保证⾃⼰不受影响,从⽽不再调⽤服务B,直接返回⼀个结果,减轻服务A和服务B的压⼒,直到服务B恢复。
2. 服务降级是指,当发现系统压⼒过载时,可以通过关闭某个服务,或限流某个服务来减轻系统压⼒,这就是服务降级。
相同点:
1. 都是为了防⽌系统崩溃
2. 都让⽤户体验到某些功能暂时不可⽤
不同点:熔断是下游服务故障触发的,降级是为了降低系统负载

SOA、分布式、微服务之间有什么关系和区别
1. 分布式架构是指将单体架构中的各个部分拆分,然后部署不同的机器或进程中去,SOA和微服务基本上都是分布式架构的
2. SOA是⼀种⾯向服务的架构,系统的所有服务都注册在总线上,当调⽤服务时,从总线上查找服务信息,然后调⽤
3. 微服务是⼀种更彻底的⾯向服务的架构,将系统中各个功能个体抽成⼀个个⼩的应⽤程序,基本保持⼀个应⽤对应的⼀个服务的架构

springcloud
1. Eureka:注册中⼼,⽤来进⾏服务的⾃动注册和发现
2. Ribbon:负载均衡组件,⽤来在消费者调⽤服务时进⾏负载均衡
3. Feign:基于接⼝的申明式的服务调⽤客户端,让调⽤变得更简单
4. Hystrix:断路器,负责服务容错
5. Zuul:服务⽹关,可以进⾏服务路由、服务降级、负载均衡等
6. Nacos:分布式配置中⼼以及注册中⼼
7. Sentinel:服务的熔断降级,包括限流
8. Seata:分布式事务
9. Spring Cloud Config:分布式配置中⼼
10. Spring Cloud Bus:消息总线

springcloud dubbo区别
Spring Cloud是⼀个微服务框架,提供了微服务领域中的很多功能组件,Dubbo⼀开始是⼀个RPC调⽤框架,核⼼是解决服务调⽤间的问题,
Spring Cloud是⼀个⼤⽽全的框架,Dubbo则更侧重于服务调⽤,所 以Dubbo所提供的功能没有Spring Cloud全⾯,但是Dubbo的服务调⽤性能⽐Spring Cloud⾼,
不过 Spring Cloud和Dubbo并不是对⽴的,是可以结合起来⼀起使⽤的。

dubbo
服务消费者在调⽤某个服务时,会将当前所调⽤的服务接⼝信息、当前⽅法信息、执⾏⽅法所传⼊的⼊参 信息等组装为⼀个Invocation对象,然后不同的协议通过不同的数据组织⽅式和传输⽅式将这个对象传送 给服务提供者,提供者接收到这个对象后,找到对应的服务实现,利⽤反射执⾏对应的⽅法,得到⽅法结 果后再通过⽹络响应给服务消费者
ip+port
缓存服务地址(均衡负载)
获取服务所以元数据进行调用(invocation)
1. 平衡加权轮询算法 
2. 加权随机算法 
3. ⼀致性哈希算法
4. 最⼩活跃数算法

Dubbo是如何完成服务导出的
1. ⾸先Dubbo会将程序员所使⽤的@DubboService注解或@Service注解进⾏解析得到程序员所定义的服务参数,
包括定义的服务名、服务接⼝、服务超时时间、服务协议等等,得到⼀个ServiceBean。
2. 然后调⽤ServiceBean的export⽅法进⾏服务导出
3.然后将服务信息注册到注册中⼼,如果有多个协议,多个注册中⼼,那就将服务按单个协议,单个注册中⼼进⾏注册
4. 将服务信息注册到注册中⼼后,还会绑定⼀些监听器,监听动态配置中⼼的变更
5. 根据服务协议启动对应的Web服务器或⽹络框架,⽐如Tomcat、Netty等

Dubbo是如何完成服务引⼊的
1. 当程序员使⽤@Reference注解来引⼊⼀个服务时,Dubbo会将注解和服务的信息解析出来,得到当前所引⽤的服务名、服务接⼝是什么
2. 然后从注册中⼼进⾏查询服务信息,得到服务的提供者信息,并存在消费端的服务⽬录中
3. 并绑定⼀些监听器⽤来监听动态配置中⼼的变更
4. 然后根据查询得到的服务提供者信息⽣成⼀个服务接⼝的代理对象,并放⼊Spring容器中作为Bean

Dubbo的架构设计是怎样的
Dubbo中的架构设计是⾮常优秀的,分为了很多层次,并且每层都是可以扩展的,⽐如: 
1. Proxy服务代理层,⽀持JDK动态代理、javassist等代理机制
2. Registry注册中⼼层,⽀持Zookeeper、Redis等作为注册中⼼
3. Protocol远程调⽤层,⽀持Dubbo、Http等调⽤协议
4. Transport⽹络传输层,⽀持netty、mina等⽹络传输框架Dubbo⽀持哪些负载均衡策略 
5. Serialize数据序列化层,⽀持JSON、Hessian等序列化机制
各层说明
config 配置层:对外配置接⼝,以 ServiceConfig , ReferenceConfig 为中⼼,可以直接初始化配置类,也可以通过spring解析配置⽣成配置类
proxy 服务代理层:服务接⼝透明代理,⽣成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中⼼,扩展接⼝为 ProxyFactory registry 注册中⼼层:封装服务地址的注册与发现,以服务 URL 为中⼼,扩展接⼝为 RegistryFactory , Registry , RegistryService cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中⼼,以Invoker为中⼼,扩展接⼝为Cluster, Directory, Router, LoadBalance monitor 监控层:RPC 调⽤次数和调⽤时间监控,以Statistics为中⼼,扩展接⼝为 MonitorFactory , Monitor , MonitorService protocol 远程调⽤层:封装 RPC 调⽤,以Invocation , Result 为中⼼,扩展接⼝为 Protocol , Invoker , Exporter exchange 信息交换层:封装请求响应模式,同步转异步,以 Request , Response 为中⼼,扩展接⼝为 Exchanger , ExchangeChannel , ExchangeClient , ExchangeServer transport⽹络传输层:抽象 mina 和 netty 为统⼀接⼝,以Message 为中⼼,扩展接⼝为 Channel , Transporter , Client , Server , Codec serialize
数据序列化层:可复⽤的⼀些⼯具,扩展接⼝为Serialization , ObjectInput , ObjectOutput , ThreadPool 在RPC中,Protocol是核⼼层,也就是只要有 Protocol + Invoker + Exporter 就可以完成⾮透明的RPC调⽤,然后在 Invoker 的主过程上 Filter 拦截点。 

图中的Consumer和Provider是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不⽤Client和Server的原因是Dubbo在很多场景下都使⽤ Provider, Consumer, 
Registry, Monitor 划分逻辑拓普节点,保持统⼀概念。⽽Cluster是外围概念,所以Cluster的⽬的是将多个Invoker伪装成⼀个Invoker,这样其它⼈ 只要关注Protocol 层Invoker即可,加上Cluster或者去掉Cluster对其它层都不会造成影响,因为只有⼀个提供者时,是不需要Cluster的。Proxy层封装了所有接⼝的透明化代理,⽽在其它层都以Invoker为中⼼,只有到了暴露给⽤户使⽤时,才⽤Proxy将Invoker转成接⼝,或将接⼝实现转成Invoker,也就是去掉Proxy 层RPC是可以Run的,只是不那么透明,不那么看起来像调本地服务⼀样调远程服务。⽽Remoting实现是Dubbo协议的实现,如果你选择RMI协议,整个Remoting 都不会⽤上, Remoting内部再划为Transport传输层和Exchange信息交换层,Transport层只负责单向消息各层传输,是对 Mina, Netty, Grizzly的抽象,它也可以扩展UDP传输,⽽Exchange层是在传输层之上封装了Request-Response语义。

分布式-锁
分布式锁所要解决的问题的本质是:能够对分布在多台机器中的线程对共享资源的互斥访问。在这个原理 上可以有很多的实现⽅式:
1. 基于Mysql,分布式环境中的线程连接同⼀个数据库,利⽤数据库中的⾏锁来达到互斥访问,但是 Mysql的加锁和释放锁的性能会⽐较低,不适合真正的实际⽣产环境
2. 基于Zookeeper,Zookeeper中的数据是存在内存的,所以相对于Mysql性能上是适合实际环境的,并且基于Zookeeper的顺序节点、临时节点、Watch机制能⾮常好的来实现的分布式锁 3. 基于Redis,Redis中的数据也是在内存,基于Redis的消费订阅功能、数据超时时间,lua脚本等功 能,也能很好的实现的分布式锁

分布式-cap
CAP理论是分布式领域⾮常重要的⼀个理论,很多分布式中间件在实现时都需要遵守这个理论,其中:
1. C表示⼀致性:指的的是分布式系统中的数据的⼀致性
2. A表示可⽤性:表示分布式系统是否正常可⽤
3. P表示分区容错性:表示分布式系统出现⽹络问题时的容错性
CAP理论是指,在分布式系统中不能同时保证C和A,也就是说在分布式系统中要么保证CP,要么保证AP,也就是⼀致性和可⽤性只能取其⼀,
如果想要数据的⼀致性,那么就需要损失系统的可⽤性,如果需要系统⾼可⽤,那么就要损失系统的数据⼀致性,特指强⼀致性。 CAP理论太过严格,在实际⽣产环境中更多的是使⽤BASE理论,BASE理论是指分布式系统不需要保证数据的强⼀致,只要做到最终⼀致,也不需要保证⼀直可⽤,保证基本可⽤即可

什么是BASE理论
由于不能同时满⾜CAP,所以出现了BASE理论:
1. BA:Basically Available,表示基本可⽤,表示可以允许⼀定程度的不可⽤,⽐如由于系统故障, 请求时间变⻓,或者由于系统故障导致部分⾮核⼼功能不可⽤都是允许的
2. S:Soft state:表示分布式系统可以处于⼀种中间状态,⽐如数据正在同步
3. E:Eventually consistent,表示最终⼀致性,不要求分布式系统数据实时达到⼀致,允许在经过⼀ 段时间后再达到⼀致,在达到⼀致过程中,系统也是可⽤的

分布式ID是什么?有哪些解决⽅案?
在开发中,我们通常会需要⼀个唯⼀ID来标识数据,如果是单体架构,我们可以通过数据库的主键,或直接在内存中维护⼀个⾃增数字来作为ID都是可以的,但对于⼀个分布式系统,就会有可能会出现ID冲突,此时有以下解决⽅案: 
1. uuid,这种⽅案复杂度最低,但是会影响存储空间和性能
2. 利⽤单机数据库的⾃增主键,作为分布式ID的⽣成器,复杂度适中,ID⻓度较之uuid更短,但是受到单机数据库性能的限制,并发量⼤的时候,此⽅案也不是最优⽅案
3. 利⽤redis、zookeeper的特性来⽣成id,⽐如redis的⾃增命令、zookeeper的顺序节点,这种⽅案和单机数据库(mysql)相⽐,性能有所提⾼,可以适当选⽤
4. 雪花算法,⼀切问题如果能直接⽤算法解决,那就是最合适的,利⽤雪花算法也可以⽣成分布式ID,底层原理就是通过某台机器在某⼀毫秒内对某⼀个数字⾃增,这种⽅案也能保证分布式架构中的系统id唯⼀,但是只能保证趋势递增。业界存在tinyid、leaf等开源中间件实现了雪花算法

分布式锁的使⽤场景是什么?有哪些实现⽅案
在单体架构中,多个线程都是属于同⼀个进程的,所以在线程并发执⾏时,遇到资源竞争时,可以利⽤ ReentrantLock、synchronized等技术来作为锁,来控制共享资源的使⽤。 ⽽在分布式架构中,多个线程是可能处于不同进程中的,⽽这些线程并发执⾏遇到资源竞争时,利⽤ReentrantLock、synchronized等技术是没办法来控制多个进程中的线程的,所以需要分布式锁,
意思就是,需要⼀个分布式锁⽣成器,分布式系统中的应⽤程序都可以来使⽤这个⽣成器所提供的锁,从⽽达到多个进程中的线程使⽤同⼀把锁。 ⽬前主流的分布式锁的实现⽅案有两种: 
1. zookeeper:利⽤的是zookeeper的临时节点、顺序节点、watch机制来实现的,
zookeeper分布式锁的特点是⾼⼀致性,因为zookeeper保证的是CP,所以由它实现的分布式锁更可靠,不会出现混乱
2. redis:利⽤redis的setnx、lua脚本、消费订阅等机制来实现的,redis分布式锁的特点是⾼可⽤, 因为redis保证的是AP,所以由它实现的分布式锁可能不可靠,不稳定(⼀旦redis中的数据出现了不⼀致),
可能会出现多个客户端同时加到锁的情况

什么是分布式事务?有哪些实现⽅案
在分布式系统中,⼀次业务处理可能需要多个应⽤来实现,⽐如⽤户发送⼀次下单请求,就涉及到订单 系统创建订单、库存系统减库存,⽽对于⼀次下单,订单创建与减库存应该是要同时成功或同时失败 的,但在分布式系统中,如果不做处理,就很有可能出现订单创建成功,但是减库存失败,那么解决这 类问题,就需要⽤到分布式事务。常⽤解决⽅案有: 
1. 本地消息表:创建订单时,将减库存消息加⼊在本地事务中,⼀起提交到数据库存⼊本地消息表,
然后调⽤库存系统,如果调⽤成功则修改本地消息状态为成功,如果调⽤库存系统失败,则由后台 定时任务从本地消息表中取出未成功的消息,重试调⽤库存系统
2. 消息队列:⽬前RocketMQ中⽀持事务消息,它的⼯作原理是: 
a. ⽣产者订单系统先发送⼀条half消息到Broker,half消息对消费者⽽⾔是不可⻅的
b. 再创建订单,根据创建订单成功与否,向Broker发送commit或rollback
c. 并且⽣产者订单系统还可以提供Broker回调接⼝,当Broker发现⼀段时间half消息没有收到任 何操作命令,则会主动调此接⼝来查询订单是否创建成功
d. ⼀旦half消息commit了,消费者库存系统就会来消费,如果消费成功,则消息销毁,分布式事务成功结束
e. 如果消费失败,则根据重试策略进⾏重试,最后还失败则进⼊死信队列,等待进⼀步处理
3. Seata:阿⾥开源的分布式事务框架,⽀持AT、TCC等多种模式,底层都是基于两阶段提交理论来实现的

分表后非sharding_key的查询怎么处理,分表后的排序
1. 可以做一个mapping表,比如这时候商家要查询订单列表怎么办呢?不带user_id查询的话你总不
能扫全表吧?所以我们可以做一个映射关系表,保存商家和用户的关系,查询的时候先通过商家查
询到用户列表,再通过user_id去查询。
2. 宽表,对数据实时性要求不是很高的场景,比如查询订单列表,可以把订单表同步到离线(实时)
数仓,再基于数仓去做成一张宽表,再基于其他如es提供查询服务。
3. 数据量不是很大的话,比如后台的一些查询之类的,也可以通过多线程扫表,然后再聚合结果的方
式来做。或者异步的形式也是可以的。

union
排序字段是唯一索引:
首先第一页的查询:将各表的结果集进行合并,然后再次排序
第二页及以后的查询,需要传入上一页排序字段的最后一个值,及排序方式。
根据排序方式,及这个值进行查询。如排序字段date,上一页最后值为3,排序方式降序。查询的
时候sql为select ... from table where date < 3 order by date desc limit 0,10。这样再将几个表的
结果合并排序即可

如何实现接口的幂等性
唯一id。每次操作,都根据操作和内容生成唯一的id,在执行之前先判断id是否存在,如果不存在则执行后续操作,并且保存到数据库或者redis等。
服务端提供发送token的接口,业务调用接口前先获取token,然后调用业务接口请求时,把token携带过去,务器判断token是否存在redis中,存在表示第一次请求,可以继续执行业务,执行业务完成后,最后需要把redis中的token删除
建去重表。将业务中有唯一标识的字段保存到去重表,如果表中存在,则表示已经处理过了
版本控制。增加版本号,当版本号符合时,才能更新数据
状态控制。例如订单有状态已支付 未支付 支付中 支付失败,当处于未支付的时候才允许修改为支
付中等

什么是ZAB协议
ZAB协议是Zookeeper⽤来实现⼀致性的原⼦⼴播协议,该协议描述了Zookeeper是如何实现⼀致性的,分为三个阶段:
1. 领导者选举阶段:从Zookeeper集群中选出⼀个节点作为Leader,所有的写请求都会由Leader节点来处理
2. 数据同步阶段:集群中所有节点中的数据要和Leader节点保持⼀致,如果不⼀致则要进⾏同步
3. 请求⼴播阶段:当Leader节点接收到写请求时,会利⽤两阶段提交来⼴播该写请求,使得写请求像事务⼀样在其他节点上执⾏,
达到节点上的数据实时⼀致 但值得注意的是,Zookeeper只是尽量的在达到强⼀致性,实际上仍然只是最终⼀致性的

为什么Zookeeper可以⽤来作为注册中⼼
可以利⽤Zookeeper的临时节点和watch机制来实现注册中⼼的⾃动注册和发现,另外Zookeeper中的 数据都是存在内存中的,并且Zookeeper底层采⽤了nio,多线程模型,所以Zookeeper的性能也是⽐较⾼的,
所以可以⽤来作为注册中⼼,但是如果考虑到注册中⼼应该是注册可⽤性的话,那么Zookeeper则不太合适,
因为Zookeeper是CP的,它注重的是⼀致性,所以集群数据不⼀致时,集群将不可⽤,
所以⽤Redis、Eureka、Nacos来作为注册中⼼将更合适

Zookeeper中的领导者选举的流程是怎样的
对于Zookeeper集群,整个集群需要从集群节点中选出⼀个节点作为Leader,⼤体流程如下:
1. 集群中各个节点⾸先都是观望状态(LOOKING),⼀开始都会投票给⾃⼰,认为⾃⼰⽐较适合作为leader
2. 然后相互交互投票,每个节点会收到其他节点发过来的选票,然后pk,先⽐较zxid,zxid⼤者获胜,zxid如果相等则⽐较myid,myid⼤者获胜
3. ⼀个节点收到其他节点发过来的选票,经过PK后,如果PK输了,则改票,此节点就会投给zxid或myid更⼤的节点,并将选票放⼊⾃⼰的投票箱中,并将新的选票发送给其他节点
4. 如果pk是平局则将接收到的选票放⼊⾃⼰的投票箱中
5. 如果pk赢了,则忽略所接收到的选票
6. 当然⼀个节点将⼀张选票放⼊到⾃⼰的投票箱之后,就会从投票箱中统计票数,看是否超过⼀半的 节点都和⾃⼰所投的节点是⼀样的,如果超过半数,那么则认为当前⾃⼰所投的节点是leader
7. 集群中每个节点都会经过同样的流程,pk的规则也是⼀样的,⼀旦改票就会告诉给其他服务器,
所以最终各个节点中的投票箱中的选票也将是⼀样的,所以各个节点最终选出来的leader也是⼀样的,
这样集群的leader就选举出来了

Zookeeper集群中节点之间数据是如何同步的
1. ⾸先集群启动时,会先进⾏领导者选举,确定哪个节点是Leader,哪些节点是Follower和Observer
2. 然后Leader会和其他节点进⾏数据同步,采⽤发送快照和发送Diff⽇志的⽅式
3. 集群在⼯作过程中,所有的写请求都会交给Leader节点来进⾏处理,从节点只能处理读请求
4. Leader节点收到⼀个写请求时,会通过两阶段机制来处理
5. Leader节点会将该写请求对应的⽇志发送给其他Follower节点,并等待Follower节点持久化⽇志成功
6. Follower节点收到⽇志后会进⾏持久化,如果持久化成功则发送⼀个Ack给Leader节点
7. 当Leader节点收到半数以上的Ack后,就会开始提交,先更新Leader节点本地的内存数据
8. 然后发送commit命令给Follower节点,Follower节点收到commit命令后就会更新各⾃本地内存数据
9. 同时Leader节点还是将当前写请求直接发送给Observer节点,Observer节点收到Leader发过来的写请求后直接执⾏更新本地内存数据
10. 最后Leader节点返回客户端写请求响应成功
11. 通过同步机制和两阶段提交机制来达到集群中节点数据⼀致

redis-数据结构及使用场景
1. 字符串:可以⽤来做最简单的数据缓存,可以缓存某个简单的字符串,也可以缓存某个json格式的字符串,Redis分布式锁的实现就利⽤了这种数据结构,还包括可以实现计数器、Session共享、分布式ID
2. 哈希表:可以⽤来存储⼀些key-value对,更适合⽤来存储对象
3. 列表:Redis的列表通过命令的组合,既可以当做栈,也可以当做队列来使⽤,可以⽤来缓存类似微信 公众号、微博等消息流数据
4. 集合:和列表类似,也可以存储多个元素,但是不能重复,集合可以进⾏交集、并集、差集操作,从⽽可以实现类似,我和某⼈共同关注的⼈、朋友圈点赞等功能
5. 有序集合:集合是⽆序的,有序集合可以设置顺序,可以⽤来实现排⾏榜功能

redis-集群策略
1. 主从模式:这种模式⽐较简单,主库可以读写,并且会和从库进⾏数据同步,这种模式下,客户端直接 连主库或某个从库,但是但主库或从库宕机后,客户端需要⼿动修改IP,另外,这种模式也⽐较难进⾏ 扩容,整个集群所能存储的数据受到某台机器的内存容量,所以不可能⽀持特⼤数据量
2. 哨兵模式:这种模式在主从的基础上新增了哨兵节点,但主库节点宕机后,哨兵会发现主库节点宕机, 然后在从库中选择⼀个库作为新的主库,另外哨兵也可以做集群,从⽽可以保证但某⼀个哨兵节点宕机 后,还有其他哨兵节点可以继续⼯作,这种模式可以⽐较好的保证Redis集群的⾼可⽤,但是仍然不能 很好的解决Redis的容量上限问题。 
3. Cluster模式:Cluster模式是⽤得⽐较多的模式,它⽀持多主多从,这种模式会按照key进⾏槽位的分 配,可以使得不同的key分散到不同的主节点上,利⽤这种模式可以使得整个集群⽀持更⼤的数据容 量,同时每个主节点可以拥有⾃⼰的多个从节点,如果该主节点宕机,会从它的从节点中选举⼀个新的主节点。

对于这三种模式,如果Redis要存的数据量不⼤,可以选择哨兵模式,如果Redis要存的数据量⼤,并且需要持续的扩容,那么选择Cluster模式

Redis分布式锁底层是如何实现的
1. ⾸先利⽤setnx来保证:如果key不存在才能获取到锁,如果key存在,则获取不到锁
2. 然后还要利⽤lua脚本来保证多个redis操作的原⼦性
3. 同时还要考虑到锁过期,所以需要额外的⼀个看⻔狗定时任务来监听锁是否需要续约
4. 同时还要考虑到redis节点挂掉后的情况,所以需要采⽤红锁的⽅式来同时向N/2+1个节点申请锁,都申请到了才证明获取锁成功,这样就算其中某个redis节点挂掉了,锁也不能被其他客户端获取到

Redis主从复制的核⼼原理
Redis的主从复制是提⾼Redis的可靠性的有效措施,主从复制的流程如下:
1. 集群启动时,主从库间会先建⽴连接,为全量复制做准备
2. 主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载,这个过程依赖于内存快照RDB
3. 在主库将数据同步给从库的过程中,主库不会阻塞,仍然可以正常接收请求。否则,redis的服务就被中断了。但是,这些请求中的写操作并没有记录到刚刚⽣成的RDB⽂件中。为了保证主从库的数据⼀致性,主库会在内存中⽤专⻔的replication buffer,记录RDB⽂件⽣成收到的所有写操作。
4. 最后,也就是第三个阶段,主库会把第⼆阶段执⾏过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成RDB⽂件发送后,就会把此时replocation buffer中修改操作发送给从库,从库再执⾏这些操作。这样⼀来,主从库就实现同步了
5. 后续主库和从库都可以处理客户端读操作,写操作只能交给主库处理,主库接收到写操作后,还会将写操作发送给从库,实现增量同步

缓存穿透、缓存击穿、缓存雪崩分别是什么
缓存中存放的⼤多都是热点数据,⽬的就是防⽌请求可以直接从缓存中获取到数据,⽽不⽤访问 Mysql。 
1. 缓存雪崩:如果缓存中某⼀时刻⼤批热点数据同时过期,那么就可能导致⼤量请求直接访问Mysql 了,解决办法就是在过期时间上增加⼀点随机值,另外如果搭建⼀个⾼可⽤的Redis集群也是防⽌缓存雪崩的有效⼿段
2. 缓存击穿:和缓存雪崩类似,缓存雪崩是⼤批热点数据失效,⽽缓存击穿是指某⼀个热点key突然失效,也导致了⼤量请求直接访问Mysql数据库,这就是缓存击穿,解决⽅案就是考虑这个热点key 不设过期时间
3. 缓存穿透:假如某⼀时刻访问redis的⼤量key都在redis中不存在(⽐如⿊客故意伪造⼀些乱七⼋糟的key),那么也会给数据造成压⼒,这就是缓存穿透,解决⽅案是使⽤布隆过滤器,它的作⽤就是如果它认为⼀个key不存在,那么这个key就肯定不存在,所以可以在缓存之前加⼀层布隆过滤器 来拦截不存在的key

Redis和Mysql如何保证数据⼀致
1. 先更新Mysql,再更新Redis,如果更新Redis失败,可能仍然不⼀致
2. 先删除Redis缓存数据,再更新Mysql,再次查询的时候在将数据添加到缓存中,这种⽅案能解决1⽅案的问题,但是在⾼并发下性能较低,⽽且仍然会出现数据不⼀致的问题,⽐如线程1删除了Redis缓存数据,正在更新Mysql,此时另外⼀个查询再查询,那么就会把Mysql中⽼数据⼜查到Redis中
3. 延时双删,步骤是:先删除Redis缓存数据,再更新Mysql,延迟⼏百毫秒再删除Redis缓存数据, 这样就算在更新Mysql时,有其他线程读了Mysql,把⽼数据读到了Redis中,那么也会被删除掉, 从⽽把数据保持⼀致

Mybatis存在哪些优点和缺点
优点:
1. 基于SQL语句编程,相当灵活,不会对应⽤程序或者数据库的现有设计造成任何影响,SQL单独写,解除sql与程序代码的耦合,便于统⼀管理。 
2. 与JDBC相⽐,减少了50%以上的代码量,消除了JDBC⼤量冗余的代码,不需要⼿动开关连接;
3. 很好的与各种数据库兼容(因为MyBatis使⽤JDB 来连接数据库,所以只要JDBC⽀持的数据库MyBatis都⽀持)。
4. 能够与Spring很好的集成;
5. 提供映射标签,⽀持对象与数据库的ORM字段关系映射;提供对象关系映射标签,⽀持对象关系组件维护。
缺点: 1. SQL 语句的编写⼯作量较⼤,尤其当字段多、关联表多时,对开发⼈员编写SQL语句的功底有⼀定要求。
2. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

Mybatis中#{}和${}的区别是什么
1. #{}是预编译处理、是占位符, ${}是字符串替换、是拼接符
2. Mybatis在处理#{}时,会将sql中的#{}替换为?号,调⽤PreparedStatement来赋值
3. Mybatis在处理${}时,就是把${}替换成变量的值,调⽤Statement来赋值
4. 使⽤#{}可以有效的防⽌SQL注⼊,提⾼系统安全性

mysql-执行计划
Explain语句结果中各个字段分表表示什么
type ---> ALL < index < range< ref < eq_ref < const < system。最好是避免ALL和index
row
key
key_len

mysql-索引失效
1. 没有符合最左前缀原则
2. 字段进⾏了隐式数据类型转化
3. ⾛索引没有全表扫描效率⾼

mysql-innodb
Innodb通过Buffer Pool,LogBuffer,RedoLog,UndoLog来实现事务,以⼀个update语句为例:
1. Innodb在收到⼀个update语句后,会先根据条件找到数据所在的⻚,并将该⻚缓存在Buffer Pool中
2. 执⾏update语句,修改Buffer Pool中的数据,也就是内存中的数据
3. 针对update语句⽣成⼀个RedoLog对象,并存⼊LogBuffer中
4. 针对update语句⽣成undolog⽇志,⽤于事务回滚
5. 如果事务提交,那么则把RedoLog对象进⾏持久化,后续还有其他机制将Buffer Pool中所修改的数据 ⻚持久化到磁盘中
6. 如果事务回滚,则利⽤undolog⽇志进⾏回滚

mysql-锁
按锁粒度分类:
1. ⾏锁:锁某⾏数据,锁粒度最⼩,并发度⾼
2. 表锁:锁整张表,锁粒度最⼤,并发度低
3. 间隙锁:锁的是⼀个区间还可以分为: 
1. 共享锁:也就是读锁,⼀个事务给某⾏数据加了读锁,其他事务也可以读,但是不能写
2. 排它锁:也就是写锁,⼀个事务给某⾏数据加了写锁,其他事务不能读,也不能写还可以分为: 
1. 乐观锁:并不会真正的去锁某⾏记录,⽽是通过⼀个版本号来实现的
2. 悲观锁:上⾯所的⾏锁、表锁等都是悲观锁
在事务的隔离级别实现中,就需要利⽤锁来解决幻读

mysql-索引
较频繁的作为查询条件的字段应该创建索引
唯一性太差的字段不适合单独创建索引,即使该字段频繁作为查询条件
更新非常频繁的字段不适合创建索引。

索引覆盖是什么
索引覆盖就是⼀个SQL在执⾏时,可以利⽤索引来快速查找,并且此SQL所要查询的字段在当前索引对应的字段中都包含了,那么就表示此SQL⾛完索引后不⽤回表了,所需要的字段都在当前索引的叶⼦节点上存在,可以直接作为结果返回了

最左前缀原则是什么
当⼀个SQL想要利⽤索引是,就⼀定要提供该索引所对应的字段中最左边的字段,也就是排在最前⾯的字段,⽐如针对a,b,c三个字段建⽴了⼀个联合索引,那么在写⼀个sql时就⼀定要提供a字段的条件,这样才能⽤到联合索引,这是由于在建⽴a,b,c三个字段的联合索引时,底层的B+树是按照a,b,c三个字段 从左往右去⽐较⼤⼩进⾏排序的,所以如果想要利⽤B+树进⾏快速查找也得符合这个规则

Mysql锁有哪些,如何理解
按锁粒度分类: 
1. ⾏锁:锁某⾏数据,锁粒度最⼩,并发度⾼
2. 表锁:锁整张表,锁粒度最⼤,并发度低 Innodb是如何实现事务的
3. 间隙锁:锁的是⼀个区间还可以分为:
1. 共享锁:也就是读锁,⼀个事务给某⾏数据加了读锁,其他事务也可以读,但是不能写
2. 排它锁:也就是写锁,⼀个事务给某⾏数据加了写锁,其他事务不能读,也不能写 还可以分为:
1. 乐观锁:并不会真正的去锁某⾏记录,⽽是通过⼀个版本号来实现的
2. 悲观锁:上⾯所的⾏锁、表锁等都是悲观锁在事务的隔离级别实现中,就需要利⽤锁来解决幻读

Mysql慢查询该如何优化
1. 检查是否⾛了索引,如果没有则优化SQL利⽤索引
2. 检查所利⽤的索引,是否是最优索引
3. 检查所查字段是否都是必须的,是否查询了过多字段,查出了多余数据
4. 检查表中数据是否过多,是否应该进⾏分库分表了
5. 检查数据库实例所在机器的性能配置,是否太低,是否可以适当增加资源

MyISAM和InnoDB的区别
MyISAM:
不支持事务,但是每次查询都是原子的;
支持表级锁,即每次操作是对整个表加锁;存储表的总行数;
一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;
采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性。
InnoDb:
支持ACID的事务,支持事务的四种隔离级别;
支持行级锁及外键约束:因此可以支持写并发;不存储总行数;
一个InnoDb引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能分布在多个文件里),
也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,一般为2G),受操作系统文件大小的限制;
主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;
因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;
最好使用自增主键,防止插入数据时,为维持B+树结构,文件的大调整

mysql中索引类型及对数据库的性能的影响
普通索引:允许被索引的数据列包含重复的值。
唯一索引:可以保证数据记录的唯一性。
主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用
关键字 PRIMARY KEY 来创建。
联合索引:索引可以覆盖多个数据列,如像INDEX(columnA, columnB)索引。
全文索引:通过建立倒排索引 ,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引
擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引
索引可以极大的提高数据的查询速度。
通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
但是会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件
索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大,
如果非聚集索引很多,一旦聚集索引改变,那么所有非聚集索引都会跟着变。

设计模式
1. 代理模式:Mybatis中⽤到JDK动态代理来⽣成Mapper的代理对象,在执⾏代理对象的⽅法时会去执⾏SQL,Spring中AOP、包括@Configuration注解的底层实现也都⽤到了代理模式
2. 责任链模式:Tomcat中的Pipeline实现,以及Dubbo中的Filter机制都使⽤了责任链模式
3. ⼯⼚模式:Spring中的BeanFactory就是⼀种⼯⼚模式的实现
4. 适配器模式:Spring中的Bean销毁的⽣命周期中⽤到了适配器模式,⽤来适配各种Bean销毁逻辑的执⾏⽅式
5. 外观模式:Tomcat中的Request和RequestFacade之间体现的就是外观模式
6. 模板⽅法模式:Spring中的refresh⽅法中就提供了给⼦类继承重写的⽅法,就⽤到了模板⽅法模式

1. 创建型
a. ⼯⼚模式(Factory Pattern)
b. 抽象⼯⼚模式(Abstract Factory Pattern)
c. 单例模式(Singleton Pattern)
d. 建造者模式(Builder Pattern)
e. 原型模式(Prototype Pattern)

2. 结构型
a. 适配器模式(Adapter Pattern)
b. 桥接模式(Bridge Pattern)
c. 过滤器模式(Filter、Criteria Pattern)
d. 组合模式(Composite Pattern)
e. 装饰器模式(Decorator Pattern)
f. 外观模式(Facade Pattern)
g. 享元模式(Flyweight Pattern)
h. 代理模式(Proxy Pattern)

3. ⾏为型
a. 责任链模式(Chain of Responsibility Pattern)
b. 命令模式(Command Pattern)
c. 解释器模式(Interpreter Pattern)
d. 迭代器模式(Iterator Pattern)
e. 中介者模式(Mediator Pattern)
f. 备忘录模式(Memento Pattern)
g. 观察者模式(Observer Pattern)
h. 状态模式(State Pattern)
i. 空对象模式(Null Object Pattern)
j. 策略模式(Strategy Pattern)
k. 模板模式(Template Pattern)
l. 访问者模式(Visitor Pattern)

linux-命令
1. 增删查改
2. 防⽕墙相关
3. ssh/scp
4. 软件下载、解压、安装
5. 修改权限

epoll和poll的区别
1. select模型,使⽤的是数组来存储Socket连接⽂件描述符,容量是固定的,需要通过轮询来判断是否发⽣了IO事件
2. poll模型,使⽤的是链表来存储Socket连接⽂件描述符,容量是不固定的,同样需要通过轮询来判断是否发⽣了IO事件
3. epoll模型,epoll和poll是完全不同的,epoll是⼀种事件通知模型,当发⽣了IO事件时,应⽤程序才进⾏IO操作,不需要像poll模型那样主动去轮询

IO
BIO、NIO、AIO分别是什么
1. BIO:同步阻塞IO,使⽤BIO读取数据时,线程会阻塞住,并且需要线程主动去查询是否有数据可读,并且需要处理完⼀个Socket之后才能处理下⼀个Socket
2. NIO:同步⾮阻塞IO,使⽤NIO读取数据时,线程不会阻塞,但需要线程主动的去查询是否有IO事件
3. AIO:也叫做NIO 2.0,异步⾮阻塞IO,使⽤AIO读取数据时,线程不会阻塞,并且当有数据可读时会通知给线程,不需要线程主动去查询

零拷⻉是什么
零拷⻉指的是,应⽤程序在需要把内核中的⼀块区域数据转移到另外⼀块内核区域去时,不需要经过先复制到⽤户空间,再转移到⽬标内核区域去了,⽽直接实现转移

Netty是什么?和Tomcat有什么区别?特点是什么
Netty是⼀个基于NIO的异步⽹络通信框架,性能⾼,封装了原⽣NIO编码的复杂度,开发者可以直接使⽤Netty来开发⾼效率的各种⽹络服务器,并且编码简单。 
Tomcat是⼀个Web服务器,是⼀个Servlet容器,基本上Tomcat内部只会运⾏Servlet程序,并处理HTTP请求,
⽽Netty封装的是底层IO模型,关注的是⽹络数据的传输,⽽不关⼼具体的协议,可定制性更⾼。
Netty的特点:
1. 异步、NIO的⽹络通信框架
2. ⾼性能
3. ⾼扩展,⾼定制性
4. 易⽤性

Netty的线程模型是怎么样的
Netty同时⽀持Reactor单线程模型、Reactor多线程模型和Reactor主从多线程模型,⽤户可根据启动参数配置在这三种模型之间切换
服务端启动时,通常会创建两个NioEventLoopGroup实例,对应了两个独⽴的Reactor线程池,bossGroup负责处理客户端的连接请求,workerGroup负责处理I/O相关的操作,执⾏系统Task、定时任务Task等。⽤户可根据服务端引导类ServerBootstrap配置参数选择Reactor线程模型,进⽽最⼤限度地满⾜⽤户的定制化需求

Netty的⾼性能体现在哪些⽅⾯
1. NIO模型,⽤最少的资源做更多的事情。
2. 内存零拷⻉,尽量减少不必要的内存拷⻉,实现了更⾼效率的传输。
3. 内存池设计,申请的内存可以重⽤,主要指直接内存。内部实现是⽤⼀颗⼆叉查找树管理内存分配情况。 
4.串⾏化处理读写 :避免使⽤锁带来的性能开销。即消息的处理尽可能再同⼀个线程内完成,期间不进⾏线程切换,这样就避免了多线程竞争和同步锁。表⾯上看,串⾏化设计似乎CPU利⽤率不⾼, 并发程度不够。但是,通过调整NIO线程池的线程参数,可以同时启动多个串⾏化的线程并⾏运⾏,这种局部⽆锁化的串⾏线程设计相⽐⼀个队⾥-多个⼯作线程模型性能更优。
5. ⾼性能序列化协议 :⽀持protobuf等⾼性能序列化协议。
6. ⾼效并发编程的体现 :volatile的⼤量、正确使⽤;CAS和原⼦类的⼴泛使⽤;线程安全容器的使Netty的线程模型通过读写锁提升并发性能

maven
1. package是打包,打成Jar或War
2. install表示将Jar或War安装到本地仓库中
3. deploy表示发布到仓库中

网络-三次握手四次挥手
TCP协议是7层⽹络协议中的传输层协议,负责数据的可靠传输。 在建⽴TCP连接时,需要通过三次握⼿来建⽴,过程是:
1. 客户端向服务端发送⼀个SYN
2. 服务端接收到SYN后,给客户端发送⼀个SYN_ACK
3. 客户端接收到SYN_ACK后,再给服务端发送⼀个ACK
在断开TCP连接时,需要通过四次挥⼿来断开,
过程是:
1. 客户端向服务端发送FIN
2. 服务端接收FIN后,向客户端发送ACK,表示我接收到了断开连接的请求,客户端你可以不发数据了,不过服务端这边可能还有数据正在处理
3. 服务端处理完所有数据后,向客户端发送FIN,表示服务端现在可以断开连接
4. 客户端收到服务端的FIN,向服务端发送ACK,表示客户端也会断开连接了

消息队列
消息可靠传输代表了两层意思,既不能多也不能少。 
1. 为了保证消息不多,也就是消息不能重复,也就是⽣产者不能重复⽣产消息,或者消费者不能重复消费消息
a. ⾸先要确保消息不多发,这个不常出现,也⽐较难控制,因为如果出现了多发,很⼤的原因是⽣产者⾃⼰的原因,如果要避免出现问题,就需要在消费端做控制
b. 要避免不重复消费,最保险的机制就是消费者实现幂等性,保证就算重复消费,也不会有问题,通过幂等性,也能解决⽣产者重复发送消息的问题
2. 消息不能少,意思就是消息不能丢失,⽣产者发送的消息,消费者⼀定要能消费到,对于这个问题,就要考虑两个⽅⾯
a. ⽣产者发送消息时,要确认broker确实收到并持久化了这条消息,⽐如RabbitMQ的confirm机制,Kafka的ack机制都可以保证⽣产者能正确的将消息发送给broker
b. broker要等待消费者真正确认消费到了消息时才删除掉消息,这⾥通常就是消费端ack机制,消费者接收到⼀条消息后,如果确认没问题了,就可以给broker发送⼀个ack,broker接收到ack后才会删除消息

消息队列有哪些作⽤
1. 解耦:使⽤消息队列来作为两个系统之间的通讯⽅式,两个系统不需要相互依赖了
2. 异步:系统A给消息队列发送完消息之后,就可以继续做其他事情了
3. 流量削峰:如果使⽤消息队列的⽅式来调⽤某个系统,那么消息将在队列中排队,由消费者⾃⼰控制消费速度

死信队列是什么?延时队列是什么?
1. 死信队列也是⼀个消息队列,它是⽤来存放那些没有成功消费的消息的,通常可以⽤来作为消息重试
2. 延时队列就是⽤来存放需要在指定时间被处理的元素的队列,通常可以⽤来处理⼀些具有过期性操作的业务,⽐如⼗分钟内未⽀付则取消订单

Kafka为什么⽐RocketMQ的吞吐量要⾼
Kafka的⽣产者采⽤的是异步发送消息机制,当发送⼀条消息时,消息并没有发送到Broker⽽是缓存起来,然后直接向业务返回成功,当缓存的消息达到⼀定数量时再批量发送给Broker。这种做法减少了⽹络io,从⽽提⾼了消息发送的吞吐量,
但是如果消息⽣产者宕机,会导致消息丢失,业务出错,所以理 论上kafka利⽤此机制提⾼了性能却降低了可靠性

Kafka的Pull和Push分别有什么优缺点
1. pull表示消费者主动拉取,可以批量拉取,也可以单条拉取,所以pull可以由消费者⾃⼰控制,根据⾃⼰的消息处理能⼒来进⾏控制,但是消费者不能及时知道是否有消息,可能会拉到的消息为空
2. push表示Broker主动给消费者推送消息,所以肯定是有消息时才会推送,但是消费者不能按⾃⼰的能⼒来消费消息,推过来多少消息,消费者就得消费多少消息,所以可能会造成⽹络堵塞,消费者压⼒⼤等问题

RocketMQ的底层实现原理
RocketMQ由NameServer集群、Producer集群、Consumer集群、Broker集群组成,消息⽣产和消费的⼤致原理如下:
1. Broker在启动的时候向所有的NameServer注册,并保持⻓连接,每30s发送⼀次⼼跳
2. Producer在发送消息的时候从NameServer获取Broker服务器地址,根据负载均衡算法选择⼀台服务器来发送消息
3. Conusmer消费消息的时候同样从NameServer获取Broker地址,然后主动拉取消息来消费

kafka和rabbitmq使用场景对比
在实际生产应用中,通常会使用kafka作为消息传输的数据管道,
rabbitmq作为交易数据作为数据传输管道,
主要的取舍因素则是是否存在丢数据的可能rabbitmq在金融场景中经常使用,具有较高的严谨性,数据丢失的可能性更小,同事具备更高的实时性;
而kafka优势主要体现在吞吐量上,虽然可以通过策略实现数据不丢失,但从严谨性角度来讲,大不如rabbitmq;
而且由于kafka保证每条消息最少送达一次,有较小的概率会出现数据重复发送的情况

RabbitMQ的架构设计
Broker:rabbitmq的服务节点
Queue:队列,是RabbitMQ的内部对象,用于存储消息。RabbitMQ中消息只能存储在队列中。生产
者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的
消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费者都收到所有的消息进行消费。(注
意:RabbitMQ不支持队列层面的广播消费,如果需要广播消费,可以采用一个交换器通过路由Key绑
定多个队列,由多个消费者来订阅这些队列的方式。
Exchange:交换器。生产者将消息发送到Exchange,由交换器将消息路由到一个或多个队列中。如果
路由不到,或返回给生产者,或直接丢弃,或做其它处理。
RoutingKey:路由Key。生产者将消息发送给交换器的时候,一般会指定一个RoutingKey,用来指定
这个消息的路由规则。这个路由Key需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。
在交换器类型和绑定键固定的情况下,生产者可以在发送消息给交换器时通过指定RoutingKey来决定消
息流向哪里。
Binding:通过绑定将交换器和队列关联起来,在绑定的时候一般会指定一个绑定键,这样RabbitMQ
就可以指定如何正确的路由到队列了。
交换器和队列实际上是多对多关系。就像关系数据库中的两张表。他们通过BindingKey做关联(多对多
关系表)。在投递消息时,可以通过Exchange和RoutingKey(对应BindingKey)就可以找到相对应的队
列。
信道:信道是建立在Connection 之上的虚拟连接。当应用程序与Rabbit Broker建立TCP连接的时候,
客户端紧接着可以创建一个AMQP 信道(Channel) ,每个信道都会被指派一个唯一的D。RabbitMQ 处
理的每条AMQP 指令都是通过信道完成的。信道就像电缆里的光纤束。一条电缆内含有许多光纤束,允
许所有的连接通过多条光线束进行传输和接收

RabbitMQ如何确保消息发送?消息接收?
信道需要设置为confirm模式,则所有在信道上发布的消息都会分配一个唯一ID。 一旦消息被投递到queue(可持久化的消息需要写入磁盘),信道会发送一个确认给生产者(包含消息唯一ID)。 
如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(未确认)消息给生产者。 
所有被发送的消息都将被confirm(即ack)或者被nack一次。但是没有对消息被confirm的快慢做任何保证,并且同一条消息不会既被confirm又被nack
发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者,生产者的回调方法会被触发。 ConfirmCallback接口:只确认是否正确到达Exchange中,成功到达则回调ReturnCallback接口:消息失败返回时回调接收方确认机制

消费者在声明队列时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(或者磁盘,持久化消息)中移去消息。否则,消息被消费后会被立即删除。 消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息, RabbitMQ才能安全地把消息从队列中删除。 

abbitMQ不会为未ack的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。
这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很长。保证数据的最终一致性; 
如果消费者返回ack之前断开了链接,RabbitMQ会重新分发给下一个订阅的消费者。(可能存在消息重复消 费的隐患,需要去重)

RabbitMQ事务消息
通过对信道的设置实现
1. channel.txSelect();通知服务器开启事务模式;服务端会返回Tx.Select-Ok
2. channel.basicPublish;发送消息,可以是多条,可以是消费消息提交ack
3. channel.txCommit()提交事务;
4. channel.txRollback()回滚事务;
消费者使用事务:
1. autoAck=false,手动提交ack,以事务提交或回滚为准;
2. autoAck=true,不支持事务的,也就是说你即使在收到消息之后在回滚事务也是于事无补的,队列已经把消息移除了
如果其中任意一个环节出现问题,就会抛出IoException异常,用户可以拦截异常进行事务回滚,或决定要不要重复消息。
事务消息会降低rabbitmq的性能

RabbitMQ死信队列、延时队列
1. 消息被消费方否定确认,使用channel.basicNack或channel.basicReject ,并且此时requeue属性被设置为false 。
2. 消息在队列的存活时间超过设置的TTL时间。
3. 消息队列的消息数量已经超过最大队列长度。
那么该消息将成为“死信”。
“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃,
为每个需要使用死信的业务队列配置一个死信交换机,这里同一个项目的死信交换机可以共用一个,然后为每个业务队列分配一个单独的路由key,
死信队列只不过是绑定在死信交换机上的队列,死信交换机也不是什么特殊的交换机,只不过是用来接受死信的交换机,所以可以为任何类型【Direct、Fanout、Topic】
TTL:一条消息或者该队列中的所有消息的最大存活时间
如果一条消息设置了TTL属性或者进入了设置TTL属性的队列,那么这条消息如果在TTL设置的时间内没有被消费,则会成为“死信”。
如果同时配置了队列的TTL和消息的TTL,那么较小的那个值将会被使用。
只需要消费者一直消费死信队列里的消息

RabbitMQ镜像队列机制
镜像queue有master节点和slave节点。master和slave是针对一个queue而言的,而不是一个node作为所有queue的master,其它node作为slave。
一个queue第一次创建的node为它的master节点,其它node为slave节点。
无论客户端的请求打到master还是slave最终数据都是从master节点获取。当请求打到master节点时,master节点直接将消息返回给client,
同时master节点会通过GM(Guaranteed Multicast)协议将queue的最新状态广播到slave节点。GM保证了广播消息的原子性,即要么都更新要么都不更新。
当请求打到slave节点时,slave节点需要将请求先重定向到master节点,master节点将将消息返回给client,
同时master节点会通过GM协议将queue的最新状态广播到slave节点。
如果有新节点加入,RabbitMQ不会同步之前的历史数据,新节点只会复制该节点加入到集群之后新增的消息

kafka架构设计
Consumer Group:消费者组,消费者组内每个消费者负责消费不同分区的数据,提高消费能力。逻辑上的一个订阅者。
Topic:可以理解为一个队列,Topic 将消息分类,生产者和消费者面向的是同一个Topic。
Partition:为了实现扩展性,提高并发能力,一个Topic 以多个Partition的方式分布到多个Broker上,每个Partition是一个有序的队列。
一个Topic的每个Partition都有若干个副本(Replica),一个Leader和若干个Follower。生产者发送数据的对象,以及消费者消费数据的对象,都是Leader。
Follower负责实时从Leader 中同步数据,保持和Leader数据的同步。Leader发生故障时,某个Follower还会成为新的Leader。
Offset:消费者消费的位置信息,监控数据消费到什么位置,当消费者挂掉再重新恢复的时候,可以从消费位置继续消费。
Zookeeper:Kafka集群能够正常工作,需要依赖于Zookeeper,Zookeeper帮助Kafka存储和管理集群信息

Kafka在什么情况下会出现消息丢失及解决方案
1)消息发送
1、ack=0,不重试 producer发送消息完,不管结果了,如果发送失败也就丢失了。 
2、ack=1,leader crash producer发送消息完,只等待lead写入成功就返回了,leader crash了,这时follower没来及同步,消息丢失。 
3、unclean.leader.election.enable配置true 允许选举ISR以外的副本作为leader,会导致数据丢失,默认为false。
producer发送异步消息完,只等待lead写入成功就返回了,leader crash了,这时ISR中没有follower,leader从OSR中选举,因为OSR中本来落后于Leader造成消息丢失。 
解决方案: 
1、配置:ack=all / -1,tries > 1,unclean.leader.election.enable : false producer发送消息完,等待follower同步完再返回,如果异常则重试。副本的数量可能影响吞吐量。 不允许选举ISR以外的副本作为leader。 
2、配置:min.insync.replicas > 1 副本指定必须确认写操作成功的最小副本数量。如果不能满足这个最小值,则生产者将引发一个异常(要么是NotEnoughReplicas,要么是NotEnoughReplicasAfterAppend)。 min.insync.replicas和ack更大的持久性保证。确保如果大多数副本没有收到写操作,则生产者将引发异常。
3、失败的offset单独记录 producer发送消息,会自动重试,遇到不可恢复异常会抛出,这时可以捕获异常记录到数据库或缓存,进行单独处理
2)消费
先commit再处理消息。如果在处理消息的时候异常了,但是offset已经提交了,这条消息对于该消费者来说就是丢失了,再也不会消费到了
3)broker的刷盘
减小刷盘间隔

Kafka是pull?push?优劣势分析
pull模式:
可以批量拉取、也可以单条拉取
可以设置不同的提交方式,实现不同的传输语义
缺点:如果kafka没有数据,会导致consumer空循环,消耗资源
解决:通过参数设置,consumer拉取数据为空或者没有达到一定数量时进行阻塞

push模式:不会导致consumer循环等待
缺点:速率固定、忽略了consumer的消费能力,可能导致拒绝服务或者网络拥塞等情况

简述kafka的rebalance机制
consumer group中的消费者与topic下的partion重新匹配的过程
何时会产生rebalance:
consumer group中的成员个数发生变化
consumer消费超时
group订阅的topic个数发生变化
group订阅的topic的分区数发生变化
coordinator:通常是partition的leader节点所在的broker,负责监控group中consumer的存活,
consumer维持到coordinator的心跳,判断consumer的消费超时
coordinator通过心跳返回通知consumer进行rebalance
consumer请求coordinator加入组,coordinator选举产生leader consumer
leader consumer从coordinator获取所有的consumer,发送syncGroup(分配信息)给到coordinator
coordinator通过心跳机制将syncGroup下发给consumer
完成rebalance
leader consumer监控topic的变化,通知coordinator触发rebalance
如果C1消费消息超时,触发rebalance,重新分配后、该消息会被其他消费者消费,此时C1消费完成提交offset、导致错误
解决:coordinator每次rebalance,会标记一个Generation给到consumer,每次rebalance该Generation会+1,consumer提交offset时,coordinator会比对Generation,不一致则拒绝提交

Kafka的性能好在什么地方
kafka不基于内存,而是硬盘存储,因此消息堆积能力更强
顺序写:利用磁盘的顺序访问速度可以接近内存,kafka的消息都是append操作,partition是有序的,
节省了磁盘的寻道时间,同时通过批量操作、节省写入次数,partition物理上分为多个segment存储,方便删除
传统:
读取磁盘文件数据到内核缓冲区
将内核缓冲区的数据copy到用户缓冲区
将用户缓冲区的数据copy到socket的发送缓冲区
将socket发送缓冲区中的数据发送到网卡、进行传输
零拷贝:
直接将内核缓冲区的数据发送到网卡传输
使用的是操作系统的指令支持
kafka不太依赖jvm,主要理由操作系统的pageCache,如果生产消费速率相当,则直接用pageCache交换数据,不需要经过磁盘IO

浏览器
浏览器发出⼀个请求到收到响应经历了哪些步骤
1. 浏览器解析⽤户输⼊的URL,⽣成⼀个HTTP格式的请求
2. 先根据URL域名从本地hosts⽂件查找是否有映射IP,如果没有就将域名发送给电脑所配置的DNS进⾏域名解析,得到IP地址
3. 浏览器通过操作系统将请求通过四层⽹络协议发送出去
4. 途中可能会经过各种路由器、交换机,最终到达服务器
5. 服务器收到请求后,根据请求所指定的端⼝,将请求传递给绑定了该端⼝的应⽤程序,⽐如8080被tomcat占⽤了
6. tomcat接收到请求数据后,按照http协议的格式进⾏解析,解析得到所要访问的servlet
7. 然后servlet来处理这个请求,如果是SpringMVC中的DispatcherServlet,那么则会找到对应的Controller中的⽅法,并执⾏该⽅法得到结果
8. Tomcat得到响应结果后封装成HTTP响应的格式,并再次通过⽹络发送给浏览器所在的服务器
9. 浏览器所在的服务器拿到结果后再传递给浏览器,浏览器则负责解析并渲染

跨域请求是什么?有什么问题?怎么解决?
跨域是指浏览器在发起⽹络请求时,会检查该请求所对应的协议、域名、端⼝和当前⽹⻚是否⼀致,
如果不⼀致则浏览器会进⾏限制,⽐如在www.baidu.com的某个⽹⻚中,如果使⽤ajax去访问 www.jd.com是不⾏的,但是如果是img、iframe、script等标签的src属性去访问则是可以的,之所以浏 览器要做这层限制,是为了⽤户信息安全。但是如果开发者想要绕过这层限制也是可以的:
1. response添加header,⽐如resp.setHeader("Access-Control-Allow-Origin", "*");
表示可以访问所有⽹站,不受是否同源的限制
2. jsonp的⽅式,该技术底层就是基于script标签来实现的,因为script标签是可以跨域的
3. 后台⾃⼰控制,先访问同域名下的接⼝,然后在接⼝中再去使⽤HTTPClient等⼯具去调⽤⽬标接⼝
4. ⽹关,和第三种⽅式类似,都是交给后台服务来进⾏跨域访问

http
HTTPS是如何保证安全传输的
https通过使⽤对称加密、⾮对称加密、数字证书等⽅式来保证数据的安全传输。
1. 客户端向服务端发送数据之前,需要先建⽴TCP连接,所以需要先建⽴TCP连接,建⽴完TCP连接后,服务端会先给客户端发送公钥,客户端拿到公钥后就可以⽤来加密数据了,服务端到时候接收到数据就可以⽤私钥解密数据,这种就是通过⾮对称加密来传输数据
2. 不过⾮对称加密⽐对称加密要慢,所以不能直接使⽤⾮对称加密来传输请求数据,所以可以通过⾮对称加密的⽅式来传输对称加密的秘钥,之后就可以使⽤对称加密来传输请求数据了
3. 但是仅仅通过⾮对称加密+对称加密还不⾜以能保证数据传输的绝对安全,因为服务端向客户端发送公钥时,可能会被截取
4. 所以为了安全的传输公钥,需要⽤到数字证书,数字证书是具有公信⼒、⼤家都认可的,服务端向客户端发送公钥时,可以把公钥和服务端相关信息通过Hash算法⽣成消息摘要,再通过数字证书提供的私钥对消息摘要进⾏加密⽣成数字签名,在把没进⾏Hash算法之前的信息和数字签名⼀起形成数字证书,最后把数字证书发送给客户端,客户端收到数字证书后,就会通过数字证书提供的公钥来解密数字证书,从⽽得到⾮对称加密要⽤到的公钥。
5. 在这个过程中,就算有中间⼈拦截到服务端发出来的数字证书,虽然它可以解密得到⾮对称加密要使⽤的公钥,但是中间⼈是办法伪造数字证书发给客户端的,因为客户端上内嵌的数字证书是全球具有公信⼒的,某个⽹站如果要⽀持https,都是需要申请数字证书的私钥的,中间⼈如果要⽣成能被客户端解析的数字证书,也是要申请私钥的,所以是⽐较安全了

tomcat
Tomcat中为什么要使⽤⾃定义类加载器
⼀个Tomcat中可以部署多个应⽤,⽽每个应⽤中都存在很多类,并且各个应⽤中的类是独⽴的,
全类名是可以相同的,⽐如⼀个订单系统中可能存在com.zhouyu.User类,⼀个库存系统中可能也存在 com.zhouyu.User类,⼀个Tomcat,不管内部部署了多少应⽤,Tomcat启动之后就是⼀个Java进程, 也就是⼀个JVM,所以如果Tomcat中只存在⼀个类加载器,⽐如默认的AppClassLoader,那么就只能 加载⼀个com.zhouyu.User类,这是有问题的,⽽在Tomcat中,会为部署的每个应⽤都⽣成⼀个类加载 器实例,名字叫做WebAppClassLoader,这样Tomcat中每个应⽤就可以使⽤⾃⼰的类加载器去加载⾃ ⼰的类,从⽽达到应⽤之间的类隔离,不出现冲突。另外Tomcat还利⽤⾃定义加载器实现了热加载功能

Tomcat如何进⾏优化
对于Tomcat调优,可以从两个⽅⾯来进⾏调整:内存和线程。 ⾸先启动Tomcat,实际上就是启动了⼀个JVM,所以可以按JVM调优的⽅式来进⾏调整,
从⽽达到Tomcat优化的⽬的。 
另外Tomcat中设计了⼀些缓存区,⽐如appReadBufSize、bufferPoolSize等缓存区来提⾼吞吐量。 还可以调整Tomcat的线程,⽐如调整minSpareThreads参数来改变Tomcat空闲时的线程数,调整maxThreads参数来设置Tomcat处理连接的最⼤线程数。 并且还可以调整IO模型,⽐如使⽤NIO、APR这种相⽐于BIO更加⾼效的IO模型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星月IWJ

曾梦想杖键走天涯,如今加班又挨

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值