Skip to content

面试煎熬成蛋_面试速刷版

待看一下

  • 1、AOP 的事务实现:编程式事务和声明式事务

  • 2、AOP 的动态代理的实现代码(JDK、CGLIB)

  • 3、Bean的生命周期

  • 4、认证授权的整体流程

  • 5、nacos 是不是去中心化的

  • 6、mysql 行转列

  • Gateway 的版本,你们怎么使用的

  • Nacos 是否是去中心化的,服务挂了,是否还能进行服务调用

  • MySQL SQL:行转列 CASE WHEN

  • Redsi 的读写策略:为什么要采用旁路缓存

  • 认证授权:讲一下你们项目的认证授权,你们用的是什么加密算法

  • 微服务怎么拆分的。

  • 令牌桶是做什么的?和synchronizied的

  • 技术选型讲了一下。

  • RocketMQ的幂等问题。

  • synchronizied锁的底层以及升级过程,CAS算法的原理,ABA问题如何解决。

  • mysql,事务,隔离等级

  • mysql索引

  • 分库分表,怎么设计的?如何在线分库分表?不知道

  • 反射具体怎么操作的

  • Nacos 的

  • 冒泡

  • 快速

  • 单例

基础

异常

image.png

Unchecked Exception不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。

Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译。

不受检查异常

  • NullPointerException(空指针错误)
  • IllegalArgumentException(参数错误比如方法入参类型错误)
  • NumberFormatException(字符串转换为数字格式错误,IllegalArgumentException的子类)
  • ArrayIndexOutOfBoundsException(数组越界错误)
  • ClassCastException(类型转换错误)
  • ArithmeticException(算术错误)
  • SecurityException (安全错误比如权限不够)
  • UnsupportedOperationException(不支持的操作错误比如重复创建同一用户)
  • ……

当我们在尝试使用 null 引用的时候,会发生 NPE

  • 避免空指针异常
      1. 在使用之前先检查一下是否为 null
      1. 调用方法的时候进行参数检查
      1. 在设计方法或者API 的时候,尽量不要返回 null 值,而且返回一个空集合或者默认值;
      1. 初始化使用字段,保证不为 null
      1. 使用 Optional
      1. 使用断言(开发和测试环境)

方法上

  • throws 方法声明上进行抛出异常
  • throw 方法体中进行抛出异常

String

  • String: 不可变,线程安全
    • 内部维护的是private final char/byte数组(不可变)
  • StringBuilder:可变字符串,线程不安全;可以使⽤append进⾏拼接字符串
  • StringBuffer:可变字符串,线程安全;通过synchronized来保证线程安全,操作和 StringBuilder⼀样
    • synchronized 对于出现在循环中会进⾏锁粗化,会将锁的范围扩 展到整个操作,从⽽避免频繁进⾏锁操作造成性能开销

String → StringBuffer → Synchronized → 锁粗化

两个的默认容量大小都是 16 字符。

final 修饰类代表不可进行继承,修饰方法代表不可重写覆盖,但是当 final 修饰引用类型的时候,引用不可变,但内容并不能保证不可变

比如:

final List<Integer> list = new ArrayList<>();
list.add(1); // 这是允许的
// list = new ArrayList<>(); // 编译错误,不能改变 final 引用的对象

关于 final 修饰 string

final String s = "Hello";
// s = "World"; // 编译错误,因为s是final,不能再指向另一个String对象。

// 以下操作不涉及改变s指向的String对象,而是尝试修改String对象的内容,这在Java中是不可能的。
String s2 = s.replace("H", "W"); // 创建了一个新的String对象,并不改变原始的s指向的对象。

在这个例子中,尽管replace方法看似是在修改String,但实际上它是创建了一个新的String对象,并将其返回。原始的String对象(即s指向的对象)并未发生改变。

(跟字符串常量池有关)

String 为什么是不可变的

String内部维护的是private final char/byte数组,

  • 不可变线程安全
    • 好处
      • 防⽌被恶意篡改
      • 作为HashMap的key可以保证不可变性
      • 可以实现字符串常量池,在Java中,创建字符串对象的⽅式
        • 通过字符串常量进⾏创建
          • 在字符串常量池判断是否存在,如果存在就返回,不存 在就在字符串常量池创建后返回
        • 通过new字符串对象进⾏创建
          • 在字符串常量池中判断是否存在,如果不存在就创建, 再判断堆中是否存在,如果不存在就创建,然后返回该 对象,总之要保证字符串常量池和堆中都有该对象

IO

image.png

NIO

image.png

  • NIO:内核读取数据会从 BIO 的阻塞等待不一样,而是变为自旋等待,等待就绪后,进行从内核数据拷贝到用户缓存区(仍然会阻塞),不同的是第一步的操作;

  • 在这种操作基础下,NIO 会有一种多路复用的操作,这种操作一般是通过 epoll、select、poll 的三种方式,其中后两者都是通过自旋等待的方式去进行监听处理,而 epoll 能精确定位到哪一个;

  • Redis 中的快速读中,就采用了这种 IO 多路复用 + 事件派发机制的方式来进行处理的。

  • 对于 Netty 的理解,封装了 NIO 的一些操作,使得相关的操作更加方便。

  • Dubbo 的话,主要是用于 Rpc 服务之间的调用,他可以通过使用基于NIO的通信框架(如Netty),Dubbo 能够高效地处理成千上万的并发连接,减少因线程切换带来的开销,提高系统整体的吞吐量。

  • 输入流:从文件读取数据,外设数据传输数据到内存

  • 输出流:向文件写入数据

SPI

  • 服务提供者的接口,Java 会向外提高一个公共接口,然后不同的服务在这个 SPI 的基础上进行实现不同的服务实现
  • 使用 Java 的 SPI 机制需要依赖 ServiceLoader 来实现

https://javaguide.cn/java/basis/spi.html#spi-介绍

Java8 常见使用

  • Lambda 表达式
  • Stream
  • Optional

反射

  • 反射技术的使用
    • Class类 代表类的实体,在运行的Java应用程序中表示类和接口
    • Field类 代表类的成员变量(成员变量也称为类的属性)
    • Method类 代表类的方法
    • Constructor类 代表类的构造方法
      • 1.getFieldgetMethodgetCostructor方法可以获得指定名字的域、方法和构造器
      • 2.getFieldsgetMethodsgetCostructors方法可以获得类提供的public域、方法和构造器数组,其中包括超类的共有成员
      • 3.getDeclatedFieldsgetDeclatedMethodsgetDeclaredConstructors方法可以获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。

集合

  • 概念前提
    • ArrayList的数组默认⻓度10,1.5倍数组扩容,扩容通过开辟新数 组进⾏转移⽼数组元素
    • HashMap、ConcurrentHashMap的数组默认⻓度16,2倍数组扩 容,扩容通过开辟新数组进⾏转移⽼数组元素
  • 核⼼思想
    • 读写分离
      • CopyOnWriteArrayList底层实现、处理快速失败
    • 分散热点
      • ConcurrentHashMap底层addCount计算
    • 索引优化
      • HashMap/ConcurrentHashMap 从数组->链表->红⿊树
  • 考察重点
    • 集合底层实现以及设计思想
  • 注意点
    • 对于存放⼤量元素到ArrayList 或 HashMap中时,建议提前初始化 好容量避免扩容导致性能损耗

成员关系

image.png

HashMap

  • 1.7 头插法会导致循环链表问题
    • 由于每次插入都会改变链表的顺序,这可能会导致长时间的性能下降
  • 1.8 改为了尾插法,在并发修改时减少了循环链表出现的风险

image.png

  • 头插
  • 尾插
  • 扩容

ConcurrentHashMap

ConcurrentHashMap 是一种线程安全的高效Map集合

  • ConcurrentHashMap
    • 底层数据结构
      • 1.7:底层采用分段 Segment,每一个 Segment 是由 数组 + 链表实现
      • 1.8:采用的数据结构跟 HashMap 1.8 的结构一样,Node 数组 + 链表/红黑二叉树
    • 加锁的方式
      • 1.7:采用 Segment 分段锁,底层使用的是 ReentrantLock
      • 1.8:采用 CAS 添加新节点,采用 synchronized 锁定链表或红黑二叉树的首节点,相对 Segment 分段锁粒度更细,性能更好
    • 新增元素(1.8)
      • hash(key) → 在该数组节点下通过 CAS 操作新增元素操作
      • 采用 synchronized 锁定链表或红黑二叉树的首节点

加锁程度

  • 1.7 通过对Segment加锁实现,每个 Segment 有自己的锁
  • 1.8 对于链表的修改操作,通过synchronized关键字对 Node 进行加锁;对于新增操作,如果需要更新链表的头部,使用CAS操作来确保原子性。当链表长度达到一定阈值后,链表会转换为红黑树,提高搜索效率

1.7 中使用了分段锁的机制,通过对于 HashEntry 数组进行加锁,每一个 Segment 元素都是使用的 ReentrantLock ,用于在高并发环境下对于集合的线程安全保护。

同时为了 hash 冲突的解决,这里是通过拉链法的方式进行解决的。

image.png

锁粒度更细

image.png

Deque/Queue

  • Queue是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循先进先出(FIFO)规则。

  • Deque是双端队列,在队列的两端均可以插入或删除元素。

SortedSet/TreeSet

  • TreeSet 是 SortedSet 的实现类
  • 一般是两种排序方式:一种是自然排序(实现 Comparable 接口,重写 compareTo 方法):还有一种是自定义比较器(Comparator)

Java集合_基础概念#四、Set

TreeSet :TreeSet 是通过 TreeMap 来实现的(内部结构)

CopyOnWriteArrayList

并发

可见性

volatile 保证

image.png

原子性

image.png

  • 改一下:原子性一般是线程通过对资源加锁的方式来实现的,这里可以引入 synchronized 和 ReetrantLock 的相关内容

  • synchronized 的重量级锁

线程状态

image.png

阻塞:没有获取到资源(没有获取到锁)

  • 线程状态

线程池

  • 使用场景

    • 1、异步
    • 2、记录日志
    • 3、定时任务
  • 线程池的核心线程数可以设置为 0 (通过非核心线程去处理任务)

  • 阻塞队列

    • Array 单个锁
    • Linked 两个锁,加锁

image.png

callable 接口回调:

FutureTask 提供了 Future 接口的基本实现,常用来封装 Callable 和 Runnable,具有取消任务、查看任务是否执行完成以及获取任务执行结果的方法

为什么不建议使用 Execution 创建线程池

Executors 返回线程池对象的弊端如下(后文会详细介绍到):

  • FixedThreadPoolSingleThreadExecutor:使用的是无界的 LinkedBlockingQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
  • CachedThreadPool:使用的是同步队列 SynchronousQueue, 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
  • ScheduledThreadPoolSingleThreadScheduledExecutor : 使用的无界的延迟阻塞队列DelayedWorkQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。

说白了就是:使用有界队列,控制线程创建数量。

除了避免 OOM 的原因之外,不推荐使用 Executors提供的两种快捷的线程池的原因还有:

  • 实际使用中需要根据自己机器的性能、业务场景来手动配置线程池的参数比如核心线程数、使用的任务队列、饱和策略等等。
  • 我们应该显示地给我们的线程池命名,这样有助于我们定位问题

线程池参数定义

核心线程数

  • IO密集型: CPU核数 * 2
  • CPU计算性:CPU核心 + 1

最大线程数

  • 一个常见的做法是设置最大线程数为核心线程数的2到4倍

阻塞队列

  • 队列大小的设置取决于你期望的任务处理模式。如果希望能够缓冲大量的请求,那么可以设置较大的队列。如果希望减少等待时间,可以设置较小的队列
  • IO密集型: 设置多一些(会有网络阻塞等,等待线程可能会多)
  • CPU计算性:可以设置小一点(任务可以迅速完成,不需要太大的等待队列)

理解有一个问题改一下(线程池的执行流程)

  • 1、提交任务
  • 2、核心线程处理任务
  • 3、核心线程忙碌,放入新任务到阻塞队列
  • 4、阻塞队列满了之后,再来一个任务,线程池会创建一个非核心线程去处理新的任务。当非核心线程完成它们的任务后,如果阻塞队列中还有待处理的任务,这些非核心线程也会从阻塞队列中取任务来执行。
  • 5、如果 核心线程数 + 非核心线程数 > 最大线程数,才会进行触发拒绝策略

拒绝策略方式

  • 1.AbortPolicy:直接抛出异常,默认策略:
  • 2.CallerRunsPolicy:用调用者所在的线程来执行任务;
  • 3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务:
  • 4.DiscardPolicy:直接丢弃任务:

image.png

线程计算公式

image.png

假设:核数 12、CPU 利用率 90%,等待时间 50ms,计算时间51ms

N = 12 * 0.9 * (1 + 50/51) ≈ 22

设置核心线程数 22 是比较符合预期的值

在真实的程序中,一般很难获得准确的等待时间和计算时间,因为程序很复杂,不只是“计算”。一段代码中会有很多的内存读写,计算,I/O 等复合操作,精确的获取这两个指标很难,所以光靠公式计算线程数过于理想化。

image.png

死锁

线程死锁:通过 JVM 排查工具去排查到具体原因,然后定位现象

MySQL:一般有监控信息,可以看到,看到后进行处理,通过现象分析死锁原因,然后去进行事务或者SQL的优化

synchronized

使用 synchronized 修饰类和方法的区别
  • synchronized修饰代码块(类锁)
    • 当使用synchronized修饰代码块时,需要指定一个锁对象。如果指定的是类的Class对象,那么这个synchronized代码块就相当于对这个类进行了锁定。
  • synchronized修饰方法
    • synchronized修饰一个实例方法时,
      • 它锁定的是当前实例对象。
      • 这意味着,同一时间内,只有一个线程能够访问这个对象的所有synchronized实例方法。如果一个线程访问该对象的一个synchronized实例方法,其他线程试图访问该对象的任何其他synchronized实例方法将会被阻塞,直到第一个线程完成方法的执行。
    • synchronized修饰一个静态方法时,
      • 它锁定的是这个类的所有实例。因为静态方法是属于类的,而不是任何实例对象的,所以此时锁定的是类的Class对象本身。这意味着,同一时间内,只有一个线程能够访问这个类的所有synchronized静态方法。

对于 Atomic 原子类 的使用

多线程环境使用原子类保证线程安全(基本数据类型)

比如:AtomicInteger

AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。

ThreadLocal

线程副本

数据库连接管理:通过在ThreadLocal对象中为每个线程存储一个数据库连接,可以保证每个线程独立管理自己的数据库连接。

ThreadLocal可以用来确保每个线程都有自己的数据库连接,这样就可以避免由于多个线程共享同一个数据库连接实例而引起的线程安全问题。

  • ThreadLocal:独立变量副本,避免线程共享可能导致的并发问题
  • 内部:ThreadLocalMap
    • 以 ThreadLocal 自己作为 key,资源对象作为 value ,放入当前线程的 ThreadLocalMap 集合中(弱引用)

image.png

使用到 ThreadLocal 的三个常用场景:

    1. 代替参数的显示传递
    1. 全局存储用户信息
    1. 解决线程安全问题(比如数据库连接的Connection)

慎用的场景:线程池中线程调用使用ThreadLocal、异步程序;同时注意的是使用完ThreadLocal ,最好手动调用 remove() 方法。

CompletableFuture

同步变为多个异步任务执行,最后汇总。

根据CompletableFuture依赖数量,可以分为:零依赖、一元依赖、二元依赖和多元依赖。

(等待 N 个 其他任务完成,启动异步任务)

CountDownLatch

  • CountDownLatch(闭锁/倒计时锁)用来进行线程同步协作,等待所有线程完成倒计时(一个或者多个线程,等待其他多个线程完成某件事情之后才能执行)
    • 其中构造参数用来初始化等待计数值
    • await() 用来等待计数归零
    • countDown()用来让计数减一

JVM

JVM 组成

image.png

类加载器

image.png

需要再补充一下 相关内容

image.png

参数调整

JVM 调优的参数可以在那里设置参数值

  • war 包部署在 tomcat 中设置
    • 修改 TOMCAT_HOME/bin/catalina.sh 文件
  • jar 包部署在启动参数设置
    • java -Xms512m -Xmx1024m -jar xxxx.jar

排查思路

分析 dump 文件

  • 1、打开 VisualVM
  • 2、选择:文件 → 装入 → (选择文件) → 打开
  • 3、概要 → 堆转储
  • 4、通过查看堆信息的情况,可以大概定位内存谥出是哪行代码出了问题

实际

  • 40% → 80%

分代收集

工作机制

  • 1、新创建的对象,都会先分配到eden区
  • 2、当伊甸园内存不足,标记伊甸园与 from(现阶段没有) 的存活对象
  • 3、将存活对象采用复制算法复制到to中,复制完毕后,伊甸园和 from内存都得到释放
  • 4、经过一段时间后伊甸园的内存又出现不足,标记eden区域to区存活的对象,将存活的对象复制到from区
  • 5、当幸存区对象熬过几次回收(最多15次),晋升到老年代(幸存区内存不足或大对象会导致提前晋升)

堆栈

说一个栈溢出的例子

方法无线递归

说几个堆溢出的例子

循环创建对象,或者一次性从数据库出

MySQL 🚩

查询优化

image.png

索引

针对索引单独复习下

  • 在创建表的时候指定索引,一般如果没有指定主键索引会有一个默认的主键索引
  • 直接添加索引
    • 单列索引 CREATE INDEX index_name ON table_name(column_name);
    • 唯一索引
    • 复合索引

添加索引需要考虑的点

  • 合适的列,索引会占用空间,使用最左前缀法则,考虑索引类型,看一下是否索引命中(避免索引失效)
  • 看一下这个列的查询频率
  • 索引类型:根据字段的数据类型和用途,可以选择不同类型的索引,如B树索引适用于一般情况,全文索引适用于文本搜索,哈希索引适用于精确匹配搜索等

添加索引(多列)

ALTER TABLE table_name ADD INDEX index_name (column1, column2, ...);

单列、多列、唯一

索引失效

  • 索引失效的十大经典场景
    • 隐式的类型转换,索引失效
    • 查询条件包含 or,可能导致索引失效
    • like 通配符可能导致索引失效
    • 查询条件不满足联合索引的最左匹配原则
    • 在索引列上使用 mysql 的内置函数
    • 对索引进行列运算(如,+、-、*、/ )
    • 索引字段上使用(!= 或者 <>),索引可能失效
    • 索引字段使用 is null,is not null,索引可能失效
    • 左右连接,关联的字段编码格式不一样
    • 优化器选错了索引

image.png

优化SQL

SQL 优化

  • select 具体字段而不是 select *
  • 多用 limit
  • 尽量用 union all 替换 union
  • 优化 group by
  • 优化 order by
  • 小表驱动大表
  • 字段类型使用合理
  • 优化 limit 分页
  • exist & in 的合理利用
  • join 关联的表不宜过多
  • delete + in 子查询不走索引
  • in 元素不要过多

分库分表

MyCAT

读写分离、分库分表

7b404549804a2c8446fbf945f33f816.png

表规范

  • 下划线分隔
  • 见名识意
  • 默认使用  InnoDB 存储引擎,字符集使用 UTF-8,表字段添加注释
  • 历史数据进行归档(大于500万)
  • 建议把 BLOB 或是 TEXT 列分离到单独的扩展表中
  • 尽可能总是让字段保持 NOT NULL
  • 不要用字符串存储日期
  • 金额类使用 decimal 类型
  • 建议单张表索引不要过多,建议不要超过 5 个

字段创建

  • 适当的数据类型:根据数据的性质选择最合适的数据类型,例如使用INT对整数进行存储,VARCHAR对短字符串进行存储,TEXT对长文本进行存储。

  • 长度限制:为VARCHAR等类型指定合适的长度,避免无谓的空间浪费或数据截断。

  • 数值类型的精度:对于存储数值的字段,合理设置其大小和精度,例如货币类数据可以使用DECIMAL类型。

  • 1、合理创建字段,使用合适的数据类型

  • 2、合理的命名规范

  • 3、当业务总是需要值的时候,可以设置字段为 not null

  • 4、可以设置合理的默认值

就是说基于业务的考虑,不考虑使用第三范式,将一些关联字段,比如订单号或者 VIN 加入到业务表中

动态数据源切换

@Ds 注解 + Mybatis-plus (切面注入)

https://www.cnblogs.com/zhaobo1997/p/17964499

@DS做了什么: https://juejin.cn/post/7132779222791258143

这里简单说下背后的原理。

首先,项目启动的时候,把配置文件里数据源配置加载进来,存在一个map里,key就是我们自定义的master、slave0。

再真正执行查询前,会有一个拦截器,把注解的value,也就是数据源的key,存储到一个 ThreadLocal 里,用栈存储。

获取数据库连接的时候,直接拿栈顶的数据集配置,这样就正好是我们配置的。

最后记得清空ThreadLocal,防止内存泄漏。

MySQL调优操作

java-site/docs/数据库/DB_MySQL/常见查询SQL 工作记录/MySQL调优操作

  • 最左前缀原则
  • 索引类上少计算数
  • 范围后面全失效
  • select 使用明确字段(尽可能使用覆盖索引)
  • 不等空值或者 or
  • VAR 引号不能丢

悲观锁和乐观锁/共享锁和排他锁

  • 悲观锁:显示实现SELECT ... FOR UPDATE

  • 乐观锁:版本号(在表中添加一个version字段)

  • 共享锁:读锁 SELECT ... LOCK IN SHARE MODE

  • 排他锁:写锁SELECT ... FOR UPDATE

避免死锁

  • 死锁例子:先查,再更新(间隙锁的原因)

image.png

隔离级别

  • MySQL的默认隔离级别:可重复读(REPEATABLE READ)
  • 与 SQL 标准不同的地方在于 InnoDB 存储引擎在 REPEATABLE-READ(可重 读) 事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生

5b38ce58a0e002b1499ebbefd5f2803.png

多数据源

数据迁移和备份

简单说一说drop、delete与truncate的区别

日志文件

image.png

SQL 怎么执行的

image.png

深度分页

去重,Limit:加一个记录点

5ba7c965ac21d9cd42d03ef755ec084.png0e360f1e0cc2dd5bc220fb7885acf9b.png

自适应哈希索引

image.png

哈希索引缺点

16ff114ebef9498f4376881d9158007.png

数据库连接

什么场景下数据库的连接会打满?

高并发场景下,新的连接建立不起来

其中还包括 连接泄露,长时间占用连接(资源管理未关闭)

慢sql会导致数据库连接打满吗?

可能会

有可能会造成一些情况,比如:时间占用连接阻塞其他查询等。

filesort

  • order by :索引使用不当

千万级数据量表如何快速添加索引/字段

参考: https://www.cnblogs.com/cgy-home/p/17275794.html

框架 🚩

Spring

image.png

  • 生命周期
循环依赖问题

image.png

常用注解补充一下

Spring 的传播事务

在spring中,通过 @Transactional 调用内部事务嵌套事务方法的时候,会出现所谓的传播事务

  • REQUIRED
  • SUPPORTS
  • REQUIRES_NEW
  • NOT_SUPPORTED
  • NEVER
  • MANDATORY
  • NESTED

3e4a1d27ae7b8bc784c3f038e0b502d.png

Spring 的事务失效
  • 事务方法的访问修饰符不正确
  • 在同一个类内部调用事务方法
  • 错误的事务传播行为配置
关于 Spring 中 事务注解中抛出异常

@Transactional 注解中,抛出的异常需要是 RuntimeException 及其子类;一般情况下,我们会进行抛出自定义异常(该异常继承 RuntimeException)

@Scheduled

@Scheduled是 SpringFramework 提供的一种基于注解的定时任务调度方式,它允许您在方法级别轻松定义定时任务。

@Scheduled 注解本身并不限定为单线程,任务的实际执行方式取决于底层调度器(如 ThreadPoolTaskScheduler)的配置和并发策略。在合理配置线程池的情况下,多个@Scheduled 任务可以并发执行。

SchedulingConfigurer默认是创建了单例线程池

SpringBoot使用@scheduled定时执行任务的时候是在一个单线程中,如果有多个任务,其中一个任务执行时间过长,则有可能会导致其他后续任务被阻塞直到该任务执行完成。也就是会造成一些任务无法定时执行的错觉。

AOP

AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。

Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理

在Spring AOP 中,关注点和横切关注的区别是什么?

关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。 横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。

那什么是连接点呢?

连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置

切入点是什么?

切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点

什么是通知呢?有哪些类型呢?

通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。

Spring切面可以应用五种类型的通知:

  • before:前置通知,在一个方法执行前被调用。

  • after: 在方法执行之后调用的通知,无论方法执行是否成功。

  • after-returning: 仅当方法成功完成后执行的通知。

  • after-throwing: 在方法抛出异常退出时执行的通知。

  • around: 在方法执行之前和之后调用的通知。

  • 动态代理方式

    • JDK 动态代理
    • CGLIB 动态代理

JDK 动态代理使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现。这种代理方式要求目标对象必须实现一个或多个接口。

SpringBoot

  • SpringBootApplication
    • SpringBootConfiguration
    • ComponentScan
    • EnableAutoConfiguration
      • @Import
      • spring-factries

image.png

starter

在项目的资源目录中创建一个 META-INF/spring.factories 文件,并指定自动配置类。

SpringMVC

  • 前端控制器
  • 处理器映射器
  • 处理器适配器
  • 处理器

image.png

常见注解

@RestController

@RestController是一个方便的注解,它本质上是@Controller@ResponseBody注解的结合体。当你在一个类上使用@RestController注解时,Spring会处理返回值,将其自动转换为JSON或XML响应。这意味着你不需要在每个处理方法上都添加@ResponseBody注解

@RequestMapping

@RequestMapping注解用于将HTTP请求映射到特定的处理方法或类上。它可以声明在类或方法上。当声明在类上时,它表示该类中的所有方法都可以处理那个基础URL路径的请求。而当声明在方法上时,则指定该方法可以处理哪种类型的请求。

Post 新增,put 更新,delete 删除;

404,200,401

在Web开发中,理解HTTP状态码和请求方法的区别,以及Spring MVC中过滤器与拦截器的不同用途,是非常重要的。下面逐一解释这些概念。

HTTP 状态码

  1. 200 OK - 请求成功,服务器返回所请求的资源。
  2. 302 Found - 临时重定向,请求的资源暂时移动到由Location头指定的URL。
  3. 400 Bad Request - 客户端请求有语法错误,服务器无法理解。
  4. 401 Unauthorized - 请求未经授权,这通常是因为认证信息未提供或不正确。
  5. 404 Not Found - 服务器找不到请求的资源。
  6. 500 Internal Server Error - 服务器内部错误,无法完成请求。

HTTP 请求方法

  • GET - 请求指定的页面信息,并返回实体主体。
  • POST - 向指定资源提交数据进行处理请求(例如提交表单或上传文件)。数据包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。

过滤器与拦截器

在Spring MVC中,过滤器和拦截器虽然都能实现请求的预处理和后处理,但它们的用途和执行时机有所不同。

过滤器 (Filter)

  • 作用范围:作用于整个Web应用,几乎所有的请求都会经过过滤器。它是基于Java Servlet API的,不仅限于Spring MVC应用。
  • 主要用途:过滤器主要用于处理通用的请求和响应转换,如日志记录、安全控制、字符集转换等。
  • 执行时机:在Servlet之前执行,可以对几乎所有请求进行预处理。同时,它们也可以修改请求和响应(添加头信息、转换数据格式等)。

拦截器 (Interceptor)

  • 作用范围:仅作用于Spring MVC的上下文中,主要用于处理由DispatcherServlet映射的控制器请求。
  • 主要用途:拦截器主要用于处理特定的应用逻辑和控制流程,如性能监测、授权验证、事务管理等。
  • 执行时机:在Controller方法调用之前和之后执行,提供了更细粒度的控制,包括对控制器执行前后的处理和渲染视图之前的处理。

Mybaits

image.png

JDBC 的执行流程
    1. 加载数据库驱动
    1. 建立连接
    1. 创建Statement或PreparedStatement
    1. 执行SQL语句
    1. 处理结果
    1. 关闭连接

Redis 🚩

持久化

image.png

分布式锁 🚩

  • redisson 的 lock 操作

  • key值设定:

    • 使用前缀+IP+方法名(HttpServletRequest)+唯一码(IP 的哈希值)
  • 分布式锁这里还是结合业务去讲解以下,就说一下常见的组件有 redission 和  lock4j 等,当时项目组使用的 是 lock4j (服从性) lock unlock

  • 业务场景:多个任务进行关键件绑定或者车辆订单更新,需要对于唯一键进行加锁操作(VIN)

@Service
public class LockServiceImpl implements LockService {

    @Autowired
    private LockTemplate lockTemplate;

    @Override
    public void lock(String resourceKey) {

        LockInfo lock = lockTemplate.lock(resourceKey, 10000L, 2000L, CustomRedissonLockExecutor.class);
        if (lock == null) {
            // 获取不到锁
            throw new FrameworkException("业务处理中,请稍后再试...");
        }
        // 获取锁成功,处理业务
        try {
            doBusiness();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lockTemplate.releaseLock(lock);
        }
    }

    private void doBusiness() {
        // TODO 业务执行逻辑
    }
}

image.png

参考: https://www.bilibili.com/video/BV186421F7xM

image.png

说一下大概的一个链路,回答的时候可以往这个方向回答

  • 1、在分布式系统下,对于共享资源的访问可以使用分布式锁,这里一般会考虑引入组件 redis ,然后使用 redis 中的 setnx 命令,这个命令的作用是 如果不存在则进行添加,如果存在则添加失败 setnx name value
  • 2、一般这种情况下就可以实现一个简易的分布式锁,但是呢,有可能出现 redis 宕机的情况,这种情况下,为了防止这个 键值 一直存在 redis 中(锁),一般会进行添加一个有效时间,从而避免锁一直未释放的操作 setnx name value ex100;同时,这里如果是在过期时间内进行释放锁,会进行 删除操作,这里一般建议使用 Lua 命令进行操作(防止误删到其他的锁)
  • 3、上面还可能出现一种情况,就是在过期时间后自动释放了锁,业务流程却未执行完,为了避免这种情况,建议引入 redission

Redission

  • 看门狗
    • 当使用 Redisson 的锁机制(如 RLock)锁定某个资源时,如果没有显式地设置锁的租期(lease time),Redisson 会启用“看门狗”机制。这个机制的核心是自动续租,确保在持有锁的服务实例正常运行时,锁不会因为超时而被自动释放
      • 默认租期设置:默认情况下,锁的租期设置为 30 秒。这意味着,如果在这段时间内没有操作完成,锁就会自动释放,防止死锁。
      • 自动续租:“看门狗”机制会在锁快到期时(默认情况下是租期的 1/3,即10秒后),自动发送一个命令给 Redis,将锁的租期重新延长到 30 秒。这个过程会一直持续,直到锁被显式释放。
      • 故障转移:如果持有锁的服务实例因为某些原因宕机或无法与 Redis 通信,锁将不会被续租,最终会在默认的租期结束后自动释放。这样,其他服务实例可以获取锁,继续处理任务,保证了系统的高可用性和一致性。
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class InventoryService {
    private static RedissonClient createClient() {
        // 配置 Redisson 客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        // 创建 Redisson 客户端实例
        return Redisson.create(config);
    }

    public void decreaseInventory(String productId, int amount) {
        RedissonClient client = createClient();
        // 获取针对给定产品的锁对象
        RLock lock = client.getLock("productLock:" + productId);
        try {
            // 尝试获取锁,启用看门狗机制自动续租
            lock.lock();
            // 以下是被保护的资源访问代码
            // 模拟读取库存、减少库存数量并更新的过程
            System.out.println("库存减少处理逻辑...");
            // 模拟处理耗时
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁
            lock.unlock();
            // 关闭 Redisson 客户端
            client.shutdown();
        }
    }

    public static void main(String[] args) {
        InventoryService service = new InventoryService();
        // 模拟减少产品库存的操作
        service.decreaseInventory("product123", 1);
    }
}

image.png

线程结构
  • 1、Redis 是 Hash
  • 分布式锁:lua 脚本

Redission 的可重入锁基于 Redis 的 hash 数据类型。这种锁允许同一个线程多次获得同一把锁而不会发生死锁。锁的每一个重入都会记录下来。

  • 数据结构:Redission 使用一个 hash 存储锁信息。主要的键是锁的名称,值包括:
    • 锁的持有者(通常是一个包含线程和客户端标识的唯一标识符)
    • 锁的重入次数

过期策略、数据结构 🚩

image.png

  • 重复提交
    • setnx IP+ 方法名 + 唯一码;SETNX 不做任何操作
  • 限制访问次数
    • INCR 和 EXPIRE 命令:INCR 用于原子性地递增键的整数值。结合 EXPIRE,可以在一定时间窗口内对操作次数进行计数和限制。

数据结构

  • String:动态字符串
  • List:双向链表
  • Set:无序的哈希表
  • ZSet:哈希表 + 跳跃表(Skip list)
    • zset底层的存储结构包括ziplist或skiplist,在同时满足以下两个条件的时候使用ziplist,其他时候使用skiplist,两个条件如下:
      • 有序集合保存的元素数量小于128个
      • 有序集合保存的所有元素的长度小于64字节
  • Hash:小型哈希表
    • 对于存储较小的哈希表,Redis 会使用一种称为 "ziplist"(压缩列表)的特殊编码格式
    • 当哈希表较大时则使用标准的哈希表。

视频: https://www.bilibili.com/video/BV1kh411x7Jc

image.png

  • 压缩列表本质上就是一个数组,在一个数组的基础上添加了 列表长度、尾部偏移量、列表元素个数、列表结束标识

image.png

  • 跳表结构:可以理解为多级索引,在普通一级索引,是一个一个元素,可以在这个基础上,建立二级索引,比如区间是3,
  • 为什么ZSet 使用跳表而不使用红黑树:跳表结构下,他的范围查询更为高效。

image.png

跳表

跳表为复合型的数据结构,可以被视为一个多层的链表,它允许快速查询、插入、删除和更新操作,其平均时间和空间复杂度都是O(logN),在大多数情况下可以替代平衡树(如红黑树)。

在Redis中,跳表主要用于实现有序集合(ZSet)数据类型。有序集合是一种存储不重复元素的集合,但每个元素关联一个浮点数分数,集合中的元素按照分数有序排列。跳表能够高效地支持按分数范围查询和按照排名查询元素,这使得它非常适合实现Redis的有序集合操作。

数据一致性

image.png

  • 旁路缓存模式
  • 读写穿透
  • 异步缓存写入

高性能读写

IO多路复用 + 事件派发机制

高可用

主从 → 哨兵 → 集群

内存淘汰策略有哪些

数据的淘汰策略:当Redis中的内存不够用时,此时在向Redisr中添加新的key,那么Redis 就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。

  • 默认策略noeviction,当内存不⾜以容纳新写⼊数据时,新写⼊操作 会报错
  • 针对设置过期时间的key
    • volatile-lru,按照LRU算法删除
    • volatile-lfu,按照LFU算法删除
    • volatile-radom,随机删除
    • volatile-ttl,按过期时间顺序删除
  • 针对所有key
    • allkeys-random,随机删除
    • allkeys-lru,按照LRU算法删除
    • allkeys-lfu,按照LFU算法删除

讲一下 主从选举的脑裂问题

集群脑裂是由于主节点和从节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到主节点,所以通过选举的方式提升了一个从节点为主,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在老的主节点那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将老的主节点降为从节点,这时再从新master 同步数据,就会导致数据丢失

解决:我们可以修改rds的配置,可以设置最少的从节点数量以及缩短主从数据同步的延迟时间,达不到要求就拒绝请求就可以避免大量的数据丢失。

常用注解

Cacheable@CacheEvict注解是用于声明方法缓存策略的两个常用注解,它们是Spring Cache抽象的一部分(简易操作)

  • @Cacheable注解标记在一个方法上,表示该方法的结果是可缓存的,查询方法
  • @CacheEvict注解标记在一个方法上,表示执行该方法时,会从缓存中移除相应的数据。这常用于数据更新或删除操作,以确保缓存中不会保留过时的数据

Redis 事务

Redis 可以通过 MULTIEXECDISCARD 和 WATCH 等命令来实现事务(Transaction)功能

  1. 开始事务(MULTI);

  2. 命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行);

  3. 执行事务(EXEC)。

  4. MULTI:标记一个事务块的开始。

  5. EXEC:执行所有被MULTI命令入队的命令。

  6. DISCARD:取消事务,放弃执行事务块内的所有命令。

  7. WATCH:监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令改变了,那么事务将被打断。

不建议使用,建议使用 Lua 命令。

线程模型

配置项

大 key

  • 后面解决问题

热点数据续期

- 当热点数据设置有效时间后,会采用预热(应用启动时加载),写入时续期,访问时续期,或者定时任务执行(为即将过期的热点数据执行续期操作);  - 定时要搞的话一般还是设置了有效时间才会这么做,并不是指刷新数据,而是延长有效时间才需要这个操作;如果不设置有效时间,反而不需要这样做

集群

  • 主从

MQ 🚩

见:07 面试煎熬成蛋_MQ

面试煎熬成蛋_MQ

RibbitMQ

说一下 RabbitMQ 的工作流程

image.png

RocketMQ 🚩

image.png

Kafka

为什么选用 Kafka,和 RocketMQ 有什么区别

选择Kafka还是RocketMQ,取决于具体的业务需求。

Kafka非常适合需要高吞吐量、大规模消息处理、日志收集和流数据处理的场景。RocketMQ在需要事务消息、定时/延时消息或更细致的消息服务质量控制的场景下更有优势。

设计模式

image.png

观察者

观察者模式允许对象在状态发生变化时通知其他依赖对象

装饰器

装饰器模式是指动态地给一个对象增加一些额外的功能,同时又不改变其结构。

重在功能的加强

SpringCloud 🚩

Nacos 工作流程

  • 服务发现
    • 服务注册,服务发现,健康检查,服务注销
    • 服务发现是先获取本地缓存,如果没有本地缓存,就请求Nacos Server服务端获取数据,如果Nacos Server挂了,也不会影响服务的调用。
  • 配置管理

服务发现:

image.png

服务高可用

image.png

相关组件

image.png

OpenFeign

ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。feign默认集成了ribbon。

  • 1.feign采用的是基于接口的注解

  • 2.feign整合了ribbon,具有负载均衡的能力

  • 3.整合了Hystrix,具 有熔断的能力

  • 使用:

    • 1.添加pom依赖。
    • 2.启动类添加@EnableFeignClients
    • 3.定义一个接口 @FeignClient(name=“xxx”)指定调用哪个服务
  • 启动类:@EnableFeignClients

总结来说,OpenFeign:

  • 提供了 RPC 风格的编程接口,使得开发者可以以一种更简洁的方式编写调用远程服务的代码。
  • 在底层实现上,它是基于 HTTP 协议的,通过构建和解析 HTTP 请求来完成服务调用。
  • 封装了 HTTP 协议的细节,让开发者可以更专注于业务逻辑而非网络通信的细节。

因此,虽然 OpenFeign 的使用体验类似于 RPC,但它并非真正意义上的 RPC 框架,而是一个基于 HTTP 的服务调用工具

Hystric

熔断机制是应对雪崩效应的一种微服务链路保护机制。当某个微服务不可用或者响应时间太长时, 会进行服务降级,进而熔断该节点微服务的调用,快速返回“错误”的响应信息。当检测到该节点微 服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现,Hystrix会监 控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内调用20次,如果失败,就会启动 熔断机制。 服务降级,一般是从整体负荷考虑。就是当某个服务熔断之后,服务器将不再被调用,此时客户端 可以自己准备一个本地的fallback回调,返回一个缺省值。这样做,虽然水平下降,但好歹可用, 比直接挂掉强。

Hystrix相关注解

  • @EnableHystrix:开启熔断
  • @HystrixCommand(fallbackMethod=”XXX”):声明 一个失败回滚处理函数XXX,当被注解的方法执行超时(默认是1000毫秒),就会执行fallback函 数,返回错误提示。

Gateway

gateway 的功能
  • 网关
    • 路由转发
    • 请求过滤拦截(请求过滤,响应过滤)
      • 过滤
        • 全局
          • GlobalFilter:全局过滤器
        • 局部
          • GatewayFilter:局部过滤器
    • 限流
      • 网关,令牌桶算法

如果是 Nginx,则是:Nginx,漏桶算法

分布式事务 🚩

image.png

  • 2PC(两阶段提交)
  • 三阶段提交(3PC)
  • 分布式事务框架
    • Seata
    • TCC(Try-Confirm-Cancel)
  • 消息队列【 事务消息(half 半消息机制)】 本地消息表(操作记录,消息处理)🚩

Seata事务管理中有三个重要的角色:

  • TC(Transaction Coordinator)-事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM(Transaction Manager)-事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM(Resource Manager)-资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

image.png


RocketMQ

依赖半消息,实现的分布式消息事务,通过 二次确认以及消息回查机制实现。

image.png

K8s

image.png

Jenkins

卷肯思

Jenkins 是一个开源的自动化服务器,主要用于持续集成(Continuous Integration, CI)和持续部署(Continuous Deployment, CD)的自动化过程。由于其强大的插件生态系统,Jenkins 可以轻松地与各种开发、测试和部署工具集成,从而构建一个高度灵活和可定制的自动化流水线。

ES

Netty

https://www.yuque.com/snailclimb/mf2z3k/wlr1b0

组件

Docket

常见命令
  • docker ps
  • docker ps -a
  • docker stop
  • docker restart
  • docker images 列出镜像
  • docker-compose up --build
  • docker-compose up -d
  • docker-compose down

Jenkins

自动部署/自动构建工具,持续集成和持续部署(CI/CD)流程

配置任务,以及构建触发器

xxl-job

界面配置定时频率和任务

使用 @xxljob 注解

import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.stereotype.Component;

@Component
public class DemoJob {

    @XxlJob("demoJobHandler")
    public void execute() throws Exception {
        // 任务逻辑
        System.out.println("XXL-JOB, Hello World.");
    }
}

任务执行失败处理:设置故障转移 + 失败重试,查看日志分析 → 邮件告警

zookeeper

Dubbo

为什么要使用 RPC

RPC通信协议通常比HTTP更加轻量,能够减少网络传输的开销。

计算机网络

TCP 和 UDP 的区别

  • TCP:面向连接,三次握手,面向字节流的
  • UDP:无连接,面向报文

三次握手

  • 第一次握手:SYN;一般是客户端发送确认消息给服务端
  • 第二次握手:SYN-ACK;服务端响应这个确认
  • 第三次握手:ACK;客户端进行响应服务器,表示进行开始连接

场景题

MVC 与 DDD

MVC

  • 模型(Model):代表应用程序的数据结构,通常包含数据的处理逻辑。
  • 视图(View):展示数据(即模型)的用户界面,不包含业务逻辑。
  • 控制器(Controller):接收用户的输入并调用模型和视图来完成用户的请求

DDD

  • 基于领域模型(Domain Model)
  • 实体(Entity)、值对象(Value Object)、聚合(Aggregate)、领域事件(Domain Event)、领域服务(Domain Service)等

性能优化

31a84718a5ffd47cc8e2f691ad3bd9e.png

授权认证

image.png

加密算法

image.png

打印操作

这个操作还能从设计模式聊到关于接口优化这里,

  • 1、首先谈一下关于自己对于设计模式的认知,首先你先说一下概念,然后说一下具体使用(将对方当作不了这方面的人,然后你给他普及一下这方面的内容)
  • 2、谈接口优化,先从大的方面去出发,一方面是结构;一方面是接口业务(异步),一方面是SQL,还有一方面是架构(这里引入了 Redis 、异步、多线程、线程池、设计模式、接口优化),建议都讲一下。准备一下相关语言。
    • 接口优化
      • 1、业务异步
      • 2、SQL优化
      • 3、DB:分库分表

image.png

自我介绍

自我介绍一定要尽可能结合对方的一个需求,然后真实去讲解你做过的一些东西,尽可能保持热情和对面试官的尊重,表现自己对于工作的热爱和喜欢,以及自己在上一个工作中是做了什么工作内容,然后给对方陈述一遍(需要提前准备和练习)

介绍 3 分钟左右的时间,将你最优秀的一面体现出来。

  • 面试官,你好,我叫 WJF;2020年软件工程专业毕业;毕业之后,我一直做的 Java 方向的一个工作;至今为止是已经工作3年多的一个时间。
  • 我最近的一份工作是在广州的一个公司,该公司做的是汽车制造、物流、供应链相关的业务,我所在的团队主要是负责整车制造 MES 的其中一个项目组。
  • 我个人还参与到一些项目的开发,最开始的时候,我是加入到生产执行的MES的一些维护变更工作,在这个项目中,我对于整体一些业务链从了解到熟悉,在此之后,我参与到开发了无纸化系统,这个系统主要做的是一些像质量录入、生产执行的操作;同时还有一些报表工作我这边也参与到了其中,技术栈使用的是 SpringBoot 的那一套,在此期间,我主要负责和产品经理需求沟通并进行 部 分 后 端 接 口 开 发工作 , 开 发 了 车 间 的 装 配 关 键 件 绑 定 、 车 辆 指 示 、 不 良 录 入 等 功 能。
  • 我最近的项目是做一个多工厂MES系统管理系统,这个项目主 要 覆 盖 了 汽 车 制 造 工 艺 中 的 计 划 、 生 产 、 打 印 、 法 规、返修等 模 块 , 我们这个项目主要针对的汽车订单从订单接口下发处理到我们系统之后,进行一个计划排产,经过排序、VIN 生成、展开、发布等操作,将关键信息下发下游物流等系统;到达链路上的一个闭环。我个人主要是负责的是法规和打印模块。
  • 这个项目主要采用是 SpringCloud 微服务 Alibaba 这一套,使用了像 Nacos、OpenFeign、Gateway、RocketMQ、Redis 这些常用组件;同时项目是通过 容器 的方式进行部署的。
  • 我看贵公司这个岗位的一个招聘是要求是常见的组件和框架使用以及对 Java语言 熟悉,我个人认知技术匹配度是较高的,希望有机会能够加入到贵公司一起工作,同时我个人对于 IOT 是比较感兴趣的,希望能和您一起共事。我的介绍完毕,请问,面试官,你有什么想问的吗。

建议准备下

  • 微信公众号
  • AI (Chatgpt、ChatGLM、星火大模型)

数据同步

可以讲一下关于某个接口数据进行同步的一个操作

  • 通过 ID 或者时间戳

系统监控

SpringBoot Actuator + Prometheus + Grafana

也可以说一下自己实际项目中的解决方案:InfluxDB + grafana 的方式,这里会引入一下时序数据库的内容。

Linux

常用命令

ls
cp
mv
cat
grep
top
ping
su

#查找所有与nginx相关的正在运行的进程:
ps aux | grep nginx
#查找日志文件中的特定条目
cat /var/log/application.log | grep 'ERROR'

对于 jvm 文件

接口幂等

  • 前端:按钮变灰
  • 后端:乐观锁、悲观锁、分布式锁,业务逻辑结合处理

接口安全

SpringBoot微服务的https配置方法

image.png

  • 如果需要对文本或者数据安全保障,进行加密传输
    • MD5

软实力

上手学习新技术

我在上手学习新技术的时候主要氛围3个步骤;

  1. 索引相关技术资料,形成知识文档。归档为;视频、文字、官网、案例、教程、环境等方面
  2. 基于归档的资料先对整个知识体系进行全貌的了解,这个时候官网资料、视频介绍会比较重要
  3. 按照我要完成的题目或解决的问题,找到对应的案例进行测试验证。这一步我了解过也叫技术调研。如果说我的学习需要是成体系的,那么会大量的完成一些案例,而不是对某个点进行简单的处理。
  4. 场景使用,当我对资料、文档、案例、流程等掌握清楚后,则开始进行场景验证使用,同时基于我使用的一些内容和产生的问题到解决的方案,做相关的笔记记录,形成资料文档,方便查阅。

代码规范

  • 代码整洁度,代码可读性,代码结构统一,见名知意
  • 代码注释
  • 异常处理

代码结构示例

servicex                 // 项目名
    |- admin-ui          // 管理服务前端代码(一般将UI和SERVICE放到一个工程中,便于管理)
    |- servicex-auth     // 模块1
    |- servicex-common   // 模块2
    |- servicex-gateway  // 模块3
    |- servicex-system   // 模块4
        |- src
            |- main                  // 业务逻辑
                |- assembly          // 基于maven assembly插件的服务化打包方案
                    |- bin           // 模块脚本(启动、停止、重启)
                    |- sbin          // 管理员角色使用的脚本(环境检查、系统检测等等)
                    |- assembly.xml  // 配置文件
                |- java              // 源码
                    |- com
                        |- hadoopx
                            |- servicex
                                |- system
                                    |- annotation     // 注解
                                    |- aspect         // 面向切面编程
                                    |- config         // 配置文件POJO
                                    |- filter         // 过滤器
                                    |- constant       // 存放常量
                                    |- utils          // 工具
                                    |- exception      // 异常
                                    |- controller     // 控制层(将请求通过URL匹配,分配到不同的接收器/方法进行处理,然后返回结果)
                                    |- service        // 服务层接口
                                        |- impl       // 服务层实现
                                    |- mapper/repository // 数据访问层,与数据库交互为service提供接口
                                    |- entity/domain     // 实体对象
                                        |- dto // 持久层需要的实体对象(用于服务层与持久层之间的数据传输对象)
                                        |- vo // 视图层需要的实体对象(用于服务层与视图层之间的数据传输对象)
                                    |- *Application.java  // 入口启动类
                |- resources         // 资源
                    |- static        // 静态资源(html、css、js、图片等)
                    |- templates     // 视图模板(jsp、thymeleaf等)
                    |- mapper        // 存放数据访问层对应的XML配置
                        |- *Mapper.xml
                        |- ...
                    |- application.yml        // 公共配置
                    |- application-dev.yml    // 开发环境配置
                    |- application-prod.yml   // 生产环境配置
                    |- banner.txt    
                    |- logback.xml            // 日志配置
            |- test                  // 测试源码
               |- java               
                    |- com
                        |- hadoopx
                            |- servicex
                                |- system
                                    |- 根据具体情况按源码目录结构存放编写的测试用例
        |- target     // 编译打包输出目录(自动生成,不需要创建)
        |- pom.xml    // 该模块的POM文件
    |- sql            // 项目需要的SQL脚本
    |- doc            // 精简版的开发、运维手册
    |- .gitignore     // 哪些文件不用传到版本管控工具中
    |- pom.xml        // 工程总POM文件
    |- README.md      // 注意事项
External Libraries    // 相关JAR包依赖

客诉项目问题

  • JVM问题
    • 发现问题
    • 定位问题
      • 通过运维平台 GC 频率,GC 占用大小,GC 释放空间
    • 什么情况下会导致 Full GC 的现象
      • 大对象
      • Edgn 区
    • Full GC 回收哪些区域
      • 堆、元空间区
    • 除了 Full GC 还有哪些 GC
    • 哪些区域会有 GC 发送
    • JDK 1.8 默认的垃圾回收器是哪个
    • 了解哪些垃圾回收算法
    • 垃圾回收器
      • G1
        • 增量并发,堆
      • G1 的好处
        • STW 消耗不会太长
  • Redis 怎么定位
    • 历史遗留问题解决
  • 详情
    • 1、主键查询
  • 分布式事务

简历项目内容

使用账号密码访问流程(JWT 方式)

image.png

  • SpringSecurity:matches 方法(提取盐操作,生成哈希值,比较哈希值)

使用 websocket 进行前后端 http 长连接操作

image.png

socket 监听,实时推送站点车辆信息到前端界面

image.png

这里可以延申说一下,就是还有解决方案,可以通过 nio 的多路复用的一种实现方式去解决。

模板文件(word)

一般生成模板文件,在 mes 生产是通过 ireport 模板的方式来进行的,

看了一下 word 的方式,也是类似,需要有一个 word 模板文件,然后进行填充内容,生成 文件操作。

一种方式也可以是引入 spring-boot-starter-freemarker 依赖进行操作(模板引擎);

预览的话,可以转换为图片或者 pdf 文件,然后在前端进行预览操作。

模板文件(ireport)

说一下大概的业务就行

模板文件(pdf)

  • 可以说直接通过原项目的工具类进行生成对应的操作
  • 预览操作
    • 1、使用浏览器内置的阅读器 :使用<iframe>标签
    • 2、使用PDF.js
    • 3、使用 第三方服务

Excel 导入导出优化

导入优化

  • 使用更快的 Excel 读取框架(推荐使用阿里 EasyExcel)。
  • 对于需要与数据库交互的校验、按照业务逻辑适当的使用缓存。用空间换时间。
  • 使用 values( ),( ),( ) 拼接长 SQL 一次插入多行数据。
  • 使用多线程插入数据,利用掉网络IO等待时间(推荐使用并行流,简单易用)。
  • 避免在循环中打印无用的日志。

导出优化

  • 1、依然是使用 esayexcel 的开源框架,内部进行了很大的一个优化,比起 Poi 的方式来说;
  • 2、基本处理方式
    • 一个SHEET一次查询导出
    • 数据量适中(100W以内):一个SHEET分批查询导出
    • 数据里很大(几百万都行):多个SHEET分批查询导出

编程内容

字符串分割(不使用分割函数)

一个字符串,通过分割符进行分割操作,得到分割后的字符数组:

f0f7236e98a628e969b8574df536bf2.png

交替打印操作

两个线程,实现对于从 0 到 200 的交互打印:

637467f52b3dc854fb1698cbd29e4c5.png

常见基础概念

PM/PL

  1. PM (Project Manager): 项目经理。这是一个关键的管理职位,负责规划、执行和监控项目的进展。项目经理需要协调团队成员之间的工作,管理资源,确保项目按时、按预算和按质量要求完成。PM 负责项目的整体策略,风险管理,以及与利益相关者沟通。

  2. TL (Team Leader 或 Tech Lead): 团队领导或技术领导。TL 通常负责领导一个小团队或工作组,专注于特定的任务或项目的一部分。作为团队领导,他们可能负责团队的日常管理、工作分配、成员培训和绩效评估。而作为技术领导,TL 则更侧重于技术方面的领导,负责技术决策、指导团队成员的技术工作,并解决技术难题。