JAVA面试题

第一天:

1.重载和重写的区别?

重载发生在一个类中,同名的方法如果有不同的参数列表(类型不同、个数不同、顺序不同)则视为重载。
重写发生在子类与父类之间,重写要求子类重写之后的方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常。

2.String 和 StringBuffer、StringBuilder 的区别是什么?

  1. 不可变性:

  • String类是不可变的,即一旦创建了String对象,就不能修改它的值。每次对String对象的操作都会创建一个新的String对象,导致内存开销较大。

  • StringBuffer和StringBuilder类是可变的,它们允许修改已有的字符串内容。对于频繁的字符串操作,使用可变的类可以避免频繁地创建新对象,提高性能。

  1. 线程安全性:

  • String类是线程安全的,因为它的不可变性使得多个线程可以共享同一个String对象,不会出现并发修改的问题。

  • StringBuffer类是线程安全的,它的方法都是同步的(synchronized),因此多个线程可以安全地使用它进行字符串操作。但是,由于同步操作的开销,它的性能相对较低。

  • StringBuilder类是非线程安全的,它的方法没有进行同步。因此,在多线程环境下使用StringBuilder时需要自行保证线程安全。

  1. 性能:

  • 由于String类的不可变性,每次对字符串进行修改时都会创建一个新的String对象,导致频繁的内存分配和回收,对性能有一定影响。

  • StringBuffer和StringBuilder类的可变性使它们在进行字符串操作时不会频繁创建新对象,性能较高。StringBuilder类比StringBuffer类的性能稍高,因为它的方法没有进行同步。

综上所述,如果需要频繁进行字符串操作且不涉及多线程环境,推荐使用StringBuilder类,它具有较高的性能。如果涉及多线程环境或需要线程安全,使用StringBuffer类。String类则适用于不需要修改字符串内容的情况。

3.== 与 equals 的区别?

 "=="操作符: 
​
- 对于基本数据类型,"=="比较的是值是否相等。
- 对于引用类型(对象),"=="比较的是对象的引用是否相等,即两个对象是否指向同一个内存地址。
​
 "equals()"方法:
​
 "equals()"方法是Object类的方法 , 默认情况下,Object类的"equals()"方法比较的是两个对象的引用是否相等,等价于"=="操作符。  通常,我们需要在自定义类中重写"equals()"方法,根据类的实际需求来定义两个对象是否相等的判断逻辑。  比如:`String str1 = "Hello"; String str2 = new String("Hello"); boolean result = str1.equals(str2); // true` 

4.抽象类和接口的区别是什么?

抽象类(Abstract Class)和接口(Interface)是Java中用于实现抽象类型的两种机制,他们的区别如下:
​
实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
​
构造函数:抽象类可以有构造函数;接口不能有。
​
实现数量:类可以实现很多个接口;但只能继承一个抽象类【java只支持单继承】。
​
访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的抽象方法可以使用Public和Protected修饰.
​
设计层面:抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。

5. 面向对象的特点

​
面向对象编程(Object-Oriented Programming,简称OOP)具有以下特征:
​
封装:
封装是将数据和对数据的操作封装在一个单元中,通过定义类来实现。
类通过公共接口(方法)来控制对内部数据的访问,隐藏了内部实现的细节,提供了数据的安全性和保护。
​
​
继承:
继承是通过定义一个新的类来继承现有类的特性(属性和方法)。
继承实现了代码的重用,可以建立类之间的层次关系,并通过父类的特性扩展子类的功能。
子类可以继承并重写父类的方法,实现多态性。
​
多态性:
多态性指的是同一类型的对象,在不同的情境下表现出不同的行为。
多态性通过方法的重写和方法的重载实现。
多态性使得程序可以更加灵活和可扩展,提高了代码的可读性和可维护性。
​
这些特征共同构成了面向对象编程的核心思想和基本原则,使得代码具有可重用性、可扩展性、可维护性和可理解性。面向对象编程使得程序更易于设计、实现和维护,提高了软件开发的效率和质量。

第二天:

6.Collection 和 Collections 有什么区别?

  1. Collection:

  • Collection是Java集合框架中定义的接口,它是所有集合类的根接口。

  • Collection接口提供了对一组对象对集合进行操作的通用方法,如添加、删除、查找等。

  • Collection接口的常见实现类包括List、Set等。

  • ArrayList:基于动态数组实现的可变大小的集合。

  • LinkedList:基于双向链表实现的可变大小的集合。

  • HashSet:基于哈希表实现的无序集合,不允许重复元素。

  • TreeSet:基于红黑树实现的有序集合,不允许重复元素。

  • LinkedHashSet:基于哈希表和双向链表实现的有序集合,按照元素插入顺序进行迭代。

  • PriorityQueue:基于堆实现的优先级队列,可以按照元素的优先级进行访问。

  1. Collections:

  • Collections是Java中提供的一个工具类,包含了各种操作集合的静态方法。

  • Collections类提供了一系列静态方法,用于对集合进行排序、查找、替换、同步等操作。

  • Collections类中的方法都是通过传入集合对象作为参数来操作集合。

需要注意的是,Collection和Collections之间没有直接的继承或关联关系。Collection是一个接口,定义了集合的通用行为,而Collections是一个工具类,提供了对集合的操作方法。我们可以使用Collections类的方法对实现了Collection接口的具体集合对象进行操作。

7.List、Set、Map 之间的区别是什么?

  • List是一个有序的集合,允许元素重复,可以通过索引访问和修改元素。

  • Set是一个不允许重复元素的集合,元素无固定顺序,无法通过索引访问元素。

  • Map是一种键值对的映射结构,键唯一,值可以重复,可以通过键来获取值。

在实际开发中,需要根据实际的需求选择合适的接口和实现类。如果需要有序的集合,可以选择List;如果需要去重的集合,可以选择Set;如果需要根据键查找对应的值,可以选择Map。

8. HashMap 和 Hashtable 有什么区别?

存储:HashMap 允许 key 和 value 为 null,而 Hashtable 不允许。 
线程安全:HashMap 是非线程安全的,Hashtable 是线程安全的。 
性能:HashMap的性能通常比Hashtable更好。Hashtable的方法是同步的,因此在单线程环境下会产生额外的性能开销。HashMap允许使用null值,避免了对null值的判断和同步操作,有利于性能优化。

9. 说一下 HashMap 的实现原理?

HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。 当传入 key 时,HashMap 会根据 key.hashCode() 计算出 hash 值,再根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时使用链表,当hash冲突的个数大于8且数组的长度大于64位时使用红黑树。

10. 说一下 HashSet 的实现原理?

HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap的相关方法来完成,HashSet 不允许重复的值。

第三天:

11.ArrayList和LinkedList的区别

ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),如果使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList (需要创建大量的node对象)
​
LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询,因为查询的时候需要逐一遍历,遍历LinkedList必须使用iterator,不能使用for循环,因为每次需要从for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。

12. Java集合容器有哪些?

Java中的集合容器是用于存储和操作一组对象的数据结构。Java提供了许多内置的集合容器。以下是一些常见的Java集合容器:

Collection:

--List: 有序可重复集合接口

ArrayList:动态数组,可以根据需要自动增长大小。

LinkedList:双向链表,可以在任意位置插入和删除元素。

--Set:无序不可重复集合接口

HashSet:基于哈希表实现的集合,不允许重复元素。

TreeSet:基于红黑树实现的有序集合,按照元素的自然顺序进行排序。

LinkedHashSet:基于哈希表和链表实现的集合,保持元素的插入顺序。

Map: 键值对集合对象

  1. HashMap:基于哈希表实现的键值对映射,不允许重复键。

  2. TreeMap:基于红黑树实现的有序键值对映射,按照键的自然顺序进行排序。

  3. LinkedHashMap:基于哈希表和链表实现的键值对映射,保持元素的插入顺序。

以上仅是一些常见的集合容器。每种集合容器都有其特定的用途和适用场景,选择合适的容器可以提高代码效率和可读性。

13.哪些集合类是线程安全的?

Vector、Hashtable、Stack 都是线程安全的,而像 HashMap 则是非线程安全的,不过在JDK1.5之后随着Java.util.concurrent 并发包的出现,它们也有了自己对应的线程安全类,比如HashMap对应的线程安全类就是 ConcurrentHashMap. 

14.创建线程有哪几种方式?

创建线程有三种方式: 
继承 Thread 重写 run 方法; 
实现 Runnable 接口; 
实现 Callable 接口。

15.说一下 runnable 和 callable 有什么区别?

runnable 没有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的补充。

第四天:

16.线程有哪些状态?

  1. 新建(New):当线程对象被创建但尚未启动时处于新建状态。

  2. 可运行(Runnable):线程对象调用了start()方法后,线程处于可运行状态。在可运行状态下,线程可能正在执行,也可能正在等待系统资源(如处理器时间片)。

  3. 运行(Running):线程获得了处理器时间片并正在执行其任务。

  4. 阻塞(Blocked):线程被阻塞并暂时停止执行,通常是因为等待某个操作的完成(如等待I/O操作、等待获取锁、等待某个条件满足等)。

  5. 等待(Waiting):线程等待某个特定条件的发生,需要其他线程显式地唤醒(如通过wait()方法)。

  6. 超时等待(Timed Waiting):线程等待一段特定的时间,超过时间后会自动唤醒。

  7. 终止(Terminated):线程执行完毕或出现异常导致终止,不再可运行。

17.sleep() 和wait() 有什么区别?

所属类的不同:sleep()来自于Thread类,wait()来自于Object类。 
释放锁:sleep()不释放锁;wait()释放锁。
用法不同:sleep()时间到了会自动恢复;wait()则需要使用notify()/notifyAll()唤醒。

18.notify()和 notifyAll()有什么区别?

notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。notifyAll()调用后会唤醒所有的线程,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果竞争不成功则留在锁池等待锁被释放后再次参与竞争。

19.线程的 run() 和 start() 有什么区别?

start()方法用于启动线程,run()方法用于执行线程的运行时代码。run()可以重复调用,而 start()只能调用一次。

20.说一说几种常见的线程池及适用场景?

  1. 固定大小线程池(FixedThreadPool):

    • 特点:创建一个固定大小的线程池,线程数量固定,任务提交后会一直保持执行状态,直到线程池关闭。

    • 适用场景:适用于需要限制并发线程数量的场景,如服务器后台处理任务、Web服务器。

  2. 缓存线程池(CachedThreadPool):

    • 特点:创建一个可以动态调整大小的线程池,根据任务的数量自动增加或减少线程数量。

    • 适用场景:适用于需要处理大量短期任务的场景,如异步任务处理、大量并发请求的服务器。

  3. 单线程线程池(SingleThreadExecutor):

    • 特点:只有一个工作线程的线程池,任务按顺序执行,保证任务的顺序性。

    • 适用场景:适用于需要顺序执行任务的场景。

  4. 调度线程池(ScheduledThreadPool):

    • 特点:用于定时执行任务和周期性执行任务的线程池。

    • 适用场景:适用于需要定时执行或周期性执行任务的场景,如定时任务调度、定时数据备份。

第五天:

21.线程池中 submit() 和 execute() 方法有什么区别?

execute():只能执行 Runnable 类型的任务。

submit():可以执行 Runnable 和 Callable 类型的任务。

Callable 类型的任务可以获取执行的返回值,而 Runnable 执行无返 回值。

22.在 Java 程序中怎么保证多线程的运行安全/如何解决线程安全问题?

方法一:使用安全类,比如 Java. util. concurrent 下的类

方法二:使用自动锁 synchronized

方法三:使用手动锁 Lock

23.什么是死锁?

当线程1持有独占锁 1,并尝试去获取独占锁2的同时,线程 2持有独占锁 2,也并尝试获取独占锁 1的情况下,就会发生线程1和线程2两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。

24.怎么防止死锁?

尽量使用 tryLock(long timeout, TimeUnit unit)的方法 (ReentrantLock、 ReentrantReadWriteLock),设置超时时间,超 时可以退出防止死锁。

尽量使用 Java. util. concurrent 并发类代替自己手写锁。

尽量降低锁的使用粒度,不要几个功能用同一把锁。

尽量减少同步的代码块。

25.ThreadLocal 是什么?有哪些使用场景?

ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 ThreadLocal 的经典使用场景就是与数据库连接和 session 管理等。

第六天:

26.synchronized 和 Lock 有什么区别?

  1. synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。

  2. synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果没有使用 unLock() 去释放锁就会造成死锁。

  3. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

27.什么是 Java 序列化?什么情况下需要序列化?

Java 序列化是为了保存各种对象在内存中的状态,并且可以把保存的对象状态再读出来。 我认为以下情况需要使用 Java 序列化: 1.想把的内存中的对象状态保存到一个文件中或者数据库中时候; 2.想用套接字在网络上传送对象的时候;

3.想通过 RMI(远程方法调用)传输

28.动态代理是什么?有哪些应用?

动态代理是代码运行时动态生成代理类。动态代理的应用有 springAop、rpc, Java 注解对象的获取等。

29.怎么实现动态代理 / 动态代理实现的方式的哪几种?

我认为实现动态代理有两种实现方式,JDK 原生动态代理和 cglib 动态代理。JDK 原生动态代理是基于接口实现的,而 cglib 是基于继承当前类的子类实现的。

30.为什么要使用克隆?

因为克隆的对象可能包含一些已经修改过的属性,而新new 出来的对象的属性还都是初始化的值,所以当需要一个新的对象来保存当前对象的“状态”就需要用到克隆方法了。

第七天:

31.session 和 cookie 有什么区别?

存储位置不同:session 存储在服务器端;cookie 存储在浏览器端。

安全性不同:cookie 在浏览器中存储,可以被伪造和修改,安全性一般。

容量和个数限制:cookie 有容量限制,最多存储4kb的容量,每个浏览器下的 cookie 的个数最多有50个。

存储方式的不同:session 可以存储在 Redis 中、数据库中、应用程序中;而 cookie 只能存储在浏览器中。

32.如何避免 SQL 注入?

我认为避免SQL注入可以使用预处理 PreparedStatement,还可以使用正则表达式过滤掉字符中的特殊字符。

33.throw 和 throws 的区别?

我认为throw和throws的区别是:throw:是真实抛出一个异常; throws:是声明方法可能会抛出一个异常;

34.final、finally、finalize 有什 么区别?

final:是修饰符,如果修饰类,此类不能被继承;如果修饰方法和变 量,则 表示此方法和此变量不能在被改变,只能使用。

finally:是 try{} catch{} finally{} 最后一部分,表示不论 发生任何情况 都会执行,finally 部分可以省略,但如果 finally 部 分存在,则一定会执行 finally 里面的代码。

finalize: 是 Object 类的一个方法,在垃圾收集器执行的时候会调 用被回 收对象的此方法。

35.常见的异常类有哪些?

空指针异常:NullPointerException ;

数据类型转换异常: ClassCastException ;

指定类不存在异常: ClassNotFoundException ;

文件不存在异常: FileNotFoundException ;

方法未找到异常: NoSuchMethodException ;

字符串转换为数字异常: NumberFormatException ;

数组下标越界异常: IndexOutOfBoundsException ;

IO异常:IOException ;

Socket异常:SocketException;

第八天:

36 .说一下你熟悉的设计模式?

单例模式:保证被创建一次,节省系统开销,单例的实现方式有:懒汉式和饿汉式。

工厂模式(简单工厂、抽象工厂):解耦代码。

观察者模式:定义了对象之间的一对多的依赖,这样一来,当一个对象改 变时, 它的所有的依赖者都会收到通知并自动更新。

外观模式:提供一个统一的接口,用来访问子系统中的一群接口,外观定 义了 一个高层的接口,让子系统更容易使用。

模版方法模式:定义了一个算法的骨架,而将一些步骤延迟到子类中,模 版方 法使得子类可以在不改变算法结构的情况下,重新定义算法的步骤。

代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制 对原对象的引用

37 .spring框架使用了那些设计模 式 ?

  1. spring的bean的创建默认使用的是单例模式(懒汉式(会出现线程安全)和饿汉式) ;

  2. spring中获取bean对象使用的是工厂模式 ;

  3. spring的aop使用的是动态代理模式 ;

  4. 在各种BeanFactory以及ApplicationContext的实现中使用到了模板模式;

38 .为什么要使用 spring?

我认为使用spring框架有以下几点原因:

spring 提供 ioc容器技术,容器会帮你管理依赖的对象,从而不需要自己创建和管理依赖对象了,实现了程序的解耦。

spring 提供了事务支持,使得事务操作变的更加方便。

spring 提供了AOP面向切片编程,这样可以更方便的处理某一类的问题,如日志信息。

spring 可以集成其他框架,比如 MyBatis、 hibernate 等。

39 .spring 常用的注入方式有哪些?

setter 属性注入 ;

构造方法注入 ;

注解方式注入 ;

40 .spring 事务实现方式有哪些?

声明式事务:声明式事务也有两种实现方式,基于 xml 配置文件的方式和注解方式(在类上添加 @Transaction 注解)。

编码式事务:是提供编码的形式管理和维护事务。

第九天:

41 .并发事务会带来哪些问题?

脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改, 而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据, 然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事 务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个 事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务 也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失 修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修 改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。

不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。 在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务 中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数 据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情 况,因此称为不可重复读。

幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务 (T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在 随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就 好像发生了幻觉一样,所以称为幻读。

42 .事务的隔离级别有哪些?

READ-UNCOMMITTED(读未提交): 最低的隔离级别,允许读取尚未提交 的数据变更,可能会导致脏读、幻读或不可重复读。

READ-COMMITTED(读已提交): 允许读取并发事务已经提交的数据,可 以阻止脏读,但是幻读或不可重复读仍有可能发生。

REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致 的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻 读仍有可能发生。

SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级 别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就 是说,该级别可以防止脏读、不可重复读以及幻读。

43.说一下springmvc运行流程?

spring mvc 先将请求发送给 DispatcherServlet。DispatcherServlet 查询一个或多个 HandlerMapping,找到处 理请求的 Controller。 DispatcherServlet 再把请求提交到对应的 Controller。 Controller 进行业务逻辑处理后,会返回一个 ModelAndView。 Dispathcher 查 询 一 个 或 多 个 ViewResolver 视 图 解 析 器 , 找 到 ModelAndView 对象指定的视图对象。视图对象负责渲染返回给客户端。

44 .spring mvc 有哪些组件?

前置控制器 DispatcherServlet;

映射控制器 HandlerMapping;

处理器 Controller;

模型和视图 ModelAndView;

视图解析器 ViewResolver;

45 .springmvc常见的注解

@Controller 作用在类上表示该类为控制层类;

@RequestMapping用来处理请求地址映射的注解;

@PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数 上,即取出uri模板中的变量作为参数 ;@requestParam主要用于在SpringMVC后台控制层获取参数值;

@ResponseBody该注解用于将Controller的方法返回的对象转换为json 格式 ;

@RequestBody用于接收前端传来的json实体,接收参数也是对应的实体;

第十天:

46 .什么是 spring boot以及作用/优点?

springboot 是为 spring 服务的,是用来简化spring 的初始搭建以及开发过程的。

作用/优点:配置简单、独立运行、自动装配、无代码生成和xml配置、提供应用监控、提高开发效率、易上手等等

47 .spring boot 核心配置文件是什么?

springboot 核心的配置文件有两个:

bootstrap (. yml 或者 . properties):boostrap 由父接口 ApplicationContext 加载的,比 applicaton 优先加载,并且 boostrap 里面的属性不能被覆盖;

application (. yml 或者 . properties):用于springboot 项目的自动化配置。

48 .springboot自动装配原理

springboot的启动类中里面有一个main方法运行了一个run()方法,在run方法中必须要传入一个被@SpringBootApplication注解标识的类。 在@SpringBootApplication包含@EnableAutoConfiguration注解,该注解开启自动配置功能,并且在该注解中包含 @Import({AutoConfigurationImportSelector.class})注解, @Import注解需要导入AutoConfigurationImportSelector自动配置选择器类,该类会自动装载一些自动配置类。而这些配置类会完成相应的自动装配。

49 .什么是 springcloud?

springcloud 是一系列框架的有序集合。它利用 springboot简化了分布式系统的开发,如服务发现注册、配置中 心、消息总线、负载均衡、断路器、数据监控等,都可以用 spring boot 的开发风格做到一键启动和部署。

50 .springcloud 的核心组件有哪些?

nacos:服务注册与发现

openFeign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。

Ribbon:实现负载均衡,从一个服务的多台机器中选择一台

Sentinel:提供了流量控制、熔断降级、系统负载保护等多个维度来保 障服务之间的稳定性。

Gateway:网关管理,由 gateway 网关转发请求给对应的服务。

第十一天:

51 .MyBatis 中 #{}和 ${}的区别是 什么?

#{}是预编译处理,${}是字符拼接。 在使用 #{}时,MyBatis 会将 SQL 中的 #{} 替换成“?”,配合 PreparedStatement中的set方法赋值,这样可以有效的防止 SQL 注入, 保证程序的运行安全。

52 .什么地方使用${}

$的作用是字符拼接,不能防止sql注入,可以把${}中的参数作为字段名或表名时使用。 比如: select * from ${tableName} 或者要用${}在MyBatis的sql中拼接排序类型的时候

53 .当实体类的属性名和表中的字段名不一致如何处理

通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。

通过resultMap映射字段名和实体类属性名的一一对应的关系 。

54 .MyBatis如何实现一对多

在resultMap中使用collection标签实现一对多

55 .ResultType和ResultMap的区别

resultmap:resultMap如果查询出来的列名和pojo的属性名不一 致,通过定义一个resultMap标签对列名和属性名之间作一个映射关系。

resulttype:使用resultType进行输出映射时,只有查询出来的列名和属性名一致,该列才可以映射成功。

第十二天:

56.Redis 是什么?都有哪些使用场景?

Redis 是一个使用 C 语言开发的高速缓存数据库。 

Redis 使用场景:
 记录帖子点赞数、点击数、评论数; 
 缓存近期热点数据; 
 记录用户会话信息。

57. Redis 有哪些功能?

 数据缓存功能 
 分布式锁的功能
 支持数据持久化
 解决分布式会话

58.什么是缓存穿透?怎么解决?

缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透

解决:
1. 最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
2.另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

59.什么是缓存击穿?如何解决?

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

缓存击穿解决方案:
  1.设置永久不过期。【这种只适合】
  2.使用互斥锁(mutex key)业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。代码如下:
public String get(key) {
      String value = redis.get(key);
      if (value == null) { //代表缓存值过期
          //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
      if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功
               value = db.get(key);
                      redis.set(key, value, expire_secs);
                      redis.del(key_mutex);
              } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
                      sleep(50);
                      get(key);  //重试
              }
          } else {
              return value;      
          }
 }

60. 什么是缓存雪崩?如何解决?

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
3.设置热点数据永远不过期。

61.Redis 支持的数据类型有哪些?

Redis 支持的数据类型:string(字符串)、list(列表)、hash(字典)、set(集 合)、zset(有序集合)。

62.怎么保证缓存和数据库数据的双写一致性?

 合理设置缓存的过期时间。 
 新增、更改、删除数据库操作时同步更新 Redis,可以使用事物机制来保证数据的一致性。

63.Redis 持久化有几种方式?

Redis 的持久化有两种方式,或者说有两种策略:
 RDB(Redis Database):指定的时间间隔能对你的数据进行快照存储。 
 AOF(Append Only File):每一个收到的写命令都通过 write 函数追加到文件中。

64.Redis 怎么实现分布式锁?

Redis分布式锁其实就是在系统里面占一个“坑”,其他程序也要占“坑”的时候,如果占用成功了就可以继续执行,失败了就只能放弃或稍后重试。
占坑一般使用 setnx(set if not exists)指令,只允许被一个程序占有,使用完调用 del 释放锁。

65.Redis 淘汰策略有哪些?

volatile-lru:从已设置过期时间的数据集(server. db[i]. expires)中挑选最近最少使用的数据淘汰。

volatile-ttl:从已设置过期时间的数据集(server. db[i]. expires)中挑选将要过期的数据淘汰。 

volatile-random:从已设置过期时间的数据集(server. db[i]. expires)中任意选择数据淘汰。 

allkeys-lru:从数据集(server. db[i]. dict)中挑选最近最少使用的数据淘汰。

allkeys-random:从数据集(server. db[i]. dict)中任意选择数据淘汰。

no-enviction(驱逐):禁止驱逐数据。

第十三天:

66. Spring的IoC理解:

(1)什么是IOC:
IOC,Inversion of Control,控制反转,就是将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。
(2)什么是DI:
 IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性.
(3)IoC的原理:
 Spring 的 IoC 的实现原理就是工厂模式加反射机制。

67.Spring的AOP理解:

OOP面向对象,允许开发者定义纵向的关系,但并不适用定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。

AOP,面向切面编程,是对面向对象编程oop的一种补充,用于将那些与业务无关,但对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。

68.BeanFactory和ApplicationContext有什么区别?

 BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。
(1)BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,ApplicationContext接口作为BeanFactory的子接口,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能。

(2)BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。ApplicationContext它是在容器启动时,一次性创建了所有的Bean。

(3)BeanFactory通常以编程的方式创建,ApplicationCotext还能以声明的方式创建。

69.Spring通知(Advice)有哪些类型?

(1)前置通知(Before Advice):在连接点(Join point)之前执行的通知。

(2)后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 

(3)环绕通知(Around Advice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

(4)返回后通知(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)

(5)抛出异常后通知(AfterThrowing advice):在方法抛出异常退出时执行的通知

70. Spring中bean的作用域:

(1)singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。

(2)prototype:为每一个bean请求创建一个实例。

(3)request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。

(4)session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。

(5)global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。

第十四天:

71.Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?

Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。

(1)对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。

(2)对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。

对于有状态的bean(比如Model和View),就需要自行保证线程安全,最直接的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。

也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。

72.Spring基于xml注入bean的几种方式:

  • set()方法注入;

  • 构造器注入:①通过index设置参数的位置;②通过type设置参数类型;

  • 静态工厂注入;

  • 实例工厂注入;

73.Spring的自动装配:

在spring中,使用autowire来配置自动装载模式,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象。

(1)在Spring框架xml配置中共有5种自动装配:

no:默认的方式是no是不进行自动装配的,通过手动设置ref属性来进行装配bean。 byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。 byType:通过参数的数据类型进行自动装配。 constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。 autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。 (2)基于注解的自动装配方式:

使用@Autowired、@Resource注解来自动装配指定的bean。

74. @Autowired和@Resource之间的区别.

(1) @Autowired(属于spring框架)默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。

(2) @Resource(属于Jakarta EE规范)默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

75.说一下Spring的事务传播行为

spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。

1. REQUIRED:如果当前存在事务,就加入该事务,如果当前没有事务,就创建一个新事务,该设置是最常用的设置。
2. REQUIRES_NEW:无论当前存不存在事务,都创建新事务。
3. SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
4. NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
5. MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
6. NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
7. NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

第15天:mybatis面试题

76.通常一个mapper.xml文件,都会对应一个Dao接口,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象 MappedProxy,代理对象会拦截接口方法,根据类的全限定名+方法名,找到唯一一个MapperStatement并调用执行器执行sql,然后将sql执行结果返回。

77.Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一查询,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true/false。

延迟加载的基本原理是使用cglib创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器调用invoke()方法发现a.getB()是null值时,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。

78.Mybatis的一级、二级缓存:

(1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

(2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache的HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置;

(3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。

79. Xml映射文件中,除了常见的select|insert|updae|delete标签外,还有哪些标签?

<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,加上动态sql的9个标签 trim | where | set | foreach | if | choose | when | otherwise | bind 等,其中 <sql> 为sql片段标签,通过<include>标签引入sql片段,<selectKey>为不支持自增的主键生成策略标签。

80.在mapper中如何传递多个参数?

(1)第一种:
//DAO层的函数
Public UserselectUser(String name,String area);  
//对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。
<select id="selectUser"resultMap="BaseResultMap">  
    select *  fromuser_user_t   whereuser_name = #{0} anduser_area=#{1}  
</select>  
 
(2)第二种: 使用 @param 注解:
public interface usermapper {
   user selectuser(@param(“username”) string username,@param(“hashedpassword”) string hashedpassword);
}
然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):
<select id=”selectuser” resulttype=”user”>
         select id, username, hashedpassword
         from some_table
         where username = #{username}
         and hashedpassword = #{hashedpassword}
</select>
 
(3)第三种:多个参数封装成map
try{
//映射文件的命名空间.SQL片段的ID,就可以调用对应的映射文件中的SQL
//由于我们的参数超过了两个,而方法中只有一个Object参数收集,因此我们使用Map集合来装载我们的参数
Map<String, Object> map = new HashMap();
     map.put("start", start);
     map.put("end", end);
     return sqlSession.selectList("StudentID.pagination", map);
 }catch(Exception e){
     e.printStackTrace();
     sqlSession.rollback();
    throw e; }
finally{
 MybatisUtil.closeSqlSession();
 }

第十六天:

81. Spring @bean 和 @component 注解有什么区别

  1. 作用对象不同:@Component 注解作用于类,而 @Bean 注解作用于方法、

  2. @Component 通常是通过路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean 告诉了 Spring 这是某个类的实例,当我们需要用它的时候还给我。

  3. @Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring 容器时,只能通过 @Bean 来实现。

82. springbean的生命周期

是的,Spring Bean的生命周期只有这四个阶段。

要彻底搞清楚Spring的生命周期,首先要把这四个阶段牢牢记住。实例化和属性赋值对应构造方法和setter方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。

  1. 实例化->Instantiation

  2. 属性赋值->Populate

  3. 初始化->Initialization

  4. 销毁->Destruction

83. springbean的循环依赖以及如何解决

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A循环依赖是指两个或多个Spring Bean之间相互引用,形成了一个循环引用的关系。这种情况下,Spring IoC容器无法完成Bean的创建和依赖注入,会导致应用程序启动失败或出现其他异常。

举例来说,假设有两个Bean A和Bean B,它们相互引用,即A依赖B,同时B也依赖A。在这种情况下,当Spring IoC容器尝试创建A时,会发现A依赖B,然后会去创建B,但在创建B的过程中发现B又依赖A,这样就形成了循环依赖,导致Bean的创建无法完成。

解决循环依赖的常见方法有以下几种:

  1. 构造器注入:使用构造器注入代替属性注入。构造器注入可以在Bean创建时就将依赖的Bean传递进去,避免了循环依赖的问题。

  2. Setter方法注入:将属性的依赖注入方式改为Setter方法注入,Setter方法可以在Bean创建后再进行依赖注入,从而避免循环依赖。

  3. 使用@Lazy注解:可以在Bean上使用@Lazy注解延迟初始化Bean,这样可以在需要的时候再进行依赖注入,从而避免循环依赖。

  4. 使用@PostConstruct:可以在Bean的初始化方法上使用@PostConstruct注解,在Bean初始化完成后进行依赖注入,从而解决循环依赖问题。

  5. 使用代理:Spring框架可以使用代理来解决循环依赖。当循环依赖发生时,Spring会创建一个代理对象来代替真正的Bean,从而解决循环依赖问题。

需要注意的是,虽然上述方法可以解决循环依赖问题,但是最好还是避免出现循环依赖的情况,因为循环依赖会增加代码的复杂性,降低应用程序的可维护性。在设计Bean之间的依赖关系时,要尽量保持简单和清晰,避免出现循环引用的情况。

84. spring事务失效的场景

Spring事务可以失效的一些常见场景包括:

  1. 不使用代理调用:Spring的事务机制是通过动态代理来实现的。如果在事务管理的Bean内部直接调用方法而不是通过代理调用,事务将会失效。因为没有经过代理,Spring无法拦截方法调用以启动和管理事务。

  2. 异常被捕获:如果在事务方法内部捕获了异常并进行处理,那么Spring可能无法察觉到异常的发生,从而无法触发事务回滚。要让事务正常工作,异常应该在事务方法内部抛出,并且不被捕获。

  3. 非受检异常:默认情况下,Spring的事务管理只对受检异常(Checked Exception)触发回滚,对于非受检异常(Unchecked Exception)不会触发回滚。如果事务方法内部抛出了非受检异常,并且没有进行处理,事务也会失效。

  4. 方法可见性:Spring的事务是通过AOP实现的,对于非public方法,AOP默认是无法拦截的。因此,如果事务注解的方法不是public的,事务将会失效。

  5. 同类方法之间的相互调用:如果一个事务方法内部调用了同类中另一个事务方法,事务将会失效。这是因为Spring默认情况下事务是基于代理实现的,同一个类内部的方法调用不会触发代理的切面拦截,导致事务无法生效。

  6. 多线程环境:在多线程环境下,如果在一个线程中调用了一个事务方法,而在另一个线程中调用了另一个事务方法,这两个事务将无法形成关联,导致事务失效。

为了确保Spring事务的正常工作,需要注意避免上述的情况,并确保事务注解的方法符合事务的要求,异常的处理方式符合事务回滚的策略。另外,建议在事务方法内部只调用公开的、被代理的方法,并且尽量避免多线程环境下的事务操作。

85. springboot常见注解

1、@SpringBootApplication

替代 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan

2、@ImportAutoConfiguration

导入配置类,一般做测试的时候使用,正常优先使用@EnableAutoConfiguration

3、@SpringBootConfiguration

替代@Configuration

4、@ImportResource

将资源导入容器

5、@PropertySource

导入properties文件

6、PropertySources

@PropertySource 的集合

7、@Role

bean角色定义为ROLE_APPLICATION(默认值)、ROLE_SUPPORT(辅助角色)、ROLE_INFRASTRUCTURE(后台角色,用户无感) 8、@Scope

指定bean的作用域,默认singleton,其它包括prototype、request、session、globalSession

9、@Lazy

使bean懒加载,取消bean预初始化。

10、@Primary

自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否者将抛出异常。

11、@Profile

指定Bean在哪个环境下被激活

12、@DependsOn

依赖的bean注册完成,才注册当前类,依赖bean不存在会报错。用于控制bean加载顺序

13、@PostConstruct

bean的属性都注入完毕后,执行注解标注的方式进行初始化工作

14、@Autowired

默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。

15、@Lookup

根据方法返回的类型,去容器中捞出对应

16、@Qualifier

申明bean名字,且可以按bean名字加载bean

17、@Required

检查bean的属性setXXX()方法,要求属性砸死配置阶段必须已配置

18、@Description

添加bean的文字描述

19、@EnableAspectConfiguration

启动AspectJ自动配置

20、EnableLoadTimeWeaving

启动类加载器动态增强功能,使用instrumentation实现

21、@AutoConfigurationPackage

包含该注解的package会被AutoConfigurationPackages注册

22、@AutoConfigureBefore

在指定配置类初始化前加载

23、@AutoConfigureAfter

在指定配置类初始化后加载

24、@AutoConfigureOrder

指定配置类初始化顺序,越小初始化越早

25、@ModelAttribute

第十七天:

86.MySQL 的内连接、左连接、右连接有什么区别?

内连接关键字:inner join;左连接:left join;右连接:rightjoin。 内连接是把匹配的关联数据显示出来;左连接是左边的表全部显示出来,右边的表显示 出符合条件的数据;右连接正好相反

87.MySQL 索引是怎么实现的?

索引是满足某种特定查找算法的数据结构,而这些数据结构会以某种方式指向数据,从而实现高效率的查找数据。
具体来说 MySQL 中的索引,不同的数据引擎实现有所不同,但目前主流的数据库引擎 的索引都是 B+ 树实现的,B+ 树的搜索效率,可以到达二分法的性能,找到数据区域之后 就找到了完整的数据结构了,所以索引的性能也是更好的。

88.索引的优点和缺点---查询频率高的字段

优势
1) 类似于书籍的目录索引,提高数据检索的效率,降低数据库的IO成本。
2) 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。
劣势
1) 实际上索引也是一张表,该表中保存了主键与索引字段,并指向表的记录,所以索引列也是要占用空间的。
2) 虽然索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行更新操作时,MySQL 不仅要保存数据,还要保存一下索引文件每次更新的索引列字段,都会调整因为更新所带来的键值变化后的索引信息。

89.B+树的特点和作用/优点----必须先看

漫画:什么是B-树? (qq.com)

漫画:什么是B+树? (qq.com)

90.什么是聚集索引和非聚集索引

在数据库的聚集索引(Clustered Index)中,叶子节点直接包含卫星数据。在非聚集索引(NonClustered Index)中,叶子节点带有指向卫星数据的指针。

91.索引的分类

1) 单值索引 :即一个索引只包含单个列,一个表可以有多个单列索引
2) 唯一索引 :索引列的值必须唯一,但允许有空值
3) 复合索引 :即一个索引包含多个列
4) 主键索引:

92.什么是最左前缀法则以及如何设计最左法则

MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city),而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。
在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面


搜的:最左前缀法则是指在MySQL建立联合索引时会遵守最左前缀匹配原则,即最左优先,在检索数据时从联合索引的最左边开始匹配 。 如果想要设计最左法则,需要根据业务需求,where子句中使用最频繁的一列放在最左边。

93.怎么验证 MySQL 的索引是否满足需求?

使用 explain 命令查看查询语句的执行计划,观察是否使用了索引,以及使用的索引是否是最优的。
explain 语法:explain select * from table where type=1。

94.说一下 MySQL 常用的引擎?

InnoDB支持事务, MyISAM不支持
InnoDB支持行级锁, MyISAM支持表级锁.
InnoDB支持外键, MyISAM不支持.
MyISAM支持全文索引, InnoDB不支持(但可以使用Sphinx插件)

95.MySQL由哪些部分组成, 分别用来做什么

连接器: 管理连接, 权限验证.
分析器: 词法分析, 语法分析.
优化器: 执行计划生成, 索引的选择.
执行器: 操作存储引擎, 返回执行结果.
存储引擎: 存储数据, 提供读写接口.

96.说说对 SQL 语句优化有哪些方法 [必须背会]

SQL语句优化的30种方法,总结的太棒了_sql优化-CSDN博客

(1)Where 子句中:where 表之间的连接必须写在其他 Where 条件之前,那些可以过滤掉最大数量记录的条件必须写在 Where 子句的末尾.HAVING 最后。
(2)用 EXISTS 替代 IN、用 NOT EXISTS 替代 NOT IN。
(3)对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及order by 涉及的列上建立索引。
(4)应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
(5)应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描
(6)应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描

97.说一下 MySQL 的行锁和表锁?

MyISAM 只支持表锁,InnoDB 支持表锁和行锁,默认为行锁。
􀀀 表级锁:开销小,加锁快,不会出现死锁。锁定粒度大,发生锁冲突的概率最 高,并发量最低。
􀀀 行级锁:开销大,加锁慢,会出现死锁。锁力度小,发生锁冲突的概率小,并 发度最高

98.说一下乐观锁和悲观锁?

􀀀 乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提 交更新的时候会判断一下在此期间别人有没有去更新这个数据。
􀀀 悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都 会上锁,这样别人想拿这个数据就会阻止,直到这个锁被释放。
数据库的乐观锁需要自己实现,在表里面添加一个 version 字段,每次修改成功值加 1,这样每次修改的时候先对比一下,自己拥有的 version 和数据库现在的 version 是否 一致,如果不一致就不修改,这样就实现了乐观锁。

99.MySQL 问题排查都有哪些手段?

􀀀 开启慢查询日志,查看慢查询的 SQL。
􀀀 使用 explain 命令查询 SQL 语句执行计划。

100.什么是存储过程?用什么来调用?

存储过程是一个预编译的 SQL 语句,优点是允许模块化的设计,就是说只需创建一次,以后在该程序中就可以调用多次。如果某次操作需要执行多次SQL,使用存储过程比单纯 SQL 语句执行要快。可以用一个命令call对象来调用存储过程。

第十八天:

101. 创建对象的方式

  1. 实例化类(通过构造函数创建对象):

  2. 通过反射

  3. 通过克隆技术

  4. 通过反序列化

102. JDK8有哪些新特性

  1. Lambda表达式

  2. Stream API

  3. 接口的默认方法和静态方法

  4. 方法引用

  5. 新的日期和时间API

103. springcloud框架从发起请求之后,整个项目的流程

Spring Cloud是一个用于构建分布式系统的框架,它基于Spring Boot,提供了一套丰富的工具和组件来简化微服务架构的开发和管理。以下是在Spring Cloud中从发起请求到整个项目流程的简要概述:

  1. 发起请求: 客户端(例如Web浏览器或其他服务)发起HTTP请求到Spring Cloud服务的API网关或负载均衡器。在微服务架构中,通常会有多个服务实例提供相同的功能,负载均衡器可以将请求分发给可用的服务实例。

  2. 网关路由: 请求到达API网关后,网关根据配置的路由规则将请求路由到相应的微服务。路由规则可以根据请求的路径、参数等信息来匹配目标服务。

  3. 身份验证和授权: 在进入具体的微服务之前,Spring Cloud可以进行身份验证和授权操作。这可以通过集成shiro或其他安全框架来实现。

  4. 微服务处理: 请求到达目标微服务后,该微服务将处理请求,并根据业务逻辑进行相应的处理。在这个阶段,可能会涉及到数据库访问、业务计算、调用其他微服务等操作。

  5. 服务间通信: 在微服务架构中,不同的微服务之间可能会存在依赖关系。在处理请求时,一个微服务可能需要调用其他微服务来获取所需的数据或执行其他操作。Spring Cloud提供了各种方式来实现服务间通信,如Feign、Ribbon和RestTemplate。

  6. 熔断与降级: 在微服务架构中,不同的服务可能存在相互依赖,当某个服务出现故障或延迟时,可能会导致级联故障。Spring Cloud通过熔断器(例如Hystrix)来处理这种情况,当某个服务不可用时,可以进行快速的降级处理,以防止整个系统崩溃。

  7. 响应结果: 微服务处理完请求后,将结果返回给客户端。在返回之前,可以进行结果的封装、转换或其他处理。

  8. 服务注册与发现: 在Spring Cloud中,服务的注册与发现是一个重要的特性。各个微服务会将自己的信息注册到服务注册中心,以便其他微服务能够发现并调用它们。常用的服务注册与发现组件是Eureka或Consul。

  9. 监控与日志: 在生产环境中,对于微服务的健康状态和性能表现进行监控是必要的。Spring Cloud提供了一系列的监控和日志组件,如Spring Cloud Sleuth和Zipkin,来帮助开发人员对微服务进行跟踪和分析。

  10. 负载均衡和高可用性: Spring Cloud提供了负载均衡的支持,以确保请求被平均分布到多个服务实例上,从而实现高可用性和扩展性。

104. Stream流用的多吗?如何把list转换为map

蛮多的。使用Collectors.toMap()方法

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Main {
public static void main(String[] args) {
  List<Person> personList = List.of(
      new Person("Alice", 30),
      new Person("Bob", 25),
      new Person("Charlie", 35)
  );

  // 将List<Person>转换为Map<String, Person>,以name属性作为key
  Map<String, Person> personMap = personList.stream()
      .collect(Collectors.toMap(Person::getName, person -> person));

  // 打印结果
  personMap.forEach((name, person) -> System.out.println(name + ": " + person));
}
}

class Person {
private String name;
private int age;

public Person(String name, int age) {
  this.name = name;
  this.age = age;
}

public String getName() {
  return name;
}

public int getAge() {
  return age;
}
}

105. 说一下你是如何操作git

106. Mybatis或MybatisPlus底层分页实现原理?

Mybatis-Plus的底层分页实现原理是通过自定义拦截器来拦截执行的SQL语句,在查询SQL的基础上增加分页逻辑,同时执行额外的查询来获取总记录数,并将结果封装到Page对象中返回给应用层。这样,使用Mybatis-Plus提供的分页功能可以简化分页查询的操作,提高开发效率。

107. 什么是服务雪崩?如何解决

所谓的服务雪崩就是在微服务调用链路中,某个服务出现故障,而导致整个调用链路上服务出现故障。

解决方式: 1. 设置超时 2. 线程隔离 3. 限流 4. 熔断降级

而使用的组件: sentinel 或 hystrix

108. 什么是自动装箱和自动拆箱

自动装箱是指将基本数据类型转换为对应的包装类对象。 例如: 将int类型的变量赋值给Integer对象

自动拆箱是指将包装类对象转换为对应的基本数据类型 .例如: 将Integer对象赋值给int类型的变量:

RabbitMQ面试题:

1、为什么要使用MQ?

1、流量消峰

举个例子:如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单时绰绰有余,正常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两万次下单操作系统是处理不了的,只能限制订单超过一万后不允许用户下单。使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体验要好。

2、应用解耦

以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,中单用户感受不到物流系统的故障,提升系统的可用性。

img

3、异步处理

有些服务间调用是异步的,例如 A 调用 B,B 需要花费很长时间执行,但是 A 需要知道 B 什么时候可以执行完,以前一般有两种方式:

  1. A 过一段时间去调用 B 的查询 api 查询

  2. A 提供一个 callback api, B 执行完之后调用 api 通知 A 服务。

这两种方式都不是很优雅,使用消息总线,可以很方便解决这个问题,A 调用 B 服务后,只需要监听 B 处理完成的消息,当 B 处理完成后,会发送一条消息给 MQ,MQ 会将此消息转发给 A 服务。这样 A 服务既不用循环调用 B 的查询 api,也不用提供 callback api。同样 B 服务也不用做这些操作。A 服务还能及时的得到异步处理成功的消息。

img

2、什么是RabbitMQ

RabbitMQ是一个消息中间件:它接受并转发消息。你可以把它当做一个快递站点,当你要发送一个包裹时,你把你的包裹放到快递站,快递员最终会把你的快递送到收件人那里,按照这种逻辑RabbitMQ是一个快递站,一个快递员帮你传递快件。RabbitMQ与快递站的主要区别在于,它不处理快件而是接收,存储和转发消息数据。

3、RabbitMQ各组件的功能

  • Server:接收客户端的连接,实现AMQP实体服务。

  • Connection:连接,应用程序与Server的网络连接,TCP连接。

  • Channel:信道,消息读写等操作在信道中进行。客户端可以建立多个信道,每个信道代表一个会话任务。如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection**极大减少了操作系统建立TCP connection的开销**

  • Message:消息,应用程序和服务器之间传送的数据,消息可以非常简单,也可以很复杂。由Properties和Body组成。Properties为外包装,可以对消息进行修饰,比如消息的优先级、延迟等高级特性;Body就是消息体内容。

  • Virtual Host:虚拟主机,用于逻辑隔离。一个虚拟主机里面可以有若干个Exchange和Queue,同一个虚拟主机里面不能有相同名称的Exchange或Queue。

  • Exchange:交换器,接收消息,按照路由规则将消息路由到一个或者多个队列。如果路由不到,或者返回给生产者,或者直接丢弃。RabbitMQ常用的交换器常用类型有direct、topic、fanout、headers四种,后面详细介绍。

  • Binding:绑定,交换器和消息队列之间的虚拟连接,绑定中可以包含一个或者多个RoutingKey,Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据

  • RoutingKey:路由键,生产者将消息发送给交换器的时候,会发送一个RoutingKey,用来指定路由规则,这样交换器就知道把消息发送到哪个队列。路由键通常为一个“.”分割的字符串,例如“com.rabbitmq”

  • Queue:消息队列,用来保存消息,供消费者消费。

4、RabbitMQ工作原理

不得不看一下经典的图了,如下

img

AMQP 协议模型由三部分组成:生产者、消费者和服务端,执行流程如下:

  1. 生产者是连接到 Server,建立一个连接,开启一个信道。

  2. 生产者声明交换器和队列,设置相关属性,并通过路由键将交换器和队列进行绑定。

  3. 消费者也需要进行建立连接,开启信道等操作,便于接收消息。

  4. 生产者发送消息,发送到服务端中的虚拟主机。

  5. 虚拟主机中的交换器根据路由键选择路由规则,发送到不同的消息队列中。

  6. 订阅了消息队列的消费者就可以获取到消息,进行消费。

4、RabbitMQ 上的一个 queue 中存放的 message 是否有数量限制?

可以认为是无限制,因为限制取决于机器的内存,但是消息过多会导致处理效率的下降。

5、RabbitMQ 允许发送的 message 最大可达多大?

根据 AMQP 协议规定,消息体的大小由 64-bit 的值来指定,所以你就可以知道到底能发多大的数据了

6、RabbitMQ的工作模式

1、simple模式(即最简单的收发模式)

img

  1. 消息产生消息,将消息放入队列

  2. 消息的消费者(consumer) 监听 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患 消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失,这里可以设置成手动的ack,但如果设置成手动ack,处理完后要及时发送ack消息给队列,否则会造成内存溢出)。

2、Work Queues(工作队列)

img

消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2同时监听同一个队列,消息被消费。C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize) 保证一条消息只能被一个消费者使用)。

3、publish/subscribe发布订阅(共享资源)

img

  1. 每个消费者监听自己的队列;

  2. 生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。

5、routing路由模式

img

  1. 消息生产者将消息发送给交换机按照路由判断,路由是字符串(info) 当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息;

  2. 根据业务功能定义路由字符串

  3. 从系统的代码逻辑中获取对应的功能字符串,将消息任务扔到对应的队列中。

  4. 业务场景:error 通知;EXCEPTION;错误通知的功能;传统意义的错误通知;客户通知;利用key路由,可以将程序中的错误封装成消息传入到消息队列中,开发者可以自定义消费者,实时接收错误;

6、topic 主题模式(路由模式的一种)

img

  1. 星号井号代表通配符

  2. 星号代表多个单词,井号代表一个单词

  3. 路由功能添加模糊匹配

  4. 消息产生者产生消息,把消息交给交换机

  5. 交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费

PS:(在我的理解看来就是routing查询的一种模糊匹配,就类似sql的模糊查询方式)

7、如何保证RabbitMQ消息的顺序性?

  • 拆分多个 queue(消息队列),每个 queue(消息队列) 一个 consumer(消费者),就是多一些 queue(消息队列)而已,确实是麻烦点;

  • 或者就一个 queue (消息队列)但是对应一个 consumer(消费者),然后这个 consumer(消费者)内部用内存队列做排队,然后分发给底层不同的 worker 来处理。

8、RabbitMQ消息丢失的情况有哪些?

1、生产者发送消息RabbitMQ Server 消息丢失。发送过程中存在网络问题,导致消息没有发送成功;代码问题,导致消息没发送;

2、RabbitMQ Server中存储的消息丢失。消息没有持久化,服务器重启导致存储的消息丢失;

3、RabbitMQ Server到消费者消息丢失。消费端接收到相关消息之后,消费端还没来得及处理消息,消费端机器就宕机了;处理消息存在异常;

9、RabbitMQ如何保证消息不丢失?

1.生产者发送消息RabbitMQ Server 消息丢失解决方案:

  • 常用解决方案:发送方确认机制(publisher confirm)

  • 开启AMQP的事务处理(不推荐)

2.RabbitMQ Server中存储的消息丢失解决方案:

  • 消息回退:通过设置 mandatory 参数可以在当消息传递过程中不可达目的地时将消息返回给生产者

  • 设置持久化:保证重启过程中,交换机和队列也是持久化的

  1. RabbitMQ Server到消费者消息丢失解决方案:

  • 手动ack确认机制

1、生产者发送消息RabbitMQ Server 消息丢失解决方案

1、发布确认机制

生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker 回传给生产者的确认消息中 delivery-tag 域包含了确认消息的序列号,此外 broker 也可以设置basic.ack 的 multiple 域,表示到这个序列号之前的所有消息都已经得到了处理。 confirm 模式最大的好处在于它是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果 RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack 消息,生产者应用程序同样可以在回调方法中处理该 nack 消息。 发布确认分为三种:

  • 单独发布确认:这是一种简单的确认方式,它是一种同步确认发布的方式,也就是发布一个消息之后只有它被确认发布,后续的消息才能继续发布,waitForConfirmsOrDie(long)这个方法只有在消息被确认的时候才返回,如果在指定时间范围内这个消息没有被确认那么它将抛出异常。 这种确认方式有一个最大的缺点就是:发布速度特别的慢,因为如果没有确认发布的消息就会阻塞所有后续消息的发布,这种方式最多提供每秒不超过数百条发布消息的吞吐量。当然对于某些应用程序来说这可能已经足够了。

  • 批量发布确认:上面那种方式非常慢,与单个等待确认消息相比,先发布一批消息然后一起确认可以极大地提高吞吐量,当然这种方式的缺点就是:当发生故障导致发布出现问题时,不知道是哪个消息出现问题了,我们必须将整个批处理保存在内存中,以记录重要的信息而后重新发布消息。当然这种方案仍然是同步的,也一样阻塞消息的发布。

  • 异步发布确认:异步确认虽然编程逻辑比上两个要复杂,但是性价比最高,无论是可靠性还是效率都没得说,他是利用回调函数来达到消息可靠性传递的,这个中间件也是通过函数回调来保证是否投递成功

2、开启AMQP的事务处理(不推荐)

为什么不推荐呢,因为它是同步的,一条消息发送之后会使发送端阻塞,以等待RabbitMQ Server的回应,之后才能继续发送下一条消息,生产者生产消息的吞吐量和性能都会大大降低,这就跟单独发布确认一样。 如何使用:在生产者发送消息之前,通过channel.txSelect开启一个事务,接着发送消息, 如果消息投递server失败,进行事务回滚channel.txRollback,然后重新发送, 如果server收到消息,就提交事务channel.txCommit

2、RabbitMQ Server中存储的消息丢失解决方案

第一种保证消息丢失,只能够保证发送方发送消息成功到达交换机,若此时服务器存在问题或者绑定的routingKey不正确,导致消息发送失败,那么消息最终也会丢失。

  • 采用消息回退:通过设置 mandatory 参数可以在当消息传递过程中不可达目的地时将消息返回给生产者

  • 设置持久化

1、消息回退

源码: mandatory参数 true:交换机无法将消息进行路由时,会将该消息返回给生产者 false:如果发现消息无法进行路由,则直接丢弃

public void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body) throws IOException { this.delegate.basicPublish(exchange, routingKey, mandatory, props, body); }

有了 mandatory 参数和回退消息,我们获得了对无法投递消息的感知能力,有机会在生产者的消息无法被投递时发现并处理。但有时候,我们并不知道该如何处理这些无法路由的消息,最多打个日志,然后触发报警,再来手动处理。而通过日志来处理这些无法路由的消息是很不优雅的做法,特别是当生产者所在的服务有多台机器的时候,手动复制日志会更加麻烦而且容易出错。

这时需要采用备份交换机

备份交换机可以理解为 RabbitMQ 中交换机的“备胎”,当我们为某一个交换机声明一个对应的备份交换机时,

就是为它创建一个备胎,当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由备份交换机来进行转发和处理,通常备份交换机的类型为 Fanout ,这样就能把所有消息都投递到与其绑定的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会都进入这个队列了。当然,我们还可以建立一个报警队列,用独立的消费者来进行监测和报警。

具体代码请参考这篇:

2、设置持久化

上面我们的角度是站在生产者的方向,但是如果服务器重启了,此时交换机和队列都不存在了,消息存在也发送不了,这时需要把交换机和队列都持久化。

/** * 生成一个队列 * 1.队列名称 * 2.队列里面的消息是否持久化 默认消息存储在内存中 * 3.该队列是否只供一个消费者进行消费 是否进行共享 true 可以多个消费者消费 * 4.是否自动删除 最后一个消费者端开连接以后 该队列是否自动删除 true 自动删除 * 5.其他参数 */ channel.queueDeclare(QUEUE_NAME, false, false, false, null);

3、RabbitMQ Server到消费者消息丢失解决方案

默认消息采用的是自动应答,所以我们要想实现消息消费过程中不丢失,需要把自动应答改为手动应答

//将自动应答关闭 boolean autoAck = false; channel.basicConsume(TASK_QUEUE_NAME, autoAck, deliverCallback, consumerTag -> { });

10、RabbitMQ消息基于什么传输?

由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ 使用信道的方式来传输数据。信道是建立在真实的 TCP 连接内的虚拟连接,且每条 TCP 连接上的信道数量没有限制。

11、RabbitMQ支持消息的幂等性吗?

支持。在消息生产时,MQ 内部针对每条生产者发送的消息生成一个 inner-msg-id,作为去重的依据(消息投递失败并重传),避免重复的消息进入队列。 在消息消费时,要求消息体中必须要有一个 bizId(对于同一业务全局唯一,如支付 ID、订单 ID、帖子 ID 等)作为去重的依据,避免同一条消息被重复消费。

12、RabbitMQ怎么确保消息已被消费?

  • 消费端配置手动ACK确认机制

  • 结合数据库进行状态标记处理

13、RabbitMQ支持事务消息吗?

支持事务消息。前面在第9题中保证生产者不丢失消息,提到可以使用AMQP的事务,但是它是同步的,所以不怎么推荐使用 事务的实现主要是对信道(Channel)的设置,主要方法如下: 1. channel.txSelect() 声明启动事务模式 2.channel.txCommit() 提交事务 3.channel.txRollback()回滚事务

14、RabbitMQ消息持久化的条件?

消息持久化,当然前提是队列必须持久化

  • 声明队列必须设置持久化 durable 设置为 true.

  • 消息推送投递模式必须设置持久化,deliveryMode 设置为 2(持久)。

  • 消息已经到达持久化交换器。

  • 消息已经到达持久化队列。

15、RabiitMQ消息什么情况下会变成死信消息?

由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信消息

16、RabbitMQ死信消息的来源?

  • 消息 TTL 过期

  • 队列达到最大长度(队列满了,无法再添加数据到 mq 中)

  • 消息被拒绝(basic.reject 或 basic.nack)并且 requeue=false.

17、RabbitMQ死信队列的用处?

  • 可以用于实现延迟队列

18、RabbitMQ支持延迟队列吗?

支持。延时队列,队列内部是有序的,最重要的特性就体现在它的延时属性上,延时队列中的元素是希望在指定时间到了以后或之前取出和处理,简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列。

19、RabbitMQ延迟队列的使用场景

  • 订单在十分钟之内未支付则自动取消

  • 新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒

  • 用户注册成功后,如果三天内没有登陆则进行短信提醒

  • 用户发起退款,如果三天内没有得到处理则通知相关运营人员

  • 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议

20、RabbitMQ实现延迟队列的有什么条件?

  • 消息设置TTL

  • 配置了死信队列

21、RabbitMQ怎么实现优先级队列?

  1. 控制台页面:添加一个x-max-priority

img

  1. 生产者添加优先级,案例代码 public class Product { private static final String QUEUE_NAME = "hello"; public static void main(String[] args) throws Exception { try(Channel channel = RabbitMQConfig.getChannel()){ //给消息赋予一个 priority 属性 AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder().priority(5).build(); for (int i = 1; i < 11; i++) { String msg = "info" + i; if(i==5){ channel.basicPublish("", QUEUE_NAME, basicProperties, msg.getBytes()); }else{ channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); } System.out.println("发送消息完成:" + msg); } } } }

  2. 消费者队列中代码添加优先级 public class Consumer { private static final String QUEUE_NAME = "hello"; public static void main(String[] args) throws Exception { Channel channel = RabbitMQConfig.getChannel(); //设置队列的最大优先级 最大可以设置到 255 官网推荐 1-10 如果设置太高比较吃内存和 CPU Map<String, Object> map = new HashMap<>(); map.put("x-max-priority", 10); channel.queueDeclare(QUEUE_NAME, true, false, false, map); System.out.println("消费者等待启动接收消息......"); DeliverCallback deliverCallback = (consumerTag, delivery) ->{ String receivedMessage = new String(delivery.getBody()); System.out.println("接收到消息:"+receivedMessage); }; channel.basicConsume(QUEUE_NAME, true, deliverCallback, (consumerTag) ->{ System.out.println("消费者无法消费消息时调用,如队列被删除"); }); } }

22、哪些情况下推荐使用RabbitMQ的惰性队列

  • 队列可能会产生消息堆积

  • 队列对性能(吞吐量)的要求不是非常高,例如TPS 1万以下的场景

  • 希望队列有稳定的生产消费性能,不受内存影响而波动

23、RabbitMQ如何处理消息堆积情况?

方法:临时扩容,快速处理积压的消息

  • 先修复 consumer 的问题,确保其恢复消费速度,然后将现有的 consumer 都停掉;

  • 临时创建原先 N 倍数量的 queue ,然后写一个临时分发数据的消费者程序,将该程序部署上去消费队列中积压的数据,消费之后不做任何耗时处理,直接均匀轮询写入临时建立好的 N 倍数量的 queue 中;

  • 接着,临时征用 N 倍的机器来部署 consumer,每个 consumer 消费一个临时 queue 的数据

  • 等快速消费完积压数据之后,恢复原先部署架构 ,重新用原先的 consumer 机器消费消息。

这种做法相当于临时将 queue 资源和 consumer 资源扩大 N 倍,以正常 N 倍速度消费。

24、RabbitMQ如何处理消息堆积过程中丢失的数据?

采用“批量重导”的方式,在流量低峰期,写一个程序,手动去查询丢失的那部分数据,然后将消息重新发送到mq里面,把丢失的数据重新补回来。

25、RabbitMQ如何处理长时间未处理导致写满的情况?

如果消息积压在RabbitMQ里,并且长时间都没处理掉,导致RabbitMQ都快写满了,这种情况肯定是临时扩容方案执行太慢;这种时候只好采用 “丢弃+批量重导” 的方式来解决了。首先,临时写个程序,连接到RabbitMQ里面消费数据,消费一个丢弃一个,快速消费掉积压的消息,降低RabbitMQ的压力,然后在流量低峰期时去手动查询重导丢失的这部分数据。

26、如何设计一个消息队列?

要考虑三点:伸缩性、持久化、可用性

  1. 伸缩性:需要扩容的时候可以快速扩容,增加吞吐量和容量;可以参考kafaka的设计理念,broker -> topic -> partition,每个partition放一个机器,就存一部分数据;资源不够了,给topic增加partition,然后做数据迁移,增加机器;

  2. 持久化:也就是数据要不要写入磁盘,不写入吧,进程挂了,数据就丢失了,写入磁盘该如何高效写入呢?kafaka的思路:顺序读写,采用磁盘缓存(Page Cache)的策略,操作系统采用预读和后写的方式,对磁盘进行优化。

  • 预读:磁盘顺序读取的效率是很高的(不需要寻道时间,只需要很少的旋转时间)。而在读取磁盘某块数据时,同时会顺序读取相邻地址的数据加载到PageCache,这样在读取后续连续数据时,只需要从PageCache中读取数据,相当于内存读写,速度会特别快

  • 后写:数据并不是直接写入到磁盘,而是默认先写入到Page Cache,再由Page Cache刷新到磁盘,刷新频率是由操作系统周期性的sync触发的(用户也可以手动调用sync触发刷新操作)。后写的方式大大减少对磁盘的总写入次数,提高写入效率

  1. 可用性:分布式系统的高可用几乎都是通过冗余实现的,Kafka同样如此。Kafka的消息存储到partition中,每个partition在其他的broker中都存在多个副本。对外通过主partition提供读写服务,当主partition所在的broker故障时,通过HA机制,将其他Broker上的某个副本partition会重新选举成主partition,继续对外提供服务。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值