整理的真实面试被问的面试题(培训机构收集的学生面试过程中被问的问题)

1.简单说一下线程和进程?
(1)进程是资源分配的基本单位,进程是程序运行的实例。java中启动main、测试test、springboot项目都是一个进程。
(2)线程是程序运行的最小单位,一个进程中可以包含多个线程,线程间共享同一个进程的所有资源,每个线程都有自己的栈和局部变量。

2.有没有使用过线程?
使用过,之前做过一个父母监督孩子的项目,其中我负责的监督模块有一个导出报告的功能,使用了线程池,来进行异步导出报告,提升了效率。

3.java中实现线程的方式?
(1)java一共有两种方式来创建线程,第一种是创建thread子类,再调用start方法,第二种是创建thread再传入执行逻辑,最后调用start方法。
(2)常见的使用线程的方式有四种,第一种是继承thread,重写run方法,创建实例,调用start方法。第二种是,实现runnable接口,实现run方法,创建thread实例,再创建runnable实例,传入thread中,调用start方法。第三种是实现callable接口,实现call方法,创建callable实例,创建futuretask实例并传入callable实例,再创建thread实例,传入futuretask实例,调用start方法。第四种是使用线程池

4.线程的生命周期?
线程的生命周期是指从创建到销毁的全部过程。
线程被创建出来是new新建状态,调用start方法后是就绪状态,获取CPU资源时间片后进入运行状态,运行状态调用wait、sleep、join等方法时进入阻塞状态,方法运行结束后又进入就绪状态,当运行状态结束后就会进入死亡状态。

拒绝策略:拒绝策略,任务超过 最大线程数+队列排队数 ,多出来的任务该如何处理取决于Handler
共有四种:

    1. AbortPolicy丢弃任务并抛出RejectedExecutionException异常;
    2. DiscardPolicy丢弃任务,但是不抛出异常;
    3. DiscardOldestPolicy丢弃队列最前面的任务,然后重新尝试执行任务;
    4. CallerRunsPolicy由调用线程处理该任务
      可以定义和使用其他种类的RejectedExecutionHandler类来定义拒绝策略。
  • KeepAliveTime: 非核心线程的最大空闲时间,到了这个空闲时间没被使用,非核心线程销毁
  • Unit: 空闲时间单位
  • ThreadFactory:使用ThreadFactory创建新线程。 推荐使用Executors.defaultThreadFactory

5.为什么需要线程池?
(1)- 控制并发数量:线程并发数量过多,线程池可以限制线程的数量,防止服务器过载。
(2)- 线程的复用:创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率和速度 。

6.说一下线程池的原理?
1当线程池中线程数小于corePoolSize时,新提交任务将创建一个新线程(使用核心)执行任务,即使此时线程池中存在空闲线程。

  1. 当线程池中线程数达到corePoolSize时(核心用完),新提交任务将被放入workQueue中,等待线程池中任务调度执行 。
  2. 当workQueue已满,且maximumPoolSize > corePoolSize时,新提交任务会创建新线程(非核心)执行任务。
  3. 当workQueue已满,且提交任务数超过maximumPoolSize(线程用完,队列已满),任务由RejectedExecutionHandler处理。
  4. 当线程池中线程数超过corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收这些线程。
  5. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收。

7.java中有哪些线程池?
(1)CachedThreadPool可缓存,适用于执行很多短期异步的小程序或者负载较轻的服务器。
(2)FixedThreadPool 定长线程池,适用于执行长期的任务,性能好很多。
(3)SingleThreadPool 单线程池,适用于一个任务一个任务执行的场景。 如同队列
(4)ScheduledThreadPool 定时任务,适用于延迟执行和周期执行任务。
(5)WorkStealingPool,一个高性能的线程池. 提高性能! 工作窃取算法
(6)自定义线程池

8.什么是线程安全?
多个线程访问某对象或某方法,不需要额外做额外的处理,程序可以正常运行,不会因为多线程而出错,就可以称为线程安全。

9.synchronized是悲观锁,隐式锁(不用我们手动加锁和释放锁,加上synchronized会由jvm自动给我们加锁和释放锁),synchronized关键字都会有一个锁标识对象。

10.synchronized原理?
synchronized是java中的一个关键词,是非公平锁,是隐式锁、是悲观锁,是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实 现。加了synchronized后,(如果锁标识对象对象头中MarkWord对应的是重量级锁),多个线程来了之后会竞争锁标识对象对应的objectMonitor来实现同步。objectmonitor中有count、owner、waitset、entrylist等部分组成。多个线程来访问时,都会进入entrylist第一个线程先使用CAS机制(操作是原子性),将owner设置为自己,如果设置成功表示拿到了锁,将count+1,如果后续同一个线程进来就继续+1(可重入),每释放一次锁就会count-1,直到count=0,owner置为空,表示释放了锁,其他线程就可以拿到锁。当线程调用wait,会释放锁,并且自己会进入waitset,当被notify(唤醒),就可以重新竞争锁。对象(对象头、实例数据、填充数据)。objectMonitor中——waitSet中存放处于wait状态的线程队列,entryList中存放等待锁且处于阻塞状态的线程队列。

11.synchronized优化?(锁消除、锁粗化、锁升级)
(1)锁消除,jvm及时编译器在运行时,通过逃逸分析,如果判断一段代码,堆上的数据不会逃逸出去从而被其他线程访问到,就可以去除这些锁。(如stringbuffer)
(2)锁粗化,将多个连续的锁扩展成一个范围更大的锁,用以减少频繁互斥同步导致的性能消耗。jvm会检测到一连串操作都对同一个对象加锁,此时jvm会将加锁的范围粗化到这一连串操作的外部,使得这一连串操作只需要加一次锁即可。
(3)偏向锁,jdk1.6引入,大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁的目的是在某个线程获得锁之后(线程的id会记录在对象的Mark Word锁标志位中),消除这个线程锁再次进入(CAS)的开销,看起来让这个线程得到了偏护。(第二次还是这个线程进来就不需要重复加锁,基本无开销),如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
(4) 轻量级锁(CAS):轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当少量线程加入锁争用的时候,偏向锁就会升级为轻量级锁自旋锁);没有抢到锁的线程将自旋,获取锁的操作。轻量级锁的意图是在没有多线程竞争的情况下,通过CAS操作尝试将MarkWord锁标志位更新为指向LockRecord的指针,减少了使用重量级锁的系统互斥量产生的性能消耗。 长时间的自旋操作是非常消耗资源的,一个线程持有锁,其他线程就只能在原地空耗CPU,执行不了任何有效的任务,这种现象叫做忙等(busy-waiting)
(5) 重量级锁: 如果锁竞争情况严重,某个达到最大自旋次数(10次默认)的线程,会将轻量级锁升级为重量级锁,重量级锁则直接将自己挂起,在JDK1.6之前,synchronized直接加重量级锁,很明显现在得到了很好的优化。虚拟机使用CAS操作尝试将MarkWord更新为指向LockRecord的指针,如果更新成功表示线程就拥有该对象的锁;如果失败,会检查MarkWord是否指向当前线程的栈帧,如果是,表示当前线程已经拥有这个锁;如果不是,说明这个锁被其他线程抢占,此时膨胀为重量级锁。

12.有没有了解过juc?
显示锁(Lock)是属于线程并发库(java.util.concurrent)里面一种功能,java.util.concurrent是专门Java并发设计编程包
(1)显示锁 java.util.concurrent.locks。显式锁:需要自己显示的加锁,比如 Synchronized 使用的是JVM内置锁实现的,它就不是显示锁
(2) 原子变量类 (乐观锁)java.util.concurrent.atomic:为了实现原子性操作提供的一些原子类,使用的是乐观锁实现
(3)线程池相关 java.util.concurrent通过线程池操作线程可以增加线程的复用性,防止频繁的创建,销毁线程
(4)并发容器类 java.util.concurrent并发容器都是线程安全的,比如在多线程中可以使用ConcurrentHashMap代替HashMap( ArrayBlcokingQueue:有长度阻塞队列
LinkedBlockingQueue:无限长度阻塞队列
SynchronousQueue:没有长度,就是不存)
(5)同步工具类 java.util.concurrent,比如semaphore、executors

13.介绍一下lock?
在java中锁的实现主要有两类:内部锁 synchronized(对象内置的monitor锁)和显示锁java.util.concurrent.locks.Lock。在 java.util.concurrent.locks 包中有很多Lock的实现类,常用的有 ReentrantLock 和ReadWriteLock,其实现都依赖 java.util.concurrent.AbstractQueuedSynchronizer(AQS) 类。
lock是一个接口,主要有以下几个方法:

  • lock():获取锁,如果锁被暂用则一直等待
  • unlock():释放锁
  • tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true

14.synchronized和lock的联系和区别?
联系:都是悲观锁,都是jvm级别
区别:(1)synchronized是个关键字,lock是一个类
(2)lock的加锁和释放锁都是由java代码来实现的,synchronized的加锁和释放锁的过程是由jvm来管理的。如果synchronized线程执行过程中发生异常,jvm会让线程释放锁,lock锁必须要在finally中释放锁,不然容易造成死锁。
(3)synchronized能锁住类、方法和代码块,而lock是块范围内的
(4)synchronized锁:如果a线程获得锁,b线程等待,如果a线程阻塞,b线程会一直等待。lock锁:分情况而定,lock锁有多个锁获取的方式,线程可以不用一直等待。
(5)synchronized锁的状态无法判断,lock锁可以判断,使用trylock(方法就可以判断)
(6)synchronized锁可重入、不可判断、非公平,lock锁,可重入、可判断、可以公平也可以不公平锁,默认是非公平锁
(7)在性能方面,synchronized锁是少量同步,lock锁是大量同步

15.说一下reentrantlock底层原理?
是一个可重入锁,reentrantlock的底层是AbstractQueueSynchronizer(AQS),每一个lock都有一个AQS与之对应,通过AQS来上锁释放锁换言之AQS中封装了上锁和释放锁的操作,,AQS有几个重要的字段,owner、state、queue,当多个线程进来之后会利用CAS(比较和交换)机制将state置为1,并把owner置为当前线程的ID,如果是重入就继续state+1,否则就将线程放入队列,并且park住,如果是unlock就将state-1,直到减为0之后,将owner也置为空(null),再从队列里中选择一个线程UNpark掉,

16.说一下java中引用类型有哪些??
Java 中有四种引用类型:强引用、软引用、弱引用和虚引用。
(1)强引用:最常见的引用类型,也是默认的引用类型。当我们通过 new 关键字创建一个对象并将其赋值给一个变量时,这个变量就持有了该对象的强引用。只要强引用存在,垃圾回收器就不会回收该对象
(2)软引用:软引用通常用于实现内存敏感的高速缓存等功能,在系统内存不足时可以被回收。如果一个对象只有软引用与之关联,在系统内存不足时,垃圾回收器就会回收这个对象。SoftReference 类可以实现软引用
(3)弱引用:弱引用比软引用更弱,只能生存到下一次垃圾回收发生之前。当垃圾回收器扫描到一个只有弱引用与之关联的对象时,会将这个对象标记为可回收。WeakReference 类可以实现弱引用
(4)虚引用:也称 为幽灵引用,是最弱的一种引用类型。无法通过虚引用访问对象本身,也无法通过虚引用获取一个对象实例。虚引用主要用于管理堆外内存。PhantomReference 类可以实现虚引用

17.锁的分类?
(1)线程是否要锁住同步资源,锁住是悲观锁,不锁住是乐观锁
(2)锁住同步资源失败,线程是否阻塞:阻塞锁,不阻塞锁,不阻塞锁又分为自旋锁和适应性自旋锁(自旋次数是一定的)
(3)多个线程竞争同步资源的流程细节有无区别,1,不锁住资源,多个线程中只有一个能修改资源成功,其他线程重试。(无锁)2.同一个线程执行同步资源时自动获取资源。(偏向锁)3,多个线程竞争同步资源时,没有获取资源的线程自旋等待锁释放(轻量级锁)4,多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒。(重量级锁)
(4)多个线程竞争锁时,释放排队:排队:公平锁,先尝试插队,插队失败再排队:非公平锁
(5)一个线程的多个流程能不能获取同一把锁,能是可重入锁,不能就是非可重入锁
(6)多个线程能不能共享一把锁:能,共享锁,不能就是排他锁

18 ArrayList和LinkedList联系和区别?
联系:ArrayList和 LinkedList都是Java中list接口的实现,都属于线性结构,存放的是一个一个的可重复的数据,都是线程不安全的。
区别:ArrayList底层是基于数组实现的,开辟的内存空间要求联系,可以根据索引随机访问元素性能高,但是插入和删除元素性能差,因为这会涉及到移位操作。
LinkedList是基于双链表实现的,开辟的内存空间不要求连续,因此不支持索引,查找元素需要从头查找,因此查找性能差,但是添加删除只需要改变指针指向即可,性能高. LinkedList会增加内存碎片化,增加内存管理难度
根据实际需要,如果项目中使用查找较多,使用ArrayList,如果使用增删较多,请使用LinkedList

19.HashMap底层为什么要用红黑树?
JDK1.7及其之前:数组,链表 ; JDK1.8开始:数组,链表,红黑树
是为了兼顾删除、插入和查询的效率。当hashmap出现hash冲突的时候,先使用拉链法,将多个相同的hash值的key挂在同一条链表上,当链表长度大于8,数组长度大于64的时侯,链表的查询效果就会变得很低,链表就会转化为红黑树,红黑树相对于链表能够存放更多的节点,当红黑树节点数小于6之后又会重新转化为链表。为什么不用二叉搜索数的原因是,二叉搜索树在每次插入删除会进行大量的平衡度计算,插入删除比较耗时,浪费性能。红黑树的查询性能略微逊色于二叉搜索树,因为其比二叉搜索树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的二叉搜索树最多多一次比较,但是,红黑树在插入和删除上优于二叉搜索树,二叉搜索树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于二叉搜索树为了维持平衡的开销要小得多`。而hashmap要兼顾查询和插入的操作的效率就选择了红黑树。

20.Mysql索引为什么要用B+树?? 必须用树,而且是B+
使用多叉树能存放更多的节点,以磁盘块来进行操作的,要以最小的IO找到更多的记录数,如果是B树,由于每个节点要存储Key和Value(数据) ,那么每个节点能存储的Key是很少的 ,而B+树每个节点只存储Key,它可以存储更多的Key, 每个节点存储的Key越多,路数越多,树的高度就越矮,需要耗时的IO就越少,查找性能就越高 , 且B+树的叶子节点是有序的,形成链表,方便区间查询和排序。叶子节点存放数据,每次查询都需要查询到叶子接口,查询速度基本一致。

  1. hash冲突的解决方案??至少三个
    拉链法/链地址法 :把Hash碰撞的元素指向一个链表
    开放寻址法:当 p=h(key)出现冲突,就以p为基础产生另一个Hash,如:p1=h§,直到不冲突了把元素放在该位置—一个hash函数多次hash
    再散列法:准备若干个hash函数,如果使用第一个hash函数发生了冲突,就使用第二个hash函数,第二个也冲突,使用第三个—多个hash函数多次hash
    建立公共溢出区:把Hash表分为基本表和溢出表,把和基本表冲突的元素移到溢出表
    Hash算法的应用:文件校验,数字签名,hash表(HashMap底层就用到了Hash算法)。 用来计算存储位置…,登录密码加密

22.写一个冒泡排序(插入/选择)和二分查找!
冒泡排序:for (int i = 0; i < nums.length -1; i++) {
for (int j = 0; j < nums.length-i-1; j++) {
if (nums[j]>nums[j+1]){
int tmp = nums[j+1];
nums[j+1] = nums[j];
nums[j] = tmp;
}
}
}
二分查找:
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}

23.说一下string的底层原理
String底层是final 修饰的char value[];一旦赋值,不能再重新赋值,不能通过追加的方式改里面内容。追加如果空间不够了,需要搞一个新的更大容量赋值给它,但是由于final修饰不行!

  1. 简单说一下string
    String是内容不可变的字符串!是final修饰的 char value[];一旦赋值,不能再重新赋值,不能通过追加的方式改里面内容。追加如果空间不够了,需要搞一个新的更大容量赋值给它. 但是不能.

25.说一下String与Stringbuilder的联系与区别
联系: 都是处理字符串,底层都是char value[];
区别: String 是内容不可变的字符串.final 修饰的char value[]; 一般用来定义变量或者常量
Stringbuider是内容可变且线程不安全字符串,底层是char value[];它有追加方法,当容量不够的时候一般就搞一个2倍+2原来的数组容量大数组,重新赋值给它. 一般用来做字符串拼接

  1. 说一下StirngBuilder与StringBuffer的联系与区别 使用场景
    联系:StringBuilder和StringBuffer是可变字符串,底层是没有final修饰的char数组,适合字符串拼接
    区别:StringBuffer是线程安全的,方法有 synchronized 修饰,但是性能较低,StringBuilder是线程不安全的,方法没有synchronized修饰,性能较高

27.java中==和equals的区别?
==如果基本数据类型,比较值.如果是引用类型比较的是内存地址,就是看他们是不是同一个对象.
equals不能用于基本数据类型.只能用于对象.是Object的的一个方法,任何对象都直接或间接继承于Object,如果该类没有重写equals比较的还是地址. 对于包装类比如:Integer都是复写过equals方法,比较的是int 值,是重写equals其实就是比较数值,String也是重写了,比较里面的内容.但是StringBuiler与StringBuffer没有重写equals所以还是比较的地址.

  1. 说一下java中的集合
    Java中的集合分为两大类,collection和map,collection下面有list和set和queue,list中使用比较多的是arraylist和linkedlist,(list中vector是线程安全的arraylist,子类是stack栈),queue中主要是链式队列linkedlist和顺序队列arrayqueue。set中主要是使用hashset和linkedhashset,hashset无序不重复,linkedhashset(双向链表)有序不重复,map下面有hashmap和hashtable、properties。
    collection是存放一个一个的值,map是存键值对的。

29.说一下list与set的联系和区别?
联系:list和set都是collection的子接口,都是放一个一个的值
区别:一般来说list是有序可重复的,set是无序不可重复的(hashset无序、linkedhashset有序、treeset排序)

30.说一下ArrayList和LinkedList的联系和区别
联系:ArrayList和 LinkedList都是Java中list接口的实现,都属于线性结构,存放的是一个一个的可重复的数据,都是线程不安全的。
区别:ArrayList底层是基于数组实现的,开辟的内存空间要求联系,可以根据索引随机访问元素性能高,但是插入和删除元素性能差,因为这会涉及到移位操作。
LinkedList是基于双链表实现的,开配的内存空间不要求连续,因此不支持索引,查找元素需要从头查
找,因此性能差,但是添加删除只需要改变指针指向即可,性能高. LinkedList会增加内存碎片化,增加
内存管理难度
根据实际需要,如果项目中使用查找较多,使用ArrayList,如果使用增删较多,请使用LinkedList

  1. 想存不重复且有序的一个一个的值用哪个集合???
    linkedhashset

  2. 集合怎么排序
    Collections.sort()

  3. 想存且有序的key-value的值用哪个集合
    linkedhashmap

  4. 说一下hashtable与hashmap的联系与区别
    联系:hashtable与hashmap:都是map
    区别:
    1)值能不能为Null hashmap可以多个null,hashtable不能为null
    2)key是否允许为null hashtable不能为null,hashmap允许一个null
    hashtable key和value都不能为null,而hashmap允许一个null key,多个null值.
    3)线程安全
    hashtable是线程安全,效率低,hashmap是线程不安全,效率高

  5. 说一下hashmap的底层原理
    在jdk1.8及其以后,hashmap的底层数据结构由数组、链表、红黑树组成。
    存值的时候先将key利用hash算法算出插入的索引位置,判断是否有hash冲突,如果没有冲突就直接存入,有hash冲突,利用equals判断是否是自己,如果是自己就替换value值,否则如果是链表就按照链表的方式插入到链表或者替换,如果链表长度大于了8并且数组长度大于64,链表还要转化为红黑树。如果是红黑树就要将值按照红黑树的方式插入红黑树或者替换。获取值的时候,先跟据key利用hashh算法获取到第一个节点,如果第一个节点就是要找的数据就返回,否则如果是链表就按照链表的方式查找数据,如果是红黑树就按照红黑树的方式查找数据。

  6. 说一下concurrenthashmap! java想要在处理key-value值的时候怎么保证线程安全!
    concurrenthashmap是线程安全的hashmap,减低了锁的粒度,通过了性能和可靠性。
    (保证处理key-value值的时候的线程安全有三种方法:1.hashtable,2.collections.synchronizedmap,3,concurrenthashmap)
    降低锁的粒度: hashtable锁全部数组的全部元素,意味着同一个hashtable对象,在读的时候,另外的线程不能读也不能写,在写的时候也一样,另外的线程不能读也不能写,在写的时候也一样.
    ?concurrenthashmap:
    jdk1.7 区间锁,锁几个元素,效率也挺高的.
    jdk1.8 锁全部数组的当前元素,读写当前元素,其他元素是可以被其他线程操作,效率高.

  7. 12 说一下hashmap的扩容机制
    初始容量是0,第一次添加的时候默认设置为16,如果传入容量就设置为比输入容量大的2次幂
    当容量大于负载容量的时候(负载容量=总容量*负载因子)就会发生扩容,容量扩容为原来的二倍。
    其他容量和扩容:
    stringbuilder和stringbuffer默认容量16,扩容为2n+2,
    ArrayList在刚刚创建的时候容量大小是0,在执行第一次add操作的时候,会扩容到10,在执行add操作时,发现集合容量不够了,就会扩容到原容量的1.5倍。

37.有没有做过linux优化?
没有做过,运维会做这个事情,但是我了解过,就是通过一些命令查看是否过载,如果是就找到使用最多的哪些程序,进行程序优化或者提升硬件配置

38.说一下linux优化用到哪些命令
uptime:系统整体性能评估
vmstat:CPU性能评估
top:(实时监控系统CPU)
free -m:内存性能评估
iostat:磁盘I/O性能评估
sar:命令系统运行状态统计
netstat -i 组合检测网络接口状况
netstat -r 组合检测系统的路由表信息
ifconfig:查看网卡
ifconfig -i eth0:查看网卡流量
fping:检测主机是否存在
iftop:实时流量监控工具
ps -aux --sort -pcpu | more:找出系统中使用CPU最多的进程
ps -aux --sort -rss | more :找出系统中使用内存最多的进程
iotop:找出系统中对磁盘读写最多的进程
nload:找出系统中使用网络最多的进程
nethogs:找出使用带宽最多的进程

39.有没有做过jvm优化?
做过,jvm优化实际上就是执行程序的时候设置一些参数,包括堆栈等配置。

40.jvm组成?

  • 类加载器子系统 :字节码加载
  • 运行时数据区 : java程序运行涉及到的区域 jvm内存结构
  • 执行引擎 :程序执行的引擎 即时编译器 垃圾回收
  • 本地方法接口 :接入其他语言lib库

41.jvm内存结构(运行时数据区)图?
Java虚拟机栈:用于存储局部变量表、操作数栈、动态链接、方法出口等信息。(栈里面存储的是的地址,实际指向的是堆里面的对象)
本地方法栈:里面并没有我们写的代码逻辑,存储c++的native方法运行时候的栈区。
程序计数器:(指向当前程序运行的位置)它是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、线程恢复等都依赖于计数器。
堆:Java虚拟机中最大的内存空间,被所有的线程共享,几乎所有的对象实例都在这里分配实例。
方法区(Java8叫元空间):用于存放已被虚拟机加载的类信息、常量和静态变量等数据。

42.java的跨平台原理?
Java 源代码经过编译,生成字节码文件,交由 Java 虚拟机jvm来执行,不同得系统有不同得JVM,借助JVM实现跨平台。

43.说一下java的类加载系统??
类加载的过程包括了加载,验证,准备,解析和初始化这5个步骤
jvm类加载子系统是用来加载类的,类的加载要使用类加载器来加载.java提供了多种类加载器,它使用双亲委派机制进行协调加载,主要有应用,扩展,启动三种类加载器,委派应用–>扩展–>启动.加载优先使用启动–>扩展–>应用.这样做的好处是不能轻易修改系统提供的类,但是可以扩展三方jar. 加载过程是加载,验证,准备,解析,初始化.

加载:找到字节码文件,读取到内存中
验证:验证此字节码文件是不是真的是一个字节码文件
准备:为类中static修饰的变量分配内存空间并设置其初始值为0或null
解析:解析阶段会将java代码中的符号引用替换为直接引用
初始化:如刚才准备阶段所说的,这个阶段就是对变量的赋初始值的阶段

44.如何判断某个对象成为垃圾?
有两种算法
引用计数器算法:给每一个对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;每当有一个地方不再引用它时,计数器值减1,这样只要计数器的值不为0,就说明还有地方引用它,它就不是无用的对象
可达性分析算法:当一个对象到GCRoots没有任何引用链相连(GCRoots到这个对象不可达)时,就说明此对象是不可用的,是死对象。
GCRoots是垃圾收集的起点,可以作为GCRoots的有虚拟机栈中本地变量表中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(Native方法)引用的对象。

45.常用垃圾回收器?
Jdk1.7.1.8新生代使用Parallel Scavenge(复制),老年代使用Parallel Old(标记整理)

jdk1.9 默认G1,被它自己淘汰.

jdk14 zgc jdk17用的zgc. 不收费

46.常用垃圾回收算法?
常用的垃圾回收算法有三种:标记-清除算法、复制算法、标记-整理算法,分代回收
标记清除算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象
缺点:标记和清除两个过程效率都不高;标记清除之后会产生大量不连续的内存碎片。

复制算法:把内存分为大小相等的两块,每次存储只用其中一块,当这一块用完了,就把存活的对象全部复制到另一块上,同时把使用过的这块内存空间全部清理掉,往复循环。缺点:实际可使用的内存空间缩小为原来的一半

标记整理算法:先对可用的对象进行标记,然后所有被标记的对象向一端移动,最后清除可用对象边界以外的内存

分代收集算法:把堆内存分为新生代和老年代,新生代又分为Eden区、FromSurvivor和ToSurvivor。一般新生代中的对象基本上都是朝生夕灭的,每次只有少量对象存活,因此新生代采用复制算法,只需要复制那些少量存活的对象就可以完成垃圾收集;老年代中的对象存活率较高,就采用标记-清除和标记-整理算法来进行回收。

  1. 怎么定位堆溢出问题
    设置堆大小,捕获jvm日志,用java自带的java Visual VM (日志分析工具)分析日志,找出原因,修改代码

48.为什么关系型数据库需要优化?
(1)高并发读写–集群。网站的用户并发性非常高,往往达到每秒上万次读写请求,对于传统关系型数据库来说,硬盘I\O是一个很大的瓶颈。
(2)海量关系读写–分表分库
网站产生的数据量是巨大的,对于关系型数据库来说,在一张包含海量数据的表中查询,效率是非常低的
(3)高扩展性和可用性
在基于web的结构(即浏览器/服务器)当中,数据库是最难进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,数据库却没有办法简单的通过添加更多的硬件和服务节点来扩展性能和负载能力。对于很多需要提供24小时不间断服务的网站来说,对数据库系统进行升级和扩展 是非常痛苦的事情,往往需要停机维护和数据迁移.

49.对数据库性能影响的因素?
(1)商业需求对性能的影响
(2)数据库设计对性能的影响
(a)某些数据不适合基于数据库查询
(b)热点数据高频查询
(c)多余的数据库交互,重复的SQL
(d)错误的表设计
(e)不合理的索引设计
(f)高并发支持
(g)海量数据支持
(h)扩展性和可用性
(3)SQL编写的不合理对性能的影响
(a)太多表join
(b)SQL太复杂
(c)不合理的查询结果
(4)硬件环境
(5)网络环境

50.常见的关系型数据库的优化技术整体介绍?
对关系型数据库的优化是一个综合性的技术,主要包括:
(1)找出执行效率低的SQL(定位慢查询)–发现问题
(2)分析慢SQL,explain sql --分析问题
(3)优化 --解决问题
常见优化手段
单机
合适的表结构3NF和反3NF
合适引擎
索引
分表(垂直分表和水平分表)
SQL优化技巧
升硬件
升网络带宽

多机
集群(读写分离,让多台服务器提供服务)
分布式(把不同的业务分给不同的集群处理)

其他方案
Redis缓存
elasticSearch全文检索
页面静态化

51.MySQL服务端的组成?
MySQL的服务端主要分为server层和存储引擎层
Server层:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog日志模块。
存储引擎: 主要负责数据的存储和读取,采用可以替换的插件式架构,支持InnoDB、MyISAM、Memory等多个存储引擎,其中InnoDB引擎有自有的日志模块redolog 模块。InnoDB 5.5版本作为默认引擎。

52.找出慢SQL可以用那些语句?
show status like ‘uptime’;Mysql运行多久
show status like ‘%Com_%’
show status like ‘%Com_select%’
show status like ‘%Com_insert%’
show status like ‘%Com_update%’
show status like ‘%Com_delete%’;CRUD执行次数
show status like ‘connections’ ;查询所有连接数
show status like ‘%Innodb_rows%’;查看INNODB引擎的CRUD行数
show status like ‘Max_used_connections’;查看服务器响应的最大使用连接数
show status like ‘slow_queries’;查询慢查询次数
show variables like ‘long_query_time’ ;可以显示当前慢查询时间 , 默认阈值 10s
show variables like ‘%slow_query_log%’; 查看慢查询的日志路径
show processlist 查看正在执行的连接-死锁

53.什么时候开启慢查询日志?
开启慢查询日志记录是会影响性能的,什么时候开启慢查询,开启后,覆盖所有功能跑一遍,没有才是暂时没有。
敏捷迭代: 确定迭代计划(要开发哪些功能,要改哪些bug)-分配对多个研发人员研发/分配给测试写测试用例----并行开发(本地)—开发者 开发完成后,需要统一打包,统一部署,统一验证,可以在自验环境开启慢查询记录自验(git代码打包部署开发环境)-转测试(部署测试环境覆盖测试)测试人员需要测试所有功能。 可以在测试环境开启慢查询记录.-- 应该有一条测试用例,测试完成后,到慢查询日志中找是否有慢查询-改bug-这个版本结束-多个迭代都开发完成-上线 开一段时间,把它关了.或者不开。用户用了所有功能。

54.数据库表设计的三范式?
(1)表的列具有原子性,不可再分解,即列的信息,不能分解,只要数据库是关系型的数据库,就自动的满足1NF,关系型数据库中是不允许分割列的,即列唯一。
(2)表中的记录是唯一的,通常设计一个主键来实现,即行唯一
(3)即表中不要有冗余数据,表的信息如果能被推导出来,就不应该单独的设计一个字段来存放,可以理解为:A表不能出现B表中非主键字段
。外键
反3NF:3NF指的是A表不能出现B表中非主键字段,如果出现了,那么这个字段就是冗余字段,违反了三范式。

55.使用冗余字段减少join连表提高查询效率?
(1)执行一条SQL,触发一个触发器执行
(2)触发器
(3)存储过程,封装很多SQL逻辑,实现某种业务逻辑。不用多次执行SQL,提前编译只需要调用就OK
(4)视图:把复杂查询封装为视图,以后只需要调用视图就OK,

56.MySQL中常见的存储引擎和区别?
mysql:myisam,innodb,memory
区别:memory存在内存中。

  • 事务安全:MYISAM不支持事务,INNODB支持
  • 查询和添加速度:MyISAM速度快,INNODB速度慢(因为要回表)
  • 锁机制 MyIsam表锁 innodb行锁
  • 支持全文索引:都支持全文索引
  • 外键 MyISAM 不支持外键约束, INNODB支持外键. (通常不设置物理外键,通常是在程序中保证数据的一致)
    使用场景:
  • MyISAM存储引擎:如果表对事务要求不高,同时是以查询和添加为主的,我们考虑使用myisam存储引擎. 比如 bbs(论坛,博客) 中的 发帖表,回复表. —项目实战:车辆百科,文章
  • INNODB存储引擎: 对事务要求高,保存的数据都是重要数据,我们建议使用INNODB,比如订单表,账号表.
  • Memory 存储:比如我们数据变化频繁,不需要入库,同时又频繁的查询和修改,我们考虑使用memory, 速度极快.

57.MySQL中常用的索引?
MySQL中常用的索引分类有:
普通索引Normal:允许重复的值出现,可以在任何字段上面添加
唯一索引unique:除了不能有重复的记录外,其他和普通索引一样,可以在值是唯一的字段添加,可以为null,并且可以有多个null
主键索引:是随着设定主键而创建,也就是在某个列设为主键的时候,数据库就会给该列创建索引,这就是主键索引,唯一且没有null值。
全文索引:用来对表中的文本域(char,varchar,text,bigtext)进行索引, 全文索引针对MyISAM有用InnoDB不支持全文索引,所以一般不用,默认只支持英文. -使用ES,Lucene代替就ok
空间索引: 空间索引是对空间数据类型的字段建立的索引,MySQL 中的空间数据类型有 4 种:geometry、point、linestring、polygon。MySQL 使用 spatial 关键字进行扩展,使得能够用于创建正规索引类似的语法创建空间索引。创建空间索引的列,必须将其声明为 not null。 空间索引只能在存储引擎为 MyISAM 的表中创建

58.MySQL中常见的存储引擎支持索引方式?
MySQL中常用的存储引擎是MyISAM和InnoDB,常用的索引方式是B-tree索引,也称为普通索引,是最常见的索引类型,适合用于全值匹配查询和前缀查询,底层用到b+树。
B-tree索引采用B+树的结构,

  1. 所有数据都存储在叶子节点中,而非叶子节点只包含指向下一级节点的指针,可以加速范围查询和区间查询。
  2. 在MySQL中,每个索引项包含一个关键字和一个对应的行指针,行指针指向数据表中的某一行。
  3. MySQL B-tree索引的分裂和合并策略较为灵活,能够自适应地调整节点大小,避免出现空洞和浪费空间。
  4. MySQL支持在B-tree索引的基础上建立前缀索引和全文搜索索引等,以满足不同的查询需求。

59.MySQL中b-tree索引方式的底层原理?
InnoDB使用了b+tree作为索引结构,在InnoDB中索引和数据在同一个文件,所以数据会存储在索引结构中。
myisam的数据和索引是分开的,所以树的节点指向的是数据的地址。所以Myiasm的索引不存储数据,而是存储数据的磁盘地址。

  • B+Tree属于多路树,每次查询都要走到叶子节点,查询效率稳定
  • 非叶子节点不存储完整数据,而是存储键值 KEY ,和子树节点的引用,可以存储更多的KEY,充分利用每个节点的存储空间,减少了节点数,树高变矮,IO次数变少,性能更高。
  • 叶子节点存储完整数据,叶子节点是有序的,每个叶子节点指向下一个节点的引用,形成一个链表,适合范围查询。

在innoDB中,对于主键默认会创建主键索引,其他列创建的索引就叫辅助索引,也叫二级索引,辅助索引的叶子节点存储的是主键索引的键值,这就意味着辅助索引需要查询两个B+Tree。辅助索引扫描完之后还会扫描主键索引,这叫回表。而myisam不需要回表,所以查询效率高

60.如果表没有主键会怎么办?

  1. 如果没有主键,Mysql会选择第一个不包含null的唯一索引作为主键索引,
  2. 如果不满足条件一,那么会选择一个隐藏的行RowID作为主键索引

61.为什么b-tree索引使用B+tree?

  • 查询效率稳定:B+Tree属于多路树,每次查询都要走到叶子节点,查询效率稳定
  • 磁盘读写能力强:非叶子节点不存储完整数据,而是存储键值 KEY ,和子树节点的指针,这样每个节点就可以存储更多的KEY,充分利用每个节点的存储空间 16KB,减少了节点数,树高变矮,IO次数变少,性能更高。
  • 排序能力强:叶子节点存储完整数据,叶子节点是有序的,每个叶子节点指向下一个节点的 应用,形成一个链表,适合范围查询,和对排序支持友好

62.什么是CAP理论?哪些是AP,哪些是CP?讲几个案例
一致性(Consistency)可以理解为分布式中存在多个数据副本,当其中某个数据副本发生数据更新需要实时同步到其他节点上。多个节点数据总是保持一致。
可用性(Availability)客户端在任何时候访问集群,请求都可以正常返回结果,可以允许有一定的延迟。不能上锁要能支持随时能用。
分区容错性(Partition tolerance)-部署多个节点。能够容忍当服务之间通信发生故障,整个集群被分割为多个无法相互通信的分区的情况。即当集群因为网络故障被划分为多个分区,集群仍然可用。重试+集群(放到不同的分区更好)
满足CA舍弃P,也就是满足一致性和可用性,舍弃容错性。但是这也就意味着你的系统不是分布式的了,因为涉及分布式的想法就是把功能分开,部署到不同的机器上。单个mysql服务器
满足CP舍弃A,也就是满足一致性和容错性,舍弃可用性。如果你的系统允许有段时间的访问失效等问题,这个是可以满足的。就好比多个人并发买票,后台网络出现故障,你买的时候系统就崩溃了。 redis.set(key,value) 当我在进行操作的时候其他的要进行等待或者超时异常:如 Redis zookeeper consol nacos(默认ap,可以修改cp,可配置)

满足AP舍弃C,也就是满足可用性和容错性,舍弃一致性。这也就是意味着你的系统在并发访问的时候可能会出现数据不一致的情况。 Mysql主从同步 eureakanacos默认实现也是ap

因为网络不可控问题,分区容错总是存在的,所以对于一个分布式系统来说,必须具备分区容错性,即需要在AP和CP做选择。 一般选ap,但是要使用最终一致性这种弱一致性

63.什么是垂直分库分表?什么是水平分库分表?
垂直分表-宽表拆分:垂直分表可以理解成按列分表,比如一个用户表中包括了 用户登录相关信息,用户基本资料,用户账户信息等等信息,这个表字段太多变得非常庞大,查询的时候必定会有性能影响。
垂直分库-按业务分库:垂直分库就是把一个数据库中的N张表,按照模块/业务 划分到多个Mysql数据库,每个数据库都有自己的服务器,进行分布式部署。[注意]微服务架构:天然就是分库. 它要求各自服务都要有自己数据库.

水平分表:按行分表,一个表中的数据量一千万行,查询注定慢,我们可以把这个表中的数据拆分成10个小表,每个表一百万行数据,每个小表拥有相同的列。分表规则有:按区间范围分表 ,按时间分表,按hash分表,按雪花算法
水平分库:是把一个数据库中的表分到多个数据库中,数据库采用分布式部署

分库分表操作思路:
1 按照业务进行垂直分库 商品和店铺隔离开来 微服务架构都要这样干
2 把商品业务进行水平分库 多个商品库服务器
3 可以对水平分库后每一个数据库服务器进行集群. 多个商品库服务器群
4 进行垂直分表 把多字段表拆分少量字段表. 每一个库里面宽表进行拆分
5 进行水平分表 把海量数据表拆分为多个小表 把每一个大表进行拆分

64.多少数据量适合分表?
500w数据量就适合分表

65.分表的规则有哪些,你们是按照什么规则分表的,具体算法是什么?
分表规则有:按区间范围分表 ,按时间分表,按hash分表,按雪花算法
按照hash算法分库分表,雪花算法分库分表

66.你们分表后的ID怎么生成?
shardingjdbc配置了雪花算法,自动生成

67.你们使用什么技术分表的?
shardingjdbc

68.shardingJDBC怎么配置读写分离?
在shardingJDBC官方文档中,拷贝读写分离的配置文件,粘贴到配置文件中,修改主从数据库

69.什么是MySQL主从复制?
MySQL主从复制是指数据从一台MySQL数据库服务器的主节点复制到一个或多个从节点的过程。这是一种异步的复制方式,默认采用基于MySQL数据库自带的二进制日志功能的复制方法。这种复制方式可以让主库负责写操作,从库负责读操作,从而在主库出现锁表等故障时,可以通过读从库来保证业务的正常运作。此外,主从复制还可以实现数据的热备份,扩展架构,降低磁盘I/O访问频率,提高单个机器的I/O性能。

70.主从复制解决什么问题?不能解决什么问题?
解决了:可以让主库负责写操作,从库负责读操作,从而在主库出现锁表等故障时,可以通过读从库来保证业务的正常运作。此外,主从复制还可以实现数据的热备份,扩展架构,降低磁盘I/O访问频率,提高单个机器的I/O性能。
没有解决:数据库之间出现主从复制延时的情况。从库机器负载,如IO、CPU等,以及主库写操作与从库自身压力过大的问题

71.主从复制的原理是什么?
主从复制的原理是将一个数据库的数据复制到另一个数据库中,从而实现数据的备份和高可用性。在主从复制中,有一个主数据库和多个从数据库,主数据库负责写入数据,从数据库则负责读取数据。当主数据库发生故障时,从数据库可以接管主数据库的工作,保证系统的正常运行。主从复制的原理非常简单,主数据库将写入的数据记录在日志文件中,从数据库通过读取主数据库的日志文件来获取数据。当从数据库连接到主数据库时,主数据库会将日志文件中的数据发送给从数据库,从数据库则将这些数据保存到自己的数据库中。当主数据库发生故障时,从数据库会自动接管主数据库的工作,成为新的主数据库,从而保证系统的正常运行。

72.大概说一下主从复制要做那些事情?
(1)配置主数据库:在主数据库上启用二进制日志,并设置主数据库的唯一标识符(例如,server-id)。
(2)配置从数据库:从数据库需要记录主节点的IP地址和端口号,以及主节点的唯一标识符。
(3)启动从数据库的I/O线程:从数据库需要启动一个I/O线程,用于读取主数据库的二进制日志。
(4)建立主从复制关系:在从数据库上执行“CHANGE MASTER TO”命令,指定主数据库的IP地址、端口号和日志文件等参数。
(5)启动主从复制进程:从数据库启动复制进程,开始从主数据库读取二进制日志并写入中继日志中。
(6)进行复制操作:从数据库通过重放中继日志中的事件来同步与主数据库的数据。

73.有没有在项目中使用过数据库优化?
(1)在设计表的时候就垂直分表、设计表的三范式、和反第三范式、创建表的时候选择合适的引擎、给查询量大的表创建索引、硬件、买服务器、主从、

74.seata的两阶段事务提交机制?
第一阶段:注册分支事务-记录快照-执行业务SQL-提交、报告事务状态
第二阶段:根据报告的事务状态-可以提交,删除快照或者不可以提交,读取快照,恢复数据。

75.SQL优化的思路?
单体优化:慢SQL的优化、引擎、索引、三范式
多机优化:集群、主从、分布式

76.说一下Redis-cluster集群是怎么存储数据的?
redis-cluster是redis3.0实现集群方案,采用无中心结构,基于hash槽位实现,多个主节点按区间均分16384个槽位 ,并且为做数据冗余,还可以为每个主节点配置多个节点.集群中的每个节点都是平等的关系,都是对等的,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据
存储数据的时候,以key来用hash算法算出槽位编号:slot=hash(key),再通过槽位编号找到对应的节点,进而确定你要操作的服务器,如果连接的是这台服务器就直接操作,否则就转到要操作的那个服务器进行操作。
获取数据也是一样,先计算槽位,进而确实操作服务器,直接或跳转后操作。

77.Redis内存不够了怎么办?
(1)增加Redis存储空间:可以增加物理磁盘或者通过配置虚拟内存来增加Redis的存储空间。
(2)删除不需要的数据:通过删除过期数据、清除不需要的数据类型或使用LUA脚本批量删除数据来释放空间。
(3)增加Redis数据库数量:通过增加Redis数据库数量来分散存储压力,并定期清理不需要的数据库。
(4)使用内存优化技术:通过使用Redis内存优化技术,如使用压缩、共享对象等来降低内存消耗。
(5)使用数据归档:将不再需要频繁访问的历史数据存储到磁盘上,以节省Redis的内存空间。

78.Redis集群方式有哪些?
Redis主从复制:没有解决单点故障,解决了高并发,海量数据没有解决
Redis哨兵模式:解决了单点故障问题,解决了高并发问题,没有解决海量数据问题
Redis-cluster集群:全部解决

79.Redis主从是如何同步数据(主从原理)?
Redis主从同步数据分为:全量复制和增量同步
全量复制:

  1. 从服务器连接主服务器,发送SYNC命令;
  2. 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
  3. 主服务器BGSAVE执行完后,向所有从服务器发送快照文件(rdb文件),并在发送期间继续记录被执行的写命令;
  4. 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
  5. 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
  6. 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令
    增量同步:
    Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

80.Redis为什么那么快?
(1)Redis的主要操作是内存操作,内存操作的速度通常比磁盘操作快得多。因为内存中的数据可以立即被读取,而不需要像磁盘操作那样需要额外的物理磁盘寻址时间。
(2)Redis采用了多路I/O复用技术,这意味着它能够使用单个线程高效地处理多个网络连接。当一个连接在等待I/O操作时,Redis会把这个连接从事件循环中移除,然后处理其他的连接。这种技术能够有效地利用CPU时间,避免因为等待I/O操作而造成的时间浪费。
(3)Redis的数据结构简单,虽然它也有一些复杂的结构,比如哈希表和有序集合,但相比其他数据库,它的复杂性要低很多。这使得Redis在实现数据结构时可以更专注于性能优化。
(4)Redis的代码经过了精心的优化,这使得它在处理内存操作和网络连接时能够尽可能地减少系统调用和额外的处理开销。

81.简单说一下主备切换时Redis如何选举master的?
主备切换分为哨兵模式和Redis-cluster集群
在哨兵模式下:
当哨兵发现了主服务器客观下线,就需要故障恢复,故障恢复由领头哨兵来完成,来保证同一时间只有一个哨兵来执行故障恢复,选出领头哨兵后,领头哨兵就会对主数据库进行故障恢复。
首先是从主服务器的从服务器中选出一个从服务器作为新的主服务器。选出之后通过slaveif no ont将该从服务器升为新主服务器。所有在线的从数据库中,选择优先级最高的从数据库。优先级通过replica-priority参数设置,优先级相同,则复制的命令偏移量越大(复制越完整)越优先 , 如果以上都一样,则选择运行ID较小的从数据库。
选出一个从数据库后,领头哨兵将向从数据库发送SLAVEOF NO ONE命令使其升格为主数据库,而后领头哨兵向其他从数据库发送 SLAVEOF命令来使其成为新主数据库的从数据库,最后一步则是更新内部的记录,将已经停止服务的旧的主数据库更新为新的主数据库的从数据库,使得当其恢复服务时自动以从数据库的身份继续服务。

在Redis-cluster集群下
master节点的选举是通过ping-pong机制实现的。当一个slave发现自己的master变为FAIL状态时,会尝试发起选举,以期成为新的master。
选举过程如下:
slave发现自己的master变为FAIL状态。
slave将自己的epoch(即当前轮次)加一,并广播 FAILOVER_AUTH_REQUEST 给集中的每一个master。
master接收到请求后,只有master响应,判断请求者的合法性,并发送结果。
slave收集master返回的结果,收到超过半数master的同意后变成新Master。
slave通过广播 Pong 消息通知其他集群节点,通知它们自己已经成为新的master。

82.Redis的优化方案?
(1)配置优化:(a)最大连接数10000(b)内存优化(c)持久化(d)安全配置:设置访问密码,bind安全访问配置
(2)代码优化:(a)避免过度使用键,Redis中的键是非常快速的查找方式,过度使用键会导致Redis性能下降。(b)键不能太长,不能太多,命名要规范(c)优化lua脚本,Redis可以通过lua脚本将多个命令组合成一个原子操作,lua脚本应该尽量简单和高效,以避免占用过多资源(d)使用pipeline,pipeline可以将多条操作命令打包发送给服务器,从而减少网络通信开销和等待时间
(3)集群优化:主从、哨兵、Redis-cluster

83.ElasticSearch为什么做全文检索比数据库使用like快?
比数据库模糊查询高是基于倒排索引
正排索引:通过文档或者数据库中行,找字段 select name from t_user where id =1
倒排索引: 先找字段(keyword),确定文档id,再通过文档id找到文档

84.说说你理解的ES分片(Shard)机制
创建索引会指定有多少primary shard,为了冗余还为每个primary shard创建多个replica shard
我们保存的文档只会在特定一个primary shard中,以及它的raplca shard! 解决了单点故障,支持高并发(多个primary shard同时并发,读写分离),海量数据存储(每个节点放到数据都是不一样)!

85.什么情况下ES集群不可用
有的primary shard分配不了,丢失数据.

86.描述一下ES添加文档的过程?
(1) 客户端请求一个协调节点coordinating node(负载均衡节点) ,时时刻刻在做shard的发现
(2) 协调节点根据算法选择一个primary shard: 算法 hash(document_id) % (num_of_primary_shards) 0 1 2 3 4
(3) 对应的primary shard 所在节点保存完数据后,将数据同步到replica node。
(4) 协调节点coordinating node 发现 primary node 和所有 replica node 都搞定之后返回结果给客户端

87.详细描述一下Elasticsearch获取文档的过程?
(1) 客户端请求一个协调节点coordinating node
(2) coordinate node 根据算法hash(document_id) % (num_of_primary_shards),找到该primary_shard所对应的replica shard.,此时会使用 round-robin随机轮询算法,在 primary shard 以及其所有 replica 中随机选择一个,让读请求负载均衡
(3) 接收到请求的 node 返回 document 给调节点 coordinate node。
(4) coordinate node 返回 document 给客户端。

88.详细描述一下Elasticsearch搜索过程 ?
(1) 在初始查询阶段时,查询会广播到索引中每一个分片拷贝(主分片或者副本分片)。
(2) 每个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。PS:在搜索的时候是会查询Filesystem Cache的,但是有部分数据还在Memory Buffer,所以搜索是近实时的。
(3) 每个分片返回各自优先队列中所有文档的 ID 和排序值给协调节点,协调节点它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
(4) 接下来就是 取回阶段,协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每个分片加载并 丰富 文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了,协调节点返回结果给客户端。

总结:查询请求 -> 每个分片执行查询,结果(文档ID,排序值)转到优先队列 -> 每个分片返回队列数据给协调节点 -> 协调节点处理全局排序 -> 根据文档ID去每个分片查询完整数据 -> 返回客户端。

89.详细描述一下Elasticsearch更新和删除文档的过程?

  • 删除和更新也都是写操作,但是Elasticsearch中的文档是不可变的,因此不能被删除或者改动以展示其变更; 磁盘上的每个段都有一个相应的.del文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del文件中被标记为删除的文档将不会被写入新段。
    在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。

90.es中分为几种节点类型?作用分别是什么?

  • 主节点master
    node.master=true,代表该节点有成为主节点资格,主节点的主要职责是和集群操作相关的内容,如创建或删除索引,跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点。一般会把主节点和数据节点分开,node.master=true , node.data=false
  • 数据节点data(shard)
    node.data=true,数据节点主要是存储索引数据的节点,主要对文档进行增删改查操作,聚合操作等,数据节点对CPU,IO,内存要求较高,优化节点的时候需要做状态监控,资源不够时要做节点扩充。配置:mode.master=false,mode.data=true
  • 负载均衡节点client
    当主节点和数据节点配置都设置为false的时候,该节点只能处理路由请求,处理搜索,分发索引操作等,从本质上来说该客户节点表现为智能负载平衡器。配置:mode.master=false,mode.data=false

91.怎么实现一个单例模式?
(1)构造方式私有化
(2)定义成员变量不赋初始值(懒)
(3)提供获取实例的静态方法:第一次调用时创建对象,同步代码块保证创建实例的代码的原子性,只会创建一个实例,双重判断,加关键词volatile防止重排序

92.rocketmq的执行流程?
RocketMQ主要由四个组件构成:
(1)Producer:它是消息的生成者,负责生成消息并发送到Broker服务器。RocketMQ提供了多种发送方式,如同步发送、异步发送、顺序发送、单向发送。同步和异步方式需要Broker返回确认信息,而单向发送则不需要。
(2)Broker:它负责存储和管理消息。在物理部署中,每个Broker可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的Broker。Broker在收到消息后,会将其转发给Consumer。
(3)Consumer:它是消息的消费者,从Broker服务器获取消息,然后处理这些消息。对于消费者来说,提供了两种消费形式:拉取式消费和推动式消费。
(4)NameServer:这是一个无状态节点,充当路由消息的提供者。Producer或Consumer能够通过NameServer查找各主题相应的Broker IP列表。
这四个组件共同协作,完成了RocketMQ的消息生产和消费过程。如需了解更多细节,建议查阅官方文档或相关资料获取更多信息。

RocketMQ的执行流程如下:
(1)启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,充当路由控制中心。
(2)Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。
(3)收发消息前,先创建Topic。创建Topic时,需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
(4)Producer发送消息到RocketMQ集群,RocketMQ集群中所有的Broker节点都可以接收消息,然后分发给对应的Consumer。
(5)Consumer通过指定主题和标签来订阅消息。当有新消息到达时,Broker会根据订阅关系将消息推送给对应的Consumer。
(6)当Broker将消息推送给Consumer时,会根据Consumer拉取消息的类型来选择消息推送方式:Push模式或Pull模式。
(7)Consumer接收到消息后,会进行相应的处理。处理结束后,Consumer向Broker发送确认消息,表示该消息已经被处理完毕。
(8)Broker收到确认消息后删除确认消息。

93.循环依赖是什么意思?
循环依赖是指在两个或更多的bean之间存在相互依赖的情况,使得它们无法正常地被实例化和注入依赖。在Spring中,循环依赖通常发生在两个或更多的bean相互注入时,因为每个bean都依赖于另一个bean的存在。

94.怎么解决循环依赖的问题?
spring中是使用setter方式解决循环依赖,主要是使用到了三级缓存:
(a)一级缓存,用来缓存实例化好的bean,单例bean缓存池
(b)二级缓存,用来缓存正在创建的bean
(c)三级缓存,用来缓存创建bean的实例工厂ObjectFactory
(1)假如有两个bean,a依赖b,b依赖a,当实例化好a,在属性注入环境,发现a依赖了b,会先将正在创建的a的实例工厂ObjectFactory放入三级缓存,再去创建b的实例
(2)在bean的实例化流程创建b,在b的属性注入环节,发现b依赖了a,这个时候就会去三级缓存中,找到a的创建工厂ObjectFactory获取a的实例,并注入到b中,此时b就实例化好了,然后将b的实例放入一级缓存中,最后将b实例注入到a中,a也就创建好了
(3)在getBean的时候,如果单例Bean缓存池没有Bean,就会走二级缓存尝试获取,如果也没有,就会走三级缓存拿到Bean的ObjectFactory创建bean,然后将bean放入二级缓存

95.说几个spring中的IOC容器工厂?
BeanFactory:ioc的容器顶层接口,提供了bean获取的基础方法
DefaultListableBeanFactory:整个bean加载的核心部分,spring注册及加载Bean的默认实现
ApplicationContext:除了实现IOC基本功能外,还扩展了国际化支持,资源访问,事件发布
ClasspathXmlApplicationContext:从classpath中获取XML配置
AnnotationConfigApplicationContext:使用注解配置类来定义bean

96.springMVC的执行流程?
(1)由中央调度器DispatcherServlet接收请求。
(2)中央调度器DispatcherServlet把请求交给了处理器映射器handlerMapping。处理器映射器handlerMapping会根据请求的信息,找到处理此请求的处理器对象handler。
(3)处理器适配器根据处理器对象找到对应的处理器适配器对象(本例中的适配器对象是Controller)。每个处理器接口(本例MyController处理器对象对应的接口为Controller)都有一个适配器对象。
(4)通过执行这个handler里面的方法,会去找具体的controller方法。找到具体的Controller之后,会返回一个ModelAndView给处理器适配器。
(5)处理器适配器获取到ModelAndView之后,会将这个结果返回给DispatcherServlet前端控制器。
(6)DispatcherServlet前端控制器通过ViewResolver视图解析器进行解析这个ModelAndView,解析完成之后,会将这个view返回给前端DispatcherServlet前端控制器。

97.spring中事务失效的场景?
spring中事务失效的场景有:
(1)事务方法访问修饰符非public。
(2)@Transactional注解的方法抛出的异常不是Spring的事务支持的异常。
(3)数据表本身是不支持事务。
(4)@Transactional注解所在的类没有被Spring管理。
(5)catch掉异常之后,没有再次抛出异常。
(6)方法自身(this)调用问题。
(7)数据源没有配置事务管理器。
(8)传播类型不支持事务。
(9)多线程调用。

98.事务的传播机制?
Spring的事务传播机制是指当多个事务方法互相调用时(例如,A类中的a方法调用B类中的b方法,那么B类中的b方法是按照b方法的事务还是A类中a方法的事务来执行),事务如何在这些方法之间进行传播。

具体来说,Spring的事务传播机制有7种:
(1)propagation_required:这是Spring的默认传播级别。如果上下文中存在事务,则加入当前事务;如果不存在事务,则新建事务执行。
(2)propagation_supports:如果上下文中存在事务,则加入当前事务;如果没有事务,则以非事务方式执行。
(3)propagation_mandatory:该传播级别要求上下文中必须存在事务,否则抛出异常
(4)propagation_required_new:每次执行都会创建新事务,并同时将上下文中的事务进行挂起,执行完当前线程后再恢复上下文事务。
(5)propagation_not_supported:该传播级别要求上下文中不能存在事务,否则抛出异常。
(6)propagation_NEVER:与propagation_not_supported相同。
(7)propagation_NESTED:如果当前存在事务,则在嵌套事务内执行(仅在单个线程中有效)。

99.数据库的隔离级别?
数据库的隔离级别是指数据库管理系统为了处理并发访问而采用的一种机制,用于控制事务之间的相互影响。下面介绍四种常见的隔离级别:
(1)Serializable(串行化)
这是数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队。这种级别下,“脏读”、“不可重复读”、“幻读”都可以被避免,但是执行效率奇差,性能开销也最大,锁基本没人会用。
(2)Repeatable Read(可重复读)
可重复读,顾名思义,就是专门针对“不可重复读”这种情况而定制的隔离级别,自然,它就可以有效避免“不可重复读”。这也是MySQL的默认隔离级别。
(3)Read Committed(读以提交)
读以提交,顾名思义,就是只能读到已经提交了的内容。该级别适用于大多数系统。
(4)Read Uncommitted(读未提交)
读未提交,顾名思义,这就是可以读到未提交的内容。因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。

100.mvcc机制?
MVCC(多版本并发控制)是一种并发控制的方法,被广泛应用在数据库管理系统中,实现对数据库的并发访问。
在MVCC机制中,每个事务在开始时都会记录下它的系统版本号。每当我们对数据进行查询时,必须去检查每行数据的版本号是否和事务的版本号相同。如果数据项的版本号小于事务的版本号,那么这个事务就不能看到这个数据项,因为事务开始的时候,这个数据项可能已经被其他事务修改或者删除。
另外,MVCC通过保存某个时间点上的数据快照,使得事务可以看到一个一致的数据视图。即使事务需要执行很长时间,或者有其他事务也在运行,事务在开始时看到的数据视图不会随着时间而改变。这样就可以避免使用锁,大大减小了并发控制的开销。
同时,各个存储引擎对于MVCC的实现是各不相同的。比如在InnoDB中,通过为每一行记录添加两个额外的隐藏的值来实现MVCC,这两个值标记着行数据何时被创建和何时过期(或者被删除)。但是InnoDB并不存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。
请注意,虽然MVCC可以提供更小的开销,但它也有一些局限性,例如可能会导致不同的事务在同一个时间点看到的同一个表的数据可能是不同的。

101.线程池的参数
线程池的参数包括以下几种:
(1)corePoolSize:线程池中的常驻核心线程数。
(2)maximumPoolSize:线程池能够容纳同时执行的最大线程数。
(3)keepAliveTime:多余的空闲线程存活时间。
(4)unit:keepAliveTime的单位。
(5)workQueue:任务队列,被提交但尚未被执行的任务。
(6)threadFactory:表示生成线程池中工作线程的线程工厂。
(7)handler:拒绝策略,表示当线程队列满了并且工作线程大于等于线程池的最大显示数时如何来拒绝。

102.线程池的提交方法execute和submit?
execute:子线程可能在主线程结束之后结束;
submit:因为提交任务后有个取数据的过程,在从Future取数据的过程中,Callable自带的阻塞机制,这个机制保证主线程一定在子线程结束之后结束。反之如果没有取数据,子线程可能会在主线程结束之后才结束。

103.Redis的AOF重写的时候会不会block线程?
Redis在重写AOF文件时,主线程会被阻塞,以防止在重写期间遗漏新的命令。但是,Redis使用写时复制技术来最小化对性能的影响,即在重写期间,新的命令会被追加到旧的AOF文件中,并在重写完成后将旧的AOF文件删除。需要注意的是,一些命令在重写AOF文件期间会被阻塞,例如DEL和FLUSHALL,因为它们需要等待AOF重写完成以确保数据一致性。为了最小化AOF重写对性能的影响,可以通过调整Redis配置中的相关参数来控制AOF重写的频率和触发条件。

104.分布式锁是一种在分布式系统中实现并发控制的技术,它的主要作用是避免多个进程或线程同时访问和修改同一资源,保证系统的数据一致性和安全性。
分布式锁的实现方式有很多种,其中包括:
(1)基于数据库实现:通过在数据库中创建锁表或使用数据库的行级锁来实现分布式锁。
(2)基于Redis实现:Redis是一个高性能的内存数据库,具有丰富的数据类型和命令支持,可以使用Redis的setnx命令或Redlock算法来实现分布式锁。
(3)基于Zookeeper实现:Zookeeper是一个分布式协调服务,可以用来实现分布式锁。通过在Zookeeper中创建临时节点或有序节点来实现锁的请求和释放。
(4)基于etcd实现:etcd是一个高可用的分布式键值对存储系统,可以配合使用put和get命令来实现分布式锁。
(5)在使用分布式锁时,需要注意以下几个方面:
(a)互斥性:分布式锁的核心作用是保证互斥性,即在同一时刻只有一个进程能够获得锁并执行相应的操作。
(b)安全性:在使用分布式锁时,需要保证锁的安全性,防止出现死锁和饥饿现象。
(c)可扩展性:分布式锁的实现需要具有可扩展性,能够支持大规模的并发访问。
(d)可靠性:分布式锁的实现需要具有可靠性,能够保证在系统故障或异常情况下的正确性和可用性。

105.如何解决幻读?
幻读(Phantom Read)是数据库事务并发控制中可能出现的一种问题,主要针对插入操作。简单来说,幻读问题是事务在查询时未被读取的数据会被其他事务插入,导致事务感觉产生了幻觉。
幻读问题的解决方式:
将数据库的隔离级别设置为Serializable。这样就可以对所有的事务进行串行化,幻读问题自然就解决了。但是,这也意味着所有的事务都需要等待前一个事务完成后才能进行,导致并发性能下降。
在可重复读的隔离级别下,MySQL的InnoDB引擎引入了间隙锁(Gap Lock)来防止幻读。当一个事务在对某些范围进行查询时,InnoDB会在那些没有数据的位置上添加锁,防止其他事务在这些位置插入新的数据。这样可以解决幻读问题,同时也不会降低并发性能。

106.MySQL中如何实现行锁?
在SQL语句中最后加for update

107.public、protected、default、private的使用范围?
public:可以被任何其他类访问,包括不同包中的类。
protected:可以被同一个包中的其他类以及不同包中的子类访问。
default(包级访问权限,没有显式指定访问修饰符):只能被同一个包中的其他类访问。
private:只能被同一个类中的方法或变量访问,其他任何类都无法访问。

108.构造方法的调用方法?
构造方法的调用顺序是先静态、后动态,先父类、后子类,先变量/初始化块,后构造方法

109.MySQL中常用到的一些函数?
MySQL提供了许多函数,用于处理和操作数据。以下是一些在MySQL中常用到的函数:
字符串函数:
CONCAT(str1, str2, …): 连接两个或更多字符串。
SUBSTRING(str, start, length): 从字符串中提取子串。
LENGTH(str): 返回字符串的长度。
LOWER(str): 将字符串转换为小写。
UPPER(str): 将字符串转换为大写。
TRIM(str): 去除字符串首尾的空格。
REPLACE(str, find, replace): 替换字符串中的子串。

数值函数:
SUM(number1, number2, …): 计算数值的总和。
AVG(number1, number2, …): 计算数值的平均值。
MAX(number1, number2, …): 返回数值中的最大值。
MIN(number1, number2, …): 返回数值中的最小值。
COUNT(*): 计算行数。
ROUND(number, decimals): 四舍五入数值。

日期和时间函数:
NOW(): 返回当前日期和时间。
DATE(): 提取日期部分。
TIME(): 提取时间部分。
YEAR(): 提取年份部分。
MONTH(): 提取月份部分。
DAY(): 提取天数部分。
HOUR(): 提取小时部分。
MINUTE(): 提取分钟部分。
SECOND(): 提取秒数部分。

条件函数:
IF(condition, true_value, false_value): 根据条件返回不同的值。
CASE WHEN condition THEN result [WHEN …] [ELSE result] END: 多条件判断。

系统函数:
VERSION(): 返回MySQL服务器的版本。
USER(): 返回当前连接的用户名。
DATABASE(): 返回当前连接的数据库名。

其他函数:
MD5(str): 计算字符串的MD5散列值。
RAND(): 生成随机数。
GROUP_CONCAT(expression ORDER BY expression ASC/DESC SEPARATOR separator): 将多个值连接成一个字符串,并按指定顺序排序。
这只是MySQL中可用函数的一小部分,还有其他许多函数可用于处理数据和执行特定任务。具体函数的使用取决于您的需求和查询要求。

110.es写入数据的过程?
1)将数据写到内存缓存区memory buffer。
2)然后将数据写到translog缓存区。
3)每隔1s数据从buffer中refresh到FileSystemCache中,生成segment文件,一旦生成segment文件,就能通过索引查询到了。
4)refresh完,memory buffer就清空了。
5)每隔5s中,translog 从buffer flush到磁盘中。
6)定期/定量从FileSystemCache中,结合translog内容flush index到磁盘中。

Translog作用
ES会将每次写入数据同时写到translog日志中,防止ES宕机造成数据丢失保证可靠存储。
translog还用于提供实时CRUD。当尝试按ID检索,更新或删除文档时,它会首先检查translog中是否有任何最近的更改,然后再尝试从相关段中检索文档。

=================================================

微服务相关的

1 说一下你的微服务架构理解
微服务是一种架构风格,它将应用程序设计为一系列独立的服务,每个服务都负责处理一个特定的业务功能,并通过标准的接口与其它服务进行通信。这种架构风格使得每个服务都可以独立开发和部署,使得应用程序能够更好地适应变化和扩展。

微服务架构风格基于几个关键的原则:
单一责任原则:每个服务都应该只有一个责任,负责完成一项特定的任务。
独立部署原则:每个服务都可以独立部署和升级,不需要其他服务的支持。
轻量级通信协议:服务之间的通信应该使用轻量级的通信协议,如 RESTful API 或者 gRPC 等。
独立数据库:每个服务应该拥有自己的数据库,避免数据一致性的问题。
最小权限原则:每个服务都应该只有完成其任务所需的最小权限。

微服务架构风格的优势包括:
(1)灵活性和可扩展性:由于每个服务都是独立的,因此可以根据需要随时升级和替换服务,而不影响整个应用程序的其它部分。
(2)快速开发和部署:由于每个服务都相对简单,因此可以更快地开发和部署。
(3)分布式管理的数据中心:微服务架构风格使得数据中心可以更加分布式,每个服务都可以在其自己的数据中心中运行。
然而,微服务架构风格也有一些挑战,例如服务间通信的开销、数据一致性问题、分布式系统的复杂性等。因此,在采用微服务架构风格时,需要仔细考虑这些因素。

2 你们用什么来实现微服务架构
Spring Cloud Alibaba 是基于 Spring Cloud 构建的微服务开发一站式解决方案,包含多个组件和功能。以下是 Spring Cloud Alibaba 的主要组件及其作用:
Nacos:Nacos 是阿里巴巴开源的一款更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它主要提供注册中心和配置中心的功能,可以代替 Eureka 和 Apollo 两个组件。Nacos 可以实现服务的注册和发现,以及配置的管理和同步,使得配置更改时自动刷新,有助于实现服务的动态管理和协同。
Sentinel:Sentinel 是阿里巴巴开源的流量控制、熔断降级、系统负载保护的组件,可以与 Spring Cloud 集成。通过 Sentinel,可以实现对系统资源的实时监控,并自定义流量控制规则,有效地防止系统过载,提高系统的稳定性和可靠性。
Dubbo:Dubbo 是一款高性能的 RPC 通信框架,提供服务限流降级、负载均衡、容错处理等功能。它支持多种协议,如 Dubbo、HTTP、gRPC 等,并可以与其他微服务组件集成。通过 Dubbo,可以方便地实现服务间的通信和远程调用。
Seata:Seata 是一款高性能、简单易用的分布式事务解决方案。它支持多种事务模式,如 RPC、TCC、Saga 等,并提供了一体化的分布式事务管理平台。通过 Seata,可以在微服务架构中实现分布式事务的统一管理和处理。
Spring Cloud Gateway:Spring Cloud Gateway 是 Spring Cloud 的一个网关组件,提供高性能异步非阻塞的网关能力。它基于 Spring 5 和 Spring Boot 2 开发,支持 RESTful、OpenFeign 等多种 API 风格。通过 Spring Cloud Gateway,可以方便地实现服务的路由和过滤,提供统一的入口和出口。
RocketMQ:RocketMQ 是阿里巴巴开源的一款高性能、高可靠的消息中间件。它支持发布/订阅、广播、集群拓扑等模式,并提供了丰富的消息处理机制。通过 RocketMQ,可以在微服务架构中实现异步通信、解耦、削峰等效果。
分布式事务:Spring Cloud Alibaba 还提供了分布式事务管理的能力,通过 @GlobalTransactional 注解可以实现跨多个数据库的事务处理。同时,还支持多种事务管理器,如 Seata、RocketMQ 等,使得在微服务架构中处理分布式事务变得简单可靠。
以上是 Spring Cloud Alibaba 的主要组件及其作用,这些组件共同构成了微服务开发的一站式解决方案,为开发者提供了丰富的功能和便利性。

Spring Cloud Netflix 是 Spring Cloud 中的一个子项目,它提供了与 Spring Boot 集成的 Netflix OSS 组件,包括以下组件及其作用:
Eureka:Eureka 是一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。它是一个服务注册中心,可以替代 Zookeeper 等注册中心,简化服务的注册和发现过程。
Ribbon:Ribbon 是一个客户端负载均衡组件,它提供了一种声明式的方式来调用远程服务。通过 Ribbon,可以配置客户端负载均衡策略,如轮询、随机等,并可以与服务接口一起使用,实现透明化的服务调用。
Feign:Feign 是一个基于 Ribbon 和 Hystrix 的声明式服务调用组件,它简化了服务调用的过程。通过 Feign,只需在服务调用方添加注解,就可以实现与服务提供方的通信。Feign 还支持负载均衡和服务熔断等功能。
Hystrix:Hystrix 是 Netflix 开源的一个延迟和容错组件,用于处理服务依赖的延迟问题。通过 Hystrix,可以将服务的延迟和故障隔离,避免故障的扩散。它还提供了断路器、线程隔离、快速失败等功能。
Zuul:Zuul 是 Netflix 开源的一个 API 网关组件,它提供了一种对请求进行路由和过滤的能力。通过 Zuul,可以将多个服务的请求路由到一个统一的管理平台,实现服务的统一管理和调用。
以上是 Spring Cloud Netflix 的主要组件及其作用。这些组件可以与 Spring Boot 集成,提供了一套完整的服务治理解决方案,包括服务的注册与发现、负载均衡、容错处理、路由和过滤等功能。

3 你们项目中有没有涉及到服务间调用 A服务调用B服务出现网络闪断怎么解决
(1) 增加重试机制:在服务A中,可以增加一个重试机制,当调用服务B出现网络闪断时,自动重试调用。可以通过配置重试次数和重试间隔时间来控制重试的次数和频率。
(2) 使用熔断器:熔断器是一种保护机制,当服务调用出现异常或错误时,可以快速中断调用,避免故障扩散。在服务A中,可以使用熔断器来监控服务B的调用情况,当出现异常或错误时,自动中断调用,并通知相关人员处理。
(3)超时控制:在服务A中,可以设置服务B的调用超时时间,当超过设定的超时时间仍未返回结果时,可以认为调用已失败,进而进行相应的处理。可以通过调整超时时间来优化调用的性能和可靠性。
(4)服务降级:在服务A中,可以设计服务降级方案,当服务B出现故障或调用失败时,自动降级为备用方案。可以通过配置备用的数据源、接口或算法等来实现服务降级。
(5)监控和告警:建立完善的监控和告警系统,及时发现网络异常和故障,并通知相关人员及时处理。可以通过监控工具如Prometheus、Zabbix等来实现。
以上是几种常见的解决方案,具体的实现方式需要根据具体的业务场景和系统架构来选择和设计。

4 微服务架构师怎么拆分的
(1)基于角色拆分:将系统拆分成多个角色,每个角色负责完成特定的任务。这些角色可以进一步拆分成更小的服务,每个服务负责完成角色中的一部分任务。这种方法可以使得每个服务都职责明确,便于维护和扩展。
(2)基于可扩展性拆分:使用AKF扩展立方体(Scalability Cube)进行拆分。AKF扩展立方体有三个轴线,X轴代表无差别的克隆服务和数据,Y轴关注应用中职责的划分,Z轴关注服务和数据的优先级划分。根据扩展性的需求,可以选择在X轴、Y轴或Z轴上进行拆分。例如,对于一个电商系统,可以将订单、库存管理和用户管理等服务沿X轴水平拆分,将交易处理、结算和物流等服务沿Y轴垂直拆分,并根据优先级将一些服务沿Z轴进行分片。
总之,微服务的拆分需要考虑到系统的可扩展性、可用性、容错性和业务需求等因素,需要深入理解业务需求和技术架构。

5 怎么定位问题的
(1)分析日志:通过分析日志,可以确定问题出现的时间、位置和原因。在分析日志时,需要关注异常、错误等信息,同时结合监控指标进行分析。
(2)监控系统:通过监控系统可以及时发现微服务应用的异常情况,比如服务响应时间、请求成功率、异常率等指标。监控系统还可以收集系统的运行信息,比如CPU、内存、网络等指标,帮助定位问题。
(3)调用链分析:通过调用链分析可以跟踪微服务之间的调用关系,以及调用过程中的参数和返回值。调用链分析可以帮助定位微服务之间的通信问题或者业务逻辑问题。
(4)故障排除:当出现问题时,可以采用故障排除的方法,比如通过AHAS的故障注入模拟生产环境产生的故障,测试微服务应用的容错能力和稳定性。
(5)代码审查:通过审查代码的规范性、可读性、可维护性等方面,可以发现潜在的问题。
以上是微服务中出现问题时可以采取的方法,需要根据具体的问题和业务场景进行选择。

6 什么是服务注册 什么是服务发现 什么是服务续约?
服务注册、服务发现和服务续约都是微服务架构中的重要概念。
(1)服务注册(Service Registration):在微服务架构中,每个服务都是独立运行的,需要通过服务注册中心来管理和调用。服务注册是指将服务实例的信息注册到服务注册中心。当一个服务实例启动时,它会向服务注册中心注册自己的信息,包括服务名称、IP地址、端口号等。
(2)服务发现(Service Discovery):在微服务架构中,服务实例会动态地注册到服务注册中心,其他服务需要通过服务注册中心来查询到需要调用的服务的信息,从而实现服务之间的通信。这个过程就叫做服务发现。
(3)服务续约(Service Renewal):在微服务架构中,为了保证服务的可用性,每个服务客户端需要定期向服务中心发送请求进行服务续约。这个过程就叫做服务续约。服务续约的目的是告诉服务中心该服务实例还活着,需要保持其在服务地址清单中的信息。
综上所述,服务注册、服务发现和服务续约都是微服务架构中的重要概念,它们共同维护了服务的可用性和稳定性。

7 如果服务挂了,注册中心要等到90s后剔除,那么在剔除前的这段时间内,挂掉的服务有可能还是会被调用,怎么处理?
使用熔断器:熔断器是一种保护机制,当服务调用出现异常或错误时,可以快速中断调用,避免故障扩散。

8 服务发现和服务续约每隔30s做一次请求是用什么技术实现的吗?
微服务中,服务发现和服务续约每隔30秒做一次请求是使用ScheduledThreadPoolExecutor线程池定时任务来实现的。
具体来说,服务发现是先判断是否开启了服务发现功能,获取定时任务的间隔时间,然后初始化服务发现的定时任务,间隔时间可以在yml中修改。服务续约是先判断是否开启服务注册功能,获取定时任务间隔时间,然后初始化心跳请求的定时任务,间隔时间可以在yml中修改。
这种实现方式可以保证服务中心不会将已经宕机或网络故障的服务实例从通讯录中删除,提高了服务的可用性和稳定性。

9 Ribbon是什么,Ribbon的工作原理讲一下
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon的工作原理可以概括为以下几点:
(1)Ribbon客户端组件提供一系列完善的配置项,如连接超时、重试等。它通过Load Balancer获取服务提供的所有机器实例,并基于某种规则(如轮询、随机)自动调用这些服务。
(2)Ribbon在工作时分成两步:先选择Eureka Server,它优先选择在同一个区域内负载较少的Server。再根据用户指定的策略,在从Server取到的服务注册列表中选择一个地址。其中Ribbon提供了多种策略,比如轮询、随机和根据相应时间加权。
(3)Ribbon可以与负载均衡软件(如Nginx、LVS)或硬件(如F5)结合使用,实现高可用性。通过负载均衡,将用户的请求平摊分配到多个服务上,达到系统的HA(高可用)。
(4)Ribbon还支持自定义的负载均衡算法,以满足特定的需求。
综上所述,Ribbon是一个强大的客户端负载均衡工具,通过自动选择合适的服务实例来提供服务,提高了系统的可用性和稳定性。

10 Ribbon有哪些负载均衡算法,怎么配置
Ribbon提供了以下几种负载均衡算法:
(1)RoundRobinRule:按照一定的顺序依次调用服务实例,比如一共有3个服务,第一次调用服务1,第二次调用服务2,第三次调用服务3,依次类推。
(2)WeightedResponseTimeRule:根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。
(3)RandomRule:从服务提供者的列表中随机选择一个服务实例。
(4)BestAvailableRule:也叫最小并发数策略,它遍历服务提供者列表,选取连接数最小的服务实例。如果有相同的最小连接数,那么会调用RoundRobinRule进行选取。
(5)AvailabilityFilteringRule:先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。
(6)ZoneAvoidanceRule:根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和RoundRobinRule类似。
要配置Ribbon的负载均衡算法,需要在Ribbon的配置类中重写getLoadBalancerRule()方法,返回所需的负载均衡规则对象。例如,如果使用Spring Cloud和Eureka,可以创建一个新的配置类,然后重写getLoadBalancerRule()方法,返回所需的负载均衡规则对象

11 OpenFeign和Ribbon的区别和联系?
OpenFeign和Ribbon都是Spring Cloud中的组件,用于实现微服务之间的调用。它们在功能和用法上有所区别,但也可以协同使用。
(1)Ribbon是一个基于HTTP和TCP等协议的负载均衡组件,它提供了客户端的软件负载均衡算法和服务调用。
(2)Ribbon主要的功能是实现客户端的负载均衡,通过与Eureka结合使用,Ribbon可以从Eureka服务器获取服务注册列表,并根据一定的策略(如轮询、随机等)选择一个服务实例进行调用。
而OpenFeign则是一个声明式的REST客户端,它简化了REST服务调用。OpenFeign通过Java的注解来定义接口,然后通过动态代理的方式实现服务调用。在实现上,OpenFeign内置了Ribbon,因此在使用OpenFeign时也可以配置Ribbon的负载均衡策略。
总的来说,OpenFeign和Ribbon可以结合使用,以满足不同的服务调用需求。当需要使用负载均衡时,可以选择使用Ribbon;而当需要简化REST服务调用时,可以选择使用OpenFeign。

12 OpengFiegn的工作流程
OpenFeign的工作流程可以大致分为以下几个步骤:
(1)在客户端应用程序中,通过@FeignClient注解指定需要调用的服务。
(2)客户端应用程序启动后,会进行包扫描,扫描所有使用@FeignClient注解的接口。
(3)扫描完成后,通过JDK的动态代理生成接口的实现类,实现类中包含了服务发现的逻辑。
(4)当实现类被生成时,根据反射的原理拿到@FeignClient的服务名执行服务发现的过程。
(5)服务发现后,根据配置的负载均衡策略选择一个服务实例进行调用。
(6)调用完成后,根据配置的文件类型(如yaml、properties)读取配置中心的配置文件名,以及该配置存在的命名空间和分组。
(7)最后,将读取到的配置文件中的参数填充到对应的接口上,完成整个调用过程。
需要注意的是,以上步骤是基于OpenFeign的默认配置,实际的工作流程可能因具体的使用场景和配置而有所不同。

13 为什么要使用Eureka 为什么要使用Ribbon 为什么要使用config配置中心
使用Eureka、Ribbon和Config的原因如下:
(1)Eureka:Eureka是Spring Cloud中的服务注册中心,用于处理服务注册与发现。在微服务架构中,各个服务之间需要进行网络通信,为了实现这一目标,各个服务需要知道彼此的通信地址。Eureka就是一个集中管理这些信息的服务,各个服务通过Eureka实现互相调用。
(2)Ribbon:Ribbon是一个客户端负载均衡工具,它基于HTTP和TCP等协议。在微服务架构中,一个服务名可能会对应多个地址,当调用该服务时,需要选择其中一个地址。Ribbon根据预设的负载均衡算法(如轮询、随机等)自动选择一个地址。这解决了单节点故障的问题,并提高了系统的可用性和稳定性。
(3)Config:Config是Spring Cloud中的分布式配置中心,它统一管理所有微服务的配置文件。在微服务架构中,每个服务都有自己的配置文件,管理起来非常麻烦。Config可以集中管理这些配置文件,并且支持在运行时动态修改配置,这大大提高了系统的可维护性。
综上所述,Eureka、Ribbon和Config在微服务架构中分别解决了服务注册与发现、客户端负载均衡和配置管理的问题,从而提高了系统的可用性、稳定性和可维护性。

14 介绍一下Hystrix
Hystrix是Netflix开发的一个开源的延迟和容错库,用于处理分布式系统的请求。它的主要目标是隔离远程系统调用,防止级联故障,并提供容错机制。Hystrix通过设置超时时间、隔离访问点、限制并发请求和提供回滚选项等方式,帮助控制分布式系统之间的交互,提高系统的整体弹性。Hystrix的名称来源于“豪猪”,寓意其能保护系统免受故障的影响。

15 什么是资源隔离?
资源隔离在微服务中是指将不同的服务调用隔离起来,避免因为其中一个服务的故障而影响其他服务调用。资源隔离的解决策略主要是限制调用分布式服务的资源使用,防止因为一个服务的故障占用大量资源,导致其他服务调用失败。在微服务中,资源隔离可以通过线程池隔离和信号量隔离两种方式实现。这样可以保证即使某个服务出现故障,也不会导致整个系统瘫痪,从而提升系统的稳定性和可用性。

16 对于CAP理论,Eureka选择的是AP还是CP?它保证了一致性还是可用性?
Eureka选择的是AP,即一致性优先。这意味着在分布式系统中,Eureka优先考虑的是可用性,而不是强一致性。Eureka认为保留可用及过期的数据总比丢掉可用数据要好。因此,在分区容忍性基础上,Eureka优先保证可用性。

17 Zuul有哪几类Filter,他们的执行顺序是怎么样的?
Zuul中的Filter按照执行顺序可分为四种,分别是:
(1)PreFilter:前置过滤器,在路由转发之前执行。
(2)RouteFilter:路由过滤器,在路由转发过程中执行。
(3)PostFilter:后置过滤器,在路由转发完成后执行。
(4)ErrorFilter:错误过滤器,在出现错误时执行。
这四种Filter的执行顺序是:PreFilter -> RouteFilter -> PostFilter(如果RouteFilter没有抛出异常)-> ErrorFilter(如果RouteFilter抛出异常)。

18 浏览器发起一个请求,在你的微服务项目中的怎么去执行的?
请求来到Nginx负载均衡到网关,网关通过请求路径负载均衡到nacos对应的微服务集群中调用对应接口,并执行业务逻辑

19 什么是分布式事务
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。

20 分布式事务你知道哪些解决方案
(1)2pc
2PC(Two-Phase Commit,两阶段提交)是一种分布式事务的解决方案,用于协调多个分布式事务的执行。它是一种协议,旨在确保在分布式系统中进行的事务的原子性和一致性。2PC 将事务的提交过程分为两个阶段来进行处理,因此得名“两阶段提交”。在第一阶段,所有的参与者都会投票表决是否提交事务;在第二阶段,如果大多数参与者都同意提交,那么事务就会被提交,否则将被回滚。这种协议可以有效地解决分布式事务中的数据一致性问题。
(2)TCC
TCC是一种分布式事务解决方案,用于解决数据一致性问题。TCC包括Try、Confirm、Cancel三部分。Try尝试操作是第一阶段,进行资源的检查和预留。Confirm确认操作是第二阶段,执行提交操作。Cancel取消操作并且取消预留的资源。
(3)最大努力通知
最大努力通知是一种分布式事务中常见的解决方案,它是一种通知策略,通过保证消息传递但不能保证消息不丢失或重复传递的机制实现。在分布式事务中,发起方会通过一定的机制将业务处理结果尽可能地通知给接收方,即使在消息无法被接收方接收时,发起方也会提供查询接口供接收方查询业务处理结果。这种方案可以确保消息的可靠性和一致性,同时也可以提高系统的可用性和稳定性。
(4)可靠消息的最终一致性
可靠消息最终一致性是一种分布式事务解决方案,它确保当事务发起方执行完成本地事务后发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功。该方案强调的是只要消息发给事务参与方最终事务要达到一致。可靠消息最终一致性方案要解决的关键问题是本地事务与消息发送的原子性问题,即事务发起方在本地事务执行成功后消息必须发出去,否则就丢弃消息,实现本地事务和消息发送的原子性,要么都成功,要么都失败。

21 什么是2pc
2PC(Two-Phase Commit,两阶段提交)是一种分布式事务的解决方案,用于协调多个分布式事务的执行。它是一种协议,旨在确保在分布式系统中进行的事务的原子性和一致性。2PC 将事务的提交过程分为两个阶段来进行处理,因此得名“两阶段提交”。在第一阶段,所有的参与者都会投票表决是否提交事务;在第二阶段,如果大多数参与者都同意提交,那么事务就会被提交,否则将被回滚。这种协议可以有效地解决分布式事务中的数据一致性问题。

22 Seata相比传统2PC有什么区别,以及优点
Seata相比传统2PC有以下区别和优点:
(1)架构层次方面:传统2PC方案的RM本质就是数据库自身,而Seata的RM是以jar包形式作为中间件层部署在应用程序上。这使得Seata可以更容易地支持不同的数据库。
(2)两阶段提交上方面:传统2PC方案是在第二阶段完成才释放资源,而Seata是在第一阶段就将本地事务提交,提高了效率。
(3)性能方面:Seata不会长时间占用链接资源,对业务零入侵,因此性能更好。
总的来说,Seata相比传统2PC具有性能好、对业务零入侵、更易于支持不同数据库等优点。

23 Seata的TC,TM,RM的含义,以及作用
Seata的TC(Transaction Coordinator,事务协调器)、TM(Transaction Manager,事务管理器)和RM(Resource Manager,资源管理器)分别具有以下含义和作用:
(1)TC(Transaction Coordinator,事务协调器):
TC是Seata框架中的核心组件,负责全局事务的协调和管理。
TC管理全局的分支事务的状态,用于全局性事务的提交和回滚。
在AT模式中,TC会创建一个全局事务,并返回一个唯一的XID。

(2) TM(Transaction Manager,事务管理器):
TM用于开启、提交或者回滚全局事务。
在AT模式中,TM通过TC申请开启一个全局事务。
在TCC模式中,TM除了开启和提交全局事务外,还需要定义事务的边界。

(3)RM(Resource Manager,资源管理器):
RM用于分支事务上的资源管理,向TC注册分支事务,上报分支事务的状态,接受TC的命令来提交或者回滚分支事务。
在Seata框架中,RM与业务系统集成在一起,负责分支事务的开启、提交和回滚操作。
RM通过向TC注册分支事务,使TC能够了解分支事务的状态。

综上所述,Seata的TC、TM和RM分别具有协调和管理全局事务、控制全局事务的开启和提交以及管理分支事务的作用。

24 说一下什么是分布式锁
分布式锁是一种控制分布式系统有序访问共享资源的一种互斥锁。它保证在多个机器节点对共享资源访问时,只有一个节点可以执行特定的操作,其他节点需要等待该节点执行完成后才能进行操作。分布式锁与线程锁的本质是一样的,都需要满足互斥性、可重入性和锁的获取与释放方法等基本特性。常见的实现方式有基于数据库的唯一约束、基于Zookeeper的机制实现等。分布式锁在分布式系统中有重要的作用,可以避免并发访问造成的数据不一致和系统宕机等问题。

25 你项目中怎么使用分布式锁的
在微服务中,可以使用分布式锁来解决多个服务之间数据一致性和互斥访问的问题。以下是使用分布式锁的一些常见方法:
(1)使用数据库的唯一约束实现分布式锁。例如,在多个服务同时修改某个数据时,可以在数据库中为该数据添加唯一约束,只有第一个服务可以修改成功,其他服务都会因为唯一约束冲突而失败。
(2)使用消息队列实现分布式锁。例如,使用RabbitMQ或Kafka等消息队列,每个服务在修改数据前先在消息队列中获取锁,修改完成后释放锁。只有获取到锁的服务才能执行修改操作,其他服务需要等待锁的释放才能继续操作。
(3)使用分布式缓存实现分布式锁。例如,使用Redis或Memcached等分布式缓存系统,在多个服务访问共享资源时,先在缓存系统中获取锁,执行完毕后再释放锁。
(4)使用分布式事务实现分布式锁。例如,使用Seata等分布式事务管理工具,将多个服务的事务统一管理,确保数据一致性。
需要注意的是,使用分布式锁需要考虑网络延迟、锁的释放机制、死锁等问题,需要合理设计程序逻辑和选择合适的实现方式来保证系统的稳定性和性能。

26 项目中有用到mq?用的是哪个

27 说下reocketmq的工作流程
RocketMQ主要由四个组件构成,分别是:
NameServer:主要负责对于源数据的管理,包括了对于Topic和路由信息的管理。NameServer是一个功能齐全的服务器,其角色类似kafka中的Zookeeper,但NameServer与Zookeeper相比更轻量。主要是因为每个NameServer节点互相之间是独立的,没有任何信息交互。
Producer:消息生产者,负责产生消息,一般由业务系统负责产生消息。Producer由用户进行分布式部署,消息由Producer通过多种负载均衡模式发送到Broker集群,发送低延时,支持快速失败。
Broker:消息中转角色,负责存储消息,转发消息。Broker是具体提供业务的服务器,单个Broker节点与所有的NameServer节点保持长连接及心跳,并会定时将Topic信息注册到NameServer,底层的通信和连接都是基于Netty实现的。Broker负责消息存储,以Topic为纬度支持轻量级的队列,单机可以支撑上万队列规模,支持消息推拉模型。
Consumer:消息消费者,负责消费消息,一般是后台系统负责异步消费。Consumer也由用户部署,支持PUSH和PULL两种消费模式,支持集群消费和广播消息,提供实时的消息订阅机制。

RocketMQ的执行流程可以概括为以下几个步骤:
(1)启动NameServer,NameServer起动后监听端口,等待Broker、Producer、Consumer连接。
(2)启动Broker,Broker与所有的NameServer保持长连接,定时发送心跳包。
(3)Producer通过指定的Topic将消息发送到Broker,Broker接收到消息后存储到本地硬盘的消息存储区中,并根据消息的主题和标签进行分类和存储。
(4)Consumer通过指定Topic和标签来订阅消息,当有新消息到达时,Broker根据订阅关系将消息推送给对应的Consumer。
(5)Consumer接收到消息后进行相应的处理,处理结束后向Broker发送确认消息,表示该消息已经被处理完毕。
(6)Broker接收到确认消息后删除确认消息。

28 说一下rocketmq的事务消息,你在项目中哪些地方用到为什么要用的
RocketMQ的事务消息是指一种高级消息类型,它支持在分布式场景下保障消息生产和本地事务的最终一致性。基于RocketMQ实现的分布式事务消息功能,在普通消息基础上,支持二阶段的提交能力。它可以将二阶段提交和本地事务绑定,实现全局提交结果的一致性。

RocketMQ的事务消息流程包括以下步骤:
(1)发送方向RocketMQ发送“待确认”消息(半消息)。
(2)RocketMQ将收到的“待确认”消息持久化成功后,向发送方回复消息已经发送成功,此时第一阶段消息发送完成。
(3)发送方开始执行本地事件逻辑。

在执行本地事件逻辑时,可能会发生以下两种情况:
(a)发送方执行本地事件逻辑成功,向RocketMQ发送“执行确认”消息。RocketMQ收到“执行确认”消息后,将该消息标记为已确认,并执行后续操作(如通知对方消息已确认收到,或者执行其他相关操作)。
(b)发送方执行本地事件逻辑失败,需要回滚操作。此时发送方需要向RocketMQ发送“回滚”消息。RocketMQ收到“回滚”消息后,将该消息标记为已回滚,并删除该消息。
通过以上流程,RocketMQ确保了事务消息的最终一致性,即在本地事务和消息的生产都成功的情况下,消息才会被确认并继续后续操作;如果本地事务失败,则消息会被回滚并删除,从而保证了系统的一致性。

在Java微服务项目中,RocketMQ的事务消息可以用于确保分布式事务的可靠性和一致性。以下是一些使用场景:
(1)银行转账:在银行转账场景中,用户从自己的账户向另一个账户转移资金。使用RocketMQ的事务消息可以确保在资金转移和记录交易的同时,保证数据的一致性。如果转账操作失败,事务消息将进行回滚,确保资金不会丢失或重复转移。
(2)订单管理:在订单管理场景中,商家需要处理客户的订单并更新库存。使用RocketMQ的事务消息可以确保在生成订单和更新库存的同时,保证数据的一致性。如果更新库存的操作失败,事务消息将进行回滚,订单状态将恢复为未完成状态,避免出现超卖的情况。
(3)支付系统:在支付系统场景中,支付网关需要确保在用户完成支付后更新商家账户的余额。使用RocketMQ的事务消息可以确保在记录交易和更新账户余额的同时,保证数据的一致性。如果更新账户余额的操作失败,事务消息将进行回滚,避免出现账户资金丢失或重复扣款的情况。
(4)上下架发送事务消息。。。。。。。。。。。。。。

通过使用RocketMQ的事务消息,可以在微服务架构中实现分布式事务的一致性和可靠性,确保数据的正确性和完整性。

29 说一下rocketmq的延迟消息的原理,项目中有用到吗?怎么用的?
RocketMQ的延迟消息是指,发送消息后,消息不会立即被消费,而是在指定的时间后才能被消费的消息。

在Java微服务项目中,延迟消息的使用场景包括但不限于以下两种:
(1)订单支付场景:用户下单后,需要在一个特定的时间内完成支付。在用户支付完成之前,可以将订单消息设置为延迟消息,在指定的时间后提醒用户完成支付。
(2)消息定时推送场景:在一些需要定时推送消息的场景中,可以使用RocketMQ的延迟消息功能,将消息设置在指定的时间后发送给消费者。例如,在某个时间点提醒用户完成某项任务,或者在某个时间点发送某个通知等。

在RocketMQ中,可以通过设置消息的Message.setDelayTimeLevel()方法来实现延迟消息。该方法接受一个参数,表示延迟级别。RocketMQ支持最多255个延迟级别,不同的级别对应不同的延迟时间。例如,Message.setDelayTimeLevel(3)表示该消息的延迟时间为30秒。需要注意的是,RocketMQ的延迟消息精度只能达到秒级,如果需要更精确的延迟时间,可能需要结合其他技术或方案来实现。

30.微服务中怎么保证各模块数据一致性?
在微服务中,由于每个服务模块是独立的,它们可以使用不同的数据库或数据存储系统。这就可能导致数据一致性的问题。以下是一些保证数据一致性的方法:
(1)同步机制:在进行数据更改时,可以使用同步机制来保证多个服务之间的数据一致性。例如,对于一个订单系统,需要在订单服务和库存服务之间保持同步,当一个订单被创建时,库存服务需要更新库存数量。
(2)事务管理:在分布式环境下,使用分布式事务管理器可以实现数据的原子性、一致性、隔离性、持久性(ACID),这样所有服务要么都处理成功,要么都处理失败,从而保证数据的一致性。
(3)异步消息:使用异步消息队列可以将服务之间的数据交互变为异步执行,在数据交换之前,先将数据写入到消息队列中,然后再由消费者服务来处理数据,这样可以避免服务之间的直接交互,从而减少数据一致性的问题。
(4)基于事件驱动的架构:在微服务架构中,使用基于事件驱动的架构可以将数据交互转换为对于事件的发布和订阅,从而实现更加松耦合的服务间通信和数据更新的一致性。
(5)统一数据存储:另一种保证数据一致性的方法是统一数据存储。不同的服务可以共享相同的数据库,或者将它们的数据存储在统一的NoSQL数据库中。这样可以确保数据在所有服务中的一致性和同步性,减少因为数据不一致而导致的问题。但是,需要注意的是这种方法会牺牲一定的可伸缩性和灵活性,因此需要在平衡数据一致性和系统可扩展性之间做出权衡。
综上所述,微服务中各服务模块可以通过同步机制、事务管理、异步消息、基于事件驱动的架构和统一数据存储等方法来保证数据一致性。

31.SpringCloud常见组件有哪些?
(1)注册中心组件:Eureka、Nacos等
(2)负载均衡组件:Ribbon
(3)远程调用组件:OpenFeign(底层集成了ribbon)
(4)网关组件:Zuul、Gateway
(5)服务保护组件:Hystrix、Sentinel
(6)服务配置管理组件:SpringCloudConfig、Nacos

32.Nacos的服务注册表结构是怎样的?
Nacos采用了数据的分级存储模型,最外层是Namespace,用来隔离环境。然后是Group,用来对服务分组。接下来就是服务(Service)了,一个服务包含多个实例,但是可能处于不同机房,因此Service下有多个集群(Cluster),Cluster下是不同的实例(Instance)。
对应到Java代码中,Nacos采用了一个多层的Map来表示。结构为Map<String, Map<String, Service>>,其中最外层Map的key就是namespaceId,值是一个Map。内层Map的key是group拼接serviceName,值是Service对象。Service对象内部又是一个Map,key是集群名称,值是Cluster对象。而Cluster对象内部维护了Instance的集合。

==============

1.1.SpringCloud常见组件有哪些?

问题说明:这个题目主要考察对SpringCloud的组件基本了解

难易程度:简单

参考话术

SpringCloud包含的组件很多,有很多功能是重复的。其中最常用组件包括:

•注册中心组件:Eureka、Nacos等

•负载均衡组件:Ribbon

•远程调用组件:OpenFeign

•网关组件:Zuul、Gateway

•服务保护组件:Hystrix、Sentinel

•服务配置管理组件:SpringCloudConfig、Nacos

1.2.Nacos的服务注册表结构是怎样的?

问题说明:考察对Nacos数据分级结构的了解,以及Nacos源码的掌握情况

难易程度:一般

参考话术

Nacos采用了数据的分级存储模型,最外层是Namespace,用来隔离环境。然后是Group,用来对服务分组。接下来就是服务(Service)了,一个服务包含多个实例,但是可能处于不同机房,因此Service下有多个集群(Cluster),Cluster下是不同的实例(Instance)。

对应到Java代码中,Nacos采用了一个多层的Map来表示。结构为Map<String, Map<String, Service>>,其中最外层Map的key就是namespaceId,值是一个Map。内层Map的key是group拼接serviceName,值是Service对象。Service对象内部又是一个Map,key是集群名称,值是Cluster对象。而Cluster对象内部维护了Instance的集合。

如图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.3.Nacos如何支撑阿里内部数十万服务注册压力?

问题说明:考察对Nacos源码的掌握情况

难易程度:难

参考话术

Nacos内部接收到注册的请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列就立即响应给客户端。然后利用线程池读取阻塞队列中的任务,异步来完成实例更新,从而提高并发写能力。

1.4.Nacos如何避免并发读写冲突问题?

问题说明:考察对Nacos源码的掌握情况

难易程度:难

参考话术

Nacos在更新实例列表时,会采用CopyOnWrite技术,首先将旧的实例列表拷贝一份,然后更新拷贝的实例列表,再用更新后的实例列表来覆盖旧的实例列表。

这样在更新的过程中,就不会对读实例列表的请求产生影响,也不会出现脏读问题了。

1.5.Nacos与Eureka的区别有哪些?

问题说明:考察对Nacos、Eureka的底层实现的掌握情况

难易程度:难

参考话术

Nacos与Eureka有相同点,也有不同之处,可以从以下几点来描述:

  • 接口方式:Nacos与Eureka都对外暴露了Rest风格的API接口,用来实现服务注册、发现等功能
  • 实例类型:Nacos的实例有永久和临时实例之分;而Eureka只支持临时实例
  • 健康检测:Nacos对临时实例采用心跳模式检测,对永久实例采用主动请求来检测;Eureka只支持心跳模式
  • 服务发现:Nacos支持定时拉取和订阅推送两种模式;Eureka只支持定时拉取模式

1.6.Sentinel的限流与Gateway的限流有什么差别?

问题说明:考察对限流算法的掌握情况

难易程度:难

参考话术

限流算法常见的有三种实现:滑动时间窗口、令牌桶算法、漏桶算法。Gateway则采用了基于Redis实现的令牌桶算法。

而Sentinel内部却比较复杂:

  • 默认限流模式是基于滑动时间窗口算法
  • 排队等待的限流模式则基于漏桶算法
  • 而热点参数限流则是基于令牌桶算法

1.7.Sentinel的线程隔离与Hystix的线程隔离有什么差别?

问题说明:考察对线程隔离方案的掌握情况

难易程度:一般

参考话术

Hystix默认是基于线程池实现的线程隔离,每一个被隔离的业务都要创建一个独立的线程池,线程过多会带来额外的CPU开销,性能一般,但是隔离性更强。

Sentinel是基于信号量(计数器)实现的线程隔离,不用创建线程池,性能较好,但是隔离性一般。

2.MQ篇

2.1.你们为什么选择了RabbitMQ而不是其它的MQ?

如图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

话术:

kafka是以吞吐量高而闻名,不过其数据稳定性一般,而且无法保证消息有序性。我们公司的日志收集也有使用,业务模块中则使用的RabbitMQ。

阿里巴巴的RocketMQ基于Kafka的原理,弥补了Kafka的缺点,继承了其高吞吐的优势,其客户端目前以Java为主。但是我们担心阿里巴巴开源产品的稳定性,所以就没有使用。

RabbitMQ基于面向并发的语言Erlang开发,吞吐量不如Kafka,但是对我们公司来讲够用了。而且消息可靠性较好,并且消息延迟极低,集群搭建比较方便。支持多种协议,并且有各种语言的客户端,比较灵活。Spring对RabbitMQ的支持也比较好,使用起来比较方便,比较符合我们公司的需求。

综合考虑我们公司的并发需求以及稳定性需求,我们选择了RabbitMQ。

2.2.RabbitMQ如何确保消息的不丢失?

话术:

RabbitMQ针对消息传递过程中可能发生问题的各个地方,给出了针对性的解决方案:

  • 生产者发送消息时可能因为网络问题导致消息没有到达交换机:
    • RabbitMQ提供了publisher confirm机制
      • 生产者发送消息后,可以编写ConfirmCallback函数
      • 消息成功到达交换机后,RabbitMQ会调用ConfirmCallback通知消息的发送者,返回ACK
      • 消息如果未到达交换机,RabbitMQ也会调用ConfirmCallback通知消息的发送者,返回NACK
      • 消息超时未发送成功也会抛出异常
  • 消息到达交换机后,如果未能到达队列,也会导致消息丢失:
    • RabbitMQ提供了publisher return机制
      • 生产者可以定义ReturnCallback函数
      • 消息到达交换机,未到达队列,RabbitMQ会调用ReturnCallback通知发送者,告知失败原因
  • 消息到达队列后,MQ宕机也可能导致丢失消息:
    • RabbitMQ提供了持久化功能,集群的主从备份功能
      • 消息持久化,RabbitMQ会将交换机、队列、消息持久化到磁盘,宕机重启可以恢复消息
      • 镜像集群,仲裁队列,都可以提供主从备份功能,主节点宕机,从节点会自动切换为主,数据依然在
  • 消息投递给消费者后,如果消费者处理不当,也可能导致消息丢失
    • SpringAMQP基于RabbitMQ提供了消费者确认机制、消费者重试机制,消费者失败处理策略:
      • 消费者的确认机制:
        • 消费者处理消息成功,未出现异常时,Spring返回ACK给RabbitMQ,消息才被移除
        • 消费者处理消息失败,抛出异常,宕机,Spring返回NACK或者不返回结果,消息不被异常
      • 消费者重试机制:
        • 默认情况下,消费者处理失败时,消息会再次回到MQ队列,然后投递给其它消费者。Spring提供的消费者重试机制,则是在处理失败后不返回NACK,而是直接在消费者本地重试。多次重试都失败后,则按照消费者失败处理策略来处理消息。避免了消息频繁入队带来的额外压力。
      • 消费者失败策略:
        • 当消费者多次本地重试失败时,消息默认会丢弃。
        • Spring提供了Republish策略,在多次重试都失败,耗尽重试次数后,将消息重新投递给指定的异常交换机,并且会携带上异常栈信息,帮助定位问题。

2.3.RabbitMQ如何避免消息堆积?

话术:

消息堆积问题产生的原因往往是因为消息发送的速度超过了消费者消息处理的速度。因此解决方案无外乎以下三点:

  • 提高消费者处理速度
  • 增加更多消费者
  • 增加队列消息存储上限

1)提高消费者处理速度

消费者处理速度是由业务代码决定的,所以我们能做的事情包括:

  • 尽可能优化业务代码,提高业务性能
  • 接收到消息后,开启线程池,并发处理多个消息

优点:成本低,改改代码即可

缺点:开启线程池会带来额外的性能开销,对于高频、低时延的任务不合适。推荐任务执行周期较长的业务。

2)增加更多消费者

一个队列绑定多个消费者,共同争抢任务,自然可以提供消息处理的速度。

优点:能用钱解决的问题都不是问题。实现简单粗暴

缺点:问题是没有钱。成本太高

3)增加队列消息存储上限

在RabbitMQ的1.8版本后,加入了新的队列模式:Lazy Queue

这种队列不会将消息保存在内存中,而是在收到消息后直接写入磁盘中,理论上没有存储上限。可以解决消息堆积问题。

优点:磁盘存储更安全;存储无上限;避免内存存储带来的Page Out问题,性能更稳定;

缺点:磁盘存储受到IO性能的限制,消息时效性不如内存模式,但影响不大。

2.4.RabbitMQ如何保证消息的有序性?

话术:

其实RabbitMQ是队列存储,天然具备先进先出的特点,只要消息的发送是有序的,那么理论上接收也是有序的。不过当一个队列绑定了多个消费者时,可能出现消息轮询投递给消费者的情况,而消费者的处理顺序就无法保证了。

因此,要保证消息的有序性,需要做的下面几点:

  • 保证消息发送的有序性
  • 保证一组有序的消息都发送到同一个队列
  • 保证一个队列只包含一个消费者

2.5.如何防止MQ消息被重复消费?

话术:

消息重复消费的原因多种多样,不可避免。所以只能从消费者端入手,只要能保证消息处理的幂等性就可以确保消息不被重复消费。

而幂等性的保证又有很多方案:

  • 给每一条消息都添加一个唯一id,在本地记录消息表及消息状态,处理消息时基于数据库表的id唯一性做判断
  • 同样是记录消息表,利用消息状态字段实现基于乐观锁的判断,保证幂等
  • 基于业务本身的幂等性。比如根据id的删除、查询业务天生幂等;新增、修改等业务可以考虑基于数据库id唯一性、或者乐观锁机制确保幂等。本质与消息表方案类似。

2.6.如何保证RabbitMQ的高可用?

话术:

要实现RabbitMQ的高可用无外乎下面两点:

  • 做好交换机、队列、消息的持久化
  • 搭建RabbitMQ的镜像集群,做好主从备份。当然也可以使用仲裁队列代替镜像集群。

2.7.使用MQ可以解决那些问题?

话术:

RabbitMQ能解决的问题很多,例如:

  • 解耦合:将几个业务关联的微服务调用修改为基于MQ的异步通知,可以解除微服务之间的业务耦合。同时还提高了业务性能。
  • 流量削峰:将突发的业务请求放入MQ中,作为缓冲区。后端的业务根据自己的处理能力从MQ中获取消息,逐个处理任务。流量曲线变的平滑很多
  • 延迟队列:基于RabbitMQ的死信队列或者DelayExchange插件,可以实现消息发送后,延迟接收的效果。

3.Redis篇

3.1.Redis与Memcache的区别?

  • redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。
  • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。
  • 集群模式:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前是原生支持 cluster 模式的.
  • Redis使用单线程:Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.2.Redis的单线程问题

面试官:Redis采用单线程,如何保证高并发?

面试话术

Redis快的主要原因是:

  1. 完全基于内存
  2. 数据结构简单,对数据操作也简单
  3. 使用多路 I/O 复用模型,充分利用CPU资源

面试官:这样做的好处是什么?

面试话术

单线程优势有下面几点:

  • 代码更清晰,处理逻辑更简单
  • 不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为锁而导致的性能消耗
  • 不存在多进程或者多线程导致的CPU切换,充分利用CPU资源

3.2.Redis的持久化方案由哪些?

相关资料:

1)RDB 持久化

RDB持久化可以使用save或bgsave,为了不阻塞主进程业务,一般都使用bgsave,流程:

  • Redis 进程会 fork 出一个子进程(与父进程内存数据一致)。
  • 父进程继续处理客户端请求命令
  • 由子进程将内存中的所有数据写入到一个临时的 RDB 文件中。
  • 完成写入操作之后,旧的 RDB 文件会被新的 RDB 文件替换掉。

下面是一些和 RDB 持久化相关的配置:

  • save 60 10000:如果在 60 秒内有 10000 个 key 发生改变,那就执行 RDB 持久化。
  • stop-writes-on-bgsave-error yes:如果 Redis 执行 RDB 持久化失败(常见于操作系统内存不足),那么 Redis 将不再接受 client 写入数据的请求。
  • rdbcompression yes:当生成 RDB 文件时,同时进行压缩。
  • dbfilename dump.rdb:将 RDB 文件命名为 dump.rdb。
  • dir /var/lib/redis:将 RDB 文件保存在/var/lib/redis目录下。

当然在实践中,我们通常会将stop-writes-on-bgsave-error设置为false,同时让监控系统在 Redis 执行 RDB 持久化失败时发送告警,以便人工介入解决,而不是粗暴地拒绝 client 的写入请求。

RDB持久化的优点:

  • RDB持久化文件小,Redis数据恢复时速度快
  • 子进程不影响父进程,父进程可以持续处理客户端命令
  • 子进程fork时采用copy-on-write方式,大多数情况下,没有太多的内存消耗,效率比较好。

RDB 持久化的缺点:

  • 子进程fork时采用copy-on-write方式,如果Redis此时写操作较多,可能导致额外的内存占用,甚至内存溢出
  • RDB文件压缩会减小文件体积,但通过时会对CPU有额外的消耗
  • 如果业务场景很看重数据的持久性 (durability),那么不应该采用 RDB 持久化。譬如说,如果 Redis 每 5 分钟执行一次 RDB 持久化,要是 Redis 意外奔溃了,那么最多会丢失 5 分钟的数据。

2)AOF 持久化

可以使用appendonly yes配置项来开启 AOF 持久化。Redis 执行 AOF 持久化时,会将接收到的写命令追加到 AOF 文件的末尾,因此 Redis 只要对 AOF 文件中的命令进行回放,就可以将数据库还原到原先的状态。
  与 RDB 持久化相比,AOF 持久化的一个明显优势就是,它可以提高数据的持久性 (durability)。因为在 AOF 模式下,Redis 每次接收到 client 的写命令,就会将命令write()到 AOF 文件末尾。
  然而,在 Linux 中,将数据write()到文件后,数据并不会立即刷新到磁盘,而会先暂存在 OS 的文件系统缓冲区。在合适的时机,OS 才会将缓冲区的数据刷新到磁盘(如果需要将文件内容刷新到磁盘,可以调用fsync()fdatasync())。
  通过appendfsync配置项,可以控制 Redis 将命令同步到磁盘的频率:

  • always:每次 Redis 将命令write()到 AOF 文件时,都会调用fsync(),将命令刷新到磁盘。这可以保证最好的数据持久性,但却会给系统带来极大的开销。
  • no:Redis 只将命令write()到 AOF 文件。这会让 OS 决定何时将命令刷新到磁盘。
  • everysec:除了将命令write()到 AOF 文件,Redis 还会每秒执行一次fsync()。在实践中,推荐使用这种设置,一定程度上可以保证数据持久性,又不会明显降低 Redis 性能。

然而,AOF 持久化并不是没有缺点的:Redis 会不断将接收到的写命令追加到 AOF 文件中,导致 AOF 文件越来越大。过大的 AOF 文件会消耗磁盘空间,并且导致 Redis 重启时更加缓慢。为了解决这个问题,在适当情况下,Redis 会对 AOF 文件进行重写,去除文件中冗余的命令,以减小 AOF 文件的体积。在重写 AOF 文件期间, Redis 会启动一个子进程,由子进程负责对 AOF 文件进行重写。
  可以通过下面两个配置项,控制 Redis 重写 AOF 文件的频率:

  • auto-aof-rewrite-min-size 64mb
  • auto-aof-rewrite-percentage 100

上面两个配置的作用:当 AOF 文件的体积大于 64MB,并且 AOF 文件的体积比上一次重写之后的体积大了至少一倍,那么 Redis 就会执行 AOF 重写。

优点:

  • 持久化频率高,数据可靠性高
  • 没有额外的内存或CPU消耗

缺点:

  • 文件体积大
  • 文件大导致服务数据恢复时效率较低

面试话术:

Redis 提供了两种数据持久化的方式,一种是 RDB,另一种是 AOF。默认情况下,Redis 使用的是 RDB 持久化。

RDB持久化文件体积较小,但是保存数据的频率一般较低,可靠性差,容易丢失数据。另外RDB写数据时会采用Fork函数拷贝主进程,可能有额外的内存消耗,文件压缩也会有额外的CPU消耗。

ROF持久化可以做到每秒钟持久化一次,可靠性高。但是持久化文件体积较大,导致数据恢复时读取文件时间较长,效率略低

3.3.Redis的集群方式有哪些?

面试话术:

Redis集群可以分为主从集群分片集群两类。

主从集群一般一主多从,主库用来写数据,从库用来读数据。结合哨兵,可以再主库宕机时从新选主,目的是保证Redis的高可用

分片集群是数据分片,我们会让多个Redis节点组成集群,并将16383个插槽分到不同的节点上。存储数据时利用对key做hash运算,得到插槽值后存储到对应的节点即可。因为存储数据面向的是插槽而非节点本身,因此可以做到集群动态伸缩。目的是让Redis能存储更多数据。

1)主从集群

主从集群,也是读写分离集群。一般都是一主多从方式。

Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。

只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。

  • 写数据时只能通过主节点完成
  • 读数据可以从任何节点完成
  • 如果配置了哨兵节点,当master宕机时,哨兵会从salve节点选出一个新的主。

主从集群分两种:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

带有哨兵的集群:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2)分片集群

主从集群中,每个节点都要保存所有信息,容易形成木桶效应。并且当数据量较大时,单个机器无法满足需求。此时我们就要使用分片集群了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

集群特征:

  • 每个节点都保存不同数据

  • 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

  • 节点的fail是通过集群中超过半数的节点检测失效时才生效.

  • 客户端与redis节点直连,不需要中间proxy层连接集群中任何一个可用节点都可以访问到数据

  • redis-cluster把所有的物理节点映射到[0-16383]slot(插槽)上,实现动态伸缩

为了保证Redis中每个节点的高可用,我们还可以给每个节点创建replication(slave节点),如图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

出现故障时,主从可以及时切换:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.4.Redis的常用数据类型有哪些?

支持多种类型的数据结构,主要区别是value存储的数据格式不同:

  • string:最基本的数据类型,二进制安全的字符串,最大512M。

  • list:按照添加顺序保持顺序的字符串列表。

  • set:无序的字符串集合,不存在重复的元素。

  • sorted set:已排序的字符串集合。

  • hash:key-value对格式

3.5.聊一下Redis事务机制

相关资料:

参考:http://redisdoc.com/topic/transaction.html

Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的。Redis会将一个事务中的所有命令序列化,然后按顺序执行。但是Redis事务不支持回滚操作,命令运行出错后,正确的命令会继续执行。

  • MULTI: 用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个待执行命令队列
  • EXEC:按顺序执行命令队列内的所有命令。返回所有命令的返回值。事务执行过程中,Redis不会执行其它事务的命令。
  • DISCARD:清空命令队列,并放弃执行事务, 并且客户端会从事务状态中退出
  • WATCH:Redis的乐观锁机制,利用compare-and-set(CAS)原理,可以监控一个或多个键,一旦其中有一个键被修改,之后的事务就不会执行

使用事务时可能会遇上以下两种错误:

  • 执行 EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
    • Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。
  • 命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。
    • 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行,不会回滚。

为什么 Redis 不支持回滚(roll back)?

以下是这种做法的优点:

  • Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
  • 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

鉴于没有任何机制能避免程序员自己造成的错误, 并且这类错误通常不会在生产环境中出现, 所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。

面试话术:

Redis事务其实是把一系列Redis命令放入队列,然后批量执行,执行过程中不会有其它事务来打断。不过与关系型数据库的事务不同,Redis事务不支持回滚操作,事务中某个命令执行失败,其它命令依然会执行。

为了弥补不能回滚的问题,Redis会在事务入队时就检查命令,如果命令异常则会放弃整个事务。

因此,只要程序员编程是正确的,理论上说Redis会正确执行所有事务,无需回滚。

面试官:如果事务执行一半的时候Redis宕机怎么办?

Redis有持久化机制,因为可靠性问题,我们一般使用AOF持久化。事务的所有命令也会写入AOF文件,但是如果在执行EXEC命令之前,Redis已经宕机,则AOF文件中事务不完整。使用 redis-check-aof 程序可以移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。

3.6.Redis的Key过期策略

参考资料:

为什么需要内存回收?
  • 1、在Redis中,set指令可以指定key的过期时间,当过期时间到达以后,key就失效了;
  • 2、Redis是基于内存操作的,所有的数据都是保存在内存中,一台机器的内存是有限且很宝贵的。

基于以上两点,为了保证Redis能继续提供可靠的服务,Redis需要一种机制清理掉不常用的、无效的、多余的数据,失效后的数据需要及时清理,这就需要内存回收了。

Redis的内存回收主要分为过期删除策略和内存淘汰策略两部分。

过期删除策略

删除达到过期时间的key。

  • 1)定时删除

对于每一个设置了过期时间的key都会创建一个定时器,一旦到达过期时间就立即删除。该策略可以立即清除过期的数据,对内存较友好,但是缺点是占用了大量的CPU资源去处理过期的数据,会影响Redis的吞吐量和响应时间。

  • 2)惰性删除

当访问一个key时,才判断该key是否过期,过期则删除。该策略能最大限度地节省CPU资源,但是对内存却十分不友好。有一种极端的情况是可能出现大量的过期key没有被再次访问,因此不会被清除,导致占用了大量的内存。

在计算机科学中,懒惰删除(英文:lazy deletion)指的是从一个散列表(也称哈希表)中删除元素的一种方法。在这个方法中,删除仅仅是指标记一个元素被删除,而不是整个清除它。被删除的位点在插入时被当作空元素,在搜索之时被当作已占据。

  • 3)定期删除

每隔一段时间,扫描Redis中过期key字典,并清除部分过期的key。该策略是前两者的一个折中方案,还可以通过调整定时扫描的时间间隔和每次扫描的限定耗时,在不同情况下使得CPU和内存资源达到最优的平衡效果。

在Redis中,同时使用了定期删除和惰性删除。不过Redis定期删除采用的是随机抽取的方式删除部分Key,因此不能保证过期key 100%的删除。

Redis结合了定期删除和惰性删除,基本上能很好的处理过期数据的清理,但是实际上还是有点问题的,如果过期key较多,定期删除漏掉了一部分,而且也没有及时去查,即没有走惰性删除,那么就会有大量的过期key堆积在内存中,导致redis内存耗尽,当内存耗尽之后,有新的key到来会发生什么事呢?是直接抛弃还是其他措施呢?有什么办法可以接受更多的key?

内存淘汰策略

Redis的内存淘汰策略,是指内存达到maxmemory极限时,使用某种算法来决定清理掉哪些数据,以保证新数据的存入。

Redis的内存淘汰机制包括:

  • noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间(server.db[i].dict)中,移除最近最少使用的 key(这个是最常用的)。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间(server.db[i].dict)中,随机移除某个 key。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间(server.db[i].expires)中,移除最近最少使用的 key。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间(server.db[i].expires)中,随机移除某个 key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间(server.db[i].expires)中,有更早过期时间的 key 优先移除。

在配置文件中,通过maxmemory-policy可以配置要使用哪一个淘汰机制。

什么时候会进行淘汰?

Redis会在每一次处理命令的时候(processCommand函数调用freeMemoryIfNeeded)判断当前redis是否达到了内存的最大限制,如果达到限制,则使用对应的算法去处理需要删除的key。

在淘汰key时,Redis默认最常用的是LRU算法(Latest Recently Used)。Redis通过在每一个redisObject保存lru属性来保存key最近的访问时间,在实现LRU算法时直接读取key的lru属性。

具体实现时,Redis遍历每一个db,从每一个db中随机抽取一批样本key,默认是3个key,再从这3个key中,删除最近最少使用的key。

面试话术:

Redis过期策略包含定期删除和惰性删除两部分。定期删除是在Redis内部有一个定时任务,会定期删除一些过期的key。惰性删除是当用户查询某个Key时,会检查这个Key是否已经过期,如果没过期则返回用户,如果过期则删除。

但是这两个策略都无法保证过期key一定删除,漏网之鱼越来越多,还可能导致内存溢出。当发生内存不足问题时,Redis还会做内存回收。内存回收采用LRU策略,就是最近最少使用。其原理就是记录每个Key的最近使用时间,内存回收时,随机抽取一些Key,比较其使用时间,把最老的几个删除。

Redis的逻辑是:最近使用过的,很可能再次被使用

3.7.Redis在项目中的哪些地方有用到?

(1)共享session

在分布式系统下,服务会部署在不同的tomcat,因此多个tomcat的session无法共享,以前存储在session中的数据无法实现共享,可以用redis代替session,解决分布式系统间数据共享问题。

(2)数据缓存

Redis采用内存存储,读写效率较高。我们可以把数据库的访问频率高的热点数据存储到redis中,这样用户请求时优先从redis中读取,减少数据库压力,提高并发能力。

(3)异步队列

Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。而且Redis中还有pub/sub这样的专用结构,用于1对N的消息通信模式。

(4)分布式锁

Redis中的乐观锁机制,可以帮助我们实现分布式锁的效果,用于解决分布式系统下的多线程安全问题

3.8.Redis的缓存击穿、缓存雪崩、缓存穿透

1)缓存穿透

参考资料:

  • 什么是缓存穿透

    • 正常情况下,我们去查询数据都是存在。那么请求去查询一条压根儿数据库中根本就不存在的数据,也就是缓存和数据库都查询不到这条数据,但是请求每次都会打到数据库上面去。这种查询不存在数据的现象我们称为缓存穿透
  • 穿透带来的问题

    • 试想一下,如果有黑客会对你的系统进行攻击,拿一个不存在的id 去查询数据,会产生大量的请求到数据库去查询。可能会导致你的数据库由于压力过大而宕掉。
  • 解决办法

    • 缓存空值:之所以会发生穿透,就是因为缓存中没有存储这些空数据的key。从而导致每次查询都到数据库去了。那么我们就可以为这些key对应的值设置为null 丢到缓存里面去。后面再出现查询这个key 的请求的时候,直接返回null 。这样,就不用在到数据库中去走一圈了,但是别忘了设置过期时间。
    • BloomFilter(布隆过滤):将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。在缓存之前在加一层 BloomFilter ,在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在就直接返回,存在再走查缓存 -> 查 DB。

话术:

缓存穿透有两种解决方案:其一是把不存在的key设置null值到缓存中。其二是使用布隆过滤器,在查询缓存前先通过布隆过滤器判断key是否存在,存在再去查询缓存。

设置null值可能被恶意针对,攻击者使用大量不存在的不重复key ,那么方案一就会缓存大量不存在key数据。此时我们还可以对Key规定格式模板,然后对不存在的key做正则规范匹配,如果完全不符合就不用存null值到redis,而是直接返回错误。

2)缓存击穿

相关资料

  • 什么是缓存击穿?

key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。

当这个key在失效的瞬间,redis查询失败,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

  • 解决方案:
    • 使用互斥锁(mutex key):mutex,就是互斥。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用Redis的SETNX去set一个互斥key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现互斥的效果。
    • 软过期:也就是逻辑过期,不使用redis提供的过期时间,而是业务层在数据中存储过期时间信息。查询时由业务程序判断是否过期,如果数据即将过期时,将缓存的时效延长,程序可以派遣一个线程去数据库中获取最新的数据,其他线程这时看到延长了的过期时间,就会继续使用旧数据,等派遣的线程获取最新数据后再更新缓存。

推荐使用互斥锁,因为软过期会有业务逻辑侵入和额外的判断。

面试话术

缓存击穿主要担心的是某个Key过期,更新缓存时引起对数据库的突发高并发访问。因此我们可以在更新缓存时采用互斥锁控制,只允许一个线程去更新缓存,其它线程等待并重新读取缓存。例如Redis的setnx命令就能实现互斥效果。

3)缓存雪崩

相关资料

缓存雪崩,是指在某一个时间段,缓存集中过期失效。对这批数据的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。

解决方案:

  • 数据分类分批处理:采取不同分类数据,缓存不同周期
  • 相同分类数据:采用固定时长加随机数方式设置缓存
  • 热点数据缓存时间长一些,冷门数据缓存时间短一些
  • 避免redis节点宕机引起雪崩,搭建主从集群,保证高可用

面试话术:

解决缓存雪崩问题的关键是让缓存Key的过期时间分散。因此我们可以把数据按照业务分类,然后设置不同过期时间。相同业务类型的key,设置固定时长加随机数。尽可能保证每个Key的过期时间都不相同。

另外,Redis宕机也可能导致缓存雪崩,因此我们还要搭建Redis主从集群及哨兵监控,保证Redis的高可用。

3.9.缓存冷热数据分离

背景资料

Redis使用的是内存存储,当需要海量数据存储时,成本非常高。

经过调研发现,当前主流DDR3内存和主流SATA SSD的单位成本价格差距大概在20倍左右,为了优化redis机器综合成本,我们考虑实现基于热度统计 的数据分级存储及数据在RAM/FLASH之间的动态交换,从而大幅度降低成本,达到性能与成本的高平衡。

基本思路:基于key访问次数(LFU)的热度统计算法识别出热点数据,并将热点数据保留在redis中,对于无访问/访问次数少的数据则转存到SSD上,如果SSD上的key再次变热,则重新将其加载到redis内存中。

目前流行的高性能磁盘存储,并且遵循Redis协议的方案包括:

  • SSDB:http://ssdb.io/zh_cn/
  • RocksDB:https://rocksdb.org.cn/

因此,我们就需要在应用程序与缓存服务之间引入代理,实现Redis和SSD之间的切换,如图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样的代理方案阿里云提供的就有。当然也有一些开源方案,例如:https://github.com/JingchengLi/swapdb

3.10.Redis实现分布式锁

分布式锁要满足的条件:

  • 多进程互斥:同一时刻,只有一个进程可以获取锁
  • 保证锁可以释放:任务结束或出现异常,锁一定要释放,避免死锁
  • 阻塞锁(可选):获取锁失败时可否重试
  • 重入锁(可选):获取锁的代码递归调用时,依然可以获取锁

1)最基本的分布式锁:

利用Redis的setnx命令,这个命令的特征时如果多次执行,只有第一次执行会成功,可以实现互斥的效果。但是为了保证服务宕机时也可以释放锁,需要利用expire命令给锁设置一个有效期

setnx lock thread-01 # 尝试获取锁
expire lock 10 # 设置有效期

面试官问题1:如果expire之前服务宕机怎么办?

要保证setnx和expire命令的原子性。redis的set命令可以满足:

set key value [NX] [EX time] 

需要添加nx和ex的选项:

  • NX:与setnx一致,第一次执行成功
  • EX:设置过期时间

面试官问题2:释放锁的时候,如果自己的锁已经过期了,此时会出现安全漏洞,如何解决?

在锁中存储当前进程和线程标识,释放锁时对锁的标识判断,如果是自己的则删除,不是则放弃操作。

但是这两步操作要保证原子性,需要通过Lua脚本来实现。

if redis.call("get",KEYS[1]) == ARGV[1] then
    redis.call("del",KEYS[1])
end

2)可重入分布式锁

如果有重入的需求,则除了在锁中记录进程标识,还要记录重试次数,流程如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下面我们假设锁的key为“lock”,hashKey是当前线程的id:“threadId”,锁自动释放时间假设为20

获取锁的步骤:

  • 1、判断lock是否存在 EXISTS lock
    • 存在,说明有人获取锁了,下面判断是不是自己的锁
      • 判断当前线程id作为hashKey是否存在:HEXISTS lock threadId
        • 不存在,说明锁已经有了,且不是自己获取的,锁获取失败,end
        • 存在,说明是自己获取的锁,重入次数+1:HINCRBY lock threadId 1,去到步骤3
    • 2、不存在,说明可以获取锁,HSET key threadId 1
    • 3、设置锁自动释放时间,EXPIRE lock 20

释放锁的步骤:

  • 1、判断当前线程id作为hashKey是否存在:HEXISTS lock threadId
    • 不存在,说明锁已经失效,不用管了
    • 存在,说明锁还在,重入次数减1:HINCRBY lock threadId -1,获取新的重入次数
  • 2、判断重入次数是否为0:
    • 为0,说明锁全部释放,删除key:DEL lock
    • 大于0,说明锁还在使用,重置有效时间:EXPIRE lock 20

对应的Lua脚本如下:

首先是获取锁:

local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间

if(redis.call('exists', key) == 0) then -- 判断是否存在
	redis.call('hset', key, threadId, '1'); -- 不存在, 获取锁
	redis.call('expire', key, releaseTime); -- 设置有效期
	return 1; -- 返回结果
end;

if(redis.call('hexists', key, threadId) == 1) then -- 锁已经存在,判断threadId是否是自己	
	redis.call('hincrby', key, threadId, '1'); -- 不存在, 获取锁,重入次数+1
	redis.call('expire', key, releaseTime); -- 设置有效期
	return 1; -- 返回结果
end;
return 0; -- 代码走到这里,说明获取锁的不是自己,获取锁失败

然后是释放锁:

local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间

if (redis.call('HEXISTS', key, threadId) == 0) then -- 判断当前锁是否还是被自己持有
    return nil; -- 如果已经不是自己,则直接返回
end;
local count = redis.call('HINCRBY', key, threadId, -1); -- 是自己的锁,则重入次数-1

if (count > 0) then -- 判断是否重入次数是否已经为0
    redis.call('EXPIRE', key, releaseTime); -- 大于0说明不能释放锁,重置有效期然后返回
    return nil;
else
    redis.call('DEL', key); -- 等于0说明可以释放锁,直接删除
    return nil;
end;

3)高可用的锁

面试官问题:redis分布式锁依赖与redis,如果redis宕机则锁失效。如何解决?

此时大多数同学会回答说:搭建主从集群,做数据备份。

这样就进入了陷阱,因为面试官的下一个问题就来了:

面试官问题:如果搭建主从集群做数据备份时,进程A获取锁,master还没有把数据备份到slave,master宕机,slave升级为master,此时原来锁失效,其它进程也可以获取锁,出现安全问题。如何解决?

关于这个问题,Redis官网给出了解决方案,使用RedLock思路可以解决:

在Redis的分布式环境中,我们假设有N个Redis master。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。之前我们已经描述了在Redis单实例下怎么安全地获取和释放锁。我们确保将在每(N)个实例上使用此方法获取和释放锁。在这个样例中,我们假设有5个Redis master节点,这是一个比较合理的设置,所以我们需要在5台机器上面或者5台虚拟机上面运行这些实例,这样保证他们不会同时都宕掉。

为了取到锁,客户端应该执行以下操作:

  1. 获取当前Unix时间,以毫秒为单位。
  2. 依次尝试从N个实例,使用相同的key和随机值获取锁。在步骤2,当向Redis设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个Redis实例。
  3. 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
  4. 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
  5. 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功)。

3.11.如何实现数据库与缓存数据一致?

面试话术:

实现方案有下面几种:

  • 本地缓存同步:当前微服务的数据库数据与缓存数据同步,可以直接在数据库修改时加入对Redis的修改逻辑,保证一致。
  • 跨服务缓存同步:服务A调用了服务B,并对查询结果缓存。服务B数据库修改,可以通过MQ通知服务A,服务A修改Redis缓存数据
  • 通用方案:使用Canal框架,伪装成MySQL的salve节点,监听MySQL的binLog变化,然后修改Redis缓存数据
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值