必会Java 开发工程师面试题

一、基础

  1. String/StringBuffer/StringBuilder 区别
    ⚫ 第一点: 可变和适用范围。String 对象是不可变的,而 StringBuffer 和
    StringBuilder 是可变字符序列。每次对 String 的操作相当于生成一个新的 String
    对象,而对 StringBuffer 和 StringBuilder 的操作是对对象本身的操作,而不会生
    成新的对象,所以对于频繁改变内容的字符串避免使用 String,因为频繁的生成对
    象将会对系统性能产生影响。
    ⚫ 第二点: 线程安全。String 由于有 final 修饰,是 immutable 的,安全性是简单而
    纯粹的。StringBuilder 和 StringBuffer 的区别在于 StringBuilder 不保证同步,
    也就是说如果需要线程安全需要使用 StringBuffer,不需要同步的 StringBuilder
    效率更高。
    ⚫ 总结:
     操作少量的数据 = String
     单线程操作字符串缓冲区下操作大量数据 = StringBuilder
     多线程操作字符串缓冲区下操作大量数据 = StringBuffer
    最简回答:String 是不可变的字符串,每次修改都会创建新的对象,适用于不经常
    修改的字符串操作;StringBuffer 是可变的字符串,线程安全,适用于多线程环境;
    StringBuilder 也是可变的字符串,但不是线程安全的,适用于单线程频繁修改字符
    串的场景。
  2. 反射机制及主要用到的方法
    在 Java 中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属
    性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息
    以及动态调用对象方法的功能成为 Java 语言的反射机制。通过反射机制使我们所写的
    代码更具有「通用性」和「灵活性」,比如 Spring/Spring Boot、MyBatis 等框架大
    量用到了反射机制。比如类上加上@Component 注解,Spring 就帮你创建对象,比
    如约定大于配置。
    以下是反射常用到的方法:
  3. Class.forName(String className):根据类名获取对应的 Class 对象。
  4. getClass():获取对象的运行时类型。
  5. getMethod(String name, Class<?>... parameterTypes):获取指定方法名
    和参数类型的方法。
  6. getField(String name):获取指定名称的字段。
  7. newInstance():使用默认的构造函数创建实例。
  8. newInstance(Object... initargs):使用指定参数类型和值的构造函数创建实
    例。
  9. invoke(Object obj, Object... args):调用指定对象的方法。
    以上只是反射中常用的一些方法,还有其他更多的方法可根据具体需求来使用。反射功
    能强大,但也要慎用,因为它可能会降低性能,且破坏了面向对象的封装性。在使用反
    射时应该知道自己的需求,并权衡利弊。
    最简回答:Java 的反射机制是指在运行时动态地获取类的信息并操作类或对象的能
    力。通过反射,我们可以在编译时无法确定的情况下,通过类名获取类的实例、获
    取类的字段、方法、构造函数等信息,并且可以在运行时调用这些方法或访问这些
    字段。
  10. JVM 内存结构
    运行时数据区域被划分为 5 个主要组件:
    ⚫ 方法区(Method Area)
    所有类级别数据将被存储在这里,包括静态变量。每个 JVM 只有一个方法区,它
    是一个共享的资源。
    ⚫ 堆区(Heap Area)
    所有的对象和它们相应的实例变量以及数组将被存储在这里。每个 JVM 同样只有
    一个堆区。由于方法区和堆区的内存由多个线程共享,所以存储的数据不是线程安全的。
    ⚫ 栈区(Stack Area)
    对每个线程会单独创建一个运行时栈。对每个函数呼叫会在栈内存生成一个栈帧
    (Stack Frame)。所有的局部变量将在栈内存中创建。栈区是线程安全的,因为它不是
    一个共享资源。栈帧被分为三个子实体:
     局部变量数组 – 包含多少个与方法相关的局部变量并且相应的值将被存储在
    这里。
     操作数栈 – 如果需要执行任何中间操作,操作数栈作为运行时工作区去执行
    指令。
     帧数据 – 方法的所有符号都保存在这里。在任意异常的情况下,catch 块的信
    息将会被保存在帧数据里面。
    ⚫ PC 寄存器
    每个线程都有一个单独的 PC 寄存器来保存当前执行指令的地址,一旦该指令被执
    行,pc 寄存器会被更新至下条指令的地址。
    ⚫ 本地方法栈
    本地方法栈保存本地方法信息。对每一个线程,将创建一个单独的本地方法栈。
    最简回答:Java 的内存结构主要包括方法区、堆、虚拟机栈、本地方法栈和程序计
    数器。方法区用于存储类信息,堆用于存储对象实例,虚拟机栈用于存储方法调用
    和局部变量,本地方法栈用于存储非 Java 方法信息,程序计数器用于记录当前线程
    执行的指令地址。
  11. ==与 equals 区别
    ⚫ 区别 1. ==是一个运算符 equals 是 Object 类的方法
    ⚫ 区别 2. 比较时的区别
     用于基本类型的变量比较时: 用于比较值是否相等,equals 不能直接用于基
    本数据类型的比较,需要转换为其对应的包装类型。
     用于引用类型的比较时。和 equals 都是比较栈内存中的地址是否相等 。
    相等为 true 否则为 false。但是通常会重写 equals 方法去实现对象内容的比
    较。
    最简回答:"
    “用于比较对象的引用或基本类型的值是否相等,而 equals 方法用
    于比较对象的内容是否相等。换句话说,”
    "比较的是身份,equals 比较的是内容。
  12. 接口和抽象类的区别
    ⚫ 抽象类可以提供成员方法的实现细节,而接口中只能存在 public abstract 方法;
    ⚫ 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static
    final 类型的;
     接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态
    方法;
    ⚫ 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
    最简回答:接口是一种规范,它定义了一组方法的签名,而不包含具体实现。一个
    类可以实现多个接口。抽象类是一种可以包含具体方法和抽象方法的类,它可以被
    继承,但不能被实例化。一个类只能继承一个抽象类。简而言之,接口关注的是行
    为的规范,而抽象类关注的是共享的功能。
  13. 重写(override)和重载(overload)的区别
    ⚫ 重载就是同一个类中,有多个方法名相同,但参数列表不同(包括参数个数和参数
    类型),与返回值无关,与权限修饰符也无关。调用重载的方法时通过传递给它们
    不同的参数个数和参数类型来决定具体使用哪个方法,这叫多态。
    ⚫ 重写就是子类重写基类的方法,方法名,参数列表和返回值都必须相同,否则就不
    是重写而是重载。权限修饰符不能小于被重写方法的修饰符。重写方法不能抛出新
    的异常或者是比被重写方法声明更加宽泛的检查型异常。
    最简回答:重写(override)指的是子类重新实现了父类中已有的方法,子类的方
    法具有相同的名称、参数列表和返回类型。重载(overload)指的是在同一个类中
    定义了多个方法,它们具有相同的名称但参数列表不同。重写主要涉及继承和多态
    性,而重载则是在同一个类中的方法之间进行区分。简而言之,重写是对已有方法
    的重新实现,而重载是创建具有相同名称但不同参数的多个方法。
  14. sleep 和 wait 的区别
    ⚫ sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会
    给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放
    对象锁。
    ⚫ wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进
    入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后
    本线程才进入对象锁定池准备获得对象锁进入运行状态。
    最简回答:sleep 是线程休眠一段时间后继续执行,期间保持对象锁不释放;wait
    是线程等待并释放对象锁,直到其他线程通知后才继续执行。
  15. String 类的常用方法
    ⚫ length();返回字符串长度。
    ⚫ getBytes();返回字符串 byte 类型数组。
    ⚫ indexof(); 返回指定字符的的索引。
    ⚫ split();字符串分割,返回分割后的字符串数组。
    ⚫ replace(); 字符串替换。
    ⚫ substring(); 字符串截取。
    ⚫ trim(); 去除字符串两端空格。
    ⚫ toLowerCase(); 将字符串转换为小写字母。
    ⚫ toUpperCase();将字符串转换为大写字母。
  16. 类加载器加载过程
    Java 类加载器的加载过程可以分为以下几个步骤:
  17. 加载:通过类的全限定名获取字节码文件,并将其转换为方法区内的运行时数据结
    构。
  18. 验证:对字节码进行校验,确保符合 Java 虚拟机规范。
  19. 准备:为类的静态变量分配内存,并设置默认初始值。
  20. 解析:将符号引用转换为直接引用,即将类、方法、字段等解析为具体的内存地址。
  21. 初始化:执行类的初始化代码,包括静态变量赋值和静态代码块的执行。
  22. 双亲委派机制
    Java 双亲委派机制是指在类加载过程中,类加载器会按照一定的顺序委派给父类加载
    器来尝试加载类。这个机制可以确保类的加载安全和防止类的重复加载。下面是一个简
    要解答核心知识的表格:
    通过 Java 双亲委派机制,可以实现类加载的安全性和避免重复加载,同时也允许开发
    者自定义 ClassLoader 来实现特定需求的类加载行为。
    最简回答:双亲委派机制是指类加载器在加载类时,首先将加载请求委托给父类加
    载器,只有当父类加载器无法加载时,才自己尝试加载。从而确保类的加载安全和
    防止类的重复加载。
  23. JVM 的 GC 垃圾回收机制
    JVM 的垃圾回收(Garbage Collection)机制是自动管理内存的一种机制,它可以识
    别和回收不再使用的对象,释放其占用的内存空间。以下是垃圾回收机制的介绍以及一
    些常见的垃圾回收算法:
  24. 垃圾回收机制介绍:
     垃圾回收器负责自动检测和回收不再使用的对象。
     它通过标记-清除、复制、标记-整理等算法来回收垃圾对象。
     垃圾回收过程包括标记、清除、整理和压缩等阶段。
  25. 常见的垃圾回收算法:
     标记-清除算法(Mark and Sweep):首先标记出所有活动对象,然后清除
    未标记的对象。
     复制算法(Copying):将可用内存分为两块,每次只使用其中一块,将存活
    对象复制到另一块中,然后清除当前使用的块。
     标记-整理算法(Mark and Compact):标记出所有活动对象,然后将活动
    对象向一端移动,之后清理边界外的内存。
     分代收集算法(Generational Collection):将堆内存分为新生代和老年代,
    并使用不同的回收算法。新生代通常使用复制算法,老年代使用标记-清除或
    标记-整理算法。
  26. 默认的垃圾回收算法:
     默认情况下,HotSpot JVM 使用分代收集算法。
     新生代使用复制算法,其中的 Eden 区和 Survivor 区各占一部分,并且采用了
    对象晋升机制。
     老年代使用标记-清除或标记-整理算法来回收内存。
    最简回答:GC 垃圾回收机制是指 Java 虚拟机自动管理内存的机制,通过自动识别
    和回收不再使用的对象,释放对应的内存资源,以避免内存泄漏和提高程序性能。
  27. JVM 如何调优(调优参数)[新]
    一般调优,通常优先对堆内存空间进行调整,-Xms 表示初始堆内存大小(默认为物理
    内存的 1/64),-Xmx 表示最大分配对内存大小(默认为物理内存的 1/4)
    举例,物理内存 4G,运行 JAR 包时可指定堆内存参数:
    1 java -Xmx3.2g -Xms1g -jar xxx.jar
  28. 项目是否出现过 OOM 问题?怎么排查的?可能原因?
    如何复现一个内存溢出场景?内存溢出和内存泄露的区
    别?[新]
    ⚫ 项目中的内存溢出出现过,是比较常见的问题。
    ⚫ 如何排查 OOM 问题?对于 OOM 问题的排查,可以采取以下步骤:
     检查日志文件:查看错误日志或异常信息,定位到具体的异常堆栈信息。
     使用内存分析工具:如 Java 的 jmap、jstack、VisualVM 等工具,分析内存
    快照以定位内存泄漏。
     进行代码审查:检查可能引起内存泄漏的代码,如长时间持有对象、未关闭资
    源等。
     增加日志和监控:使用合适的日志和监控工具,记录系统的内存使用情况及峰
    值。
    ⚫ 可能的原因:
     对象过多:创建了大量对象并占用了大量内存。
     内存泄漏:某些对象被错误地保持引用,使得其无法被垃圾回收器回收。
     不合理的内存设置:如分配给应用程序的内存不足导致 OOM。
    ⚫ 如何复现一个内存溢出场景?复现内存溢出的场景可以通过以下方法:
     增加测试数据量:模拟大量数据的输入,使得应用程序需要占用更多的内存。
     重复执行某些操作:如循环读取文件或者对对象进行不断修改等。
     设置小的堆大小:通过设置较小的堆大小,可以更容易地触发 OOM 异常。
    ⚫ 内存溢出和内存泄露的区别:
     内存溢出:指应用程序在申请内存时,无法获得足够的内存空间,造成异常终
    止。常见原因是使用了过多的对象并且没有妥善释放。
     内存泄漏:指应用程序中存在无用的对象占用内存,并且这些对象无法被垃圾
    回收器回收。随着时间的推移,内存资源逐渐耗尽,最终导致内存溢出
  29. JDK1.8 的新特性
    好的,以下是除去 CompletableFuture、重复注解和接口默认方法之外的 JDK 1.8 的
    新特性,并附上一些参考代码案例:
  30. Lambda 表达式:Lambda 允许在 Java 中更简洁地使用函数式编程风格。它提供了
    一种简洁的方式来表示匿名函数,并使代码更易读、易写。
    1 // 使用 Lambda 表达式实现 Runnable 接口
    2 Runnable runnable = () -> System.out.println(“Hello, Lambda!”);
    3 new Thread(runnable).start();
  31. Stream API:Stream API 提供了一种用于处理集合和数组的声明性编程模型。它
    使得可以更直观地操作数据集合,如过滤、映射、排序等操作。
    1 // 使用 Stream API 过滤集合中的元素
    2 List numbers = Arrays.asList(1, 2, 3, 4, 5);
    3 List evenNumbers = numbers.stream()
    4 .filter(n -> n % 2 == 0)
    5 .collect(Collectors.toList());
    6 System.out.println(evenNumbers); // 输出:[2, 4]
  32. 新的日期与时间 API:JDK 1.8 引入了全新的日期和时间 API(java.time 包)。它
    提供了更好的处理日期、时间和时间间隔的方式,并且修复了旧 API 中存在的许多问题。
    1 // 使用新的日期与时间 API 获取当前日期
    2 LocalDate currentDate = LocalDate.now();
    3 System.out.println(currentDate); // 输出当前日期(例如:2023-07-21)
  33. 方法引用:方法引用允许通过方法的名称来引用已存在的方法。这种方式常用于
    Lambda 表达式中,使得代码更简洁、易读。
    1 // 使用方法引用调用静态方法
    2 List names = Arrays.asList(“Alice”, “Bob”, “Charlie”);
    3 names.forEach(System.out::println); // 输出列表中的每个元素
    这些示例代码展示了 JDK 1.8 中的一些新特性的使用。请注意,这只是一部分特性,还
    有更多特性可供探索和学习。
    二、集合
  34. Java 常用的集合、分类、涉及到的接口
    ⚫ Collection 接口: Collection 是集合 List、 Set、 Queue 的最基本的接口。
     List(列表):按照元素插入的顺序保存元素,允许重复元素。常见的有ArrayList、
    LinkedList 等。
     Set(集合):不允许重复元素的无序集合。常见的有 HashSet、TreeSet 等。
     Queue(队列):按照先进先出(FIFO)的原则进行元素操作的集合。常见的
    有 ArrayDeque、PriorityQueue 等。
    ⚫ Map 接口:是映射表的基础接口
     Map(映射):存储键值对(key-value)的集合,根据唯一的键来查找和访
    问值。常见的有 HashMap、TreeMap 等。
    ⚫ Iterator 接口:迭代器,可以通过迭代器遍历集合中的数据
    Java 中的所有集合类都实现了 Iterator 接口。Iterator 接口是 Java 集合框架提供
    的一种用于遍历集合元素的通用方式。它定义了一系列用于访问集合元素的方法,包括
    判断是否还有下一个元素、获取下一个元素、删除当前元素等操作
  35. HashMap 底层原理
    ⚫ JDK1.8 之前
    HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。
    HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,
    然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的
    长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值
    以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲
    突。
     所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是
    扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰
    动函数之后可以减少碰撞。
    ⚫ JDK1.8 及以后
     相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表
    长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的
    长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链
    表转化为红黑树,以减少搜索时间。
     TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都用到了红黑树。
    红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化
    成一个线性结构。
    最简回答:HashMap 在 JDK1.7 使用的是数组+链表的结构,在哈希冲突时通过链
    表进行解决;而 JDK1.8 引入了红黑树的概念,当链表长度超过一定阈值时,链表会
    转换成红黑树,以提高查询效率。
  36. HashMap 为什么用红黑树不用 B 树
    HashMap 使用红黑树(Red-Black Tree)而不是 B 树的主要原因是效率和复杂度。
    ⚫ 效率:红黑树相对于 B 树,在插入、删除和查找操作上具有更好的平均性能。红
    黑树的平衡性质可以保证树的高度相对较小,从而减少了搜索的路径长度,提高了
    操作的效率。
    ⚫ 复杂度:B 树是一种多路搜索树,节点可以包含多个关键字和指针,适合用于磁盘
    存储等场景,可优化磁盘 IO 操作。然而,在内存中的数据结构中,红黑树的实现
    更为简单,代码的复杂度较低。同时,红黑树的性能在典型的 HashMap 使用场
    景中通常表现出良好的性能。
    另外,HashMap 维护了一个哈希表和一个链表或红黑树的混合结构(JDK8 之后),
    当发生哈希冲突时,会使用链表或红黑树来处理冲突。链表适合处理冲突较少的情况,
    而红黑树则适合处理冲突较多的情况。红黑树相对于链表具有更高的查找效率,因此在
    冲突较多的情况下能够提供更好的性能。
    总之,HashMap 使用红黑树而不是 B 树主要是出于对效率和复杂度的考虑。红黑树
    在内存中的实现更简单,对于典型的 HashMap 使用场景能够提供良好的性能,且适
    用于处理冲突较多的情况。
    最简回答:HashMap 使用红黑树而不是 B 树,是因为红黑树相对于 B 树在插入、
    删除和查找等操作上的平衡性能更好,且红黑树的节点比 B 树的节点更小,占用的
    内存更少,适合存储在内存中的数据结构。
  37. HashMap 什么时候扩容
    ⚫ 在 JDK1.7 中,当 HashMap 中元素数量超过当前容量与负载因子(默认为 0.75)
    的乘积时,会触发扩容操作,扩容后的容量为当前容量的两倍。例如,初始容量为
    16,当元素数量达到 12 时会触发扩容,扩容后的容量为 32。
    ⚫ 在 JDK 1.8 中,HashMap 的扩容和红黑树转换是两个独立的操作,且顺序是先扩
    容,然后再进行红黑树的转换。当 HashMap 中的元素数量超过负载因子(默认为
    0.75)与数组容量的乘积时,会触发扩容操作。扩容会重新调整数组的大小,并将
    原来数组中的元素重新分配到新的数组位置上。扩容操作会导致原本哈希冲突较多
    的链表长度变长,因此当链表长度超过阈值(默认为 8)时,会将链表转化为红黑
    树。综上所述,在 JDK 1.8 中,HashMap 的操作顺序是先扩容,然后再进行红黑
    树的转换。扩容是为了减少哈希冲突,提高 HashMap 的性能和效率,而链表转红
    黑树的操作则是为了在特定情况下提供更好的查找、插入和删除元素的性能。
  38. HashMap 的长度为什么是 2 的 N 次方
    为了能让 HashMap 存数据和取数据的效率高,尽可能地减少 hash 值的碰撞,
    也就是说尽量把数
    据能均匀的分配,每个链表或者红黑树长度尽量相等。我们首先可能会想到 % 取
    模的操作来实现。
    下面是回答的重点哟:
    取余(%)操作中如果除数是 2 的幂次,则等价于与其除数减一的与(&)操作
    (也就是说 hash % length == hash &(length - 1) 的前提是 length 是 2 的 n
    次方)。并且,采用二进制位操作 & ,相对于 % 能够提高运算效率。
    这就是为什么 HashMap 的长度需要 2 的 N 次方了
    最简回答:HashMap 的长度选择为 2 的 N 次方是为了提高散列算法的效率和分布
    均匀性,通过使用 2 的幂次方作为长度,可以确保哈希码的高位和低位可以均匀参
    与到散列计算中,减少哈希冲突的发生,并提高散列算法的效率和性能。
  39. HashMap 和 HashTable 区别
    ⚫ 线程是否安全: HashMap 是非线程安全的,Hashtable 是线程安全的,因为
    Hashtable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安
    全的话就使用 ConcurrentHashMap 吧!);
    ⚫ 对 Null key 和 Null value 的支持: HashMap 可以存储 null 的 key 和
    value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许
    有 null 键和 null 值,否则会抛出 NullPointerException。
    ⚫ 效率: 因为线程安全的问题,HashMap 要比 Hashtable 效率高一点。另外,
    Hashtable 基本被淘汰,不要在代码中使用它;
    ⚫ 初始容量大小和每次扩充容量大小的不同: ① 创建时如果不指定容量初始值,
    Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。
    HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创
    建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而
    HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的 tableSizeFor()方法
    保证,下面给出了源代码)。也就是说 HashMap 总是使用 2 的幂作为哈希表的
    大小,后面会介绍到为什么是 2 的幂次方。
    ⚫ 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,
    当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组
    的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表
    转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
    最简回答:HashMap 是非线程安全的、允许空键和空值,并使用数组+链表/红黑
    树底层实现;HashTable 是线程安全的、不允许空键和空值,并使用数组+链表底
    层实现。
  40. ConcurrentHashMap 和 HashMap 的区别
    ConcurrentHashMap 和 HashMap 是 Java 中常用的两种 Map 实现,它们之间有以
    下几个区别:
  41. 线程安全性:ConcurrentHashMap 是线程安全的,多个线程可以同时对其进行读
    写操作而不需要额外的同步措施;而 HashMap 是非线程安全的,如果多个线程同
    时对其进行读写操作,可能会导致数据不一致或抛出异常。
  42. 锁粒度:ConcurrentHashMap 通过分段锁(Segment)实现并发访问的高效性。
    相比之下,HashMap 只能通过全局锁来保证线程安全性,因此在并发场景下性能
    较低。
  43. 迭代器弱一致性:ConcurrentHashMap 的迭代器是弱一致的,即在迭代过程中可
    以容忍在迭代开始时已有的修改和在迭代期间的新增修改。HashMap 的迭代器在
    迭代过程中如果发生结构性修改(例如增加或删除元素),会抛出
    ConcurrentModificationException 异常。
  44. 初始容量和扩容机制:ConcurrentHashMap 在初始化时可以指定并发级别和初始
    容量,可以通过合理设置参数来提高并发访问的效率。而 HashMap 默认的初始容
    量较小,加载因子较大,在元素数量达到一定阈值时会触发扩容操作,而扩容操作
    比较耗时。
  45. null 值和 null 键:ConcurrentHashMap 中既不允许存储 null 值,也不允许存储
    null 键;而 HashMap 允许存储一个 null 值和多个 null 键。
    总体来说,ConcurrentHashMap 适用于高并发的场景,能够提供更好的性能和线程安
    全性;而 HashMap 适用于单线程环境或者在已知不存在并发访问的情况下。选择使用
    哪种 Map 实现要根据具体的需求和场景进行权衡。
    最简回答:ConcurrentHashMap 是线程安全的并发哈希表,支持高效地并发访问,
    而 HashMap 是非线程安全的哈希表,适用于单线程环境下的使用。
  46. ConcurrentHashMap 和 HashTable 区别,他们如何
    保证线程安全
     底层数据结构:
    ◼ JDK1.7 的 ConcurrentHashMap 底层采用分段的数组+链表 实现,
    JDK1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组+链表/红黑
    二叉树。
    ◼ Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采
    用数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解
    决哈希冲突而存在的;
     实现线程安全的方式(重要):
    ◼ ① 在 JDK1.7 的时候,ConcurrentHashMap(分段锁) 对整个桶数
    组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线
    程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。到
    了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数
    组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和
    CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化) 整个
    看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看
    到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
    ◼ ② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非
    常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会
    进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put
    添加元素,也不能使用 get,竞争会越来越激烈效率越低。
    最简回答:ConcurrentHashMap 和 HashTable 的区别在于并发性和线程安全性,
    ConcurrentHashMap 利用分段锁(Segment)实现高效的并发访问和更新,而
    HashTable 使用全局锁(synchronized)来保证线程安全。
  47. ArrayList 与 LinkedList 区别
    ⚫ 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程
    安全;
    ⚫ 底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是
    双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链
    表和双向循环链表的区别,下面有介绍到!)
    ⚫ 插入和删除是否受元素位置的影响:
     ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影
    响。比如:执行 add(E e)方法的时候,ArrayList 会默认在将指定的元素追加
    到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插
    入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因
    为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执
    行向后位/向前移一位的操作。
     LinkedList 采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位
    置的影响(add(E e)、addFirst(E e)、addLast(E e)、removeFirst() 、
    removeLast()),近似 O(1),如果是要在指定位置 i 插入和删除元素的话
    (add(int index, E element),remove(Object o))时间复杂度近似为 O(n) ,
    因为需要先移动到指定位置再插入。
    ⚫ 是否支持快速随机访问:LinkedList 不支持高效的随机元素访问,而 ArrayList 支
    持。快速随机访问就是通过元素的序号快速获取元素对象(对应于 get(int index)方
    法)。
    ⚫ 内存空间占用: ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定
    的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比
    ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
    ⚫ 时间复杂度:
     对于 ArrayList:查询单个元素的时间复杂度为 O(1),即常数时间。由于
    ArrayList 使用了数组来存储元素,每个元素都可以通过索引直接访问,因此
    根据索引获取元素的操作非常高效。
     对于 LinkedList:查询单个元素的时间复杂度为 O(n),即线性时间。由于
    LinkedList 是基于链表实现的,每个元素只能通过遍历链表来找到目标元素,
    因此随着链表长度的增加,查询操作需要遍历更多的元素,导致时间复杂度增
    加。
    最简回答:ArrayList 基于数组实现,适合随机访问但插入/删除效率低;LinkedList
    基于链表实现,适合频繁的插入/删除操作但访问元素效率较低。
  48. 遍历 LIST 有哪些方式
     普通遍历:for(int i=0; i< arrays.size(); i++)
     增强 for 遍历:for(String str : arrays)
     foreach 遍历:list.forEach((str) -> xxxxx)
     使用 Iterator 迭代器遍历
     java8 stream 遍历
  49. 数组 (Array) 和列表 (ArrayList) 区别
    ⚫ Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
    ⚫ Array 大小是固定的,ArrayList 的大小是动态变化的。
    ArrayList 处理固定大小的基本数据类型的时候,这种方式相对比较慢
  50. 线程安全的集合类和有序的集合类
    ⚫ 有序的集合类包括:
     TreeMap - 基于红黑树实现的有序 Map。
     LinkedHashMap - 基于哈希表和双向链表实现的有序 Map。
     TreeSet - 基于红黑树实现的有序 Set。
     LinkedHashSet - 基于哈希表和双向链表实现的有序 Set。
    示例:
     有序 Map:TreeMap
     有序 List:LinkedList
     有序 Set:LinkedHashSet
    ArrayList 虽然是一个有序集合,但它是按添加顺序进行排序而不是根据元素
    的值进行排序。如果需要根据元素的值进行排序,应该使用 TreeSet 或
    TreeMap。
    ⚫ 线程安全的集合类有以下几种:
     ConcurrentHashMap - 线程安全的哈希表实现的 Map。
     CopyOnWriteArrayList - 线程安全的数组列表实现的 List。
     ConcurrentSkipListSet - 线程安全的跳表实现的有序 Set。
    示例:
     Map 的例子:ConcurrentHashMap
     List 的例子:CopyOnWriteArrayList
     Set 的例子:ConcurrentSkipListSet
    三、线程和锁
  51. 线程的状态
    在 Java 开发中,线程的状态可以通过 Thread 类中的 getState()方法来获取。以下是
    Java 开发中常见的线程状态:
  52. NEW(新建):线程被创建但尚未启动。
  53. RUNNABLE(可运行):线程可以在任意时刻运行。处于这个状态的线程可能正
    在运行,也可能正在等待 CPU 分配时间片。
  54. BLOCKED(阻塞):线程被阻止执行,因为它正在等待监视器锁定。其他线程正
    在占用所需的锁定,因此线程被阻塞。
  55. WAITING(等待):线程进入等待状态,直到其他线程显式地唤醒它。线程可以
    调用 Object 类的 wait()方法、join()方法或 Lock 类的条件等待方法进入此状态。
  56. TIMED_WAITING(计时等待):线程进入计时等待状态,等待一段指定的时间。
    线程可以调用 Thread.sleep()方法、Object 类的 wait()方法、join()方法或 Lock
    类的计时等待方法进入此状态。
  57. TERMINATED(终止):线程完成了其任务,或者因为异常或其他原因而终止运
    行。
    以上是 Java 开发中线程的常见状态。线程可以根据业务逻辑和操作系统的调度来在不
    同状态之间转换。了解线程状态对于编写并发程序和调试多线程应用程序非常重要。
    最简回答:线程的状态包括:新建(New)、就绪(Runnable)、运行(Running)、
    阻塞(Blocked)、等待(Waiting)、超时等待(Timed Waiting)、终止(Terminated)。
  58. 创建线程的方式
    在 Java 中,创建线程有以下几种方式:
  59. 继承 Thread 类:
    1 // 定义一个继承自 Thread 类的线程类
    2 class MyThread extends Thread {
    3 public void run() {
    4 // 线程执行的代码
    5 System.out.println(“Thread running”);
    6 }
    7 }
    8
    9 // 创建线程实例,并启动线程
    10 public class Main {
    11 public static void main(String[] args) {
    12 MyThread thread = new MyThread();
    13 thread.start();
    14 }
    15 }
  60. 实现 Runnable 接口:
    1 // 定义一个实现 Runnable 接口的线程类
    2 class MyRunnable implements Runnable {
    3 public void run() {
    4 // 线程执行的代码
    5 System.out.println(“Thread running”);
    6 }
    7 }
    8
    9 // 创建线程实例,并启动线程
    10 public class Main {
    11 public static void main(String[] args) {
    12 MyRunnable runnable = new MyRunnable();
    13 Thread thread = new Thread(runnable);
    14 thread.start();
    15 }
    16 }
  61. 使用匿名内部类:
    1 public class Main {
    2 public static void main(String[] args) {
    3 Thread thread = new Thread(new Runnable() {
    4 public void run() {
    5 // 线程执行的代码
    6 System.out.println(“Thread running”);
    7 }
    8 });
    9 thread.start();
    10 }
    11 }
  62. 使用 Lambda 表达式:
    1 public class Main {
    2 public static void main(String[] args) {
    3 Thread thread = new Thread(() -> System.out.println(“Thread running”));
    4 thread.start();
    5 }
    6 }
    这些都是 Java 中创建线程的常见方式,你可以根据具体需求选择合适的方式来创建线
    程。
  63. 线程池核心参数
    1 public ThreadPoolExecutor(int corePoolSize,
    2 int maximumPoolSize,
    3 long keepAliveTime,
    4 TimeUnit unit,
    5 BlockingQueue workQueue,
    6 ThreadFactory threadFactory,
    7 RejectedExecutionHandler handler)
    ⚫ corePoolSize => 线程池核心线程数量
    ⚫ maximumPoolSize => 线程池最大数量(包含核心线程数量)
    ⚫ keepAliveTime => 当前线程池数量超过 corePoolSize 时,多余的空闲线程的
    存活时间,即多次时间内会被销毁。
    ⚫ unit => keepAliveTime 的单位
    ⚫ workQueue => 线程池所使用的缓冲队列,被提交但尚未被执行的任务
    ⚫ threadFactory => 线程工厂,用于创建线程,一般用默认的即可
    ⚫ handler => 拒绝策略,当任务太多来不及处理,如何拒绝任务
  64. 如何创建线程池
    企业最佳实践:不要使用 Executors 直接创建线程池,会出现 OOM 问题,要使用
    ThreadPoolExecutor 构造方法创建,引用自《阿里巴巴开发手册》
    【强制】线程池不允许使用 Executors 去创建,而是通过
    ThreadPoolExecutor 的方式,这 样的处理方式让写的同学更加明确线程池
    的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的
    弊端如下: 1) FixedThreadPool 和 SingleThreadPool: 允许的请求队
    列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
    2) CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,
    可能会创建大量的线程,从而导致 OOM。
    ⚫ 创建线程池方式一:new ThreadPoolExecutor 方式
    1 ExecutorService executorService = new ThreadPoolExecutor(3,5,10,
    TimeUnit.SECONDS,new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new
    ThreadPoolExecutor.AbortPolicy());
    2 for (int i = 0; i < 9; i++) {
    3 executorService.execute(()->{
    4 System.out.println(Thread.currentThread().getName() + "开始办理业务了。。。。。。
    ");
    5 });
    6 }
    ⚫ 创建线程池方式二:spring 的 ThreadPoolTaskExecutor 方式
    1 @Configuration
    2 public class ExecturConfig {
    3 @Bean(“taskExector”)
    4 public Executor taskExector() {
    5 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    6 executor.setCorePoolSize(3);//核心池大小
    7 executor.setMaxPoolSize(5);//最大线程数
    8 executor.setQueueCapacity(3);//队列长度
    9 executor.setKeepAliveSeconds(10);//线程空闲时间
    10 executor.setThreadNamePrefix(“tsak-asyn”);//线程前缀名称
    11 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//
    配置拒绝策略
    12 return executor;
    13 }
    14 }
    创建线程池的注意事项:
  65. 线程池的大小:线程池的大小应根据任务的类型和系统资源进行合理的配置。如果
    线程池的大小过小,可能会导致任务排队等待执行,影响系统的响应性能;如果线
    程池的大小过大,可能会占用过多的系统资源,导致系统负载过重。可以通过监控
    和调整线程池的大小来优化性能。
  66. 任务队列的选择:线程池通常会使用一个任务队列来保存待执行的任务。任务队列
    的选择应根据任务的特性进行合理的选择。如果任务较多且执行时间较短,可以选
    择无界队列(如 LinkedBlockingQueue);如果任务较少且执行时间较长,可以
    选择有界队列(如 ArrayBlockingQueue)或者优先级队列(如
    PriorityBlockingQueue)。
  67. 线程池的拒绝策略:当任务无法被线程池接收执行时,需要定义适当的拒绝策略。
    常见的拒绝策略有:抛出异常(AbortPolicy)、丢弃任务(DiscardPolicy)、丢
    弃最早的任务(DiscardOldestPolicy)和调用者运行任务(CallerRunsPolicy)。
    根据业务需求选择合适的拒绝策略。
  68. 线程池的生命周期管理:线程池的生命周期包括初始化、执行任务和关闭。在初始
    化时,需要设置线程池的参数;在执行任务时,需要提交任务到线程池;在关闭时,
    需要调用线程池的 shutdown()或 shutdownNow()方法来关闭线程池,并等待所
    有任务完成。正确地管理线程池的生命周期可以避免资源泄漏和线程阻塞的问题。
  69. 线程安全性:在自定义线程池时,需要考虑线程安全性。多个任务并发执行时,可
    能会涉及到共享资源的访问,需要使用合适的同步机制来保证线程安全。
    总之,自定义线程池需要合理配置线程池的大小、选择适当的任务队列和拒绝策略,正
    确管理线程池的生命周期,并考虑线程安全性。这些注意事项可以帮助我们设计高效、
    可靠的线程池。
  70. 线程池工作原理
    线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列
    里面有任务,线程池也不会马上执行它们。
    当调用 execute() 方法添加一个任务时,线程池会做如下判断:
    ⚫ 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
    ⚫ 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
    ⚫ 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还
    是要创建非核心线程立刻运行这个任务;
    ⚫ 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线
    程池会抛出异常 RejectExecutionException。
    当一个线程完成任务时,它会从队列中取下一个任务来执行。
    当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当
    前运
    行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成
    后,它
    最终会收缩到 corePoolSize 的大小。
  71. 线程池大小如何设定
    线程池使用面临的核心的问题在于:线程池的参数并不好配置。线程池的数量应该综合
    考虑 CPU 核心数、并发请求数量、任务类型和任务队列容量等因素。根据具体情况进
    行调试和压测,逐步调整线程池大小,以找到最佳配置,以提高系统性能和资源利用率。
    ⚫ 有一个简单并且适用面比较广的公式:2
     CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数
    设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止
    线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务
    暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充
    分利用 CPU 的空闲时间。
     I/O 密集型任务(2N):这种任务应用起来,系统会用大部分的时间来处理 I/O
    交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将
    CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配
    置一些线程,具体的计算方法是 2N。
    ⚫ 如何判断是 CPU 密集任务还是 IO 密集任务?
    CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据
    进行排序。但凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是
    CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等
    待 IO 操作完成上。
     如果是 CPU 密集型的,可以把核心线程数设置为核心数+1。
     如果是 IO 密集型的,可以把核心线程数设置 2*CPU 核心数
    最简回答:生产环境中,Java 线程池大小的设定与硬件资源和并发需求密切相关。
    通常可以考虑 CPU 核心数、内存容量、网络带宽等硬件资源,并结合预估的并发请
    求量来确定线程池大小,以充分利用资源并保持合理的并发处理能力。较多的硬件
    资源和高并发通常需要更大的线程池来提高并发处理效率。
  72. 线程池的拒绝策略有哪些
    以上列出的是 Java 线程池中常见的拒绝策略,具体可以根据实际情况选择合适的拒绝
    策略,也可以自定义实现 RejectedExecutionHandler 接口来定义自己的拒绝策略。默
    认的拒绝策略是 AbortPolicy,即直接抛出异常。
    最简回答:线程池的拒绝策略有四种:AbortPolicy(默认方式,中止并抛出
    RejectedExecutionException 异常)、CallerRunsPolicy(使用调用线程来执行被
    拒绝的任务)、DiscardPolicy(默默地丢弃被拒绝的任务)以及 DiscardOldestPolicy
    (丢弃最早被添加到队列的任务,然后尝试重新提交新任务)。。如果希望快速失
    败并将异常传递给调用者,则选择 AbortPolicy。如果希望尽可能保证任务的执行
    而不堆积在队列中,则选择 CallerRunsPolicy。如果对任务的丢失情况不敏感,则
    选择 DiscardPolicy。而如果希望尽可能保留最新的任务而不是旧的任务,则选择
    DiscardOldestPolicy。
  73. synchronized 和 lock 的区别
    最简回答:synchronized 和 Lock 的区别是:synchronized 是 Java 关键字,是内
    置的同步机制,能修饰方法或代码块,锁的获取是隐式的,底层实现原理是基于 JVM
    内置监视器锁;而 Lock 是一个接口,提供更灵活的同步机制,可以手动控制锁的获
    取和释放,底层实现可以是 ReentrantLock 等,性能在高竞争环境下通常较好。
  74. 什么情况下会产生死锁,如何解决
    死锁是多线程编程中常见的问题,当多个线程相互等待对方释放资源时,就可能导致死
    锁的发生。通常情况下,死锁发生的四个必要条件是:互斥条件、请求与保持条件、不
    可剥夺条件和循环等待条件。
    产生死锁的情况:
  75. 互斥锁顺序问题:如果多个线程按不同的顺序获取锁,并且互相依赖对方释放的锁,
    就有可能导致死锁。
  76. 资源竞争问题:当多个线程同时竞争有限的资源,例如共享的数据库连接、文件等,
    在资源分配不当的情况下,可能导致死锁。
    解决死锁方法:
  77. 避免使用嵌套锁:尽量避免在一个锁内部再次申请其他锁资源,减少死锁可能性。
  78. 统一锁申请顺序:对于需要多个锁的场景,确保所有线程以相同的顺序请求锁,避
    免出现循环等待的情况。
  79. 加锁超时或自动释放:在申请锁时,设置一个等待时间或使用可重入锁,并且设置
    超时时间,避免线程长时间等待而导致死锁。同时,在使用完锁后,及时释放资源,避
    免持有锁时间过长。
  80. 死锁检测和恢复:通过死锁检测算法,定期检测系统中的死锁情况,并尝试解决死
    锁,然后恢复运行。
  81. 资源分配策略优化:评估和优化资源的分配策略,避免资源竞争和瓶颈情况的发生。
  82. 避免长时间持有锁:在代码设计中,尽量减少需要锁的代码块,避免长时间持有锁,
    减少死锁的机会。可以使用并发集合或并发算法来减少对锁的需求。
    总的来说,解决死锁问题需要注意锁的申请顺序、资源分配策略、超时设置等,通过优
    化设计和避免资源竞争,可以减少死锁的可能性。在发生死锁时,通过死锁检测和恢复
    等方法解决死锁问题。
    最简回答:产生死锁的情况是在多线程程序中,每个线程都持有一些资源,并且等
    待其他线程释放它所需的资源。解决死锁可采取以下方法:避免死锁的发生,通过
    破坏死锁产生的四个必要条件之一来预防;检测死锁,使用算法检测出是否存在死
    锁,并采取相应的措施解除死锁;恢复死锁,即进行资源的强制抢占或进行回滚操
    作,将进程回退到安全状态以解除死锁。
  83. ThreadLocal 是一个什么样的技术
    ThreadLocal 的实现原理:
    ThreadLocal 通过在每个线程中维护一个 ThreadLocalMap 对象来实现线程隔离。
    ThreadLocalMap 以 ThreadLocal 对象作为键,线程私有的变量副本作为值。每个线
    程都有自己的 ThreadLocalMap,线程可以通过 ThreadLocal 的 get()和 set()方法来
    获取和设置自己线程的 ThreadLocal 变量的值。
    应用场景:
  84. 多线程环境下需要独立存储和获取数据的场景,例如线程池中的任务需要使用各自
    独立的数据库连接、计数器等。
  85. 线程上下文传递,例如 Web 框架中将请求信息或用户登录信息存储在 ThreadLocal
    中,方便各层次方法调用时获取,避免了传递参数的麻烦。
    坑与解决方法:
  86. 内存泄漏问题:由于 ThreadLocal 的生命周期和线程的生命周期绑定,使用完
    ThreadLocal 后,需要调用 remove()方法进行清理,避免内存泄漏。
    解决方法:在使用完 ThreadLocal 后,在合适的地方调用 remove()方法清理资源,可
    以使用 try-finally 语句块确保清理操作的执行,或者使用 ThreadLocal 的 initialValue()
    方法设置初始值,这样在线程结束后会自动清理。
    1 private static ThreadLocal threadLocal = ThreadLocal.withInitial(() -> {
    2 return “initial value”;
    3 });
    4
    5 // 使用完 ThreadLocal 后,调用 remove()方法清理
    6 threadLocal.remove();
  87. 共享变量问题:如果多个线程共享了同一个 ThreadLocal 变量,可能会导致数据错
    乱或不确定的结果。每个线程应持有自己的 ThreadLocal 变量实例。
    解决方法:对于需要在多个线程之间共享变量的情况,应该创建多个 ThreadLocal 实
    例,每个线程持有自己的实例。
  88. 线程池使用时注意:在使用线程池时,需要特别小心 ThreadLocal 的使用,避免由
    于线程的重用而导致 ThreadLocal 数据的混乱。
    解决方法:使用线程池时,应避免使用 ThreadLocal 变量或者在使用前后显式清理
    ThreadLocal 变量,确保每次任务执行时 ThreadLocal 的状态是干净的。
    总的来说,使用 ThreadLocal 时需要注意其生命周期、清理和共享的问题,合理使用
    并及时清理 ThreadLocal,可以避免潜在的问题发生。
    最简回答:
    ThreadLocal 是一种 Java 技术,它允许在多线程环境中维护线程私有的变量副本。
    底层实现会使用一个类似于 Map 的结构来存储每个线程的变量副本。ThreadLocal
    并不是强引用或弱引用,它使用弱引用作为键来维护各个线程的变量副本,但变量
    本身由线程强引用。在使用 ThreadLocal 时,可能会出现内存泄漏的问题。如果线
    程结束了,但 ThreadLocal 中的变量没有被手动清理,那么该变量会一直存在于
    ThreadLocal 的 Map 中,导致内存泄漏。解决这个问题的常见方式是在使用完
    ThreadLocal 后调用 remove()方法将变量从 ThreadLocal 中移除,或者使用 Java 8
    中的 ThreadLocal 的 InitialValue 方法来提供默认值。另外,也可以使用
    ThreadLocal 的弱引用方式来解决内存泄漏问题,例如使用
    InheritableThreadLocal。
  89. 悲观锁和乐观锁的区别[新]
    悲观锁:认为自己在使用数据的时候一定有别的线程来修改数据,在获取数据的时候会
    先加锁,确保数据不会被别的线程修改。
    锁实现:关键字 synchronized、接口 Lock 的实现类
    适用场景:写操作较多,先加锁可以保证写操作时数据正确
    乐观锁:认为自己使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新
    数据的时候去判断之前有没有别的线程更新了这个数据
    锁实现 1:CAS 算法, CAS 即 Compare And Swap,是一种更新的原子操作,比较
    当前值跟传入值是否一样,一样则更新,否则返回 false,不进行任何操作;例如
    ActomicInteger 类的原子自增是通过 CAS 自选实现。
    锁实现 2:版本号控制:数据表中加上版本号字段 version,表示数据被修改的次数。
    当数据被修改时,这个字段值会加 1,提交必须满足“ 提交版本必须大于记录当前版
    本才能执行更新 “ 的乐观锁策略
    适用场景:读操作较多,不加锁的特点能够使其读操作的性能大幅提升
    四、设计模式
  90. 单例模式的几种写法
    懒加载-懒汉模式
    1 public class Singleton {
    2
    3 private volatile static Singletoninstance;
    4
    5 private Singleton() {
    6 }
    7
    8 public static Singleton getInstance() {
    9 if (instancenull) { //第一次检查
    10 synchronized (Singleton.class) {
    11 if (instance
    null) { //第二次检查
    12 instance=new Singleton();
    13 }
    14 }
    15 }
    16 return instance;
    17 }
    18 }
    在上述代码中,使用双重检查锁实现了线程安全的单例模式。getInstance()方法首
    先检查 instance 是否为 null,如果为 null,则进入同步块。在同步块内部,再次检
    查 instance 是否为 null,这是为了防止多个线程同时通过了第一次检查,然后一个
    线程创建了实例,而另一个线程又创建了一个实例的情况发生。
    使用双重检查锁可以在保证线程安全的前提下,减少锁的竞争,提高性能。但需要
    注意的是,使用双重检查锁需要将 instance 声明为 volatile 类型,以确保多线程环
    境下的可见性。
    预加载-饿汉模式
    1 public class Singleton {
    2
    3 private static final Singleton instance = new Singleton();
    4
    5 private Singleton(){
    6 }
    7
    8 public static Singleton getInstance(){
    9 return instance;
    10 }
    11 }
    静态内部类模式
    1 public class Singleton {
    2
    3 private static class SingletonHolder {
    4 private static Singleton instance=new Singleton();
    5 }
    6
    7 private Singleton(){}
    8
    9 public static Singleton getInstance() {
    10 return SingletonHolder.instance;
    11 }
    12 }
    13
    枚举模式
    1 public enum Singleton {
    2 /**
    3 * 单例实例
    4 */
    5 INSTANCE;
    6
    7 public void doSomeThing(){
    8 System.out.println(“done”);
    9 }
    10 }
    单元素的枚举类型是实现 Singleton 的最佳方法
  91. Spring 中的设计模式有哪些
     工厂模式: Spring 使用工厂模式通过 BeanFactory 和 ApplicationContext
    创建 bean 对 象。
     单例模式: Spring 中的 bean 默认都是单例的。
     代理模式: Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成
    技术;
  92. MyBatis 中有哪些设计模式
     工厂模式,例如 SqlSessionFactory、ObjectFactory、MapperProxyFactory;
     单例模式,例如 ErrorContext 和 LogFactory;
     代理模式,Mybatis 实现的核心,比如 MapperProxy、ConnectionLogger,
    用的 jdk 的动态代理;还有 executor.loader 包使用了 cglib 或者 javassist 达
    到延迟加载的效果;
    五、MySQL
  93. 数据库的 ACID 特性
  94. 原子性(Atomicity):事务中的操作要么全部成功,要么全部失败。事务是一个
    不可分割的单元,要么全部执行,要么全部回滚。如果事务中的任何操作失败,所
    有操作都将被回滚到事务开始之前的状态,以保证数据的一致性。
  95. 一致性(Consistency):事务的执行应使数据库从一个一致性状态转移到另一个
    一致性状态。在事务开始和结束时,数据库的完整性约束应得到满足,确保数据的
    正确性和一致性。
  96. 隔离性(Isolation):每个事务在执行过程中都应该与其他事务隔离。并发事务
    的执行应当互不干扰,每个事务应该感知不到其他事务的存在或并发执行。隔离级
    别定义了不同事务之间的可见性和互相影响的程度。
  97. 持久性(Durability):一旦事务提交成功,其对数据库的修改应该永久保存,即
    使系统发生故障或重启,也应该能够保持数据的持久性。
    最简回答:ACID 特性是指原子性(Atomicity)、一致性(Consistency)、隔离
    性(Isolation)和持久性(Durability),用于保证数据库事务的可靠性和一致性。
  98. MySQL 存储引擎
    MySQL 默认是 Innodb 存储引擎,适合比较庞大的应用场景
    ⚫ InnoDB:MySql 5.6 版本默认的存储引擎。InnoDB 是一个事务安全的存储引擎,
    它具备提交、回滚以及崩溃恢复的功能以保护用户数据。InnoDB 的行级别锁定以
    及 Oracle 风格的一致性无锁读提升了它的多用户并发数以及性能。InnoDB 将用
    户数据存储在聚集索引中以减少基于主键的普通查询所带来的 I/O 开销。为了保
    证数据的完整性,InnoDB 还支持外键约束。
    ⚫ MyISAM:MyISAM 既不支持事务、也不支持外键、其优势是访问速度快,但是
    表级别的锁定限制了它在读写负载方面的性能,因此它经常应用于只读或者以读为
    主的数据场景。
    最简回答:InnoDB 是 MySQL 的默认存储引擎,支持事务处理、行级锁和外键;而
    MyISAM 不支持事务、只有表级锁,并且不支持外键。
  99. 数据库的事务隔离级别
    SQL 标准定义了四个隔离级别:
     READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提
    交的数据变更,可能会导致脏读、幻读或不可重复读。
     READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可
    以阻止脏读,但是幻读或不可重复读仍有可能发生。
     REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,
    除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有
    可能发生。
     SERIALIZABLE(可串行化): 最高的隔离级别,完全服从 ACID 的隔离级别。
    所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,
    该级别可以防止脏读、不可重复读以及幻读。
    隔离级别 脏读 不可重复读 幻读
    READ-UNCOMMITTED √ √ √
    READ-COMMITTED × √ √
    REPEATABLE-READ × × √
    SERIALIZABLE × × ×
    事务隔离级别越严格,数据库效率越低。MySQL 默认的事务隔离级别是:
    REPEATABLE-READ 可重复读级别,简称 RR 级别,会出现幻读问题。
    最简回答:数据库事务隔离级别是指在多个并发事务同时执行时,各个事务之间的
    隔离程度,常见的隔离级别有读未提交(Read Uncommitted)、读已提交(Read
    Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
  100. 索引的类型(种类)
     从数据结构角度
    树索引 (O(log(n)))
    Hash 索引
     从物理存储角度
    聚集索引(clustered index)
    非聚集索引(non-clustered index)
     从逻辑角度
    普通索引
    唯一索引
    主键索引
    联合索引
  101. 索引会失效的情况有哪些
    下面列举几种不走索引的 SQL 语句:
    ⚫ 索引列参与表达式计算:
    1 SELECT ‘sname’ FROM ‘stu’ WHERE ‘age’ + 10 = 30;
    ⚫ 函数运算:
    1 SELECT ‘sname’ FROM ‘stu’ WHERE LEFT(‘date’,4) < 1990;
    ⚫ %词语%–模糊查询:
    1 SELECT * FROM ‘manong’ WHERE uname LIKE ‘码农%’ – 走索引
    2 SELECT * FROM ‘manong’ WHERE uname LIKE ‘%码农%’ – 不走索引
    ⚫ 字符串与数字比较不走索引:
    1 CREATE TABLE ‘a’ (‘a’ char(10));
    2 EXPLAIN SELECT * FROM ‘a’ WHERE ‘a’=“1” — 走索引
    3 EXPLAIN SELECT * FROM 'a’WHERE ‘a’=1 — 不走索引,同样也是使用了函数运算
    ⚫ 查询条件中有 or ,即使其中有条件带索引也不会使用。换言之,就是要求使用的
    所有字段,都必须建立索引:
    1 select * from dept where dname=‘xxx’ or loc=‘xx’ or deptno = 45;
    ⚫ 正则表达式不使用索引。
  102. MySQL 索引底层原理
    MySQL 的索引底层结构是 B+树。
    B+树是一种平衡多路搜索树,具有以下特点:
  103. 所有关键字保存在叶子节点,并且叶子节点之间通过链表连接,形成一个有序的叶
    子节点序列。
  104. 非叶子节点只存储索引字段的值和子节点的指针,不保存实际的数据。这样可以使
    得一个节点可以存储更多的关键字,减少了树的高度,加快搜索速度。
  105. 叶子节点包含所有索引字段的值和指向对应数据的指针。
    在 B+树索引中,每个节点的大小是固定的,与磁盘页的大小相当。节点的大小通常是
    数据库页的大小,例如 16KB 或 32KB。每个节点可以存储多个关键字和指针。叶子节
    点的关键字是有序的,且通过链表连接在一起。
    索引查询快的原因有以下几点:
  106. 路径长度短:B+树具有平衡性,所有叶子节点的深度相同,因此在查询过程中只需
    要沿着树的高度进行几次磁盘 I/O 操作,所以查询速度较快。
  107. 顺序访问优势:B+树的叶子节点之间使用链表连接,并且叶子节点的关键字是有序
    的,因此对于范围查询操作,可以通过顺序扫描叶子节点来获取有序的数据结果,提高
    查询速度。
  108. 最小化磁盘 I/O 操作:B+树具有较高的填充因子,每个磁盘页上存储的关键字数量
    较多,能够减少磁盘 I/O 操作的次数,提高查询效率。
    综上所述,B+树的平衡性、有序的叶子节点、顺序访问以及最小化的磁盘 I/O 操作是
    使得索引查询快速的关键因素。通过 B+树的数据结构和索引的建立,可以大幅度减少
    磁盘访问次数,提高数据库查询的效率。
    最简回答:MySQL 索引底层原理使用了 B+树数据结构,它是一种平衡树,能快速
    定位和检索数据;B+树的叶子节点存储实际数据,中间节点存储索引,通过减少磁
    盘 IO 来提高查询效率;索引按照值的大小顺序排列,使得范围查询效率更高。
  109. MySQL 优化方案
     服务器优化(增加 CPU、内存、网络、更换高性能磁盘)
     表设计优化(字段长度控制、添加必要的索引)
     SQL 优化(避免 SQL 命中不到索引的情况)
     架构部署优化(一主多从集群部署)
     分库分表(垂直分库、水平分表)
     编码优化实现读写分离
  110. SQL 优化方案
  111. 优化查询条件
    使用索引:确保所有涉及到的列都有适当的索引。
    1 SELECT * FROM table WHERE column = ‘value’;
    2 CREATE INDEX idx_column ON table(column);
    避免模糊查询:%开头的通配符会使索引失效,尽量避免在查询条件中使用以%开
    头的 LIKE 语句。
    1 SELECT * FROM table WHERE column LIKE ‘value%’;
  112. 使用合适的数据类型
    使用最小可能的数据类型:选择最合适的数据类型,不要使用比实际需要更大的数
    据类型。
    1 CREATE TABLE example (column1 INT, column2 VARCHAR(50));
    避免使用存储过大的数据类型:避免使用 TEXT、BLOB 等存储过大的数据类型,因
    为它们会占用更多的存储空间和 I/O 操作。
  113. 减少查询次数
    使用 JOIN 查询:通过优化 JOIN 语句,避免使用多个单表查询。
    1 SELECT * FROM table1 INNER JOIN table2 ON table1.id = table2.id;
    使用批量操作:合并多个相似的操作为一个更大的操作,减少多次查询和事务提交。
    1 INSERT INTO table (column1, column2) VALUES (value1, value2), (value3, value4), …;
  114. 优化索引
    检查索引使用情况:通过 EXPLAIN 或其他性能分析工具,检查查询是否使用了适当的
    索引。
    1 EXPLAIN SELECT * FROM table WHERE column = ‘value’;
  • 删除不必要的索引:移除未使用或被其他索引覆盖的冗余索引,减少索引维护的
    开销。
  1. 避免使用 SELECT *
    明确列出所需的列:只选择需要的列,避免不必要的数据传输和处理。
    1 SELECT column1, column2 FROM table;
  2. 如何设计数据库表
    ⚫ 数据库设计规范
     根据业务模块拆分数据库(业务模块垂直分库)
     同一业务模块的表在一个数据库里
    ⚫ 表设计规范
     表名、字段名全部小写英文带下划线
     字段类型长度根据实际需求选择
     设计基础字段(主外键 ID、时间、逻辑删除、版本)
     添加必要冗余字段
     添加必要索引(常用于查询的单字段或组合索引),单表索引建议控制在 5 个
    以内
     规划分表(单表超 500 万行或容量超 2G 才分表)
  3. MySQL 都有哪些函数?
    ⚫ 数字函数
     聚合函数:avg、sum、count、min、min
     绝对值:ABS(x)
     取余:MOD(x,y)
    ⚫ 日期函数
     系统当前时间:NOW()
     日期转字符串:DATE_FORMAT(date,format)
     字符串转日期:STR_TO_DATE(str,format)
     日期间隔天数:DATEDIFF(date1,date2)
    ⚫ 字符串函数
     拼接字符串:CONCAT(s1,s2…sn)
     截取字符串:SUBSTR(s, start, length)
     替换字符串:REPLACE(s,s1,s2)
    六、Redis
  4. Redis 为什么快
     (内存操作)完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
     (单线程,省去线程切换、锁竞争的开销)采用单线程,避免了不必要的上下
    文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不
    用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导
    致的性能消耗;
     (NIO 的 IO 多路复用模型)使用多路 I/O 复用模型,非阻塞 IO;这里“多路”
    指的是多个网络连接,“复用”指的是复用同一个线程
    最简回答:Redis 之所以快是因为它采用了内存存储和非阻塞的 I/O 模型,避免了
    磁盘 IO 的延迟;同时,Redis 使用了 IO 多路复用技术,通过一个线程同时处理多
    个客户端请求,减少了线程切换的开销,提高了并发处理能力。
  5. Redis 可以用来做什么
     缓存
     排行榜
     分布式计数器
     分布式锁
     消息队列
     分布式 token
     限流
  6. Redis 的持久化机制
    在 Redis 4.0 之后提供了混合持久化的方式,顾名思义就是把 RDB 持久化和 AOF 持
    久化结合起来的一种方式。混合持久化就是快照以一定的频率执行,而在两次快照之间,
    使用 AOF 日志记录这期间的所有命令操作。
    选择 RDB 还是 AOF?
    ⚫ 如果你的业务场景需要很高的性能,或者宕机之后能够尽快的恢复,而对数据完整
    性的要求不是那么高,那么可以采用 RDB 持久化的方式。
    ⚫ 如果你的业务场景对数据完整性的要求很高,那么可以采用 AOF 的持久化方式,
    而至于采用那种回写策略,则取决于你对数据完整性的要求程度。
    ⚫ 如果你的业务场景既要兼顾性能,又注重数据完整性,那么可以采用混合持久化的
    方式。
    ⚫ 如果你对数据丢失无所谓,追求性能最大化的情况下,甚至可以禁用持久化。
    最简回答:RDB 是 Redis 的快照持久化方式,通过周期性的快照将数据保存到硬盘,
    占用更少的磁盘空间和 CPU 资源,适用于数据备份和恢复,但可能存在数据丢失的
    风险。AOF 是追加日志持久化方式,将每个写操作以追加的方式记录到日志文件中,
    确保了更高的数据完整性和持久性,但相对于 RDB 消耗更多的磁盘空间和写入性能,
    适用于数据持久化和灾难恢复,且可以通过配置实现不同的同步频率。
  7. Redis 常用的数据类型
    ⚫ String:存储单个值,适用于缓存和键值存储,常用命令:SET 用于设置值,GET
    用于获取值。
    ⚫ List:有序、可重复的字符串集合,适用于消息队列和发布/订阅系统,常用命令:
    LPUSH 用于从列表左侧添加元素,LRANGE 用于获取指定范围的元素。
    ⚫ Set:无序、不可重复的字符串集合,适用于标签系统和好友关系等,常用命令:
    SADD 用于向集合添加成员,SMEMBERS 用于获取集合所有成员。
    ⚫ Hash:字段-值对的无序散列,适用于存储对象、缓存和计数器,常用命令:HSET
    用于设置字段值,HGETALL 用于获取散列的所有字段和值。
    ⚫ Sorted Set:有序的字符串集合,每个成员关联一个分数,适用于排行榜和按分数
    范围获取成员,常用命令:ZADD 用于添加成员及其分数,ZRANGE 用于获取指
    定范围的成员。
  8. Redis 的缓存雪崩、缓存穿透、缓存击穿(缓存三兄弟)
     缓存穿透
    ◼ 缓存穿透:缓存中和数据库中都没有所查询的东西,从而使数据库崩掉。
    ◼ 解决方案:将一条数据库不存在的数据也放入缓存中这样即使数据库不存
    在但缓存中有,还可
    以使用布隆过滤器。
     缓存击穿
    ◼ 缓存击穿:缓存中没有但数据库中有,如果同一时间访问量过大会使数据
    崩掉。
    ◼ 解决方案:加分布式锁,一条数据访问数据库;将数据存储到缓存中,其
    他线程从缓存中拿。
     缓存雪崩
    ◼ 缓存雪崩:缓存中的数据正好在一个时间删除,当请求来时穿过缓存访问
    数据库。
    ◼ 解决方案:
    ⚫ 设置合理的缓存失效时间:针对不同的业务需求,设置不同的缓存失
    效时间,避免缓存同时失效,引发雪崩效应。
    ⚫ 设置缓存锁:在缓存失效时,设置一个短暂的锁定时间,只允许一个
    请求查询数据库并刷新缓存,其他请求等待锁释放后再读取缓存。
    最简回答:
    缓存穿透可以通过使用布隆过滤器来快速判断请求的 Key 是否合法,避免查询不存
    在的数据。
    缓存击穿可以通过使用互斥锁来保护数据库查询,确保只有一个线程能够查询数据
    库并写入缓存,避免多个并发请求同时访问数据库。
    缓存雪崩可以通过合理设置缓存的失效时间,使用热点数据预热来减少对缓存的依
    赖,从而避免整个缓存层的崩溃。
  9. Redis 的哨兵集群(Redis Sentinel)
    Redis 的哨兵(Sentinel)机制是一种用于监控和管理 Redis 实例的高可用性解决方案。
    它由一组独立运行的 Redis 哨兵节点组成,用于监控主节点和从节点的状态,并在主节
    点故障或其他变化时进行自动切换和故障恢复。
    哨兵机制的主要功能和意义如下:
  10. 监控:哨兵会定期检查 Redis 实例的状态,包括主节点和从节点的可用性。
  11. 自动故障检测和转移:当主节点失效或不可用时,哨兵可以自动检测到故障,并根
    据预先配置的策略进行主节点切换,将一个可用的从节点提升为新的主节点。
  12. 高可用性:哨兵可以确保在主节点故障的情况下,系统仍能继续提供服务,通过将
    从节点升级为主节点,保持系统的可用性。
  13. 配置提供:哨兵还可以提供配置信息,使客户端能够动态获取 Redis 节点的最新状
    态,以实现自动的节点发现和故障转移过程。
    哨兵的工作机制如下:
  14. 哨兵节点发现彼此,互相通信。
  15. 检测主节点是否可用,如果不可用,则标记为下线。
  16. 哨兵节点相互投票,选举一个新的主节点候选人。
  17. 当大多数哨兵节点投票给同一个候选人时,选举成功,该候选人成为新的主节点。
  18. 通知其他从节点重新连接到新的主节点。
    使用哨兵机制可以提高 Redis 的高可用性和可靠性,确保 Redis 集群在节点故障或其他
    变化时能够自动进行故障转移和恢复。客户端可以通过连接到哨兵节点,获取正确的
    Redis 节点信息,并保持与 Redis 集群的稳定连接。
    需要注意的是,哨兵机制并不是分布式存储方案,它只实现了高可用性和故障转移,不
    提供数据分片的功能。如果需要实现数据分片和水平扩展,可以使用 Redis 集群(Redis
    Cluster)来实现。
    最简回答:
    Redis 的哨兵集群主要用于实现高可用性,监控 Redis 主、从节点的状态变化,并
    在主节点失效时自动将从节点升级为主节点。哨兵集群由多个哨兵节点组成,工作
    原理是哨兵节点通过相互通信,监测主节点的健康状态,当主节点失效时,选举新
    的主节点,并通知其他从节点进行切换,确保系统的可用性。
  19. Redis 的分片集群(Redis Cluster)
    Redis 分片集群是一种将数据分布到多个节点的解决方案,用于实现数据的水平扩展和
    提高系统的吞吐量。
  20. 节点数量:Redis 分片集群通常由多个节点组成,每个节点负责存储部分数据。节点
    数量可以根据需求进行配置,常见的节点数量为 3 主 3 从,或 3 主 6 从或更多。
  21. 故障恢复:当一个节点出现故障或下线时,集群会进行自动的故障恢复。其他节点
    会接管故障节点上的数据,保证数据的可用性。
  22. 哈希槽原理:Redis 分片集群使用哈希槽来分配和管理数据。哈希槽是一个固定数
    量的槽,总共槽位数量 16384 个,每个数据被映射到其中一个槽上。每个节点负责管
    理一部分槽和相应的数据。
  23. 弹性伸缩:Redis 分片集群支持弹性伸缩,可以根据需要增加或减少节点的数量。
    当节点数量增加时,集群会自动重新计算和迁移哈希槽,确保数据均匀分布在新的节点
    上。当节点数量减少时,集群会迁移数据,让剩余节点负责故障节点的数据。
    通过以上机制,Redis 分片集群实现了数据的分布式存储和高可用性。它可以根据实际
    需求进行节点数量的调整,具备故障恢复能力,并通过哈希槽实现数据的均匀分布,同
    时支持弹性伸缩以适应业务需求的变化。
    读写数据时是如何定位哈希槽?
    在 Redis 分片集群中,确切的说法是使用 CRC16 算法计算 Key 的哈希值,而不是定位
    哈希槽。
    CRC16 算法(循环冗余校验算法)是一种广泛应用于网络通信和数据校验领域的哈希
    算法,用于计算 Key 的哈希值。
    举例说明:
    假设有一个包含三个节点的 Redis 分片集群,节点 A、节点 B 和节点 C。每个节点负责
    存储哈希槽的范围如下:
  • 节点 A:0-5499
  • 节点 B:5500-10999
  • 节点 C:11000-16383
    现在有一个 Key 为"example_key"需要进行操作。
  1. 使用 CRC16 算法计算"example_key"的哈希值,例如为 12345。
  2. 将哈希值对 16384(2^14)求余,得到计算结果为 12345 % 16384 = 12345。
  3. 根据计算结果 12345,确定该数据应该存储在节点 C 的哈希槽 11000-16383 范围
    内。
  4. 客户端将数据写入节点 C,或者从节点 C 读取相关数据。
    通过 CRC16 算法计算 Key 的哈希值,可以得到一个范围在 0 到 16383 的整数值,然
    后根据这个哈希值确定数据所对应的哈希槽范围,进而确定数据的存储位置。
    最简回答:
    Redis 的分片集群主要用于实现数据的横向扩展,将数据分散存储在多个节点上,
    提高系统的并发能力和存储能力。分片集群由多个节点组成,根据 Key 经过哈希算
    法映射到不同的节点上,每个节点负责存储和处理一部分数据,工作原理是通过一
    致性哈希算法将数据按照一定规则分配到不同的节点上,实现数据的均衡存储和查
    询。
  5. Redis 分布式锁命令使用
    可以使用分布式锁来实现多个客户端之间的协调和同步,以避免并发操作引起的数据冲
    突。下面是使用 Redis 实现分布式锁的一般步骤:
  6. 获取锁:
     使用 SET 命令尝试在指定的键上设置一个唯一的值作为锁。
     设置键的超时时间,以防止获取锁后出现异常导致锁一直被占用(避免死锁)。
    SET 命令常用的选项是:
     NX:仅在键不存在时设置键的值,用于确保只有一个客户端能够成功设置锁。
     PX:设置键的过期时间,用于防止锁被长时间占用。
    注意:SETNX 命令也可以获取锁,但还得执行其他命令设置过期时间,不如 SET 一
    条命令搞定:
    例如,SET lock_key value PX 10000 NX 会将名为 lock_key 的锁设置为在10秒后过期。
  7. 释放锁:
     使用 DEL 命令删除键来释放锁。
     确保只有锁的持有者能够释放锁,可通过比较锁的值来进行验证。
  8. Redis(或 ElasticSearch)和 MySQL 如何保持数据一
    致性
    Redis(或 ElasticSearch)和 MySQL 如何保持数据一致性,以及它们的具体实现和优
    缺点如下:
  9. 同步双写方案:
    ⚫ 实现方式:在应用程序中,将数据同时写入 Redis(或 ElasticSearch)和 MySQL,
    确保两个写操作要么同时成功,要么同时失败。
    ⚫ 优点:简单直接,实时性较高,不依赖额外组件。
    ⚫ 缺点:增加了系统复杂性,需要处理并发写冲突,可能导致写入延迟增加。
  10. 异步队列方案:
    ⚫ 实现方式:将数据更新操作以消息的方式发送到消息队列中,并在消费者程序中分
    别更新 Redis(或 ElasticSearch)和 MySQL。
    ⚫ 优点:实现了数据解耦和异步处理,提高了系统的可伸缩性和稳定性。
    ⚫ 缺点:增加了系统复杂性,引入了消息队列系统,可能会有一定的延迟,需要确保
    消息消费的可靠性。
  11. Canal 方案:
    ⚫ 实现方式:使用 Canal 工具监听 MySQL 的 binlog,将数据变更事件发送到 Redis
    (或 ElasticSearch)来更新数据。
    ⚫ 优点:实时性较高,不需要修改应用程序代码,可以实现 MySQL 的逻辑解耦和数
    据的多重同步。
    ⚫ 缺点:增加了系统复杂性,需要额外的 Canal 工具和客户端程序来处理数据同步,
    可能额外占用资源。
    双写模式简单直接,适用于数据实时性要求较高的场景。异步队列适用于要求解耦和异
    步处理的场景,但可能增加了系统复杂性和引入延迟。Canal 方案适用于希望实现
    MySQL 的逻辑解耦和多重数据同步的场景,但需要额外维护 Canal 工具和客户端程序。
    最简回答:
    Elasticsearch 和 MySQL 的数据一致性可以通过以下三种方案实现:
  12. 双写:每次写入操作同时将数据写入Elasticsearch和MySQL,确保数据一致性,
    但可能增加写延迟和复杂性。
  13. 异步队列:将写入操作请求放入队列中,后台任务异步地将数据写入
    Elasticsearch 和 MySQL,提高写入性能,但可能导致一定的数据不一致性。
  14. Canal 方案:使用 Canal 工具订阅 MySQL 的 binlog 日志,实时将数据同步到
    Elasticsearch,实现数据的实时增量同步,但需要额外的工具和配置。
  15. Redisson 是什么,怎么用
    Redisson 是一个用于 Java 的简单易用的 Redis 客户端,它封装了常见的分布式操作和
    并发控制的功能,提供了丰富的 API 和功能,使得开发人员可以轻松地与 Redis 进行交
    互。
    在 Spring Boot 中使用 Redisson,需要进行以下步骤:
  16. 添加 Redisson 的依赖:在项目的构建文件(如 pom.xml)中添加 Redisson 的依
    赖。可以通过 Maven 引入相应的依赖。
  17. 配置 Redisson 连接信息:在 Spring Boot 的配置文件(如 application.properties
    或 application.yml)中配置 Redisson 的连接信息,包括 Redis 的主机地址、端口号、
    密码等。
  18. 创建 RedissonClient 对象:可以通过自动注入或手动创建 RedissonClient 对象。
    如果使用自动注入,可以在配置类中添加@Bean注解将RedissonClient注入为Spring
    的 Bean;如果手动创建,可以在需要的地方创建 RedissonClient 对象。
  19. 使用 Redisson 功能:通过 RedissonClient 对象,可以使用 Redisson 提供的各种
    功能,如分布式锁、分布式集合、分布式队列等。根据具体需求,调用相应的方法来操
    作 Redis。
    例如,使用 Redisson 的分布式锁,可以注入 RedissonClient 对象后调用 getLock 方
    法来获取锁对象,然后使用 lock 方法加锁,并在需要时执行相应的操作,最后使用
    unlock 方法释放锁。
    最简回答:Redisson 是一个 Java 的 Redis 客户端,提供丰富的 API 和功能,用于
    封装分布式操作和并发控制。在 Spring Boot 中使用 Redisson,首先添加 Redisson
    的依赖,然后在配置文件中配置 Redisson 连接信息,接着通过@Autowired 注解
    或手动创建 RedissonClient 对象。最后,利用 RedissonClient 对象可以使用各种
    功能,如分布式锁、分布式集合等,与 Redis 进行交互。
  20. Redisson 看门狗机制的原理
    ⚫ Redisson 的看门狗机制是为了解决分布式环境下使用分布式锁时的问题。它通过
    周期性地对锁进行心跳续期,保证在业务执行期间锁不会被自动释放,防止因执行
    时间过长或节点宕机而导致锁提前释放的情况发生。
    ⚫ 实现原理是在获取锁时,Redisson 会使用一个独立的线程启动一个定时任务,定
    时更新锁的过期时间。同时,每个 Redisson 实例都会在 Redis 中生成一个唯一 ID
    作为锁的标识,以避免其他实例错误地释放锁。
    ⚫ 看门狗机制的目的是防止锁过期时间超时,而业务执行仍在进行中的情况发生。通
    过定时地续期锁的过期时间,确保锁在业务执行期间一直有效。这样可以避免获得
    锁的实例因为处理时间过长而导致锁过期被其他实例获取,保证分布式环境下的锁
    的可靠性。
    ⚫ 续期与看门狗机制相关,通过定时续期,锁的过期时间会在业务执行期间不断更新,
    从而避免锁过期。一旦锁的续期失败,即续期任务运行失败或锁的标识不匹配,
    Redisson 会立即释放该锁,以避免业务不再持有锁而导致的问题。
    ⚫ 总结来说,Redisson 的看门狗机制通过定时续期锁的过期时间,保证在业务执行
    期间锁不会被自动释放,解决了在分布式环境下使用分布式锁时锁过期的问题,提
    高了锁的可靠性和使用效果。
    最简回答:Redisson 的看门狗机制通过定时续期锁的过期时间,保证在业务执行期
    间锁不会被自动释放。它解决了分布式环境下锁过期导致的资源竞争问题,确保业
    务能够完成。续期是看门狗机制的核心,它通过定时更新锁的过期时间来实现锁的
    持久性,以防止锁过期并被其他实例获得。
    七、服务框架
  21. MyBatis 里#和KaTeX parse error: Expected 'EOF', got '#' at position 17: …别 在 MyBatis 中,`#̲`和``在 SQL 语句中的使用具有以下区别:
  22. 参数替换方式:
  • #符号表示使用预编译语句,并将参数值进行占位符替换。
  • $符号表示直接将参数值替换到 SQL 语句中。
  1. 预编译处理:
  • #符号在替换时会自动进行预编译处理,可以防止 SQL 注入攻击,并确保查询参
    数的类型正确匹配。
  • $符号在替换时不会进行预编译处理,替换后的参数直接拼接到 SQL 语句中,潜
    在地存在 SQL 注入的风险。
  1. 适用场景:
  • #符号适用于参数值,如查询条件、更新数据等需要进行预处理操作的场景。
  • $符号适用于非参数值,如表名、列名等不需要进行预处理操作的场景。
    最简回答:在 MyBatis 中,# 用于参数的替换,并会进行预编译处理,以防止 SQL
    注入;而 $则直接将参数值替换进 SQL 语句有 SQL 注入风险。
  1. MyBatis 动态 SQL
    MyBatis 动态 SQL 可以让我们在 XML 映射文件内,以标签的形式编写动态
    SQL,完成逻辑判断和动态拼接 SQL 的功能;
    MyBatis 提供了 9 种动态 SQL 标签:trim、where、set、foreach、if、choose、
    when、otherwise、bind;
    执行原理:使用 OGNL 从 SQL 参数对象中计算表达式的值,根据表达式的值动
    态拼接 SQL,以此来完成动态 SQL 的功能。
  2. ResultType 和 ResultMap 的区别
     如果数据库结果集中的列名和要封装实体的属性名完全一致的话用
    resultType 属性
     如果数据库结果集中的列名和要封装实体的属性名有不一致的情况用
    resultMap 属 性,通过 resultMap 手动建立对象关系映射,resultMap 要
    配置一下表和类的一一对应关系,所以说就算你的字段名和你的实体类的属性
    名不一样也没关系,都会给你映射出来
  3. MyBatisPlus 的常用注解
  4. @TableName:用于指定实体类对应的数据库表名。该注解可以在实体类上添
    加,表示该类映射到指定的数据库表。例如:@TableName(“user”)。
  5. @TableField:用于指定实体类的字段对应的数据库表的字段名。该注解可以在
    实体类的字段上添加,表示该字段映射到指定的数据库表字段。例如:
    @TableField(“name”)。
  6. @TableId:用于指定实体类的字段作为数据库表的主键。该注解可以在实体类的
    字段上添加,表示该字段作为数据库表的主键。例如:@TableId(value = “id”, type
    = IdType.AUTO)。
  7. @Version:用于指定实体类的字段作为乐观锁的版本字段。乐观锁是一种并发控
    制机制,通过版本号的变化来判断数据是否被修改。该注解可以在实体类的字段上
    添加,表示该字段作为乐观锁的版本字段。例如:@Version。
  8. Spring 里 IOC 是什么
    ⚫ IOC(Inversion Of Controll,控制反转)是一种设计思想,就是将原本在程序中
    手动创建对 象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,
    并非 Spring 特有。IOC 容器是 Spring 用来实现 IOC 的载体,IOC 容器实际上就
    是一个 Map(key, value),Map 中存放 的是各种对象。
    ⚫ 将对象之间的相互依赖关系交给 IOC 容器来管理,并由 IOC 容器完成对象的注入。
    这样可以很 大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC
    容器就像是一个工厂一 样,当我们需要创建一个对象的时候,只需要配置好配置
    文件/注解即可,完全不用考虑对象是 如何被创建出来的。在实际项目中一个
    Service 类可能由几百甚至上千个类作为它的底层,假 如我们需要实例化这个
    Service,可能要每次都搞清楚这个 Service 所有底层类的构造函数,这 可能会把
    人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,大
    大增加了项目的可维护性且降低了开发难度。
    最简回答:在 Spring 中,IOC (Inversion of Control,控制反转) 是一种设计模式,
    通过它,对象的创建和依赖关系的管理被交由容器负责,从而实现对象之间的解耦
    和灵活性的提升。简单说,就是由容器来控制对象的生命周期和依赖关系。
  9. Bean 有几种注入方式
     构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该
    类有一系列参数,每个参数代表一个对其他类的依赖。
     Setter 方法注入:Setter 方法注入是容器通过调用无参构造器或无参 static
    工厂方法实例化 bean 之后,调用该 bean 的 Setter 方法,即实现了基于
    Setter 的依赖注入。
     基于注解的注入:最好的解决方案是用构造器参数实现强制依赖,Setter 方法
    实现可选依赖。
    最简回答:在 Spring 中,Bean 的注入方式有三种:构造器注入、Setter 方法注入
    和注解注入。
  10. Spring 里 AOP 是什么
     AOP:全称 Aspect Oriented Programming,即:面向切面编程。
     AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务
    无关,却为业务 模块所共同调用的逻辑或责任(例如事务处理、日志管理、
    权限控制等)封装起来,便于减少 系统的重复代码,降低模块间的耦合度,
    并有利于未来的可扩展性和可维护性。
     Spring AOP 是基于动态代理的,如果要代理的对象实现了某个接口,那么
    Spring AOP 就会使 用 JDK 动态代理去创建代理对象;而对于没有实现接口
    的对象,就无法使用 JDK 动态代理,转 而使用 CGlib 动态代理生成一个被代
    理对象的子类来作为代理。 当然也可以使用 AspectJ,Spring AOP 中已经集
    成了 AspectJ,AspectJ 应该算得上是 Java 生 态系统中最完整的 AOP 框架
    了。使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到 的地方直
    接使用即可,这样可以大大简化代码量。我们需要增加新功能也方便,提高了
    系统的扩展性。日志功能、事务管理和权限管理等场景都用到了 AOP。
    最简回答:在 Spring 中,AOP (Aspect-Oriented Programming,面向切面编程)
    是一种编程范式,通过在不修改原有代码的情况下,将横切关注点(如事务管理、
    日志记录等)从核心业务逻辑中解耦出来,并在需要的地方进行统一的切面逻辑处
    理。
  11. AOP 主要用在哪些场景中
     事务管理
     日志
     性能监视
     安全检查
     缓存
     公共字段填充
     全局异常处理
     自定义分布式锁组件
  12. Spring 的常用注解
     @Autowired:用于有值设值方法、非设值方法、构造方法和变量。
     @Component:用于注册所有 bean
     @Repository:用于注册 dao 层的 bean
     @Controller:用于注册控制层的 bean
     @Service:用于注册服务层的 bean
     @Component: 用于实例化对象
     @Value: 简单属性的依赖注入
     @ComponentScan: 组件扫描
  13. @Resource 和@Autowired 的区别
     都是用来自动装配的,都可以放在属性的字段上
     @Autowired 通过 byType 的方式实现,而且必须要求这个对象存在!
     @Resource 默认通过 byName 的方式实现,如果找不到名字,则通过 byType
    实现!如果两个都找不到的情况下,就报错!
  14. SpringMVC 执行流程(工作原理)
    MVC 是 Model — View — Controler 的简称,它是一种架构模式,它分离了表现与
    交互。它被分为三个核心部件:模型、视图、控制器。
     Model(模型):是程序的主体部分,主要包含业务数据和业务逻辑。在模型
    层,还会涉及到用户发布的服务,在服务中会根据不同的业务需求,更新业务
    模型中的数据。
     View(视图):是程序呈现给用户的部分,是用户和程序交互的接口,用户会
    根据具体的业务需求,在 View 视图层输入自己特定的业务数据,并通过界面
    的事件交互,将对应的输入参数提交给后台控制器进行处理。
     Controller(控制器):Controller 是用来处理用户输入数据,以及更新业务
    模型的部分。控制器中接收了用户与界面交互时传递过来的数据,并根据数据
    业务逻辑来执行服务的调用和更新业务模型的数据和状态。
    工作原理:
    ⚫ 1.客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
    ⚫ 2.DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的
    Handler。
    ⚫ 3.解析到对应的 Handler(也就是我们平常说的 Controller 控制器)。
    ⚫ 4.HandlerAdapter 会根据 Handler 来调用真正的处理器来处理请求和执行相对应
    的业务逻 辑。
    ⚫ 5.处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据
    对象,View 是逻辑上的 View。
    ⚫ 6.ViewResolver 会根据逻辑 View 去查找实际的 View。
    ⚫ 7.DispatcherServlet 把返回的 Model 传给 View(视图渲染)。
    ⚫ 8.把 View 返回给请求者(浏览器)。
    最简回答:Spring MVC 的工作原理是基于 Servlet 容器的前端控制器模式,首先
    DispatcherServlet 接收并分发请求,然后根据 HandlerMapping 找到对应的
    Controller 进行业务处理,最后通过 ViewResolver 渲染视图并返回响应给客户端。
  15. SpringMVC 的常用注解
     Controller:用于标识一个类为控制器。
     RequestMapping:用于映射请求 URL 与处理方法。
     GetMapping:用于映射 GET 请求的 URL 到处理方法。
     PostMapping:用于映射 POST 请求的 URL 到处理方法。
     PutMapping:用于映射 PUT 请求的 URL 到处理方法。
     DeleteMapping:用于映射 DELETE 请求的 URL 到处理方法。
     PathVariable:用于获取 URL 路径中的参数。
     RequestParam:用于获取请求参数。
     ResponseBody:用于直接返回数据给客户端。
  16. 为什么用 SpringBoot(优点)
     独立运行 Spring Boot 而且内嵌了各种 servlet 容器,Tomcat、Jetty 等,
    现在不再需要打成 war 包部署到容器中,Spring Boot 只要打成一个可执行
    的 jar 包就能独立运行,所有的依赖包都在一个 jar 包内。
     简化配置 spring-boot-starter-web 启动器自动依赖其他组件,简少了
    maven 的配置。
     自动配置 Spring Boot 能根据当前类路径下的类、jar 包来自动配置 bean,
    如添加一个 spring-boot-starter-web 启动器就能拥有 web 的功能,无需
    其他配置。
     无代码生成和 XML 配置 Spring Boot 配置过程中无代码生成,也无需 XML
    配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是
    Spring4.x 的核心功能之一。
     避免大量的 Maven 导入和各种版本冲突
     应用监控 Spring Boot 提供一系列端点可以监控服务及应用,做健康检测。
    最简回答:
    使用 Spring Boot 可以简化编码、简化配置、简化部署、简化监控、简化依赖坐标
    导入、简化整合其他技术,并提供强大的微服务支持,减少开发人员在搭建和配置
    项目上的工作量,使开发者能够更专注于业务逻辑的实现,快速构建可靠的、可扩
    展的应用程序。
  17. SpringBoot 自动配置原理
    Spring Boot 的自动配置原理基于条件化配置和约定优于配置的机制。它通过扫描类路
    径下的依赖、配置文件和注解等信息,结合 Spring Boot 提供的自动配置类和条件注解,
    根据条件判断自动配置哪些组件,然后将它们注入到 Spring 容器中。这种方式使得开
    发者能够快速搭建应用程序,并根据自己的需求进行个性化的配置和扩展。
    Spring Boot 的自动配置原理主要分为以下几个步骤:
  18. 扫描:Spring Boot 会扫描应用程序的类路径,查找特定的依赖、配置文件和注解
    等信息。
  19. 条件判断:根据条件注解,比如@ConditionalOnClass
    @ConditionalOnProperty等,判断是否满足自动配置的条件。
  20. 自动配置:如果满足条件,Spring Boot 会根据自动配置类(通常以
    *-autoconfigure结尾)中的配置代码,自动创建和配置所需的组件。
  21. 组件注册:自动配置完成后,Spring Boot 会将这些组件注册到 Spring 容器中,使
    其可供应用程序使用。
  22. 自定义配置:开发者可以通过自定义配置文件或者注解,覆盖或修改自动配置的行
    为,实现个性化的配置和扩展。
    通过这些步骤,Spring Boot 实现了简化配置和快速开发的目标,提供了便捷的开发方
    式,同时保持了灵活性和可扩展性。
    最简回答:Spring Boot 的自动配置原理是基于条件注解和配置文件的机制。它通
    过扫描 classpath 下的自动配置类,根据条件注解(如@ConditionalOnClass、
    @ConditionalOnProperty)判断是否满足配置条件,从而决定是否启用对应的自
    动配置。配置文件中的属性值可以通过@ConfigurationProperties 注解与配置类
    进行绑定,实现属性的自动注入。最终,这些自动配置类会被注册到 Spring 容器中,
    完成自动配置的过程。
  23. SpringBoot 启动时都做了什么
    Spring Boot 启动主要经历以下几个步骤:
  24. 加载配置:Spring Boot 启动时首先加载META-INF/spring.factories文件,这个
    文件指定了各个自动配置类的路径。
  25. 扫描自动配置类:根据spring.factories中配置的自动配置类路径,Spring Boot
    会扫描这些自动配置类,并将它们实例化。
  26. 条件判断:通过条件注解,如@ConditionalOnClass@ConditionalOnProperty
    等,决定是否应用该自动配置类。
  27. 自动配置:根据自动配置类中的代码逻辑,自动配置相应的组件,例如数据库连接
    池、Web 服务器、日志等。
  28. 注册组件:将自动配置的组件注册到 Spring 容器中,使其可以被应用程序使用。
  29. 启动应用:执行应用程序的启动逻辑,包括初始化、加载数据、启动定时任务等。
    通过使用spring.factories文件,Spring Boot 实现了自动配置的功能,大大简化了应
    用程序的配置和开发过程。
    最简回答:Spring Boot 启动时会加载配置文件(如 application.properties)并扫
    描自动配置类(通过spring.factories),根据条件判断(如@ConditionalOnClass
    @ConditionalOnProperty)自动配置各个组件,将它们注册到 Spring 容器中。
    最后,启动应用程序,处理请求,响应用户。简而言之,Spring Boot 启动时负责
    配置加载、自动装配和应用程序的启动。
  30. SpringBoot 的核心注解是哪个?由哪些注解组成?
    SpringBootApplication,由 3 个注解组成:
     @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文
    件的功能。
     @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配
    置的选项,如关闭数据源自动配置功能。
     @ComponentScan:Spring 组件扫描。
  31. SpringCloud 和 SpringBoot 的区别和关系
     Spring Boot 专注于快速方便的开发单个个体微服务。
     Spring Cloud 是关注全局的微服务协调整理治理框架以及一整套的落地解决
    方案,它 将 Spring Boot 开发的 一个个单体微服务整合并管理起来,为各
    个微服务之间提供:配置管理,服务发现,断路器,路由,微代理,事件总线
    等的集成服务。
     Spring Boot 可以离开 Spring Cloud 独立使用,但是 Spring Cloud 离不
    开 SpringBoot ,属于依赖的关系。
    最简回答:Spring Boot 是用于简化 Spring 应用的开发和部署的框架,而 Spring
    Cloud 是构建分布式系统的解决方案,它基于 Spring Boot 并提供了分布式系统开
    发所需的组件与工具。简而言之,Spring Boot 是单体应用的生产力工具,Spring
    Cloud 是构建分布式系统的工具集。
  32. SpringCloud 五大组件是哪几个
    Spring Cloud 是一个用于构建分布式系统的开发工具包,它提供了一系列组件来简化
    分布式系统的开发和管理。以下是 Spring Cloud 中的五个核心组件:
  33. 服务注册与发现(Eureka):Eureka 是一个用于实现服务注册与发现的组件,提
    供了服务注册中心来管理服务实例的注册和发现,使得服务之间可以方便地进行通信和
    调用。
  34. 客户端负载均衡(Ribbon):Ribbon 是一个用于在客户端实现负载均衡的组件,
    它可以根据一定的策略选择合适的服务实例进行负载均衡,提高系统的可用性和性能。
  35. 服务调用(Feign):Feign 是一个声明式的服务调用组件,它基于注解和动态代理,
    可以让开发者使用简单的接口定义服务调用,而无需关注底层的具体实现。
  36. 熔断器(Hystrix):Hystrix 是一个用于实现服务容错和熔断的组件,它可以保护
    系统免受服务故障的影响,通过实现服务降级、熔断和隔离等机制,提高系统的稳定性
    和可靠性。
  37. 网关(Gateway):Zuul 或 Gateway 是用于构建统一的 API 网关的组件,它可以
    实现请求的路由、过滤和转发等功能,提供了对外的统一的接入点,并可以对请求进行
    安全验证、限流和监控等。
    这五个组件是 Spring Cloud 中常用的核心组件,它们提供了一系列功能来简化构建分
    布式系统的工作,实现了服务注册与发现、负载均衡、服务调用、熔断和容错、网关等
    关键功能,极大地提高了分布式系统的可靠性和可用性。
  38. SpringCloud 组件分类哪几类?
  39. Eureka 工作原理?
    Eureka 是 Spring Cloud 中的服务注册与发现组件,其工作原理可以简述为以下几个步
    骤:
  40. 服务注册:服务提供者在启动时,将自己的服务实例信息(包括名称、IP 地址、端
    口号等)注册到 Eureka 注册中心。
  41. 心跳检测与续约:注册后的服务实例会周期性地发送心跳给 Eureka 服务器来表明自
    己仍然可用。默认情况下,心跳时间间隔为 30 秒。
  42. 服务发现:服务消费者从 Eureka 注册中心获取注册的服务列表,并缓存在本地。它
    可以根据服务名称找到可用的服务实例。
  43. 负载均衡:服务消费者使用负载均衡算法从本地的服务列表中选择一个服务实例进
    行调用。
  44. 容错处理:Eureka 客户端会维护服务实例的健康状态信息,并自动剔除故障节点,
    进行容错处理。
    总结起来,Eureka 通过服务注册、心跳检测与续约、服务发现、负载均衡和容错处理
    等步骤来实现服务注册与发现的功能。心跳默认每 30 秒发送一次。
    最简回答:Eureka 是 Spring Cloud 中的服务注册与发现组件。它通过服务注册,
    在启动时将服务实例信息注册到 Eureka 注册中心;通过心跳检测与续约,实现服务
    实例的健康状态监测;通过服务发现,让消费者能够从注册中心获取可用的服务列
    表;最后,通过负载均衡和容错处理,实现服务调用的负载均衡和容错能力。
  45. Nacos 作为注册中心的工作原理
  46. 注册过程:
     服务提供者启动后,将自己的元数据(如 IP、端口、健康状态等)和服务信息
    注册到 Nacos 注册中心。
     注册中心将该服务实例的元数据存储起来,并根据服务名进行索引。
  47. 发现过程:
     服务调用者通过向 Nacos 注册中心发送服务发现请求,并提供服务名。
     注册中心根据服务名从存储的服务实例信息中找到对应的实例列表,并返回给
    服务调用者。
     服务调用者可以通过负载均衡算法选择其中一个实例进行服务调用。
  48. 临时实例与非临时实例:
     非临时实例:这是最常见的情况。服务提供者启动后,将自己的实例信息注册
    到注册中心,并持续提供服务,直到主动注销或注册中心失去与其连接。
     临时实例:某些场景下,服务提供者需要临时性地提供服务,如临时 Worker
    节点。对于临时实例,服务提供者需要定期发送心跳信号给注册中心进行续约。
    如果注册中心在预定的时间内没有收到心跳信号,它会将该实例从注册表中删
    除,服务调用者将无法获取到该临时实例信息。
  49. 心跳检查和实例剔除机制:
     注册中心会周期性地进行心跳检查,检查服务实例是否发送了续约的心跳信号。
    默认情况下,Nacos 的配置为每隔 5 秒进行一次心跳检查。
     如果发现某个实例在一定时间内没有发送心跳,注册中心会认为该实例失去连
    接,将其从注册表中剔除。默认情况下,Nacos 在 30 秒内未收到心跳信号时
    将实例剔除。
     服务调用者在从注册中心获取实例列表时,会只拿到存活的健康实例列表。
    通过临时实例和心跳检查等机制,Nacos 注册中心能够动态管理服务实例,自动剔除失
    去连接或不健康的实例,确保服务的可靠性和稳定性。这样的机制使得服务调用者能够
    根据实时的服务实例信息来进行负载均衡和服务发现,从而实现更高效的服务调用和部
    署。
    最简回答:Nacos 注册中心是一个服务注册与发现的工具,它提供了服务提供者将
    服务注册到注册中心,并让服务消费者从注册中心获取服务实例的能力。注册中心
    通过两种类型的实例进行管理,包括临时实例和非临时实例。临时实例会定期发送
    心跳信号以保持连接,而非临时实例则长时间提供服务。注册中心通过定期的心跳
    检查来监测实例的可用性,并在长时间没有心跳信号时将失效实例剔除。
  50. Nacos 和 Eureka 的区别
  51. 功能范围:
  • Nacos 是一个综合的服务注册中心和配置中心,提供服务注册与发现、动态配置
    管理、服务健康检查等功能。
  • Eureka 专注于服务注册和发现的功能,不包含配置管理等其他功能。
  1. 元数据管理:
  • Nacos 支持服务元数据管理和配置元数据管理,可以管理和扩展服务和配置的元
    信息。
  • Eureka 仅支持服务实例的元数据管理,而不支持配置元数据的管理。
  1. 健康检查:
  • Nacos 支持主动和被动的健康检查机制,可以检测服务实例的健康状态。
  • Eureka 仅支持被动的健康检查机制,只能通过心跳检测来判断服务实例的健康状
    态。
  1. 高可用性实现方式:
  • Nacos 通过基于 RAFT 协议的一致性算法实现高可用性,支持集群部署和多数据
    中心存储。
  • Eureka 通过多实例部署来实现高可用性,通过多个 Eureka 服务器相互注册和互
    相同步状态。
    这四点是 Nacos 和 Eureka 之间最明显的区别,包括功能范围、元数据管理、健康检查
    和高可用性实现方式。这些区别将影响您在选择适合项目需求的服务注册中心时的决策。
    最简回答:Nacos 和 Eureka 都是服务注册与发现组件,但 Nacos 比 Eureka 功能
    更丰富,除了服务注册与发现外,还提供了配置管理和服务治理功能。Nacos 支持
    多种服务发现协议,如 DNS、HTTP 和 gRPC,而 Eureka 仅支持自身的协议。此外,
    Nacos 还具备更好的可扩展性和容错性,能够应对更复杂的场景。
  1. Feign 工作原理?
    Feign 是一个声明式的 Web 服务客户端,用于简化和优化服务之间的 HTTP 通信。其
    工作原理可以分为以下几个步骤:
  2. 接口定义:开发者通过定义 Java 接口来描述服务间的通信协议,包括 URL、请求方
    法、请求参数等。
  3. 代理生成:在应用启动时,Feign 会根据接口定义生成代理对象。
  4. 请求发送:当调用代理对象的方法时,Feign 会根据方法的注解和参数生成 HTTP
    请求,并发送给目标服务。
  5. 负载均衡:Feign 集成了负载均衡组件(如 Ribbon),可以自动将请求分发到不同
    的服务实例。
  6. 响应处理:目标服务处理完请求后,将响应返回给 Feign 客户端。
  7. 结果解析:Feign 会根据接口定义和注解,将 HTTP 响应解析为 Java 对象,并返回
    给调用者。
    总结起来,Feign 的工作原理就是根据接口定义生成代理对象,通过代理对象发送 HTTP
    请求给目标服务,并将响应解析为 Java 对象返回给调用者。这样,开发者可以使用简
    洁的接口定义来实现服务之间的通信。
    最简回答:Feign 是一个声明式的 Web 服务客户端,通过定义 Java 接口来描述服
    务间的通信协议,包括 URL、请求方法、请求参数等。在应用启动时,Feign 会根
    据接口定义生成代理对象。当调用代理对象的方法时,Feign 会根据方法的注解和
    参数生成 HTTP 请求,并发送给目标服务。目标服务处理完请求后,将响应返回给
    Feign 客户端,Feign 会将响应解析为 Java 对象并返回给调用者。
  8. 什么是 Hystrix?
    Hystrix 是一个用于实现服务容错和熔断的库,在分布式系统中起到了保护系统免受服
    务故障的影响的作用。下面是 Hystrix 熔断的条件、处理方式和好处的说明:
  9. 熔断条件:
  • Hystrix 根据一定的规则监控执行的服务请求,当错误率超过一定阈值时,触发熔
    断。
  • 默认情况下,如果在 10 秒内的请求错误率超过 50%,则触发熔断。
  1. 熔断后的处理:
  • 当熔断触发后,Hystrix 会停止请求该服务的发送,而是快速返回一个预设的
    fallback(降级)响应或空响应。
  • 在熔断打开期间,Hystrix 还会定时尝试发起一些请求观察该服务是否已经恢复,
    如果请求成功率较高,熔断器将逐渐关闭。
  1. 熔断的好处:
  • 减少联动效应:当一个服务发生故障或响应时间过长时,传统的做法是等待它超
    时并再次尝试。这会浪费系统资源,而 Hystrix 的熔断机制可以快速失败并返回降级的
    响应,减少了等待和资源浪费。
  • 提高系统稳定性:通过熔断机制,当某个服务出现故障或不可用时,可以快速切
    换到备用的降级逻辑,保护整个系统免受服务故障的影响。
  • 预防雪崩效应:当一个服务不可用或响应缓慢时,传统的做法是继续发送请求,
    导致失败的请求堆积并最终耗尽系统资源。而 Hystrix 的熔断机制能够避免这种情况的
    发生,减少了对失败服务的依赖,提高了系统的稳定性。
    总而言之,Hystrix 的熔断机制通过监控服务请求的错误率,当错误率超过阈值时,快
    速切换到备用逻辑,避免了对失败服务的不必要请求,提高了系统的稳定性和可靠性。
    熔断可以在故障发生时快速失败并返回降级响应,避免资源浪费和雪崩效应,保护系统
    免受故障服务的影响。
    最简回答:Hystrix 的作用是提供容错和延迟容忍的机制,通过降级、熔断和限流等
    措施保护系统。触发条件包括服务故障、响应时间过长和并发请求达到阈值。使用
    Hystrix 时,需要在服务调用的方法上添加@HystrixCommand 注解,并配置具体
    的降级逻辑和触发条件。
  1. Gateway 网关的特性
    Gateway(网关)是一种在微服务架构中起到请求转发、路由和过滤的作用的组件。它
    作为系统的入口点,接收所有的客户端请求,并将它们转发到相应的服务上进行处理。
    以下是 Gateway 网关的作用和使用方式的说明:
    作用:
  2. 请求路由:Gateway 可以根据请求的 URL 路径将请求动态路由到不同的服务实例上,
    实现动态路由的功能。
  3. 负载均衡:Gateway 可以根据负载均衡策略将请求分发到多个服务实例中,平衡负
    载,提高系统的可用性和性能。
  4. 安全控制:Gateway 可以集成认证和授权机制,对请求进行鉴权,保护系统的安全
    性。
  5. 请求过滤:Gateway 可以对请求进行过滤和校验,例如对请求进行验证、请求参数
    转换、请求日志记录等。
  6. 降级和熔断:Gateway 可以对服务进行降级和熔断处理,当服务出现故障或超时时,
    返回预设的响应或转发到备用服务。
  7. 监控和统计:Gateway 可以对请求进行监控和统计,记录请求的响应时间、流量等
    指标,方便分析系统性能和问题排查。
    使用方式:
  8. 定义路由规则:在 Gateway 配置中定义路由规则,指定请求的 URL 路径和对应的
    目标服务。
  9. 配置过滤器:可以配置各种过滤器,如鉴权、请求转换、日志记录等,对请求进行
    处理。
  10. 配置负载均衡:通过配置负载均衡策略,将请求分发到多个服务实例上。
  11. 集成安全机制:可以集成认证和授权机制,保护系统的安全性。
  12. 配置降级和熔断:通过配置降级和熔断策略,对服务进行保护,防止故障扩散和资
    源浪费。
  13. 监控和统计:可以通过与监控工具集成,对 Gateway 的请求进行监控和统计,获取
    系统的性能指标。
    最简回答:Gateway 网关是一种 API 网关,具有路由、负载均衡、安全认证、限流
    熔断等特性,用于统一管理和分发请求,提高系统的可用性、安全性和可扩展性。
    网关可以根据请求的路由规则将请求转发到不同的服务实例,并提供过滤器机制来
    实现请求的加解密、鉴权、限流等功能。通过 Gateway 的配置,可以有效管理和保
    护微服务架构中的各个服务。
  14. Gateway 的路由断言工厂有哪些?
    在网关的路由配置里只需要在-Path 中配置/user/**,那么以 user 开头的请求就会被网
    关处理,这是如何实现的呢?事实上,Gateway 中有很多的路由断言工厂,当我们在
    配置文件中对断言进行配置后,这些配置就会被路由断言工厂进行解析并处理,而-Path
    配置就是由
    org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFact
    ory 来处理的。
    SpringCloud Gateway 中一共提供了 11 种基本的路由断言工厂,分别如下:
  15. 事务失效的场景有哪些?
     非 public 方法上加了事务注解
     非事务方法调用了事务方法
     异常被 catch 掉,没有外抛
     类上未加 @Service 注解,spring 不会管理此组件
     方法上未加 @Transactional 注解
    @Transactional 注解失效场景详细参考参考:
    https://www.jb51.net/article/233682.htm
    八、分布式
  16. RabbitMQ 工作模式
    RabbitMQ 是一个广泛使用的开源消息代理,它支持多种工作模式。以下是 RabbitMQ
    的几种常见工作模式以及它们的特点和应用场景:
  17. 简单模式(Simple Mode)
    ⚫ 特点:使用单个生产者将消息发送到单个消费者。
    ⚫ 应用场景:适用于简单的任务分发,消息的顺序不重要。
  18. 工作队列模式(Work Queue Mode)
    ⚫ 特点:多个生产者将消息发送到一个或多个消费者。
    ⚫ 应用场景:适用于任务分发,提高系统的并发处理能力。
  19. 发布/订阅模式(Publish/Subscribe Mode)
    ⚫ 特点:消息发送者将消息发布到交换机,多个消费者通过绑定到交换机的队列接收
    消息。
    ⚫ 应用场景:适用于消息广播,例如日志记录、实时聊天、新闻发布等。
  20. 路由模式(Routing Mode)
    ⚫ 特点:消息发送者通过指定不同的路由键将消息发送到交换机,交换机根据路由
    键将消息发送到对应的队列。
    ⚫ 应用场景:适用于消息的有选择性地路由,例如根据消息内容进行过滤。
  21. 主题模式(Topic Mode)
    ⚫ 特点:消息发送者通过指定主题(可以使用通配符)将消息发送到交换机,交换
    机根据主题将消息发送到对应的队列。
    ⚫ 应用场景:适用于消息的多样性路由,例如根据不同的主题进行过滤和选择。
    最简回答:RabbitMQ 工作模式包括简单模式、工作队列模式、发布/订阅模式、路
    由模式、主题模式和 RPC 模式,分别适用于不同的消息传递和处理需求。
  22. RabbitMQ 消息可靠性保证(防止数据丢失)
    RabbitMQ 提供的消息可靠性保证措施可以从生产端、服务端和消费端的角度来说明:
    生产端(Producer)的可靠性保证措施:
    ⚫ 发布者确认(Publisher Confirms):生产者可以通过启用发布者确认机制,在消
    息成功发送给 RabbitMQ 后接收确认回执,确保消息已被正确接收。
    服务端(Broker)的可靠性保证措施:
    ⚫ 持久化(Durability):队列和交换机可设置为持久化,使其在 RabbitMQ 重新启
    动后不会丢失。
    ⚫ 持久化消息:被标记为持久化的消息,会写入磁盘,确保消息在服务器故障时不会
    丢失。
    ⚫ 事务机制(Transactions):可通过启用事务机制将一组操作包装在事务中,要么
    全部成功执行,要么全部回滚,保证消息的原子性和一致性处理。
    消费端(Consumer)的可靠性保证措施:
    ⚫ 手动消息确认(Manual Message Acknowledgement):消费者在处理完消息
    后,发送确认回执给 RabbitMQ,告知消息已被成功处理,RabbitMQ 可以删除
    该消息。
    通过组合使用以上的措施,可以实现全方位的消息可靠性保证,减少消息丢失和处理错
    误的风险,并确保消息系统的高可用性和稳定性。
    最简回答:RabbitMQ 消息可靠性保证措施包括发布者确认、消费者确认、持久化
    队列和交换机、事务机制以及手动消息确认等。
  23. RabbitMQ 死信队列
    RabbitMQ 的死信队列(Dead Letter Queue)是用来处理无法被正常消费或处理的
    消息的特殊队列。产生死信的情况有以下几种:
  24. 消息被拒绝(Rejected):当消费者拒绝消费消息或者消息超过消费者的最大重试
    次数时,消息会被发送到死信队列。
  25. 消息过期(Expired):如果消息在一定时间内没有被消费者处理,即超过了消息的
    过期时间,该消息也会被发送到死信队列。
  26. 队列达到最大长度(Queue Length Limit):当队列达到了定义的最大长度限制,
    新的消息无法进入队列,会将旧的消息发送到死信队列。
    通过配置死信交换机(Dead Letter Exchange)和死信队列(Dead Letter Queue)
    的绑定关系,可以将满足上述条件的消息发送到指定的死信队列中,以便进行后续的处
    理或分析。
    RabbitMQ 死信队列用于处理未能被消费者处理的消息,主要发生在消息被拒绝、
    消息过期和队列达到最大长度时。
  27. RabbitMQ 消息重复消费问题
    RabbitMQ 消息重复消费问题通常是由以下原因导致的:
  28. 消费者应用程序在处理消息时发生了错误,导致消息确认(ack)没有发送给
    RabbitMQ,从而导致 RabbitMQ 将消息重新分发给其他消费者进行消费。
  29. 网络问题或消费者应用程序重启时,RabbitMQ 无法收到消息确认,也会导致消息
    重新分发。
    为了解决消息重复消费问题,可以采取以下措施:
  30. 消费端幂等性:消费者应用程序在处理消息时需要保证幂等性,即无论接收到相同
    的消息多少次,处理结果都保持一致。这可以通过使用唯一标识符、幂等存储等方式实
    现。
  31. 消息去重:消费者应用程序在处理消息之前,可以在自己的系统中维护一个消息记
    录表,记录已经处理过的消息的唯一标识符。在接收到新消息时,先检查该消息是否已
    经处理过,如果已经处理过,则忽略重复消息。
  32. 消息确认机制:消费者应及时地发送消息确认(ack)给 RabbitMQ,表示已经成功
    处理了消息。这样 RabbitMQ 就不会将消息重新分发给其他消费者。
    保证消费端的幂等性是解决消息重复消费问题的关键。通过在消费者应用程序中实现幂
    等性逻辑和消息去重措施,可以保证即使同一条消息被重复消费,也不会对系统产生重
    复、不一致的影响。
    最简回答:RabbitMQ 消息重复消费问题是由消费者应用程序错误或网络问题导致
    的,造成消息未得到确认,从而重新分发。为了解决问题,可以实现消费端的幂等
    性来保证消息处理结果一致,同时使用消息去重和及时发送消息确认,避免重复消
    费。确保消费幂等性是解决消息重复消费问题的重要措施之一。
  33. 为什么使用 Xxl-Job 而不是 SpringTask
    xxl-job 是一个分布式任务调度框架,主要用于解决大规模分布式系统中的定时调度和
    任务管理问题。它提供了可视化的任务管理界面和强大的调度功能,可以方便地实现定
    时任务的配置、监控和执行。
    为什么要使用 xxl-job,而不是用 springTask 呢?这主要取决于具体的需求和场景。下
    面是一些使用 xxl-job 的主要优势:
  34. 分布式支持:xxl-job 是为分布式系统设计的,可以很好地支持分布式环境下的任务
    调度和管理。它提供了任务分片、任务路由等功能,使得任务可以在不同的节点上并行
    执行,提高了系统的吞吐量和可扩展性。
  35. 可视化管理:xxl-job 提供了一个简洁易用的任务管理界面,可以方便地配置和监控
    任务。管理员可以通过界面进行任务的添加、修改、删除和触发,同时还能实时查看任
    务的执行情况和日志,方便故障排查和性能优化。
  36. 异常处理和报警:xxl-job 对任务执行过程中的异常进行了细致处理和监控,可以及
    时捕获任务的异常情况,并通过邮件、短信等方式进行报警通知。这些功能有助于提高
    系统的稳定性和可靠性。
    相比之下,springTask 是 Spring 框架提供的一个任务调度模块,它也可以用于实现定
    时任务的调度和管理。但是,相对于 xxl-job,springTask 的功能和扩展性相对较弱。
    它主要适用于单机和简单的任务调度场景,对于分布式系统和大规模任务管理来说,
    xxl-job 更具优势。
    最简回答:xxl-job 是一个分布式任务调度框架,用于解决大规模分布式系统中的定
    时调度和任务管理问题。使用 xxl-job 可以实现任务的分片、路由和可视化管理,
    同时提供异常处理和报警功能。相比于 springTask,xxl-job 具有更强的分布式支
    持和可扩展性,适用于复杂的分布式系统和大规模任务管理场景,而 springTask
    更适用于简单的任务调度和单机环境。
  37. Xxl-Job 在项目中的使用步骤
    将 xxl-job 集成到 Spring Boot 工程中,一般需要以下步骤:
  38. 添加依赖:在 Maven 配置文件中添加 xxl-job 的依赖,用于引入 xxl-job 的核心功
    能和相关组件。
  39. 配置 xxl-job 的参数:在 Spring Boot 的配置文件(如 application.properties 或
    application.yml)中,配置 xxl-job 的相关参数,包括调度中心地址、执行器名称、日
    志路径等。
  40. 创建执行器类:在 Spring Boot 工程中,创建一个实现了 XxlJobExecutor 类的执
    行器类,并将该类注册为 Spring 的 Bean。
  41. 编写任务类:根据具体的业务需求,编写定时执行的任务类,可以使用@XxlJob 注
    解标记任务方法。
  42. 启动任务调度:在项目启动时,通过编写一个启动类或在执行器类中初始化任务调
    度器,启动 xxl-job 的任务调度功能。
  43. 配置任务调度:通过 xxl-job 的管理界面,配置定时任务的执行计划、路由策略等参
    数,可以通过调度中心进行任务的触发和管理。
    最简回答:xxl-job 集成到 Spring Boot 工程中的使用步骤包括:添加 xxl-job 的依
    赖到构建文件中、配置 xxl-job 的参数,如调度中心地址和执行器名称,创建执行
    器类并注册为 Spring 的 Bean,编写定时任务类并在项目启动时初始化任务调度器。
  44. ElasticSearch 倒排索引
    Elasticsearch 的倒排索引是一种高效的数据结构,存储了词条(term)和文档 ID 之间
    的对应关系。
    倒排索引的结构如下:
  • 词项(term)存储在一个有序的词典(Dictionary)中,每个词项都关联着一个唯一
    的词项编号。
  • 对于每个词项,倒排索引会有一个倒排列表(Inverted List),它记录了包含该词项
    的文档 ID 列表。
    在倒排列表中,文档 ID 序列通常是有序的,这使得搜索引擎可以快速地按照相关性进
    行排序。倒排索引通过将文档 ID 与词项的对应关系反转,实现了从词项到文档的映射,
    从而方便快速定位包含特定词项的文档。
    搜索效率很快的原因是:
  1. 避免全文扫描:倒排索引可以快速定位到包含特定词项的文档,而无需扫描所有文
    档。
  2. 压缩存储:倒排索引使用紧凑的数据结构,采用压缩算法可以大幅度减少存储空间。
  3. 内存缓存:Elasticsearch 可以将经常访问的索引数据缓存在内存中,加速搜索操作。
    综上所述,Elasticsearch 的倒排索引通过存储词项和文档 ID 的对应关系以及使用紧凑
    的数据结构和内存缓存,实现了高效的搜索效率。它能够快速定位到包含特定词项的文
    档,避免了全文扫描,从而提高了搜索速度。
    最简回答:ElasticSearch 的倒排索引是一种将词条和文档 ID 之间的对应关系反转
    存储的结构,通过快速定位包含特定词条的文档来提高搜索效率。倒排索引的存储
    方式避免了全文扫描,通过将关键词与文档 ID 建立映射关系,使得搜索引擎能够快
    速检索出包含关键词的文档,从而提高了搜索效率。
  4. Seata 分布式事务框架使用
    使用 Seata 分布式事务框架的步骤如下:
  5. 在服务中引入 Seata 依赖。
  6. 配置 Seata 的全局事务切面和数据源代理。
  7. 在需要进行分布式事务管理的方法上添加@GlobalTransactional 注解。
  8. 在代码中进行正常的业务操作,Seata 会自动进行事务管理和协调。
    AT 模式的几个核心特性:
  9. 效率:AT 模式的效率相对较高。在一阶段,参与者执行本地事务时不需要进行加锁
    或阻塞,只需生成 Undo Log 和执行结果。在二阶段,参与者根据 Undo Log 进行提
    交或回滚操作,效率较高。
  10. 回滚机制:AT 模式中,如果全局事务失败,TC 会向所有参与者发送回滚请求。参
    与者接收到回滚请求后,根据 Undo Log 进行事务回滚,保证数据的一致性。
  11. 最终一致性:AT 模式通过两阶段提交协议保证分布式事务的一致性。在二阶段,TC
    根据参与者的提交完成通知,控制全局事务的最终提交或回滚,从而实现最终一致性。
  12. 一阶段和二阶段工作原理:一阶段是提交请求阶段,TC 向所有参与者发送事务开始
    请求,并收集参与者的执行结果和 Undo Log。二阶段是提交确认/回滚阶段,TC 向所
    有参与者发送事务提交请求,参与者根据 Undo Log 进行事务提交,并向 TC 发送提交
    完成通知。最后,TC 根据参与者的提交完成通知,发送最终的全局提交或回滚指令,
    参与者执行最终操作完成事务的提交或回滚。这样,通过一阶段和二阶段的协调,实现
    了分布式事务的一致性。
    最简回答:Seata 分布式事务框架的使用步骤为:引入依赖、配置事务切面和数据
    源代理、添加@GlobalTransactional 注解。AT 模式:效率较高、回滚机制通过发
    送回滚请求和 Undo Log 实现、通过两阶段提交协议实现最终一致性,一阶段提交
    请求阶段收集执行结果和 Undo Log,二阶段提交确认/回滚阶段执行最终操作。
  13. 为什么用 MongoDB
  14. 非结构化数据存储:MongoDB 是一个面向文档的 NoSQL 数据库,它不需要定义
    固定的数据模式,适用于存储不同结构和半结构化的数据,如日志、JSON 文档、传感
    器数据等。
  15. 可扩展性和高性能:MongoDB 采用分布式架构,支持分片和复制集,可以实现水
    平扩展和高并发访问,处理大规模数据和高负载要求,提供快速的读写操作和查询性能。
  16. 灵活性和快速开发:相比关系型数据库,MongoDB 不需要事先定义数据结构和关
    系模型,可以动态地插入、更新和删除数据,方便快捷。这使得开发迭代过程更加灵活,
    能够快速构建和调整数据模型。
  17. 高可用性和容错性:MongoDB 支持数据复制和故障检测机制,可以设置主从复制
    或复制集群,提供自动故障转移和容错能力,确保数据的可靠性和可用性。
  18. 强大的查询功能:MongoDB 支持丰富的查询表达式和查询引擎,包括多类型索引、
    分布式查询和聚合框架,可以灵活地进行复杂的查询、聚合和地理空间分析。
  19. 社区支持和生态系统:MongoDB 拥有活跃的开源社区和庞大的生态系统,提供大
    量的文档、教程和工具支持,简化开发和维护工作。
    最简回答:MongoDB 的使用能够带来非结构化数据存储、高可扩展性和性能、灵
    活的开发过程、高可用性和容错性、强大查询功能以及活跃的社区支持和生态系统,
    适用于许多大规模数据处理和非结构化数据存储的应用场景。
    MongoDB 的常用命令:
  20. show dbs:显示当前所有的数据库。
  21. use :切换到指定的数据库。
  22. db:显示当前所在的数据库。
  23. show collections:显示当前数据库中的所有集合。
  24. db..find():查询指定集合中的所有文档。
  25. db..findOne():查询指定集合中的第一个文档。
  26. db..insertOne():向指定集合中插入一个文档。
    注意:老版本的 MongoDB 中使用 db..save()插入一个
    文档,新版本中这个命令已过时;建议使用 insertOne 命令。
  27. db..updateOne(, ):更新指定集合中符合条
    件的第一个文档。
  28. db..deleteOne():删除指定集合中符合条件的第一个文
    档。
  29. db..createIndex(, ):为指定集合创建索引。
  30. 为什么 MongoDB 比 MySQL 快
  31. 内存映射:MongoDB 使用内存映射技术将数据文件映射到内存中。这使得磁盘读
    写操作变得更加高效,可以避免频繁的磁盘 IO,提高数据访问速度。
  32. 索引优化:MongoDB 支持多种类型的索引,包括 B 树、哈希索引和地理空间索引
    等。合理使用索引可以快速定位和检索数据,提升查询性能。
  33. 分布式存储和查询:MongoDB 可以将数据分布在多个节点上,实现分布式存储和
    查询。这使得在大规模数据场景下,可以通过并行查询和数据分片技术提高查询效
    率。
  34. 不需要复杂的关系模型:相比于关系型数据库,MongoDB 不需要事先定义数据结
    构和关系模型,可以更加灵活地存储和查询数据。这在一些非结构化和半结构化数
    据的场景下,节省了建模和查询的时间。
    最简回答:MongoDB 比 MySQL 快的原因在于它使用内存映射技术提高数据访问
    速度、支持多类型索引优化查询、具备分布式存储和查询能力,并且不需要复杂的
    关系模型,适用于大规模数据处理和非结构化数据存储的场景。
  35. MongoDB 和 Redis 的区别
    最简回答:MongoDB 和 Redis 在几个关键方面有明显区别。首先,数据模型不同,
    MongoDB 支持复杂数据类型和非结构化数据,而 Redis 主要支持键值对。其次,
    存储方式不同,MongoDB 将数据持久化在磁盘上,而 Redis 可以将数据保存在内
    存中作为缓存。最后,应用场景不同,MongoDB 适用于大规模数据存储和复杂查
    询,而 Redis 适用于高性能的键值对缓存和消息队列等场景。根据具体需求,选择
    适合的数据库方案。
    九、部署
  36. Nginx 的作用
    ⚫ 反向代理:将多台服务器代理成一台服务器。
    ⚫ 负载均衡:将多个请求均匀的分配到多台服务器上,减轻每台服务器的压力,提高
    服务的吞吐量。
    ⚫ 动静分离:nginx 可以用作静态文件的缓存服务器,提高访问速度
  37. Linux 常用命令
    ⚫ 系统类的
     查看 IP:ifconfig
     重启:reboot
     查看内存:free
     查看磁盘:df
    ⚫ 进程类的
    ps: 显示当前运行的进程
    ◼ ps -ef | grep java(查找所有正在运行的 Java 进程)。
    kill: 终止指定的进程
    ◼ kill -9 PID(终止具有指定 PID 的进程,PID 是进程 ID 的占位符)
    java: 运行 Java 应用程序
    ◼ java -jar myapp.jar(运行名为"myapp.jar"的 Spring Boot JAR 文件)
    nohup: 后台执行命令,并忽略挂断信号
    ◼ nohup java -jar myapp.jar &(在后台执行名为"myapp.jar"的 Spring
    Boot JAR 文件)
    ⚫ 文件类的
    tail: 查看文件的后几行内容
    ◼ tail -n 10 file.txt(显示名为"file.txt"的文件的最后 10 行内容)
    find: 在指定目录下搜索文件或目录
    ◼ find /path/to/directory -name “*.txt”(在指定路径下查找所有扩展名为
    ".txt"的文件
    cat: 查看文件内容
    ◼ cat file.txt(显示名为"file.txt"的文件的内容)
  38. Jenkins 自动部署原理
  39. 配置 Jenkins 项目:在 Jenkins 上创建一个新项目,并设置相关参数,例如项目
    名称、源码管理方式、构建触发条件等。
  40. 源码拉取:当触发条件满足时,Jenkins 会从 Git 仓库中获取最新的代码。
  41. 构建过程:在构建过程中,Jenkins 将执行一系列操作来构建 Java 项目,例如:
     设置构建环境:确定使用的 Java 开发工具、编译器版本等。
     安装依赖:如果项目需要依赖其他库或框架,Jenkins 会通过构建工具 Maven
    下载和安装所需的依赖项。
     编译项目:执行编译命令,将 Java 代码编译成可执行的字节码文件。
     运行测试:执行单元测试、集成测试等,以确保代码质量和功能正确性。
     代码打包:将编译后的代码打包成 JAR 文件,以备部署使用。
  42. 部署到服务器:完成构建后,Jenkins 可以使用不同的方式将构建结果部署到服务
    器上,例如:
     远程拷贝:通过 SSH 或其他协议将构建生成的文件复制到目标服务器上。
     容器化部署:使用 Docker 或其他容器技术,在目标服务器上创建并运行包
    含应用程序的容器。
  43. 验证部署:Jenkins 可以通过发送请求或执行自动化测试等方式验证部署的应用程
    序是否正常运行。
    Jenkins 通过提供一个可定制的、持续集成和交付的自动化流程,结合不同的插件和工
    具,实现了 Java 项目的自动化部署。它可以自动化执行构建、测试和部署等任务,减
    少人工操作,提高交付速度和质量。同时,Jenkins 还支持日志记录、通知、报告生成
    等功能,方便开发团队进行监控和跟踪。
    十、项目
  44. 技术团队组成
    我们技术团队总共 12 个人,1 个技术经理、4 个 Java 、其他各 1 人,包括产品经
    理、UI、前端、IOS、安卓、运维、测试。(注意小公司内是没有架构师的和项目
    经理)。公司总人数二三十人。
  45. 软件开发流程

软件开发的流程一般分为 5 个阶段:需求、设计、编码、测试和实现,具体解释:

  1. 每个迭代开始前产品经理会整理好需求然后组织需求评审会

  2. 需求评审通过后,我们技术团队就会进行开发排期

  3. 排期完成后就要先完成设计工作,比如设计数据库表和 API 接口文档;再进行前后
    端的代码开发

  4. 开发完毕后我们 Java 工程要自测,自测后,前后端要进行接口联调

  5. 下一步就是测试人员来测试我们开发的成果

  6. 开发没有问题就由运维部署上线了

  7. 如果生产有问题,我们会修复 BUG 再次上线

  8. 分支管理规范
    ⚫ 【master】主干永远是最新的代码,跟生产运行的代码保持一致,项目第一版一般
    只有 master 分支
    ⚫ 【开发分支】每次开发新版功能时,都基于 master 创建新的分支:分支命名规则
    与业务功能有关,团队所有成员基于此分支开发
    ⚫ 【测试分支】每次迭代开发完毕,完成自测和前后端联调后,基于开发分支创建测
    试分支
    ⚫ 【测试分支】测试团队基于测试分支部署并进行测试,测试过程中有问题,继续修
    改开发分支,开发完毕再合并到测试分支
    ⚫ 【生产分支】测试通过后基于测试分支新建生产分支(注意版本号,此分支代码方
    便回滚), 基于生产分支进行部署,如果部署失败则回滚。如果部署成功,则将生
    产分支代码合并到 master 分支。

  9. 生产问题如何解决
    ⚫ 首先要定位哪里出的问题,登录生产服务器,然后从日志文件中定位关键信息!可
    以通过 VI 命令编辑日志文件后添加斜杠跟上要搜索的词定位日志内容
    ⚫ 然后根据日志的报错信息找对应的代码,排查代码可能出现的问题,尽量在开发环
    境重现
    ⚫ 确定问题后,创建 bugfix 修复分支修复代码
    ⚫ 修改完 Bug 之后自测,没问题后再合并代码到上次的测试分支上,测试人员再测,
    测试通过后再合并到生产分支,就可以再让运维部署上线
    ⚫ 如果问题不在代码上,而是数据库的问题,则应该对数据库进行相应的优化
    具体解决的问题:
    是一个 ThreadLocal 的问题,具体表现为在生产环境出现了大量的 OOM 异常。通过
    日志定位发现是由于使用 ThreadLocal 保存登录用户信息这里,用完没有及时清理,
    所以导致内存占用越来越多。最后添加了一段代码 threadLocal.remove()及时清理就
    解决了这个问题。

  10. 高并发解决方案
    ⚫ 静态资源优化
     CSS/JS/图片等静态资源上传到云存储(阿里云 OSS 或者开源 MinIO)服务
    ⚫ 服务器优化
     增加服务器的 CPU、内存、磁盘等配置
    ⚫ 部署优化
     Nginx 配置负载均衡、限流
     微服务多节点部署
     基础服务 MySQL、Redis 等多节点部署
    ⚫ 代码优化
     答题:延迟请求到达时间
     网关限流:限制单位时间内的请求数
     Hystrix:熔断降级保证不会级联失败
     MQ:削峰、异步、解耦
     Redis:缓存加速查询
     MySQL:分库分表、读写分离、添加索引
     合并写请求

  11. 项目周期为什么这么久?需要六七个月?
    ⚫ 因为这是一个从无到有的项目,小外包公司基本都这样,人手不是特别够,所以
    很少的人干的很多的活,开发周期自然就长了。

  12. 为什么选择微服务项目?为什么项目选择这么多分布式
    技术?
    ⚫ 领导决定的,不是我定的,我猜测是因为他预估我们的项目将来会有很多用户使
    用,既然用户很多肯定会面临各种高并发问题,所以他才这么定的。

  13. 对接第三方阿里云 OSS 的步骤
    ⚫ 注册阿里云账号并实名认证
    ⚫ 开通 OSS 服务
    ⚫ 创建 Bucket 并设置公开读权限
    ⚫ 创建 RAM 用户生成 AK 和 SK,添加用户对 OSS 服务的管理权限
    ⚫ 在项目中根据阿里云官方提供的 OSS 的上传、下载、删除的案例代码进行测试
    ⚫ 测试通过后将上传、下载、删除功能都封装到组件类里,将 AK、SK、bucket 名称、
    域名等添加到配置文件中
    ⚫ 最后在业务层代码中注入组件类的实例,并调用对应方法完成文件的上传、下载或
    删除功能
    附录
    ⚫ 常见人事面试题
    常见人事面试题

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@山雨欲来风满楼

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值