当前位置:   article > 正文

面试问题集锦_引 计数法,可能会出现a 引 了 b,b 引 了 a,这时候就算他们都不再使

引 计数法,可能会出现a 引 了 b,b 引 了 a,这时候就算他们都不再使

1、List和Set的区别

        List:有序,按对象进⼊的顺序保存对象,可重复,允许多个Null元素对象,可以使⽤Iterator取出 所有元素,在逐⼀遍历,还可以使⽤get(int index)获取指定下标的元素。

        Set:⽆序,不可重复,最多允许有⼀个Null元素对象,取元素时只能⽤Iterator接⼝取得所有元 素,在逐⼀遍历各个元素。

2、ArrayList和LinkedList区别

        ArrayList:底层是基于数组实现,更适合随机查找

        LinkedList:基于链表实现,更适合删除和添加

        ArrayList和LinkedList都实现了List接⼝,LinkedList还额外实现了Deque接⼝,所以 LinkedList还可以当做队列来使⽤。

3、HashMap和HashTable有什么区别?其底层实现是什么?

区别 :

        1. HashMap⽅法没有synchronized修饰,线程⾮安全,HashTable线程安全;

        2. HashMap允许key和value为null,⽽HashTable不允许

        hashMap底层实现:数组+链表实现,jdk8开始链表⾼度到8、数组⻓度超过64,链表转变为红⿊树,元素以内部 类Node节点存在

put步骤:

        1. 计算key的hash值,⼆次hash然后对数组⻓度取模,对应到数组下标

        2. 如果没有产⽣hash冲突(下标位置没有元素),则直接创建Node存⼊数组

         3. 如果产⽣hash冲突,先进⾏equal⽐较,相同则取代该元素,不同,则判断链表⾼度插⼊链表,链 表⾼度达到8,并且数组⻓度到64则转变为红⿊树,⻓度低于6则将红⿊树转回链表

        4. key为null,存在下标0的位置

4、谈谈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. 在转移元素时,先将原数组分组,将每组分给不同的线程来进⾏元素的转移,每个线程负责⼀组或 多组的元素转移⼯作

5、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资源

6、说⼀下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         

                        ⅰ. 如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去,在这个 过程中会判断红⿊树中是否存在当前key,如果存在则更新value

                        ⅱ. 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插 法插⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会 判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插⼊到链 表中,插⼊到链表后,会看当前链表的节点个数,如果⼤于等于8,那么则会将该链表转成 红⿊树

                        ⅲ. 将key和value封装为Node插⼊到链表或红⿊树中后,再判断是否需要进⾏扩容,如果需要 就扩容,如果不需要就结束PUT⽅法

7、说一下深拷⻉和浅拷⻉区别

        深拷⻉和浅拷⻉就是指对象的拷⻉,⼀个对象中存在两种类型的属性,⼀种是基本数据类型,⼀种是实 例对象的引⽤。

        1. 浅拷⻉是指,只会拷⻉基本数据类型的值,以及实例对象的引⽤地址,并不会复制⼀份引⽤地址所 指向的对象,也就是浅拷⻉出来的对象,内部的类属性指向的是同⼀个对象

        2. 深拷⻉是指,既会拷⻉基本数据类型的值,也会针对实例对象的引⽤地址所指向的对象进⾏复制, 深拷⻉出来的对象,内部的属性指向的不是同⼀个对象

8、HashMap的扩容机制原理

1.7版本

        1. 先⽣成新数组

        2. 遍历⽼数组中的每个位置上的链表上的每个元素

        3. 取每个元素的key,并基于新数组⻓度,计算出每个元素在新数组中的下标

        4. 将元素添加到新数组中去

        5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

1.8版本

        1. 先⽣成新数组         

        2. 遍历⽼数组中的每个位置上的链表或红⿊树

        3. 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去

        4. 如果是红⿊树,则先遍历红⿊树,先计算出红⿊树中每个元素对应在新数组中的下标位置                 a. 统计每个下标位置的元素个数

                b. 如果该位置下的元素个数超过了8,则⽣成⼀个新的红⿊树,并将根节点的添加到新数组的对应 位置

                c. 如果该位置下的元素个数没有超过8,那么则⽣成⼀个链表,并将链表的头节点添加到新数组的 对应位置

          5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

9、讲讲CopyOnWriteArrayList

        1. ⾸先CopyOnWriteArrayList内部也是⽤过数组来实现的,在向CopyOnWriteArrayList添加元素 时,会复制⼀个新的数组,写操作在新数组上进⾏,读操作在原数组上进⾏

        2. 写操作会加锁,防⽌出现并发写⼊丢失数据的问题

        3. 写操作结束之后会把原数组指向新数组

        4. CopyOnWriteArrayList允许在写操作时来读取数据,⼤⼤提⾼了读的性能,因此适合读多写少的应 ⽤场景,但是CopyOnWriteArrayList会⽐较占内存,同时可能读到的数据不是实时最新的数据,所 以不适合实时性要求很⾼的场景

10、说说类加载器双亲委派模型

        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进⾏加载,如果没加载到才由⾃⼰ 进⾏加载。

11、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()⽅法运⾏代价⾼昂,不确定性⼤,⽆法保证各个对象的调⽤顺序,不推荐使⽤,建议 遗忘它。

12、JVM中哪些是线程共享区

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

13、如何排查JVM问题

jmap jstat jstack jvisualvm jsisualvm  dump文件 

需要分析、推理、实践、总结、再分析,最终定位到具体的问题

14、⼀个对象从加载到JVM,再到被GC清除,都经历了什么过程?

        1. ⽤户创建⼀个对象,JVM⾸先需要到⽅法区去找对象的类型信息。然后再创建对象。

        2. JVM要实例化⼀个对象,⾸先要在堆当中先创建⼀个对象。-> 半初始化状态

        3. 对象⾸先会分配在堆内存中新⽣代的Eden。然后经过⼀次Minor GC,对象如果存活,就会进⼊S 区。在后续的每次GC中,如果对象⼀直存活,就会在S区来回拷⻉,每移动⼀次,年龄加1。-> 多 ⼤年龄才会移⼊⽼年代? 年龄最⼤15, 超过⼀定年龄后,对象转⼊⽼年代。

         4. 当⽅法执⾏结束后,栈中的指针会先移除掉。

        5. 堆中的对象,经过Full GC,就会被标记为垃圾,然后被GC线程清理掉。

15、JVM有哪些垃圾回收算法?

        1. MarkSweep 标记清除算法:这个算法分为两个阶段,标记阶段:把垃圾内存标记出来,清除阶 段:直接将垃圾内存回收。这种算法是⽐较简单的,但是有个很严重的问题,就是会产⽣⼤量的内 存碎⽚。

        2. Copying 拷⻉算法:为了解决标记清除算法的内存碎⽚问题,就产⽣了拷⻉算法。拷⻉算法将内存 分为⼤⼩相等的两半,每次只使⽤其中⼀半。垃圾回收时,将当前这⼀块的存活对象全部拷⻉到另 ⼀半,然后当前这⼀半内存就可以直接清除。这种算法没有内存碎⽚,但是他的问题就在于浪费空 间。⽽且,他的效率跟存货对象的个数有关。

        3. MarkCompack 标记压缩算法:为了解决拷⻉算法的缺陷,就提出了标记压缩算法。这种算法在标 记阶段跟标记清除算法是⼀样的,但是在完成标记之后,不是直接清理垃圾内存,⽽是将存活对象 往⼀端移动,然后将端边界以外的所有内存直接清除。

16、垃圾回收分为哪些阶段

G1GC:

        第⼀:初始标记 标记出GCRoot直接引⽤的对象。STW

        第⼆:标记Region,通过RSet标记出上⼀个阶段标记的Region引⽤到的Old区Region。

        第三:并发标记阶段:跟CMS的步骤是差不多的。只是遍历的范围不再是整个Old区,⽽只需要遍 历第⼆步标记出来的Region。

        第四:重新标记: 跟CMS中的重新标记过程是差不多的。

        第五:垃圾清理:与CMS不同的是,G1可以采⽤拷⻉算法,直接将整个Region中的对象拷⻉到另 ⼀个Region。⽽这个阶段,G1只选择垃圾较多的Region来清理,并不是完全清理。

17、线程的⽣命周期?线程有⼏种状态

线程通常有五种状态,创建,就绪,运⾏、阻塞和死亡状态:

        1. 新建状态(New):新创建了⼀个线程对象。

        2. 就绪状态(Runnable):线程对象创建后,其他线程调⽤了该对象的start⽅法。该状态的线程位于 可运⾏线程池中,变得可运⾏,等待获取CPU的使⽤权。

        3. 运⾏状态(Running):就绪状态的线程获取了CPU,执⾏程序代码。

        4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使⽤权,暂时停⽌运⾏。直到线程 进⼊就绪状态,才有机会转到运⾏状态。

        5. 死亡状态(Dead):线程执⾏完了或者因异常退出了run⽅法,该线程结束⽣命周期。

阻塞的情况⼜分为三种:

        1. 等待阻塞:运⾏的线程执⾏wait⽅法,该线程会释放占⽤的所有资源,JVM会把该线程放⼊“等待 池”中。进⼊这个状态后,是不能⾃动唤醒的,必须依靠其他线程调⽤notify或notifyAll⽅法才能被 唤醒,wait是object类的⽅法

        2. 同步阻塞:运⾏的线程在获取对象的同步锁时,若该同步锁被别的线程占⽤,则JVM会把该线程放 ⼊“锁池”中。

        3. 其他阻塞:运⾏的线程执⾏sleep或join⽅法,或者发出了I/O请求时,JVM会把该线程置为阻塞状 态。当sleep状态超时、join等待线程终⽌或者超时、或者I/O处理完毕时,线程重新转⼊就绪状 态。sleep是Thread类的⽅法

18、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经典的应⽤场景就是连接管理(⼀个线程持有⼀个连接,该连接对象可以在不同的⽅ 法之间进⾏传递,线程之间不共享同⼀个连接)

19、并发的特性

原⼦性:原⼦性是指在⼀个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执⾏完成,要 不都不执⾏。关键字:synchronized

可⻅性:当多个线程访问同⼀个变量时,⼀个线程修改了这个变量的值,其他线程能够⽴即看得到修改的值。关键字:volatile、synchronized、final

有序性:虚拟机在进⾏代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不⼀定会按 照我们写的代码的顺序来执⾏,有可能将他们重排序。实际上,对于有些代码进⾏重排序之后,虽然对 变量的值没有造成影响,但有可能会出现线程安全问题。关键字:volatile、synchronized

volatile本身就包含了禁⽌指令重排序的语义,⽽synchronized关键字是由“⼀个变量在同⼀时刻只允许 ⼀条线程对其进⾏lock操作”这条规则明确的。

synchronized关键字同时满⾜以上三种特性,但是volatile关键字不满⾜原⼦性。

在某些情况下,volatile的同步机制的性能确实要优于锁(使⽤synchronized关键字或 java.util.concurrent包⾥⾯的锁),因为volatile的总开销要⽐锁低。

我们判断使⽤volatile还是加锁的唯⼀依据就是volatile的语义能否满⾜使⽤的场景。

20、讲讲volatile关键字

被volatile修饰的共享变量对所有线程总是可⻅的,也就是当⼀个线程修改了⼀个被volatile修饰共 享变量的值,新值总是可以被其他线程⽴即得知。volatile禁⽌指令重排序优化。

21、为什么⽤线程池?解释下线程池参数?

        1、降低资源消耗;提⾼线程利⽤率,降低创建和销毁线程的消耗。

        2、提⾼响应速度;任务来了,直接有线程可⽤可执⾏,⽽不是先创建线程,再执⾏。

        3、提⾼线程的可管理性;线程是稀缺资源,使⽤线程池可以统⼀分配调优监控。

corePoolSize:代表核⼼线程数,也就是正常情况下创建⼯作的线程数,这些线程创建后并不 会消除,⽽是⼀种常驻线程

maxinumPoolSize:代表的是最⼤线程数,它与核⼼线程数相对应,表示最⼤允许被创建的线程 数,⽐如当前任务较多,将核⼼线程数都⽤完了,还⽆法满⾜需求时,此时就会创建新的线程,但 是线程池内线程总数不会超过最⼤线程数

keepAliveTime 、 unit:表示超出核⼼线程数之外的线程的空闲存活时间,也就是核⼼线程 不会消除,但是超出核⼼线程数的部分线程如果空闲⼀定的时间则会被消除,我们可以通过 setKeepAliveTime 来设置空闲时间

workQueue:⽤来存放待执⾏的任务,假设我们现在核⼼线程都已被使⽤,还有任务进来则全部 放⼊队列,直到整个队列被放满但任务还再持续进⼊则会开始创建新的线程

ThreadFactory:实际上是⼀个线程⼯⼚,⽤来⽣产线程执⾏任务。我们可以选择使⽤默认的创 建⼯⼚,产⽣的线程都在同⼀个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选 择⾃定义线程⼯⼚,⼀般我们会根据业务来制定不同的线程⼯⼚

Handler:任务拒绝策略,有两种情况,第⼀种是当我们调⽤ shutdown 等⽅法关闭线程池 后,这时候即使线程池内部还有没执⾏完的任务正在执⾏,但是由于线程池已经关闭,我们再继续 想线程池提交任务就会遭到拒绝。另⼀种情况就是当达到最⼤线程数,线程池已经没有能⼒继续处 理新提交的任务时,这是也就拒绝

22、线程池的底层⼯作原理

线程池内部是通过队列+线程实现的,当我们利⽤线程池执⾏任务时:

1. 如果此时线程池中的线程数量⼩于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建 新的线程来处理被添加的任务。

2. 如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放⼊ 缓冲队列。

3. 如果此时线程池中的线程数量⼤于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数 量⼩于maximumPoolSize,建新的线程来处理被添加的任务。

4. 如果此时线程池中的线程数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等 于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

5. 当线程池中的线程数量⼤于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被 终⽌。这样,线程池可以动态的调整池中的线程数

23、线程池空闲线程过期机制

keepAliveTime WorkQueue

24、公平锁和⾮公平锁区别

公平锁会先检查队列中是否存在线程在排队,如果有线程在排队, 则当前线程也进⾏排队。

⾮公平锁不会去检查是否有线程在排队,⽽是直接竞争锁。

当锁释放时,都是唤醒排在最前⾯的线 程,所以⾮公平锁只是体现在了线程加锁阶段,⽽没有体现在线程被唤醒阶段。

25、Sychronized的偏向锁、轻量级锁、重量级锁

1. 偏向锁:在锁对象的对象头中记录⼀下当前获取到该锁的线程ID,该线程下次如果⼜来获取该锁就 可以直接获取到了

2. 轻量级锁:由偏向锁升级⽽来,当⼀个线程获取到锁后,此时这把锁是偏向锁,此时如果有第⼆个 线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻 量级锁底层是通过⾃旋来实现的,并不会阻塞线程

3. 如果⾃旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞

4. ⾃旋锁:⾃旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就⽆所谓唤醒线程,阻塞和唤醒 这两个步骤都是需要操作系统去进⾏的,⽐较消耗时间,⾃旋锁是线程通过CAS获取预期的⼀个标 记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程⼀直在运 ⾏中,相对⽽⾔没有使⽤太多的操作系统资源,⽐较轻量。

26、Sychronized和ReentrantLock的区别

1. sychronized是⼀个关键字,ReentrantLock是⼀个类

2. sychronized会⾃动的加锁与释放锁,ReentrantLock需要程序员⼿动加锁与释放锁

3. sychronized的底层是JVM层⾯的锁,ReentrantLock是API层⾯的锁

4. sychronized是⾮公平锁,ReentrantLock可以选择公平锁或⾮公平锁

5. sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识 来标识锁的状态

6. sychronized底层有⼀个锁升级的过程

27、谈谈你对AQS的理解,AQS如何实现可重⼊锁?

1. AQS是⼀个JAVA线程同步的框架。是JDK中很多锁⼯具的核⼼实现框架。

2. 在AQS中,维护了⼀个信号量state和⼀个线程组成的双向链表队列。其中,这个线程队列,就是⽤ 来给线程排队的,⽽state就像是⼀个红绿灯,⽤来控制线程排队或者放⾏的。 在不同的场景下,有 Sychronized的偏向锁、轻量级锁、重量级锁 Sychronized和ReentrantLock的区别 谈谈你对AQS的理解,AQS如何实现可重⼊锁? 40 不⽤的意义。

3. 在可重⼊锁这个场景下,state就⽤来表示加锁的次数。0标识⽆锁,每加⼀次锁,state就加1。释 放锁state就减1。

28、synchronized是可重入锁吗?为什么?

是可重入锁。

可重入锁:当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。

在 java 内部,同一线程在调用自己类中其他 synchronized 方法/块或调用父类的 synchronized 方法/块都不会阻碍该线程的执行,所以synchronized是可重入锁。

重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

29、java的乐观锁和悲观锁

        Atomic开头类都是乐观锁。synchronized和lock是悲观锁。

        乐观锁:CAS算法、版本号。

30、谈谈Spring⽀持的⼏种bean的作⽤域。

singleton:默认,每个容器中只有⼀个bean的实例,单例的模式由BeanFactory⾃身来维护。该对 象的⽣命周期是与Spring IOC容器⼀致的(但在第⼀次被注⼊时才会创建)。

prototype:为每⼀个bean请求提供⼀个实例。在每次注⼊时都会创建⼀个新的对象

request:bean被定义为在每个HTTP请求中创建⼀个单例对象,也就是说在单个请求中都会复⽤这 ⼀个单例对象。

session:与request范围类似,确保每个session中有⼀个bean的实例,在session过期后,bean 会随之失效。

application:bean被定义为在ServletContext的⽣命周期中复⽤⼀个单例对象。

websocket:bean被定义为在websocket的⽣命周期中复⽤⼀个单例对象。

global-session:全局作⽤域,global-session和Portlet应⽤相关。当你的应⽤部署在Portlet容器 中⼯作时,它包含很多portlet。如果你想要声明让所有的portlet共⽤全局的存储变量的话,那么这 全局变量需要存储在global-session中。全局作⽤域与Servlet中的session作⽤域效果相同。

31、说下Spring事务的实现⽅式和原理以及隔离级别

在使⽤Spring框架时,可以有两种使⽤事务的⽅式,⼀种是编程式的,⼀种是申明式的, @Transactional注解就是申明式的。

⾸先,事务这个概念是数据库层⾯的,Spring只是基于数据库中的事务进⾏了扩展,以及提供了⼀些能 让程序员更加⽅便操作事务的⽅式。

⽐如我们可以通过在某个⽅法上增加@Transactional注解,就可以开启事务,这个⽅法中所有的sql都 会在⼀个事务中执⾏,统⼀成功或失败。

在⼀个⽅法上加了@Transactional注解后,Spring会基于这个类⽣成⼀个代理对象,会将这个代理对象 作为bean,当在使⽤这个代理对象的⽅法时,如果这个⽅法上存在@Transactional注解,那么代理逻辑 会先把事务的⾃动提交设置为false,然后再去执⾏原本的业务逻辑⽅法,如果执⾏业务逻辑⽅法没有出 现异常,那么代理逻辑中就会将事务进⾏提交,如果执⾏业务逻辑⽅法出现了异常,那么则会将事务进 ⾏回滚。

当然,针对哪些异常回滚事务是可以配置的,可以利⽤@Transactional注解中的rollbackFor属性进⾏配 置,默认情况下会对RuntimeException和Error进⾏回滚。

spring事务隔离级别就是数据库的隔离级别:外加⼀个默认级别

read uncommitted(未提交读)

read committed(提交读、不可重复读)

repeatable read(可重复读)

serializable(可串⾏化)

32、数据库的配置隔离级别是Read Commited,⽽Spring配置的隔离级别是Repeatable Read,请问这 时隔离级别是以哪⼀个为准?

以Spring配置的为准,如果spring设置的隔离级别数据库不⽀持,效果取决于数据库

33、Spring事务传播机制

多个事务⽅法相互调⽤时,事务如何在这些⽅法间传播,⽅法A是⼀个事务的⽅法,⽅法A执⾏过程中调 ⽤了⽅法B,那么⽅法B有⽆事务以及⽅法B对事务的要求不同都会对⽅法A的事务具体执⾏造成影响, 同时⽅法A的事务对⽅法B的事务执⾏也有影响,这种影响具体是什么就由两个⽅法所定义的事务传播类 型所决定。

1. REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则⾃⼰新建⼀个事务,如果当前存 在事务,则加⼊这个事务

2. SUPPORTS:当前存在事务,则加⼊当前事务,如果当前没有事务,就以⾮事务⽅法执⾏

3. MANDATORY:当前存在事务,则加⼊当前事务,如果当前事务不存在,则抛出异常。

4. REQUIRES_NEW:创建⼀个新事务,如果存在当前事务,则挂起该事务。

5. NOT_SUPPORTED:以⾮事务⽅式执⾏,如果当前存在事务,则挂起当前事务

6. NEVER:不使⽤事务,如果当前事务存在,则抛出异常

7. NESTED:如果当前事务存在,则在嵌套事务中执⾏,否则和REQUIRED的操作⼀样(开启⼀个事 务)

34、Spring事务什么时候会失效?

spring事务的原理是AOP,进⾏了切⾯增强,那么失效的根本原因是这个AOP不起作⽤了!常⻅情况有 如下⼏种:

1、发⽣⾃调⽤,类⾥⾯使⽤this调⽤本类的⽅法(this通常省略),此时这个this对象不是代理类,⽽ 是UserService对象本身! 解决⽅法很简单,让那个this变成UserService的代理类即可!

2、⽅法不是public的:@Transactional 只能⽤于 public 的⽅法上,否则事务不会失效,如果要⽤在⾮ public ⽅法上,可以开启 AspectJ 代理模式。

3、数据库不⽀持事务

4、没有被spring管理

5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)

35、什么是bean的⾃动装配,有哪些⽅式?

开启⾃动装配,只需要在xml配置⽂件中定义“autowire”属性。

autowire属性有五种装配的⽅式:

no – 缺省情况下,⾃动配置是通过“ref”属性⼿动设定 。

        ⼿动装配:以value或ref的⽅式明确指定属性值都是⼿动装配。

        需要通过‘ref’属性来连接bean。

byName-根据bean的属性名称进⾏⾃动装配。

byType-根据bean的类型进⾏⾃动装配。

constructor-类似byType,不过是应⽤于构造器的参数。如果⼀个bean与构造器参数的类型形 同,则进⾏⾃动装配,否则导致异常。

autodetect-如果有默认的构造器,则通过constructor⽅式进⾏⾃动装配,否则使⽤byType⽅式进 ⾏⾃动装配。

@Autowired⾃动装配bean,可以在字段、setter⽅法、构造函数上使⽤。

36、Spring中的Bean创建的⽣命周期有哪些步骤

Spring中⼀个Bean的创建⼤概分为以下⼏个步骤:

1. 推断构造⽅法

2. 实例化

3. 填充属性,也就是依赖注⼊

4. 处理Aware回调

5. 初始化前,处理@PostConstruct注解

6. 初始化,处理InitializingBean接⼝

7. 初始化后,进⾏AOP

37、ApplicationContext和BeanFactory有什么区别

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

38、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

39、Spring容器启动流程是怎样的

1. 在创建Spring容器,也就是启动Spring时:

2. ⾸先会进⾏扫描,扫描得到所有的BeanDefinition对象,并存在⼀个Map中

3. 然后筛选出⾮懒加载的单例BeanDefinition进⾏创建Bean,对于多例Bean不需要在启动过程中去进 ⾏创建,对于多例Bean会在每次获取Bean时利⽤BeanDefinition去创建

4. 利⽤BeanDefinition创建Bean就是Bean的创建⽣命周期,这期间包括了合并BeanDefinition、推断 构造⽅法、实例化、属性填充、初始化前、初始化、初始化后等步骤,其中AOP就是发⽣在初始化 后这⼀步骤中

5. 单例Bean创建完了之后,Spring会发布⼀个容器启动事件

6. Spring启动结束

在源码中会更复杂,⽐如源码中会提供⼀些模板⽅法,让⼦类来实现,⽐如源码中还涉及到⼀些 BeanFactoryPostProcessor和BeanPostProcessor的注册,Spring的扫描就是通过 BenaFactoryPostProcessor来实现的,依赖注⼊就是通过BeanPostProcessor来实现的。在Spring启动过程中还会去处理@Import等注解

40、Spring⽤到了哪些设计模式

1.工厂模式

        BeanFactory、FactoryBean

2.适配器模式

        AdvisorAdapter,对Advisor进行了适配

3.访问者模式

        PropertyAccessor接口、属性访问器,用来访问和设置某个对象的某个属性

4.装饰器模式

        BeanWrapper

5.代理模式

        AOP

6.观察者模式

        事件监听器

7.策略模式

        InstantiationStrategy,根据不同情况进行实例化

8.模板模式

        JdbcTemplate

9.委派模式

        BeanDefinitionParserDelegate

10.责任链模式

        BeanPostProcessor

41、Spring Boot ⾃动配置原理?

@Import + @Configuration + Spring spi

⾃动配置类由各个starter提供,使⽤@Configuration + @Bean定义配置类,放到METAINF/spring.factories下

使⽤Spring spi扫描META-INF/spring.factories下的配置类

使⽤@Import导⼊⾃动配置类

42、如何理解 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。

43、Spring Boot、Spring MVC 和 Spring 有什么区别

spring是⼀个IOC容器,⽤来管理Bean,使⽤依赖注⼊实现控制反转,可以很⽅便的整合各种框架,提 供AOP机制弥补OOP的代码重复问题、更⽅便将不同类不同⽅法中的共同处理抽取成切⾯、⾃动注⼊给 ⽅法执⾏,⽐如⽇志、异常等

springmvc是spring对web框架的⼀个解决⽅案,提供了⼀个总的前端控制器Servlet,⽤来接收请求, 然后定义了⼀套路由策略(url到handle的映射)及适配执⾏handle,将handle结果使⽤视图解析技术⽣ 成视图展现给前端

springboot是spring提供的⼀个快速开发⼯具包,让程序员能更⽅便、更快速的开发spring+springmvc 应⽤,简化了配置(约定了默认配置),整合了⼀系列的解决⽅案(starter机制)、redis、 mongodb、es,可以开箱即⽤

44、Spring Boot中常⽤注解及其底层实现

1. @SpringBootApplication注解:这个注解标识了⼀个SpringBoot⼯程,它实际上是另外三个注解的 组合,这三个注解是:

        a. @SpringBootConfiguration:这个注解实际就是⼀个@Configuration,表示启动类也是⼀个 配置类

        b. @EnableAutoConfiguration:向Spring容器中导⼊了⼀个Selector,⽤来加载ClassPath下 SpringFactories中所定义的⾃动配置类,将这些⾃动加载为配置Bean

        c. @ComponentScan:标识扫描路径,因为默认是没有配置实际扫描路径,所以SpringBoot扫 描的路径是启动类所在的当前⽬录

2. @Bean注解:⽤来定义Bean,类似于XML中的标签,Spring在启动时,会对加了@Bean注 解的⽅法进⾏解析,将⽅法的名字做为beanName,并通过执⾏⽅法得到bean对象

3. @Controller、@Service、@ResponseBody、@Autowired都可以说

45、Spring Boot是如何启动Tomcat的

1. ⾸先,SpringBoot在启动时会先创建⼀个Spring容器

2. 在创建Spring容器过程中,会利⽤@ConditionalOnClass技术来判断当前classpath中是否存在 Tomcat依赖,如果存在则会⽣成⼀个启动Tomcat的Bean

3. Spring容器创建完之后,就会获取启动Tomcat的Bean,并创建Tomcat对象,并绑定端⼝等,然后 启动Tomcat

46、Spring Boot中配置⽂件的加载顺序是怎样的?

优先级从⾼到低,⾼优先级的配置覆盖低优先级的配置,所有配置会形成互补配置。

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

47、MyBatis #{}和${}的区别是什么?

#{}是预编译处理、是占位符, ${}是字符串替换、是拼接符。

Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调⽤ PreparedStatement 来赋值;

Mybatis 在处理 {}替换成变量的值,调⽤ Statement 来赋值;#{} 的变量替换是在DBMS 中、变量替换后,

#{} 对应的变量⾃动加上单引号,{} 的变量替换是在 DBMS 外、变量替换后,{} 对应的变量不会加上单引号

使⽤#{}可以有效的防⽌ SQL 注⼊, 提⾼系统安全性。

48、简述 Mybatis 的插件运⾏原理,如何编写⼀个插件。

Mybatis 只⽀持针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种 接⼝的插件, Mybatis 使⽤ JDK 的动态代理, 为需要拦截的接⼝⽣成代理对象以实现接⼝⽅法拦截功 能, 每当执⾏这 4 种接⼝对象的⽅法时,就会进⼊拦截⽅法,具体就是 InvocationHandler 的 invoke() ⽅法, 拦截那些你指定需要拦截的⽅法。

编写插件: 实现 Mybatis 的 Interceptor 接⼝并复写 intercept()⽅法, 然后在给插件编写注解, 指定 要拦截哪⼀个接⼝的哪些⽅法即可, 在配置⽂件中配置编写的插件。

  1. @Intercepts({@Signature(type = StatementHandler.class, method = "query",
  2. args = {Statement.class, ResultHandler.class}),
  3. @Signature(type = StatementHandler.class, method = "update", args
  4. = {Statement.class}),
  5. @Signature(type = StatementHandler.class, method = "batch", args =
  6. { Statement.class })})
  7. @Component

invocation.proceed()执⾏具体的业务逻辑

49、索引的基本原理

索引⽤来快速地寻找那些具有特定值的记录。如果没有索引,⼀般来说执⾏查询时遍历整张表。

索引的原理:就是把⽆序的数据变成有序的查询

1. 把创建了索引的列的内容进⾏排序

2. 对排序结果⽣成倒排表

3. 在倒排表内容上拼上数据地址链

4. 在查询的时候,先拿到倒排表内容,再取出数据地址链,从⽽拿到具体数据

50、Mysql聚簇和⾮聚簇索引的区别

都是B+树的数据结构

         聚簇索引:将数据存储与索引放到了⼀块、并且是按照⼀定的顺序组织的,找到索引也就找到了数 据,数据的物理存放顺序与索引顺序是⼀致的,即:只要索引是相邻的,那么对应的数据⼀定也是 相邻地存放在磁盘上的

        ⾮聚簇索引:叶⼦节点不存储数据、存储的是数据⾏地址,也就是说根据索引查找到数据⾏的位置 再取磁盘查找数据,这个就有点类似⼀本树的⽬录,⽐如我们要找第三章第⼀节,那我们先在这个 ⽬录⾥⾯找,找到对应的⻚码后再去对应的⻚码看⽂章。

优势:

1、查询通过聚簇索引可以直接获取数据,相⽐⾮聚簇索引需要第⼆次查询(⾮覆盖索引的情况 下)效率要⾼

2、聚簇索引对于范围查询的效率很⾼,因为其数据是按照⼤⼩排列的

3、聚簇索引适合⽤在排序的场合,⾮聚簇索引不适合

劣势:

1、维护索引很昂贵,特别是插⼊新⾏或者主键被更新导⾄要分⻚(page split)的时候。建议在⼤量插⼊ 新⾏后,选在负载较低的时间段,通过OPTIMIZE TABLE优化表,因为必须被移动的⾏数据可能造成碎 ⽚。使⽤独享表空间可以弱化碎⽚

2、表因为使⽤UUId(随机ID)作为主键,使数据存储稀疏,这就会出现聚簇索引有可能有⽐全表扫⾯ 更慢,所以建议使⽤int的auto_increment作为主键

3、如果主键⽐较⼤的话,那辅助索引将会变的更⼤,因为辅助索引的叶⼦存储的是主键值;过⻓的主 键值,会导致⾮叶⼦节点占⽤占⽤更多的物理空间

InnoDB中⼀定有主键,主键⼀定是聚簇索引,不⼿动设置、则会使⽤unique索引,没有unique索引, 则会使⽤数据库内部的⼀个⾏的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要⼆次查找,⾮聚簇索引都是辅助索引,像复合索引、前缀索引、唯⼀索引, 辅助索引叶⼦节点存储的不再是⾏的物理位置,⽽是主键值

MyISM使⽤的是⾮聚簇索引,没有聚簇索引,⾮聚簇索引的两棵B+树看上去没什么不同,节点的结构完 全⼀致只是存储的内容不同⽽已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。 表数据存储在独⽴的地⽅,这两颗B+树的叶⼦节点都使⽤⼀个地址指向真正的表数据,对于表数据来 说,这两个键没有任何差别。由于索引树是独⽴的,通过辅助键检索⽆需访问主键的索引树。

如果涉及到⼤数据量的排序、全表扫描、count之类的操作的话,还是MyISAM占优势些,因为索引所占 空间⼩,这些操作是需要在内存中完成的。

51、索引设计的原则?

查询更快、占⽤空间更⼩

1. 适合索引的列是出现在where⼦句中的列,或者连接⼦句中指定的列

2. 基数较⼩的表,索引效果较差,没有必要在此列建⽴索引

3. 使⽤短索引,如果对⻓字符串列进⾏索引,应该指定⼀个前缀⻓度,这样能够节省⼤量索引空间, 如果搜索词超过索引前缀⻓度,则使⽤索引排除不匹配的⾏,然后检查其余⾏是否可能匹配。

4. 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进 ⾏更新甚⾄重构,索引列越多,这个时间就会越⻓。所以只保持需要的索引有利于查询即可。

5. 定义有外键的数据列⼀定要建⽴索引。

6. 更新频繁字段不适合创建索引

7. 若是不能有效区分数据的列不适合做索引列(如性别,男⼥未知,最多也就三种,区分度实在太低)

8. 尽量的扩展索引,不要新建索引。⽐如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修 改原来的索引即可。

9. 对于那些查询中很少涉及的列,重复值⽐较多的列不要建⽴索引。

10. 对于定义为text、image和bit的数据类型的列不要建⽴索引。

52、InnoDB存储引擎的锁的算法

Record lock:单个⾏记录上的锁

Gap lock:间隙锁,锁定⼀个范围,不包括记录本身

Next-key lock:record+gap 锁定⼀个范围,包含记录本身

相关知识点:

1. innodb对于⾏的查询使⽤next-key lock

2. Next-locking keying为了解决Phantom Problem幻读问题

3. 当查询的索引含有唯⼀属性时,将next-key lock降级为record key

4. Gap锁设计的⽬的是为了阻⽌多个事务将记录插⼊到同⼀范围内,⽽这会导致幻读问题的产⽣

5. 有两种⽅式显式关闭gap锁:(除了外键约束和唯⼀性检查外,其余情况仅使⽤record lock) A. 将 事务隔离级别设置为RC B. 将参数innodb_locks_unsafe_for_binlog设置为1

53、对慢查询是怎么优化的

查出必要的列、分析执行计划、优化索引、调整连接数、调整buffer pool、拆表(水平、垂直)

54、事务的基本特性和隔离级别

事务基本特性ACID分别是:

原⼦性:⼀个事务中的操作要么全部成功,要么全部失败。

⼀致性:数据库总是从⼀个⼀致性的状态转换到另外⼀个⼀致性的状态。⽐如A转账给B100块钱, 假设A只有90块,⽀付之前我们数据库⾥的数据都是符合约束的,但是如果事务执⾏成功了,我们的数据库 数据就破坏约束了,因此事务不能成功,这⾥我们说事务提供了⼀致性的保证

隔离性:⼀个事务的修改在最终提交前,对其他事务是不可⻅的。

持久性:⼀旦事务提交,所做的修改就会永久保存到数据库中。

隔离性有4个隔离级别,分别是:

read uncommit 读未提交,可能会读到其他事务未提交的数据,也叫做脏读。 ⽤户本来应该读取到id=1的⽤户age应该是10,结果读取到了其他事务还没有提交的事务,结果读 取结果age=20,这就是脏读。

read commit 读已提交,两次读取结果不⼀致,叫做不可重复读。 不可重复读解决了脏读的问题,他只会读取已经提交的事务。 ⽤户开启事务读取id=1⽤户,查询到age=10,再次读取发现结果=20,在同⼀个事务⾥同⼀个查询 读取到不同的结果叫做不可重复读。

repeatable read 可重复复读,这是mysql的默认级别,就是每次读取结果都⼀样,但是有可能产 ⽣幻读。

serializable 串⾏,⼀般是不会使⽤的,他会给每⼀⾏读取的数据加锁,会导致⼤量超时和锁竞争 的问题。

脏读(Drity Read):某个事务已更新⼀份数据,另⼀个事务在此时读取了同⼀份数据,由于某些原因, 前⼀个RollBack了操作,则后⼀个事务所读取的数据就会是不正确的。

不可重复读(Non-repeatable read):在⼀个事务的两次查询之中数据不⼀致,这可能是两次查询过程中 间插⼊了⼀个事务更新的原有的数据。

幻读(Phantom Read):在⼀个事务的两次查询中数据笔数不⼀致,例如有⼀个事务查询了⼏列(Row)数 据,⽽另⼀个事务却在此时插⼊了新的⼏列数据,先前的事务在接下来的查询中,就会发现有⼏列数据 是它先前所没有的。

55、ACID靠什么保证的

A原⼦性由undo log⽇志保证,它记录了需要回滚的⽇志信息,事务回滚时撤销已经执⾏成功的sql

C⼀致性由其他三⼤特性保证、程序代码要保证业务上的⼀致性

I隔离性由MVCC来保证

D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,宕机的时候可 以从redo log恢复

InnoDB redo log 写盘,InnoDB 事务进⼊ prepare 状态。 如果前⾯ prepare 成功,binlog 写盘,再继续将事务⽇志持久化到 binlog,如果持久化成功, 那么 InnoDB 事务则进⼊ commit 状态(在 redo log ⾥⾯写⼀个 commit 记录)

redolog的刷盘会在系统空闲时进⾏

56、什么是MVCC

多版本并发控制:读取数据时通过⼀种类似快照的⽅式将数据保存下来,这样读锁就和写锁不冲突了, 不同的事务session会看到⾃⼰特定版本的数据,版本链

MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下⼯作。其他两个隔离级别够和 MVCC不兼容, 因为 READ UNCOMMITTED 总是读取最新的数据⾏, ⽽不是符合当前事务版本的数据 ⾏。⽽ SERIALIZABLE 则会对所有读取的⾏都加锁。

聚簇索引记录中有两个必要的隐藏列:

trx_id:⽤来存储每次对某条聚簇索引记录进⾏修改的时候的事务id。

roll_pointer:每次对哪条聚簇索引记录有修改的时候,都会把⽼版本写⼊undo⽇志中。这个 roll_pointer就是存了⼀个指针,它指向这条聚簇索引记录的上⼀个版本的位置,通过它来获得上⼀个版 本的记录信息。(注意插⼊操作的undo⽇志没有这个属性,因为它没有⽼版本)

已提交读和可重复读的区别就在于它们⽣成ReadView的策略不同。

开始事务时创建readview,readView维护当前活动的事务id,即未提交的事务id,排序⽣成⼀个数组

访问数据,获取数据中的事务id(获取的是事务id最⼤的记录),对⽐readview:

如果在readview的左边(⽐readview都⼩),可以访问(在左边意味着该事务已经提交)

如果在readview的右边(⽐readview都⼤)或者就在readview中,不可以访问,获取roll_pointer,取 上⼀版本重新对⽐(在右边意味着,该事务在readview⽣成之后出现,在readview中意味着该事务还未 提交)

已提交读隔离级别下的事务在每次查询的开始都会⽣成⼀个独⽴的ReadView,⽽可重复读隔离级别则在 第⼀次读的时候⽣成⼀个ReadView,之后的读都复⽤之前的ReadView。

这就是Mysql的MVCC,通过版本链,实现多版本,可并发读-写,写-读。通过ReadView⽣成策略的不 同实现不同的隔离级别。

57、Mysql主从同步原理

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 的位置发起同步。

由于mysql默认的复制⽅式是异步的,主库把⽇志发送给从库后不关⼼从库是否已经处理,这样会产⽣ ⼀个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,⽇志就丢失了。由此产⽣两个 概念。

全同步复制

主库写⼊binlog后强制同步⽇志到从库,所有的从库都执⾏完成后才返回给客户端,但是很显然这个⽅ 式的话性能会受到严重影响。

半同步复制

和全同步不同的是,半同步复制的逻辑是这样,从库写⼊⽇志成功后返回ACK确认给主库,主库收到⾄ 少⼀个从库的确认就认为写操作完成。

58、简述MyISAM和InnoDB的区别

MyISAM:

不⽀持事务,但是每次查询都是原⼦的;

⽀持表级锁,即每次操作是对整个表加锁;

存储表的总⾏数;

⼀个MYISAM表有三个⽂件:索引⽂件、表结构⽂件、数据⽂件;

采⽤⾮聚集索引,索引⽂件的数据域存储指向数据⽂件的指针。辅索引与主索引基本⼀致,但是辅 索引不⽤保证唯⼀性。

InnoDb:

⽀持ACID的事务,⽀持事务的四种隔离级别;

⽀持⾏级锁及外键约束:因此可以⽀持写并发;

不存储总⾏数;

⼀个InnoDb引擎存储在⼀个⽂件空间(共享表空间,表⼤⼩不受操作系统控制,⼀个表可能分布在 多个⽂件⾥),也有可能为多个(设置为独⽴表空,表⼤⼩受操作系统⽂件⼤⼩限制,⼀般为 2G),受操作系统⽂件⼤⼩的限制;

主键索引采⽤聚集索引(索引的数据域存储数据⽂件本身),辅索引的数据域存储主键的值;因此 从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使⽤⾃增主键,防⽌插⼊ 数据时,为维持B+树结构,⽂件的⼤调整。

59、简述Mysql中索引类型及对数据库的性能影响

普通索引:允许被索引的数据列包含重复的值。

唯⼀索引:可以保证数据记录的唯⼀性。

主键:是⼀种特殊的唯⼀索引,在⼀张表中只能定义⼀个主键索引,主键⽤于唯⼀标识⼀条记录,使⽤ 关键字 PRIMARY KEY 来创建。

联合索引:索引可以覆盖多个数据列,如像INDEX(columnA, columnB)索引。

全⽂索引:通过建⽴ 倒排索引 ,可以极⼤的提升检索效率,解决判断字段是否包含的问题,是⽬前搜索 引擎使⽤的⼀种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全⽂索 引

索引可以极⼤的提⾼数据的查询速度。

通过使⽤索引,可以在查询的过程中,使⽤优化隐藏器,提⾼系统的性能。

但是会降低插⼊、删除、更新表的速度,因为在执⾏这些写操作时,还要操作索引⽂件

索引需要占物理空间,除了数据表占数据空间之外,每⼀个索引还要占⼀定的物理空间,如果要建⽴聚 簇索引,那么需要的空间就会更⼤,如果⾮聚集索引很多,⼀旦聚集索引改变,那么所有⾮聚集索引都 会跟着变。

60、Explain语句结果中各个字段分表表示什么

列名描述
id查询语句中每出现⼀个SELECT关键字,MySQL 就会为它分配⼀个唯⼀的id值,某些⼦查询会被 优化为join查询,那么出现的id会⼀样
select_typeSELECT关键字对应的那个查询的类型
table表名
partitions匹配的分区信息
type针对单表的查询⽅式(全表扫描、索引)
possible_keys可能⽤到的索引
key实际上使⽤的索引
key_len实际使⽤到的索引⻓度
ref当使⽤索引列等值查询时,与索引列进⾏等值匹 配的对象信息
rows预估的需要读取的记录条数
filtered某个表经过搜索条件过滤后剩余记录条数的百分 ⽐
Extra⼀些额外的信息,⽐如排序等

61、覆盖索引是什么

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

62、最左前缀原则是什么

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

63、Innodb是如何实现事务的

Innodb通过Buffer Pool,LogBuffer,Redo Log,Undo Log来实现事务,以⼀个update语句为例:

1. Innodb在收到⼀个update语句后,会先根据条件找到数据所在的⻚,并将该⻚缓存在Buffer Pool中

2. 执⾏update语句,修改Buffer Pool中的数据,也就是内存中的数据

3. 针对update语句⽣成⼀个RedoLog对象,并存⼊LogBuffer中

4. 针对update语句⽣成undolog⽇志,⽤于事务回滚

5. 如果事务提交,那么则把RedoLog对象进⾏持久化,后续还有其他机制将Buffer Pool中所修改的数 据⻚持久化到磁盘中

6. 如果事务回滚,则利⽤undolog⽇志进⾏回滚

64、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语句。

65、Mysql锁有哪些,如何理解

按锁粒度分类:

1. ⾏锁:锁某⾏数据,锁粒度最⼩,并发度⾼

2. 表锁:锁整张表,锁粒度最⼤,并发度低

3. 间隙锁:锁的是⼀个区间

还可以分为:

1. 共享锁:也就是读锁,⼀个事务给某⾏数据加了读锁,其他事务也可以读,但是不能写

2. 排它锁:也就是写锁,⼀个事务给某⾏数据加了写锁,其他事务不能读,也不能写

还可以分为:

1. 乐观锁:并不会真正的去锁某⾏记录,⽽是通过⼀个版本号来实现的

2. 悲观锁:上⾯所的⾏锁、表锁等都是悲观锁

在事务的隔离级别实现中,需要利⽤锁来解决幻读

66、Redis的持久化机制

RDB:Redis DataBase,在指定的时间间隔内将内存中的数据集快照写⼊磁盘,实际操作过程是fork⼀ 个⼦进程,先将数据集写⼊临时⽂件,写⼊成功后,再替换之前的⽂件,⽤⼆进制压缩存储。

⼿动触发:

save命令,使 Redis 处于阻塞状态,直到 RDB 持久化完成,才会响应其他客户端发来的命令,所 以在⽣产环境⼀定要慎⽤

bgsave命令,fork出⼀个⼦进程执⾏持久化,主进程只在fork过程中有短暂的阻塞,⼦进程创建 之后,主进程就可以响应客户端请求了

⾃动触发:

save m n :在 m 秒内,如果有 n 个键发⽣改变,则⾃动触发持久化,通过bgsave执⾏,如果设置 多个、只要满⾜其⼀就会触发,配置⽂件有默认配置(可以注释掉)

flushall:⽤于清空redis所有的数据库,flushdb清空当前redis所在库数据(默认是0号数据库),会 清空RDB⽂件,同时也会⽣成dump.rdb、内容为空

主从同步:全量同步时会⾃动触发bgsave命令,⽣成rdb发送给从节点

优点:

1. 整个Redis数据库将只包含⼀个⽂件 dump.rdb,⽅便持久化。

2. 容灾性好,⽅便备份。

3. 性能最⼤化,fork ⼦进程来完成写操作,让主进程继续处理命令,所以是 IO 最⼤化。使⽤单独⼦进 程来进⾏持久化,主进程不会进⾏任何 IO 操作,保证了 redis 的⾼性能

4. 相对于数据集⼤时,⽐ AOF 的启动效率更⾼。

缺点:

1. 数据安全性低。RDB 是间隔⼀段时间进⾏持久化,如果持久化之间 redis 发⽣故障,会发⽣数据丢 失。所以这种⽅式更适合数据要求不严谨的时候)

2. 由于RDB是通过fork⼦进程来协助完成数据持久化⼯作的,因此,如果当数据集较⼤时,可能会导 致整个服务器停⽌服务⼏百毫秒,甚⾄是1秒钟。

AOF:Append Only File,以⽇志的形式记录服务器所处理的每⼀个写、删除操作,查询操作不会记 录,以⽂本的⽅式记录,可以打开⽂件看到详细的操作记录

1. 所有的写命令会追加到 AOF 缓冲中。

2. AOF 缓冲区根据对应的策略向硬盘进⾏同步操作。

3. 随着 AOF ⽂件越来越⼤,需要定期对 AOF ⽂件进⾏重写,达到压缩的⽬的。

4. 当 Redis 重启时,可以加载 AOF ⽂件进⾏数据恢复。

同步策略:

每秒同步:异步完成,效率⾮常⾼,⼀旦系统出现宕机现象,那么这⼀秒钟之内修改的数据将会丢 失

每修改同步:同步持久化,每次发⽣的数据变化都会被⽴即记录到磁盘中,最多丢⼀条

不同步:由操作 系统控制,可能丢失较多数据

优点:

1. 数据安全,Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也 是异步完成的,其效率也是⾮常⾼的,所差的是⼀旦系统出现宕机现象,那么这⼀秒钟之内修改的 数据将会丢失。⽽每修改同步,我们可以将其视为同步持久化,即每次发⽣的数据变化都会被⽴即 记录到磁盘中。。

2. 通过 append 模式写⽂件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过 redischeck-aof ⼯具解决数据⼀致性问题。

3. AOF 机制的 rewrite 模式。定期对AOF⽂件进⾏重写,以达到压缩的⽬的

缺点:

1. AOF ⽂件⽐ RDB ⽂件⼤,且恢复速度慢。

2. 数据集⼤的时候,⽐ rdb 启动效率低。

3. 运⾏效率没有RDB⾼

对⽐:

AOF⽂件⽐RDB更新频率⾼,优先使⽤AOF还原数据,AOF⽐RDB更安全也更⼤,RDB性能⽐AOF好, 如果两个都配了优先加载AOF。

67、Redis的过期键的删除策略

Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当 Redis中缓存的key过期了,Redis如何处理。

惰性过期:只有当访问⼀个key时,才会判断该key是否已过期,过期则清除。该策略可以最⼤化地 节省CPU资源,却对内存⾮常不友好。极端情况可能出现⼤量的过期key没有再次被访问,从⽽不 会被清除,占⽤⼤量内存。

定期过期:每隔⼀定的时间,会扫描⼀定数量的数据库的expires字典中⼀定数量的key,并清除其 中已过期的key。该策略是⼀个折中⽅案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可 以在不同情况下使得CPU和内存资源达到最优的平衡效果。

(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的 指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有 键。)

Redis中同时使⽤了惰性过期和定期过期两种过期策略。

68、Redis线程模型、单线程快的原因

Redis基于Reactor模式开发了⽹络事件处理器,这个处理器叫做⽂件事件处理器 file event handler。这 个⽂件事件处理器,它是单线程的,所以 Redis 才叫做单线程的模型,它采⽤IO多路复⽤机制来同时监 听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现⾼性能的 ⽹络通信模型,⼜可以跟内部其他单线程的模块进⾏对接,保证了 Redis 内部的线程模型的简单性。

⽂件事件处理器的结构包含4个部分:多个Socket、IO多路复⽤程序、⽂件事件分派器以及事件处理器 (命令请求处理器、命令回复处理器、连接应答处理器等)。

多个 Socket 可能并发的产⽣不同的操作,每个操作对应不同的⽂件事件,但是IO多路复⽤程序会监听 多个 Socket,会将 Socket 放⼊⼀个队列中排队,每次从队列中取出⼀个 Socket 给事件分派器,事件 分派器把 Socket 给对应的事件处理器。

然后⼀个 Socket 的事件处理完之后,IO多路复⽤程序才会将队列中的下⼀个 Socket 给事件分派器。⽂ 件事件分派器会根据每个 Socket 当前产⽣的事件,来选择对应的事件处理器来处理。

1. Redis启动初始化时,将连接应答处理器跟AE_READABLE事件关联。

2. 若⼀个客户端发起连接,会产⽣⼀个AE_READABLE事件,然后由连接应答处理器负责和客户端建 ⽴ 连接,创建客户端对应的socket,同时将这个socket的AE_READABLE事件和命令请求处理 器关联,使 得客户端可以向主服务器发送命令请求。

3. 当客户端向Redis发请求时(不管读还是写请求),客户端socket都会产⽣⼀个AE_READABLE事 件,触发命令请求处理器。处理器读取客户端的命令内容, 然后传给相关程序执⾏。

4. 当Redis服务器准备好给客户端的响应数据后,会将socket的AE_WRITABLE事件和命令回复处理器 关联,当客户端准备好读取响应数据时,会在socket产⽣⼀个AE_WRITABLE事件,由对应命令回 复处 理器处理,即将准备好的响应数据写⼊socket,供客户端读取。

5. 命令回复处理器全部写完到 socket 后,就会删除该socket的AE_WRITABLE事件和命令回复处理器 的映射。

单线程快的原因:

1)纯内存操作

2)核⼼是基于⾮阻塞的IO多路复⽤机制

3)单线程反⽽避免了多线程的频繁上下⽂切换带来的性能问题

69、Redis有哪些数据结构?分别有哪些典型的应⽤场景?

Redis的数据结构有:

1. 字符串:可以⽤来做最简单的数据,可以缓存某个简单的字符串,也可以缓存某个json格式的字符 串,Redis分布式锁的实现就利⽤了这种数据结构,还包括可以实现计数器、Session共享、分布式 ID

2. 哈希表:可以⽤来存储⼀些key-value对,更适合⽤来存储对象

3. 列表:Redis的列表通过命令的组合,既可以当做栈,也可以当做队列来使⽤,可以⽤来缓存类似微 信公众号、微博等消息流数据

4. 集合:和列表类似,也可以存储多个元素,但是不能重复,集合可以进⾏交集、并集、差集操作, 从⽽可以实现类似,我和某⼈共同关注的⼈、朋友圈点赞等功能

5. 有序集合:集合是⽆序的,有序集合可以设置顺序,可以⽤来实现排⾏榜功能

70、Redis分布式锁底层是如何实现的?

1. ⾸先利⽤setnx来保证:如果key不存在才能获取到锁,如果key存在,则获取不到锁

2. 然后还要利⽤lua脚本来保证多个redis操作的原⼦性

3. 同时还要考虑到锁过期,所以需要额外的⼀个看⻔狗定时任务来监听锁是否需要续约

4. 同时还要考虑到redis节点挂掉后的情况,所以需要采⽤红锁的⽅式来同时向N/2+1个节点申请锁, 都申请到了才证明获取锁成功,这样就算其中某个redis节点挂掉了,锁也不能被其他客户端获取到

71、缓存穿透、缓存击穿、缓存雪崩分别是什么

缓存雪崩:缓存同⼀时间⼤⾯积的失效,所以,后⾯的请求都会落到数据库上,造成数据库短时间内 承受⼤量请求⽽崩掉。

解决⽅案:

缓存数据的过期时间设置随机,防⽌同⼀时间⼤量数据过期现象发⽣。

给每⼀个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓 存。

缓存预热互斥锁

缓存穿透:缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承 受⼤量请求⽽崩掉。

解决⽅案:

接⼝层增加校验,如⽤户鉴权校验,id做基础校验,id<=0的直接拦截;

从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有 效时间可以设置短点,如30秒(设置太⻓会导致正常情况也没法使⽤)。这样可以防⽌攻击⽤户 反 复⽤同⼀个id暴⼒攻击

采⽤布隆过滤器,将所有可能存在的数据哈希到⼀个⾜够⼤的 bitmap 中,⼀个⼀定不存在的数据 会被这个 bitmap 拦截掉,从⽽避免了对底层存储系统的查询压⼒

缓存击穿是指缓存中没有但数据库中有的数据(⼀般是缓存时间到期),这时由于并发⽤户特别多,同 时读缓存没读到数据,⼜同时去数据库去取数据,引起数据库压⼒瞬间增⼤,造成过⼤压⼒。和缓存雪 崩不同的是,缓存击穿指并发查同⼀条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从⽽ 查 数据库。

解决⽅案: 设置热点数据永远不过期。加互斥锁

72、Redis和Mysql如何保证数据⼀致

先更新数据库再删除缓存

73、简述Redis事务实现

事务开始:MULTI命令的执⾏,标识着⼀个事务的开始。MULTI命令会将客户端状态的 flags属性中 打开REDIS_MULTI标识来完成的。

命令⼊队:当⼀个客户端切换到事务状态之后,服务器会根据这个客户端发送来的命令来执⾏不同 的操作。如果客 户端发送的命令为MULTI、EXEC、WATCH、DISCARD中的⼀个,⽴即执⾏这个 命令,否则将命令放⼊⼀ 个事务队列⾥⾯,然后向客户端返回QUEUED回复,如果客户端发送的 命令为 EXEC、DISCARD、WATCH、MULTI 四个命令的其中⼀个,那么服务器⽴即执⾏这个命 令。如果客户端发送的是四个命令以外的其他命令,那么服务器并不⽴即执⾏这个命令。⾸先检查 此命令的格式是否正确,如果不正确,服务器会在客户端状态(redisClient)的 flags 属性关闭 REDIS_MULTI 标识,并且返回错误信息给客户端。如果正确,将这个命令放⼊⼀个事务队列⾥ ⾯,然后向客户端返回 QUEUED 回复事务队列是按照FIFO的⽅式保存⼊队的命令

事务执⾏:客户端发送 EXEC 命令,服务器执⾏ EXEC 命令逻辑。如果客户端状态的 flags 属性不 包含 REDIS_MULTI 标识,或者包含 REDIS_DIRTY_CAS 或者REDIS_DIRTY_EXEC 标识,那么就 直接取消事务的执⾏。 否则客户端处于事务状态(flags有 REDIS_MULTI 标识),服务器会遍历 客户端的事务队列,然后执⾏事务队列中的所有命令,最后将返回结果全部返回给客户端;Redis不 ⽀持事务回滚机制,但是它会检查每⼀个事务中的命令是否错误。Redis事务不⽀持检查那些程序员 ⾃⼰逻辑错误。例如对 String 类型的数据库键执⾏对 HashMap 类型的操作!

74、简述CAP定理

CAP理论是分布式领域中⾮常重要的⼀个指导理论,C(Consistency)表示强⼀致性,A (Availability)表示可⽤性,P(Partition Tolerance)表示分区容错性,CAP理论指出在⽬前的硬件 条件下,⼀个分布式系统是必须要保证分区容错性的,⽽在这个前提下,分布式系统要么保证CP,要么 保证AP,⽆法同时保证CAP。

分区容错性表示,⼀个系统虽然是分布式的,但是对外看上去应该是⼀个整体,不能由于分布式系统内 部的某个结点挂点,或⽹络出现了故障,⽽导致系统对外出现异常。所以,对于分布式系统⽽⾔是⼀定 要保证分区容错性的。

强⼀致性表示,⼀个分布式系统中各个结点之间能及时的同步数据,在数据同步过程中,是不能对外提 供服务的,不然就会造成数据不⼀致,所以强⼀致性和可⽤性是不能同时满⾜的。

可⽤性表示,⼀个分布式系统对外要保证可⽤。

 75、简述BASE理论

由于不能同时满⾜CAP,所以出现了BASE理论:

1. BA:Basically Available,表示基本可⽤,表示可以允许⼀定程度的不可⽤,⽐如由于系统故障, 请求时间变⻓,或者由于系统故障导致部分⾮核⼼功能不可⽤,都是允许的

2. S:Soft state:表示分布式系统可以处于⼀种中间状态,⽐如数据正在同步

3. E:Eventually consistent,表示最终⼀致性,不要求分布式系统数据实时达到⼀致,允许在经过⼀ 段时间后再达到⼀致,在达到⼀致过程中,系统也是可⽤的

76、什么是RPC

RPC,表示远程过程调⽤,对于Java这种⾯试对象语⾔,也可以理解为远程⽅法调⽤,RPC调⽤和 HTTP调⽤是有区别的,RPC表示的是⼀种调⽤远程⽅法的⽅式,可以使⽤HTTP协议、或直接基于TCP 协议来实现RPC,在Java中,我们可以通过直接使⽤某个服务接⼝的代理对象来执⾏⽅法,⽽底层则通 过构造HTTP请求来调⽤远端的⽅法,所以,有⼀种说法是RPC协议是HTTP协议之上的⼀种协议,也是 可以理解的。

77、数据⼀致性模型有哪些

强⼀致性:当更新操作完成之后,任何多个后续进程的访问都会返回最新的更新过的值,这种是对 ⽤户 最友好的,就是⽤户上⼀次写什么,下⼀次就保证能读到什么。根据 CAP理论,这种实现需 要牺牲可⽤性。

弱⼀致性:系统在数据写⼊成功之后,不承诺⽴即可以读到最新写⼊的值,也不会具体的承诺多久 之后 可以读到。⽤户读到某⼀操作对系统数据的更新需要⼀段时间,我们称这段时间为“不⼀致性 窗⼝”。

最终⼀致性:最终⼀致性是弱⼀致性的特例,强调的是所有的数据副本,在经过⼀段时间的同步之 后, 最终都能够达到⼀个⼀致的状态。因此,最终⼀致性的本质是需要系统保证最终数据能够达到 ⼀致,⽽ 不需要实时保证系统数据的强⼀致性。到达最终⼀致性的时间 ,就是不⼀致窗⼝时间, 在没有故障发⽣的前提下,不⼀致窗⼝的时间主要受通信延迟,系统负载和复制副本的个数影响。 最终⼀致性模型根据其提供的不同保证可以划分为更多的模型,包括因果⼀致性和会话⼀致性等。

78、分布式ID是什么?有哪些解决⽅案?

在开发中,我们通常会需要⼀个唯⼀ID来标识数据,如果是单体架构,我们可以通过数据库的主键,或 直接在内存中维护⼀个⾃增数字来作为ID都是可以的,但对于⼀个分布式系统,就会有可能会出现ID冲 突,此时有以下解决⽅案:

1. uuid,这种⽅案复杂度最低,但是会影响存储空间和性能

2. 利⽤单机数据库的⾃增主键,作为分布式ID的⽣成器,复杂度适中,ID⻓度较之uuid更短,但是受 到单机数据库性能的限制,并发量⼤的时候,此⽅案也不是最优⽅案

3. 利⽤redis、zookeeper的特性来⽣成id,⽐如redis的⾃增命令、zookeeper的顺序节点,这种⽅案 和单机数据库(mysql)相⽐,性能有所提⾼,可以适当选⽤

4. 雪花算法,⼀切问题如果能直接⽤算法解决,那就是最合适的,利⽤雪花算法也可以⽣成分布式 ID,底层原理就是通过某台机器在某⼀毫秒内对某⼀个数字⾃增,这种⽅案也能保证分布式架构中 的系统id唯⼀,但是只能保证趋势递增。业界存在tinyid、leaf等开源中间件实现了雪花算法。

79、什么是分布式事务?有哪些实现⽅案?

在分布式系统中,⼀次业务处理可能需要多个应⽤来实现,⽐如⽤户发送⼀次下单请求,就涉及到订单 系统创建订单、库存系统减库存,⽽对于⼀次下单,订单创建与减库存应该是要同时成功或同时失败,但在分布式系统中,如果不做处理,就很有可能出现订单创建成功,但是减库存失败,那么解决这 类问题,就需要⽤到分布式事务。常⽤解决⽅案有:

1. 本地消息表:创建订单时,将减库存消息加⼊在本地事务中,⼀起提交到数据库存⼊本地消息表, 然后调⽤库存系统,如果调⽤成功则修改本地消息状态为成功,如果调⽤库存系统失败,则由后台 定时任务从本地消息表中取出未成功的消息,重试调⽤库存系统

2. 消息队列:⽬前RocketMQ中⽀持事务消息,它的⼯作原理是: a. ⽣产者订单系统先发送⼀条half消息到Broker,half消息对消费者⽽⾔是不可⻅的 b. 再创建订单,根据创建订单成功与否,向Broker发送commit或rollback c. 并且⽣产者订单系统还可以提供Broker回调接⼝,当Broker发现⼀段时间half消息没有收到任 何操作命令,则会主动调此接⼝来查询订单是否创建成功 d. ⼀旦half消息commit了,消费者库存系统就会来消费,如果消费成功,则消息销毁,分布式事 务成功结束 e. 如果消费失败,则根据重试策略进⾏重试,最后还失败则进⼊死信队列,等待进⼀步处理

3. Seata:阿⾥开源的分布式事务框架,⽀持AT、TCC等多种模式,底层都是基于两阶段提交理论来 实现的

80、Dubbo的架构设计是怎样的?

Dubbo中的架构设计是⾮常优秀的,分为了很多层次,并且每层都是可以扩展的,⽐如:

1. Proxy服务代理层,⽀持JDK动态代理、javassist等代理机制

2. Registry注册中⼼层,⽀持Zookeeper、Redis等作为注册中⼼

3. Protocol远程调⽤层,⽀持Dubbo、Http等调⽤协议

4. Transport⽹络传输层,⽀持netty、mina等⽹络传输框架

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

81、Dubbo⽀持哪些负载均衡策略

1. 随机:从多个服务提供者随机选择⼀个来处理本次请求,调⽤量越⼤则分布越均匀,并⽀持按权重 设置随机概率

2. 轮询:依次选择服务提供者来处理请求, 并⽀持按权重进⾏轮询,底层采⽤的是平滑加权轮询算法

3. 最⼩活跃调⽤数:统计服务提供者当前正在处理的请求,下次请求过来则交给活跃数最⼩的服务器 来处理

4. ⼀致性哈希:相同参数的请求总是发到同⼀个服务提供者

82、讲一下Dubbo服务调用过程

1. 服务提供者在启动时,向注册中心注册自己提供的服务。
2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

83、负载均衡算法有哪些

1、轮询法:将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每⼀台服务器,⽽不关⼼ 服务器实际的连接数和当前的系统负载。

2、随机法:通过系统的随机算法,根据后端服务器的列表⼤⼩值来随机选取其中的⼀台服务器进⾏访 问。由概率统计理论可以得知,随着客户端调⽤服务端的次数增多,其实际效果越来越接近于平均分配调⽤量到后端的每⼀台服务器,也就是轮询的结果。

3、源地址哈希法:源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的⼀个数值, ⽤该数值对服务器列表的⼤⼩进⾏取模运算,得到的结果便是客服端要访问服务器的序号。采⽤源地址 哈希法进⾏负载均衡,同⼀IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同⼀台后端 服务器进⾏访问。

4、加权轮询法:不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能⼒ 也不相同。给配置⾼、负载低的机器配置更⾼的权重,让其处理更多的请;⽽配置低、负载⾼的机器, 给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这⼀问题,并将请求顺序且按照权重分 配到后端。

5、加权随机法:与加权轮询法⼀样,加权随机法也根据后端机器的配置,系统的负载分配不同的权 重。不同的是,它是按照权重随机请求后端服务器,⽽⾮顺序。

6、最⼩连接数法:最⼩连接数算法⽐较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处 理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的⼀台服务器 来处理当前的请求,尽可能地提⾼后端服务的利⽤效率,将负责合理地分流到每⼀台服务器。

84、分布式架构下如何实现Session 共享

1、采⽤⽆状态服务,抛弃session

2、存⼊cookie(有安全⻛险)

3、服务器之间进⾏ Session 同步,这样可以保证每个服务器上都有全部的 Session 信息,不过当服务 器数量⽐较多的时候,同步是会有延迟甚⾄同步失败;

4、 IP 绑定策略

使⽤ Nginx (或其他复杂均衡软硬件)中的 IP 绑定策略,同⼀个 IP 只能在指定的同⼀个机器访问,但 是这样做失去了负载均衡的意义,当挂掉⼀台服务器的时候,会影响⼀批⽤户的使⽤,⻛险很⼤;

5、使⽤ Redis 存储

把 Session 放到 Redis 中存储,虽然架构上变得复杂,并且需要多访问⼀次 Redis ,但是这种⽅案带来 的好处也是很⼤的:

实现了 Session 共享;

可以⽔平扩展(增加 Redis 服务器);

服务器重启 Session 不丢失(不过也要注意 Session 在 Redis 中的刷新/失效机制);

不仅可以跨服务器 Session 共享,甚⾄可以跨平台(例如⽹⻚端和 APP 端)。

85、简述你对RPC、RMI的理解

RPC:在本地调⽤远程的函数,远程过程调⽤,可以跨语⾔实现 httpClient

RMI:远程⽅法调⽤,java中⽤于实现RPC的⼀种机制,RPC的java版本,是J2EE的⽹络调⽤机制,跨 JVM调⽤对象的⽅法,⾯向对象的思维⽅式

直接或间接实现接⼝ java.rmi.Remote 成为存在于服务器端的远程对象,供客户端访问并提供⼀定的 服务

远程对象必须实现java.rmi.server.UniCastRemoteObject类,这样才能保证客户端访问获得远程对象 时,该远程对象将会把⾃身的⼀个拷⻉以Socket的形式传输给客户端,此时客户端所获得的这个拷⻉称 为“存根”,⽽服务器端本身已存在的远程对象则称之为“⻣架”。其实此时的存根是客户端的⼀个代理, ⽤于与服务器端的通信,⽽⻣架也可认为是服务器端的⼀个代理,⽤于接收客户端的请求之后调⽤远程 ⽅法来响应客户端的请求。

86、如何实现接⼝的幂等性

1、唯⼀id。每次操作,都根据操作和内容⽣成唯⼀的id,在执⾏之前先判断id是否存在,如果不存在则 执⾏后续操作,并且保存到数据库或者redis等。

2、服务端提供发送token的接⼝,业务调⽤接⼝前先获取token,然后调⽤业务接⼝请求时,把token携 带过去,务器判断token是否存在redis中,存在表示第⼀次请求,可以继续执⾏业务,执⾏业务完成 后,最后需要把redis中的token删除

3、建去重表。将业务中有唯⼀标识的字段保存到去重表,如果表中存在,则表示已经处理过了

4、版本控制。增加版本号,当版本号符合时,才能更新数据

5、状态控制。例如订单有状态已⽀付 未⽀付 ⽀付中 ⽀付失败,当处于未⽀付的时候才允许修改为⽀ 付中等

87、Zookeeper的数据模型和节点类型

数据模型:树形结构

zk维护的数据主要有:客户端的会话(session)状态及数据节点(dataNode)信息。

zk在内存中构造了个DataTree的数据结构,维护着path到dataNode的映射以及dataNode间的树状层级 关系。为了提⾼读取性能,集群中每个服务节点都是将数据全量存储在内存中。所以,zk最适于读多写 少且轻量级数据的应⽤场景。

数据仅存储在内存是很不安全的,zk采⽤事务⽇志⽂件及快照⽂件的⽅案来落盘数据,保障数据在不丢 失的情况下能快速恢复。

树中的每个节点被称为— Znode

Znode 兼具⽂件和⽬录两种特点。可以做路径标识,也可以存储数据,并可以具有⼦ Znode。具有增、 删、改、查等操作。

Znode 具有原⼦性操作,读操作将获取与节点相关的所有数据,写操作也将 替换掉节点的所有数据。另 外,每⼀个节点都拥有⾃⼰的 ACL(访问控制列 表),这个列表规定了⽤户的权限,即限定了特定⽤户对 ⽬标节点可以执⾏的操作

Znode 存储数据⼤⼩有限制。每个 Znode 的数据⼤⼩⾄多 1M,常规使⽤中应该远⼩于此值。

Znode 通过路径引⽤,如同 Unix 中的⽂件路径。路径必须是绝对的,因此他们必须由斜杠字符来开 头。除此以外,他们必须是唯⼀的,也就是说每⼀个路径只有⼀个表示,因此这些路径不能改变。在 ZooKeeper 中,路径由 Unicode 字符串组成,并且有⼀些限制。字符串"/zookeeper"⽤以保存管理信 息,⽐如关键配额信息。

持久节点:⼀旦创建、该数据节点会⼀直存储在zk服务器上、即使创建该节点的客户端与服务端的会话 关闭了、该节点也不会被删除

临时节点:当创建该节点的客户端会话因超时或发⽣异常⽽关闭时、该节点也相应的在zk上被删除 。

有序节点:不是⼀种单独种类的节点、⽽是在持久节点和临时节点的基础上、增加了⼀个节点有序的性 质 。

88、简述zk的命名服务、配置管理、集群管理

命名服务:

通过指定的名字来获取资源或者服务地址。Zookeeper可以创建⼀个全局唯⼀的路径,这个路径就可以 作为⼀个名字。被命名的实体可以是集群中的机器,服务的地址,或者是远程的对象等。⼀些分布式服 务框架(RPC、RMI)中的服务地址列表,通过使⽤命名服务,客户端应⽤能够根据特定的名字来获取 资源的实体、服务地址和提供者信息等

配置管理:

实际项⽬开发中,经常使⽤.properties或者xml需要配置很多信息,如数据库连接信息、fps地址端⼝等等。程序分布式部署时,如果把程序的这些配置信息保存在zk的znode节点下,当你要修改配置,即znode会发⽣变化时,可以通过改变zk中某个⽬录节点的内容,利⽤watcher通知给各个客户端,从⽽ 更改配置。

集群管理:

集群管理包括集群监控和集群控制,就是监控集群机器状态,剔除机器和加⼊机器。zookeeper可以⽅ 便集群机器的管理,它可以实时监控znode节点的变化,⼀旦发现有机器挂了,该机器就会与zk断开连 接,对应的临时⽬录节点会被删除,其他所有机器都收到通知。新机器加⼊也是类似。

89、讲下Zookeeper中的watch机制

客户端可以通过在znode上设置watch,实现实时监听znode的变化

Watch事件是⼀个⼀次性的触发器,当被设置了Watch的数据发⽣了改变的时候,则服务器将这个改变 发送给设置了Watch的客户端。

⽗节点的创建,修改,删除都会触发Watcher事件。

⼦节点的创建,删除会触发Watcher事件。

⼀次性:⼀旦被触发就会移除,再次使⽤需要重新注册,因为每次变动都需要通知所有客户端,⼀次性 可以减轻压⼒,3.6.0默认持久递归,可以触发多次

轻量:只通知发⽣了事件,不会告知事件内容,减轻服务器和带宽压⼒

Watcher 机制包括三个⻆⾊:客户端线程、客户端的 WatchManager 以及 ZooKeeper 服务器

1. 客户端向 ZooKeeper 服务器注册⼀个 Watcher 监听 

2. 把这个监听信息存储到客户端的 WatchManager 中

3. 当 ZooKeeper 中的节点发⽣变化时,会通知客户端,客户端会调⽤相应 Watcher 对象中的回调⽅ 法。watch回调是串⾏同步。

90、Zookeeper和Eureka的区别

zk:CP设计(强⼀致性),⽬标是⼀个分布式的协调系统,⽤于进⾏资源的统⼀管理。当节点crash后,需要进⾏leader的选举,在这个期间内,zk服务是不可⽤的。

eureka:AP设计(⾼可⽤),⽬标是⼀个服务注册发现系统,专⻔⽤于微服务的服务发现注册。

Eureka各个节点都是平等的,⼏个节点挂掉不会影响正常节点的⼯作,剩余的节点依然可以提供注册和 查询服务。⽽Eureka的客户端在向某个Eureka注册时如果发现连接失败,会⾃动切换⾄其他节点,只要 有⼀台Eureka还在,就能保证注册服务可⽤(保证可⽤性),只不过查到的信息可能不是最新的(不保 证强⼀致性)

同时当eureka的服务端发现85%以上的服务都没有⼼跳的话,它就会认为⾃⼰的⽹络出了问题,就不会 从服务列表中删除这些失去⼼跳的服务,同时eureka的客户端也会缓存服务信息。eureka对于服务注册 发现来说是⾮常好的选择。

91、如何实现分库分表

将原本存储于单个数据库上的数据拆分到多个数据库,把原来存储在单张数据表的数据拆分到多张数据 表中,实现数据切分,从⽽提升数据库操作性能。分库分表的实现可以分为两种⽅式:垂直切分和⽔平 切分。

⽔平:将数据分散到多张表,涉及分区键,

分库:每个库结构⼀样,数据不⼀样,没有交集。库多了可以缓解io和cpu压⼒

分表:每个表结构⼀样,数据不⼀样,没有交集。表数量减少可以提⾼sql执⾏效率、减轻cpu压⼒

垂直:将字段拆分为多张表,需要⼀定的重构

分库:每个库结构、数据都不⼀样,所有库的并集为全量数据

分表:每个表结构、数据不⼀样,⾄少有⼀列交集,⽤于关联数据,所有表的并集为全量数据

92、雪花算法原理

第⼀位符号位固定为0,41位时间戳,10位workId,12位序列号,位数可以有不同实现。

优点:每个毫秒值包含的ID值很多,不够可以变动位数来增加,性能佳(依赖workId的实现)。时间戳 值在⾼位,中间是固定的机器码,⾃增的序列在低位,整个ID是趋势递增的。能够根据业务场景数据库 节点布置灵活调整bit位划分,灵活度⾼。

缺点:强依赖于机器时钟,如果时钟回拨,会导致重复的ID⽣成,所以⼀般基于此的算法发现时钟回 拨,都会抛异常处理,阻⽌ID⽣成,这可能导致服务不可⽤。

93、如何解决不使⽤分区键的查询问题

映射:将查询条件的字段与分区键进⾏映射,建⼀张单独的表维护(使⽤覆盖索引)或者在缓存中维 护

基因法:分区键的后x个bit位由查询字段进⾏hash后占⽤,分区键直接取x个bit位获取分区,查询字 段进⾏hash获取分区,适合⾮分区键查询字段只有⼀个的情况

冗余:查询字段冗余存储

94、Spring Cloud有哪些常⽤组件,作⽤是什么?

1. Eureka:注册中⼼

2. Nacos:注册中⼼、配置中⼼

3. Consul:注册中⼼、配置中⼼

4. Spring Cloud Config:配置中⼼

5. Feign/OpenFeign:RPC调⽤

6. Kong:服务⽹关

7. Zuul:服务⽹关

8. Spring Cloud Gateway:服务⽹关

9. Ribbon:负载均衡

10. Spring CLoud Sleuth:链路追踪

11. Zipkin:链路追踪

12. Seata:分布式事务

13. Dubbo:RPC调⽤

14. Sentinel:服务熔断

15. Hystrix:服务熔断

95、分布式系统中常⽤的缓存⽅案有哪些

客户端缓存:⻚⾯和浏览器缓存,APP缓存,H5缓存,localStorage 和 sessionStorage

CDN缓 存:内容存储:数据的缓存,内容分发:负载均衡

nginx缓存:静态资源

服务端缓存:本地缓存,外部缓存

数据库缓存:持久层缓存(mybatis,hibernate多级缓存),mysql查询缓存 操作系统缓存: PageCache、BufferCache

96、缓存过期都有哪些策略?

定时过期:每个设置过期时间的key都需要创建⼀个定时器,到过期时间就会⽴即清除。该策略可以 ⽴ 即清除过期的数据,对内存很友好;但是会占⽤⼤量的CPU资源去处理过期的数据,从⽽影响缓 存的响应时间和吞吐量

惰性过期:只有当访问⼀个key时,才会判断该key是否已过期,过期则清除。该策略可以最⼤化地 节省CPU资源,但是很消耗内存、许多的过期数据都还存在内存中。极端情况可能出现⼤量的过期 key没有 再次被访问,从⽽不会被清除,占⽤⼤量内存。

定期过期:每隔⼀定的时间,会扫描⼀定数量的数据库的expires字典中⼀定数量的key(是随机 的), 并清除其中已过期的key。该策略是定时过期和惰性过期的折中⽅案。通过调整定时扫描的 时间间隔和 每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

分桶策略:定期过期的优化,将过期时间点相近的key放在⼀起,按时间扫描分桶。

97、常⻅的缓存淘汰算法

FIFO(First In First Out,先进先出),根据缓存被存储的时间,离当前最远的数据优先被淘汰;

LRU(LeastRecentlyUsed,最近最少使⽤),根据最近被使⽤的时间,离当前最远的数据优先被 淘汰;

LFU(LeastFrequentlyUsed,最不经常使⽤),在⼀段时间内,缓存数据被使⽤次数最少的会被 淘汰。

98、布隆过滤器原理,优缺点

位图:int[10],每个int类型的整数是4*8=32个bit,则int[10]⼀共有320 bit,每个bit⾮0即1,初始 化时都是0

添加数据时:将数据进⾏hash得到hash值,对应到bit位,将该bit改为1,hash函数可以定义多个, 则 ⼀个数据添加会将多个(hash函数个数)bit改为1,多个hash函数的⽬的是减少hash碰撞的 概率

查询数据:hash函数计算得到hash值,对应到bit中,如果有⼀个为0,则说明数据不在bit中,如果 都为1,则该数据可能在bit中

优点:

占⽤内存⼩

增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,⼀般⽐较⼩),与数据量⼤⼩⽆关哈 希函数相互之间没有关系,⽅便硬件并⾏运算

布隆过滤器不需要存储元素本身,在某些对保密要求⽐较严格的场合有很⼤优势 数据量很⼤时,布 隆过滤器可以表示全集

使⽤同⼀组散列函数的布隆过滤器可以进⾏交、并、差运算

缺点:

误判率,即存在假阳性(False Position),不能准确判断元素是否在集合中不能获取元素本身

⼀般情况下不能从布隆过滤器中删除元素

99、分布式缓存寻址算法

hash算法:根据key进⾏hash函数运算、结果对分⽚数取模,确定分⽚ 适合固定分⽚数的场景, 扩展分⽚或者减少分⽚时,所有数据都需要重新计算分⽚、存储

⼀致性hash:将整个hash值区间组织成⼀个闭合的圆环,计算每台服务器的hash值、映射到圆环 中。使⽤相同的hash算法计算数据的hash值,映射到圆环,顺时针寻找,找到的第⼀个服务器就是 数据存储的服务器。新增及减少节点时只会影响节点到他逆时针最近的⼀个服务器之间的值 存在 hash环倾斜的问题,即服务器分布不均匀,可以通过虚拟节点解决

hash slot:将数据与服务器隔离开,数据与slot映射,slot与服务器映射,数据进⾏hash决定存放 的slot,新增及删除节点时,将slot进⾏迁移即可

100、什么是Hystrix?简述实现机制

分布式容错框架

阻⽌故障的连锁反应,实现熔断

快速失败,实现优雅降级

提供实时的监控和告警

资源隔离:线程隔离,信号量隔离

线程隔离:Hystrix会给每⼀个Command分配⼀个单独的线程池,这样在进⾏单个服务调⽤的时 候,就可以在独⽴的线程池⾥⾯进⾏,⽽不会对其他线程池造成影响

信号量隔离:客户端需向依赖服务发起请求时,⾸先要获取⼀个信号量才能真正发起调⽤,由于信 号量的数量有限,当并发请求量超过信号量个数时,后续的请求都会直接拒绝,进⼊fallback流 程。信号量隔离主要是通过控制并发请求量,防⽌请求线程⼤⾯积阻塞,从⽽达到限流和防⽌雪崩 的⽬的。

熔断和降级:调⽤服务失败后快速失败

熔断是为了防⽌异常不扩散,保证系统的稳定性

降级:编写好调⽤失败的补救逻辑,然后对服务直接停⽌运⾏,这样这些接⼝就⽆法正常调⽤,但⼜不 ⾄于直接报错,只是服务⽔平下降

通过HystrixCommand 或者HystrixObservableCommand 将所有的外部系统(或者称为依赖)包 装起来,整个包装对象是单独运⾏在⼀个线程之中(这是典型的命令模式)。

超时请求应该超过你定义的阈值

为每个依赖关系维护⼀个⼩的线程池(或信号量); 如果它变满了,那么依赖关系的请求将⽴即被 拒绝,⽽不是排队等待。

统计成功,失败(由客户端抛出的异常),超时和线程拒绝。

打开断路器可以在⼀段时间内停⽌对特定服务的所有请求,如果服务的错误百分⽐通过阈值,⼿动 或⾃动的关闭断路器。

当请求被拒绝、连接超时或者断路器打开,直接执⾏fallback逻辑。

近乎实时监控指标和配置变化。

101、什么是服务雪崩?什么是服务限流?

1、当服务A调⽤服务B,服务B调⽤C,此时⼤量请求突然请求服务A,假如服务A本身能抗住这些请 求,但是如果服务C抗不住,导致服务C请求堆积,从⽽服务B请求堆积,从⽽服务A不可⽤,这就 是服务雪崩,解决⽅式就是服务降级和服务熔断。

2. 服务限流是指在⾼并发请求下,为了保护系统,可以对访问服务的请求进⾏数量上的限制,从⽽防 ⽌系统不被⼤量请求压垮,在秒杀中,限流是⾮常重要的。

102、什么是服务熔断?什么是服务降级?区别是什么?

1. 服务熔断是指,当服务A调⽤的某个服务B不可⽤时,上游服务A为了保证⾃⼰不受影响,从⽽不再 调⽤服务B,直接返回⼀个结果,减轻服务A和服务B的压⼒,直到服务B恢复。

2. 服务降级是指,当发现系统压⼒过载时,可以通过关闭某个服务,或限流某个服务来减轻系统压 ⼒,这就是服务降级。

相同点:

1. 都是为了防⽌系统崩溃

2. 都会让⽤户体验到某些功能暂时不可⽤

不同点:

熔断是下游服务故障触发的,降级是为了降低系统负载

103、SOA、分布式、微服务之间有什么关系和区别?

1. 分布式架构是指将单体架构中的各个部分拆分,然后部署不同的机器或进程中去,SOA和微服务都是分布式架构

2. SOA是⼀种⾯向服务的架构,系统的所有服务都注册在总线上,当调⽤服务时,从总线上查找服务 信息,然后调⽤

3. 微服务是⼀种更彻底的⾯向服务的架构,将系统中各个功能个体抽成⼀个个⼩的应⽤程序,基本保 持⼀个应⽤对应的⼀个服务的架构

104、怎么拆分微服务?

拆分微服务的时候,为了尽量保证微服务的稳定,会有⼀些基本的准则:

1. 微服务之间尽量不要有业务交叉。

2. 微服务之前只能通过接⼝进⾏服务调⽤,⽽不能绕过接⼝直接访问对⽅的数据。

3. ⾼内聚,低耦合。

105、怎样设计出⾼内聚、低耦合的微服务?

⾼内聚低耦合,是⼀种从上⽽下指导微服务设计的⽅法。实现⾼内聚低耦合的⼯具主要有 同步的接⼝调 ⽤ 和 异步的事件驱动 两种⽅式。

106、有没有了解过DDD领域驱动设计?

什么是DDD: 在2004年,由Eric Evans提出了, DDD是⾯对软件复杂之道。Domain-Driven- Design –Tackling Complexity in the Heart of Software

⼤泥团: 不利于微服务的拆分。⼤泥团结构拆分出来的微服务依然是泥团机构,当服务业务逐渐复杂, 这个泥团⼜会膨胀成为⼤泥团。

DDD只是⼀种⽅法论,没有⼀个稳定的技术框架。DDD要求领域是跟技术⽆关、跟存储⽆关、跟通信⽆ 关。

107、什么是中台?

所谓中台,就是将各个业务线中可以复⽤的⼀些功能抽取出来,剥离个性,提取共性,形成⼀些可复⽤ 的组件。

⼤体上,中台可以分为三类 业务中台、数据中台和技术中台。⼤数据杀熟-数据中台

中台跟DDD结合: DDD会通过限界上下⽂将系统拆分成⼀个⼀个的领域, ⽽这种限界上下⽂,天⽣就 成了中台之间的逻辑屏障。

DDD在技术与资源调度⽅⾯都能够给中台建设提供不错的指导。

DDD分为战略设计和战术设计。 上层的战略设计能够很好的指导中台划分,下层的战术设计能够很好的 指导微服务搭建。

108、你的项⽬中是怎么保证微服务敏捷开发的?

开发运维⼀体化。

敏捷开发 ⽬的就是为了提⾼团队的交付效率,快速迭代,快速试错

每个⽉固定发布新版本,以分⽀的形式保存到代码仓库中。快速⼊职。任务⾯板、站⽴会议。团队 ⼈员灵活流动,同时形成各个专家代表

测试环境- ⽣产环境 -开发测试环境SIT-集成测试环境-压测环境STR-预投产环境-⽣产环境PRD

晨会、周会、需求拆分会

109、用过那些mq,说一下各自特点

Kafka:

优点: 吞吐量⾮常⼤,性能⾮常好,集群⾼可⽤。

缺点:会丢数据,功能⽐较单⼀。

使⽤场景:⽇志分析、⼤数据采集

RabbitMQ:

优点: 消息可靠性⾼,功能全⾯。

缺点:吞吐量⽐较低,消息积累会严重影响性能。erlang语⾔不好定制。

使⽤场景:⼩规模场景。

RocketMQ:

优点:⾼吞吐、⾼性能、⾼可⽤,功能⾮常全⾯。

缺点:开源版功能不如云上商业版。官⽅⽂档和周边⽣态还不够成熟。客户端只⽀持java。

使⽤场景:⼏乎是全场景。

110、简述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 指令都是通过信道完成的。信道就像电缆⾥的光纤束。⼀条电缆内含有许多光纤束,允 许所有的连接通过多条光线束进⾏传输和接收。

111、RabbitMQ如何确保消息发送 ? 消息接收?

发送⽅确认机制:信道需要设置为 confirm 模式,则所有在信道上发布的消息都会分配⼀个唯⼀ ID。⼀ 旦消息被投递到queue(可持久化的消息需要写⼊磁盘),信道会发送⼀个确认给⽣产者(包含消息唯 ⼀ ID)。如果 RabbitMQ 发⽣内部错误从⽽导致消息丢失,会发送⼀条 nack(未确认)消息给⽣产 者。所有被发送的消息都将被 confirm(即 ack) 或者被nack⼀次。但是没有对消息被 confirm 的快慢 做任何保证,并且同⼀条消息不会既被 confirm⼜被nack 发送⽅确认模式是异步的,⽣产者应⽤程序在 等待确认的同时,可以继续发送消息。当确认消息到达⽣产者,⽣产者的回调⽅法会被触发。

ConfirmCallback接⼝:只确认是否正确到达 Exchange 中,成功到达则回调

ReturnCallback接⼝:消息失败返回时回调

接收⽅确认机制:消费者在声明队列时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消 费者显式发回ack信号后才从内存(或者磁盘,持久化消息)中移去消息。否则,消息被消费后会被⽴即删 除。

消费者接收每⼀条消息后都必须进⾏确认(消息接收和消息确认是两个不同操作)。只有消费者确认了 消息,RabbitMQ 才能安全地把消息从队列中删除。

RabbitMQ不会为未ack的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯⼀依据是消 费该消息的消费者连接是否已经断开。这么设计的原因是RabbitMQ允许消费者消费⼀条消息的时间可 以很⻓。保证数据的最终⼀致性;

如果消费者返回ack之前断开了链接,RabbitMQ 会重新分发给下⼀个订阅的消费者。(可能存在消息 重复消费的隐患,需要去重)

112、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的性能

113、RabbitMQ死信队列、延时队列

1. 消息被消费⽅否定确认,使⽤ channel.basicNack 或 channel.basicReject ,并且此 时 requeue 属性被设置为 false 。

2. 消息在队列的存活时间超过设置的TTL时间。

3. 消息队列的消息数量已经超过最⼤队列⻓度。

那么该消息将成为“死信”。“死信”消息会被RabbitMQ进⾏特殊处理,如果配置了死信队列信息,那么 该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃那么该消息将成为“死信”。“死信”消息会被RabbitMQ进⾏特殊处理,如果配置了死信队列信息,那么 该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃

为每个需要使⽤死信的业务队列配置⼀个死信交换机,这⾥同⼀个项⽬的死信交换机可以共⽤⼀个,然 后为每个业务队列分配⼀个单独的路由key,死信队列只不过是绑定在死信交换机上的队列,死信交换 机也不是什么特殊的交换机,只不过是⽤来接受死信的交换机,所以可以为任何类型【Direct、 Fanout、Topic】

TTL:⼀条消息或者该队列中的所有消息的最⼤存活时间

如果⼀条消息设置了TTL属性或者进⼊了设置TTL属性的队列,那么这条消息如果在TTL设置的时间内 没有被消费,则会成为“死信”。如果同时配置了队列的TTL和消息的TTL,那么较⼩的那个值将会被使 ⽤。

只需要消费者⼀直消费死信队列⾥的消息

114、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不会同步之前的历史数据,新节点只会复制该节点加⼊到集群之后新增 的消息。

115、Kafka为什么吞吐量⾼

1、磁盘顺序读写:保证了消息的堆积 顺序读写,磁盘会预读,预读即在读取的起始地址连续读取多个页面,主要时间花费在了传输时 间,而这个时间两种读写可以认为是一样的。 随机读写,因为数据没有在一起,将预读浪费掉了。需要多次寻道和旋转延迟。而这个时间可能是 传输时间的许多倍。

2、零拷贝:避免 CPU 将数据从一块存储拷贝到另外一块存储的技术

传统的数据复制:

        1.读取磁盘文件数据到内核缓冲区

        2.将内核缓冲区的数据copy到用户缓冲区

        3.将用户缓冲区的数据copy到socket的发送缓冲区

        4.将socket发送缓冲区中的数据发送到网卡、进行传输

零拷贝: 磁盘文件->内核空间读取缓冲区->网卡接口->消费者进程

3、分区分段+索引 Kafka的message消息实际上是分布式存储在一个一个小的segment中的,每次文件操作也是直接操 作的segment。为了进一步的查询优化,Kafka又默认为分段后的数据文件建立了索引文件,就是文件 系统上的.index文件。这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据操 作的并行度

4、批量压缩:多条消息一起压缩,降低带宽

5、批量读写

6、直接操作page cache,而不是JVM、避免GC耗时及对象创建耗时,且读写速度更高,进程重启、缓 存也不会丢失

broker:Kafka 服务器,负责消息存储和转发

topic:消息类 别, Kafka 按照 topic 来分类消息

partition:topic 的分区,⼀个 topic 可以包含多个 partition, topic 消息保存在各个partition 上

offset:消息在⽇志中的位置,可以理解是消息在 partition 上的偏移 量,也是代表该消息的唯⼀序号

Producer:消息⽣产者

Consumer:消息消费者

Consumer Group:消 费者分组,每个 Consumer 必须属于⼀个 group

Zookeeper:保存着集群 broker、 topic、 partition 等 meta 数据;另外,还负责 broker 故障发现, partition leader 选举,负载均衡等功能

116、为什么要使⽤ kafka,为什么要使⽤消息队列?

缓冲和削峰 :上游数据时有突发流量,下游可能扛不住,或者下游没有⾜够多的机器来保证冗余, kafka在中间可以起到⼀个缓冲的作⽤,把消息暂存在kafka中,下游服务就可以按照⾃⼰的节奏进⾏慢慢处理。

解耦和扩展性 :项⽬开始的时候,并不能确定具体需求。消息队列可以作为⼀个接⼝层,解耦重要的业 务流程。只需要遵守约定,针对数据编程即可获取扩展能⼒。

冗余 :可以采⽤⼀对多的⽅式,⼀个⽣产 者发布消息,可以被多个订阅topic的服务消费到,供多个毫⽆关联的业务使⽤。

健壮性 :消息队列可 以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进⾏。

异步通信 :很多时 候,⽤户不想也不需要⽴即处理消息。消息队列提供了异步处理机制,允许⽤户把⼀个消息放⼊队列, 但并不⽴即处理它。想向队列中放⼊多少消息就放多少,然后在需要的时候再去处理它们。

117、Kafka中的ISR、AR⼜代表什么?ISR的伸缩⼜指什么

ISR:In-Sync Replicas 副本同步队列

AR:Assigned Replicas 所有副本ISR是由leader维护,follower从leader同步数据有⼀些延迟(包括延迟 时间replica.lag.time.max.ms和延迟条数replica.lag.max.messages两个维度, 当前最新的版本0.10.x中 只⽀持replica.lag.time.max.ms这个维度),任意⼀个超过阈值都会把follower剔除出ISR, 存⼊OSR(Outof-Sync Replicas)列表,新加⼊的follower也会先存放在OSR中。AR=ISR+OSR。

118、Kafka⾼效⽂件存储设计特点:

1. Kafka 把 topic 中⼀个 parition ⼤⽂件分成多个⼩⽂件段,通过多个⼩⽂件段,就容易定期清除或 删除已经消费完⽂件,减少磁盘占⽤。

2. 通过索引信息可以快速定位 message 和确定 response 的最⼤⼤⼩。

3. 通过 index 元数据全部映射到 memory,可以避免 segment file 的 IO 磁盘操作。

4. 通过索引⽂件稀疏存储,可以⼤幅降低 index ⽂件元数据占⽤空间⼤⼩。

119、Kafka与传统消息系统之间有三个关键区别

1. Kafka 持久化⽇志,这些⽇志可以被重复读取和⽆限期保留

2. Kafka 是⼀个分布式系统:它以集群的⽅式运⾏,可以灵活伸缩,在内部通过复制数据提升容错能 ⼒和⾼可⽤性

3. Kafka ⽀持实时的流式处理

120、Kafka创建 Topic 时如何将分区放置到不同的 Broker 中

1. 副本因⼦不能⼤于 Broker 的个数;

2. 第⼀个分区(编号为 0)的第⼀个副本放置位置是随机从 brokerList 选择的;

3. 其他分区的第⼀个副本放置位置相对于第 0 个分区依次往后移。也就是如果我们有 5 个Broker, 5 个分区,假设第⼀个分区放在第四个 Broker 上,那么第⼆个分区将会放在第五个 Broker 上;第三 个分区将会放在第⼀个 Broker 上;第四个分区将会放在第⼆个Broker 上,依次类推;

4. 剩余的副本相对于第⼀个副本放置位置其实是由 nextReplicaShift 决定的,⽽这个数也是随机产⽣ 的

121、Kafka的消费者如何消费数据

消费者每次消费数据的时候,消费者都会记录消费的物理偏移量( offset)的位置等到下次消费时,他 会接着上次位置继续消费

122、Kafka消费者负载均衡策略

⼀个消费者组中的⼀个分⽚对应⼀个消费者成员,他能保证每个消费者成员都能访问,如果组中成员太 多会有空闲的成员

123、kafaka⽣产数据时数据的分组策略

⽣产者决定数据产⽣到集群的哪个 partition 中每⼀条消息都是以( key, value)格式 Key是由⽣产者 发送数据传⼊所以⽣产者( key)决定了数据产⽣到集群的哪个 partition

124、Kafka中是怎么体现消息顺序性的?

kafka每个partition中的消息在写⼊时都是有序的,消费时,每个partition只能被每⼀个group中的⼀个 消费者消费,保证了消费时也是有序的。整个topic不保证有序。如果为了保证topic整个有序,那么将 partition调整为1.

125、Kafka如何实现延迟队列?

Kafka并没有使⽤JDK⾃带的Timer或者DelayQueue来实现延迟的功能,⽽是基于时间轮⾃定义了⼀个 ⽤于实现延迟功能的定时器(SystemTimer)。

JDK的Timer和DelayQueue插⼊和删除操作的平均时间 复杂度为O(nlog(n)),并不能满⾜Kafka的⾼性能要求,⽽基于时间轮可以将插⼊和删除操作的时间复杂度都降为O(1)。

时间轮的应⽤并⾮Kafka独有,其应⽤场景还有很多,在Netty、Akka、Quartz、 Zookeeper等组件中都存在时间轮的踪影。底层使⽤数组实现,数组中的每个元素可以存放⼀个 TimerTaskList对象。

TimerTaskList是⼀个环形双向链表,在其中的链表项TimerTaskEntry中封装了 真正的定时任务

126、TimerTask.Kafka中到底是怎么推进时间的呢?

Kafka中的定时器借助了JDK中的 DelayQueue来协助推进时间轮。

具体做法是对于每个使⽤到的TimerTaskList都会加⼊到DelayQueue 中。

Kafka中的TimingWheel专⻔⽤来执⾏插⼊和删除TimerTaskEntry的操作,⽽DelayQueue专⻔负 责时间推进的任务。

再试想⼀下,DelayQueue中的第⼀个超时任务列表的expiration为200ms,第⼆个 超时任务为840ms,这⾥获取DelayQueue的队头只需要O(1)的时间复杂度。如果采⽤每秒定时推进, 那么获取到第⼀个超时的任务列表时执⾏的200次推进中有199次属于“空推进”,⽽获取到第⼆个超时任 务时有需要执⾏639次“空推进”,这样会⽆故空耗机器的性能资源,这⾥采⽤DelayQueue来辅助以少量 空间换时间,从⽽做到了“精准推进”。Kafka中的定时器真可谓是“知⼈善⽤”,⽤TimingWheel做最擅 ⻓的任务添加和删除操作,⽽⽤DelayQueue做最擅⻓的时间推进⼯作,相辅相成。

127、RocketMQ为什么速度快

因为使⽤了顺序存储、Page Cache和异步刷盘。我们在写⼊commitlog的时候是顺序写⼊的,这样⽐随 机写⼊的性能就会提⾼很多,写⼊commitlog的时候并不是直接写⼊磁盘,⽽是先写⼊操作系统的 PageCache,最后由操作系统异步将缓存中的数据刷到磁盘

128、如何保证消息的⾼效读写

零拷⻉: kafka和RocketMQ都是通过零拷⻉技术来优化⽂件读写。

传统⽂件复制⽅式: 需要对⽂件在内存中进⾏四次拷⻉。

零拷⻉: 有两种⽅式, mmap和transfile,Java当中对零拷⻉进⾏了封装, Mmap⽅式通过 MappedByteBuffer对象进⾏操作,⽽transfile通过FileChannel来进⾏操作。Mmap 适合⽐较⼩的⽂件,通常⽂件⼤⼩不要超过1.5G ~2G 之间。Transfile没有⽂件⼤⼩限制。RocketMQ当中使⽤Mmap⽅ 式来对他的⽂件进⾏读写。

在kafka当中,他的index⽇志⽂件也是通过mmap的⽅式来读写的。在其他⽇志⽂件当中,并没有使⽤ 零拷⻉的⽅式。Kafka使⽤transfile⽅式将硬盘数据加载到⽹卡。

129、零拷⻉是什么

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

130、TCP的三次握⼿和四次挥⼿

TCP协议是7层⽹络协议中的传输层协议,负责数据的可靠传输。

在建⽴TCP连接时,需要通过三次握⼿来建⽴,过程是:

1. 客户端向服务端发送⼀个SYN

2. 服务端接收到SYN后,给客户端发送⼀个SYN_ACK

3. 客户端接收到SYN_ACK后,再给服务端发送⼀个ACK

在断开TCP连接时,需要通过四次挥⼿来断开,过程是:

1. 客户端向服务端发送FIN

2. 服务端接收FIN后,向客户端发送ACK,表示我接收到了断开连接的请求,客户端你可以不发数据 了,不过服务端这边可能还有数据正在处理

3. 服务端处理完所有数据后,向客户端发送FIN,表示服务端现在可以断开连接

4. 客户端收到服务端的FIN,向服务端发送ACK,表示客户端也会断开连接了

131、跨域请求是什么?有什么问题?怎么解决?

跨域是指浏览器在发起⽹络请求时,会检查该请求所对应的协议、域名、端⼝和当前⽹⻚是否⼀致,如 果不⼀致则浏览器会进⾏限制,⽐如在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. ⽹关,和第三种⽅式类似,都是交给后台服务来进⾏跨域访问

132、netty与mina对比

1、相同点:一个作者开发,异步非阻塞线程通信框架

2、不同点:

         mina 抽象模型简单,容易上手,定制化特殊需求困难

        netty 抽象模型复杂,定制化需要容易做到

        mina 对 buffer 只是做了简单的封装, netty 使用了buffer-pool 等,减少gc的压力

        netty 提供了高效的selector 事件监听,减少了和内核打交道。

3、其他 mina 目前社区不够活跃,netty目前社区活跃。线上的服务建议使用netty

133、反转链表

134、统计N以内的素数

解法⼀:暴⼒算法 直接从2开始遍历,判断是否能被2到⾃身之间的数整除

解法2:埃⽒筛

利⽤合数的概念(⾮素数),素数*n必然是合数,因此可以从2开始遍历,将所有的合数做上标记

135、寻找数组的中⼼索引

数组中某⼀个下标,左右两边的元素之后相等,该下标即为中⼼索引

思路:先统计出整个数组的总和, 然后从第⼀个元素开始叠加,总和递减当前元素,叠加递增当前元素,直到两个值相等

136、删除排序数组中的重复项

⼀个有序数组 nums ,原地删除重复出现的元素,使每个元素只出现⼀次 ,返回删除后数组的新⻓度。 不要使⽤额外的数组空间,必须在原地修改输⼊数组并在使⽤ O(1) 额外空间的条件下完成。

双指针算法:

数组完成排序后,我们可以放置两个指针 i 和 j,其中 i 是慢指针,⽽ j 是快指针。只要 nums[i]=nums[j],我们就增加 j 以跳过重复项。

当遇到 nums[j] != nums[i]时,跳过重复项的运⾏已经结束,必须把nums[j])的值复制到 nums[i + 1]。然后递增 i,接着将再次重复相同的过程,直到 j 到达数组的末尾为⽌。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/942230
推荐阅读
相关标签
  

闽ICP备14008679号