昆山java后端小厂面经

比我第一次面试还要崩溃,10分钟结束,面完红温,感觉我是如此渺小。

项目相关

  1. 这两个项目是你自己做的?创意从哪里来?
  2. 这两个项目是不是用AI辅助做的?是你自己设计,AI负责实现吗?
  3. 讲一下你做的最出彩的事情,完整的技术实现路径。
  4. 这个秒杀功能是不是看视频照着实现的?
  5. 有没有脱离视频教程,自己独立完成的练习/实践?

Redis

  1. HyperLogLog是干什么的?

    嗯,支持百万级的UV,可以做访问统计。

  2. 从哪里了解到HyperLogLog?它的底层实现原理是什么?

    在学习 Redis 的时候,了解到的底层实现原理是有一个复杂的函数计算吧,跟哈希有关

    HyperLogLog(简称 HLL)是基数估算的概率型数据结构,用来统计集合中不重复元素的个数,不存储原始数据,只存少量统计寄存器。

  3. 了解Redis分布式锁吗?讲原理、怎么配置。

    了解 Redis 分布式锁,是为了解决传统锁在分布式系统下不能在每个微服务之间(多个进程之间)生效的问题

    实现的话主要有两种: 一种就是传统的Redis+setnx+TTL+Lua脚本加上随机uuid。 另一种就是用分装好的Redisson框架来实现先说传统的Redis。嗯,他用的是setnx这个命令setnx是不存在才设置它这个命令具有原子性操作,可以让这个判断锁和锁和获取锁之间保证原子性,不被其他客户端抢占,TTL的话是为了给锁设置自动过期时间防止死锁,uuid防止锁误删,Lua脚本用于释放锁时的原子性,防止业务时间大于自动删除时间,导致自动删锁时业务还没执行完,再手动删锁时删掉别的线程持有锁

    ​

    Redisson的话,它可以实现了一个重试机制和一个看门狗机制。Redisson的try lock里面有3个参数: 一个是wait time, 一个是lease time, 一个是时间单位。 Wait time就是线程等待这个锁的时间,在这个时间内可以进行重试,如果超出这个时间就不再竞争。 Lease time就是每个线程持有锁的时间,如果不传入参数的话,它会有一个看门狗的自动续期机制,默认是30秒如果30秒到了的时候。业务还没有执行完,会自动给他设置回30s

  4. 分布式锁的随机TTL了解吗?

    了解,设置随机 TTL 主要是为了解决缓存雪崩的问题,防止大量的缓存在同一时间过期,导致大量请求都打到数据库,给数据库造成压力

  5. Redis缓存过期/内存淘汰策略有哪些?

    Redis 的过期策略有两种:

    1. 惰性删除:会在你下一次访问该缓存的时候,才会真正删除。
    2. 定时删除:设置定时时间,然后进行删除。

    Redis 的删除策略有两种:

    1. 惰性删除:
      当 key 过期时,它不会立即删除,而是在下一次访问这个 key 的时候对它进行判断,看它是否过期。如果过期的话再删除它,如果没有过期的话就返回正常数据。

    2. 定期删除:
      这是 Redis 的后台定时任务,默认是每 10 秒执行一次。它会从过期字典中随机抽取 20 个 key,删除里面过期的 key。如果过期的 key 占比超过 25%,会继续抽样。

    Redis 的过期删除策略主要有惰性删除和定时删除。

    1. 惰性删除:
      是指当设置了 TTL 的 Key 过期时,Redis 不会立即删除,而是等到下一次访问这个 Key 时,再对它进行判断。如果未过期,就返回原来的值;如果已过期,就把值删掉并返回空值。
      这种策略会出现一种情况:如果有大量的 Key 过期了,但没有被下一次访问,就会导致这些过期的 Key 残留在内存里。

    2. 定时删除:
      为了防止大量过期的 Key 堆积,Redis 还有一个定时删除策略。它会定期在 Redis 中抽检一部分 Key,判断是否过期,如果过期就进行删除。防止大量的过期key堆积

    3. 为什么不给所有的 Key 都定时删除,而是要抽检?就是因为如果给每一个 Key 都维护的话,会浪费大量的 CPU 性能。Redis 是高性能数据库,这会影响它的性能

Java基础 & 数据结构

  1. 数据结构和算法掌握得怎么样?

  2. 了解切面(AOP)吗?

    AOP 是面向切面编程,就是把重复的方法抽取出来作为一个切面在不改变源代码的情况下,横向添加新的功能。比如说登录校验呀,还有日志之类的可以减少重复代码,降低代码之间的耦合度。实现 AOP 主要是通过动态代理的方式,动态代理又有 JDK 动态代理和 CGLib 动态代理两种。JDK动态代理,它依赖接口,通过创建一个实现了原来接口的代理对象,在代理对象中对方法进行增强。

    CGLIB 是给这个类创建一个子类对象,在子类对象中进行方法增强。

  3. AOP有几种通知类型?环形切面的三个方法是什么?

  4. 了解Java泛型吗?泛型中T、?、extends是什么?

Docker & Linux运维

  1. Docker有哪些常用指令?

反问环节

  1. 你有什么想问我的?

浩鲸科技Java面经

  1. Synchronized锁升级

    3个 nice 的锁升级主要是偏向锁、轻量级锁和重量级锁。

    先说轻量级锁:Java 对象的对象头里面有个 Markword,存放的是锁的状态。当用 synchronized 给这个对象加锁的时候,线程的栈帧中会创建出一个 Lock Record。Local Record 会与对象头中的 Mark Word 进行交换:

    1. 把 Mark Word 的指针指向 Local Record。
    2. 在 Local Record 中复制一份 Mark Word 原来的内容。
    3. 通过 CAS 操作,预期值是复制的 Mark Word,修改值是指向 Local Record 的指针,将对象的 Mark Word 指向 Local Record。
    4. Local Record 的后两位,由 01 的无锁状态变为 00 的轻量级锁状态。

    如果 CAS 失败,就说明发生了锁竞争。

    1. 锁竞争处理流程:
      (a) 判断锁持有者:通过向上搜索 Java 栈,查看是否有 LockRecord,判断当前锁是否由当前线程持有。
      (b) 递归加锁:如果是当前线程持有,就将可重入次数加 1。
      (c) 自旋与锁膨胀:如果不是当前线程持有,会先进行自旋。如果自旋了很多次还是没有获得到锁,就会发生锁膨胀,从操作系统申请 Monitor 锁,将 Monitor 的 owner 改为当前线程,并将竞争锁的线程放入等待队列。
      (d) 锁释放与唤醒:前面的线程执行完之后,将 owner 改为 null,再由等待队列里的线程来进行竞争。

    2. 轻量级锁释放:
      释放轻量级锁时,也是通过 CAS 进行一个反操作,目标是将原来的 Mark Word 里的锁标志由 01 改为 00。

    3. 偏向锁优化:
      如果是同一个线程多次申请这个锁,会将这个锁变为偏向锁。这是为了防止同一个线程多次申请锁而造成不必要的消耗。

    synchronized 锁升级顺序:偏向锁、轻量级锁、重量级锁,单向升级不可降级。 轻量级锁加锁时,线程栈帧创建 LockRecord 保存对象原始 MarkWord 副本;通过 CAS 将对象 MarkWord 替换成指向 LockRecord 的指针,标记位变为 00。CAS 失败发生竞争,如果是重入则新增 LockRecord;不是本线程则自适应自旋,自旋失败膨胀重量级锁,创建 ObjectMonitor,竞争线程进入 EntryList 阻塞队列。释放锁通过 CAS 把备份 MarkWord 恢复回对象头。 偏向锁针对单线程反复获取锁的场景,MarkWord 记录线程 ID,省去 CAS 开销;出现其他线程竞争,触发撤销,升级轻量级锁。 重量级锁依靠 ObjectMonitor,线程会内核阻塞,开销较大。

  2. Synchronized与ReentrantLock的区别

    Synchronized是非公平锁,Retry lock可以是公平锁,也可以是非公平锁,synchronized不支持响应中断,而retry lock支持响应中断,synchronized不支持多个条件,它必须和wait和notify notify方法一起使用,而retry lock可以和多个condition来完成复杂的线程操作,比如分组唤醒synchronized的底层是基于互斥锁monitor retry lock底层是基于qos框架,PY的加锁会自动加锁,look是手动加速,日产look可以提供更细的力度,性能比second的更高

  3. 线程池的工作流程

    先看核心线程数:

    1. 如果核心线程没满,就用核心线程来执行。

    2. 如果核心线程满了,就去看最大线程数:
      (a) 如果最大线程数范围内的线程没满,就创建工作线程来执行。
      (b) 如果最大线程数也满了,就去看等待队列有没有满:
      (i) 如果等待队列没满,就先将线程放到等待队列中。
      (ii) 如果等待队列满了,就执行拒绝策略。
      顺序错了!!

      核心 -> 等待队列 -> 最大线程数 ->拒绝策略

      线程池提交任务的流程如下:

      1. 判断核心线程数:如果核心线程没有满,就创建核心线程执行任务。
      2. 进入阻塞队列:如果核心线程满了,将任务加入阻塞队列。
      3. 判断最大线程数:如果阻塞队列满了,判断是否达到了最大线程数。如果没有达到,就创建非核心线程执行任务。
      4. 执行拒绝策略:如果队列满了,并且已经达到最大线程数,就执行拒绝策略。
  4. 线程池的拒绝策略

    线程池的拒绝策略:

    1. CallerRunsPolicy:用当前线程去执行任务。

    2. AbortPolicy:直接抛出线程池满的异常。

    3. 静默处理:不做反应,静默处理线程池满的异常。

    4. 停掉最老的线程,然后执行新的任务。

    5. AbortPolicy 默认,直接抛出拒绝异常;

    6. CallerRunsPolicy,让提交任务的调用线程运行任务;

    7. DiscardPolicy 静默丢弃新任务,不抛异常;

    8. DiscardOldestPolicy 丢弃阻塞队列中最老的任务,再尝试执行新任务。

  5. MySQL的ACID

    MySQL 的 ACID 分别是原子性、一致性、隔离性、持久性:

    1. 原子性:要保证操作之间不能被其他操作插入。

    2. 一致性:要保证修改前后的总量是不变的。比如说库存出库,不管怎样,出库量加剩余量都要是一定的。

    3. 隔离性:多个数据表之间相互隔离,互不影响。

    4. 持久性:数据修改完之后要持久化保存,确定是修改了的。

    ACID 分别是原子性、一致性、隔离性、持久性。

    原子性:事务里面的操作,要么全部成功,要么全部回滚失败,依靠 undo log 实现。

    一致性:事务执行前后业务数据符合约束,是最终目标,由原子性、隔离性、持久性共同保证。

    隔离性:多个并发事务之间相互隔离,避免并发带来脏读、不可重复读、幻读问题,由锁和 MVCC 实现。

    持久性:事务一旦提交,修改永久生效,即使宕机数据也不会丢失,依靠 redo log 实现。

  6. 原子性是什么、如何实现

  7. 乐观锁的优缺点、CAS

  8. MySQL的事务分级

    MySQL的事务分级分为读未提交、读已提交、可重复读和串行化

  9. 可重复读解决了哪些问题

    可重复读解决了脏读和不可重复读的问题,并且可以通过 MVCC 和间隙锁来实现解决幻读的问题

  10. JVM中对象何时被回收?

    JVM 使用可达性分析算法判断对象是否可以回收。以 GC Roots 为起点遍历引用链,如果对象没有 GC Roots 的强引用链,则该对象具备回收条件。

  11. 不可达后会被立刻回收吗?

    具备回收条件不等于马上回收,需要等待 GC 触发(YoungGC/FullGC)。对象可以通过 finalize 方法完成一次自救复活,但不推荐使用。

    不同引用类型回收时机不同:强引用不会回收;软引用内存不足回收;弱引用只要 GC 就回收;虚引用只用于回收通知。调用 System.gc 只是提醒,不能强制回收。

  12. 类加载过程有哪几个阶段?

    5

  13. 双亲委派模型原理是什么?

    双亲委派机制是指在类加载时,子类加载器如果收到类加载请求,会先将请求传递给父类加载器。如果父类加载器无法实现,再由子类加载器来进行加载。

    例如,应用程序类加载器会先将请求传给扩展类加载器,扩展类加载器再传给启动类加载器。首先看启动类加载器能否实现,如果不能,再由扩展类加载器实现;若仍不能,最后再由应用程序类加载器实现。

    双亲委派机制的好处主要有两点:

    1. 避免核心类被修改:核心类都存在启动类加载器中。该机制可以防止在应用程序类加载器中重写启动类的方法,从而避免核心方法被篡改(例如,可以避免重写 String 类)。
    2. 避免重复加载:让父类加载后,子类在调用时就可以复用父类加载的类,无需自己多次重新加载。
  14. MyBatis中绑定SQL入参有哪几种方式?

  15. 如何避免MyBatis结果字段与实体属性不一致?

    驼峰

    sqlas别名

    resultmap

    mybatisplus@TableField

靖安科技Java全栈开发秋招一面面经

9.14投递
9.16笔试
9.17一面

一面(不到1小时):

1、自我介绍
2、你现在是处于离职状态吗
3、上家公司离职原因
4、HashMap底层原理

链表+数组+红黑树

5、为什么jdk1.8之后数据结构加了红黑树

链表过长时查找效率低

6、HashMap是线程安全的吗,如果多线程同时put一个数据的话会发生什么

不是线程安全的,如果多线程同时 put 一个数据的话,在 JDK 1.7 之前由于它是头插法,可能会造成死链;在 1.8 之后改为尾插法,可能会造成数据覆盖

7、HashMap为什么不是线程安全的

HashMap在多线程操作的情况下,由于它内部没有加锁,所以不是线程安全的

8、为什么ConcurrentHashMap可以保证线程安全

Current hash map是在hash map的基础上加锁,JDK1.7的时候,使用的是分段锁,就是将整个哈希表分为多个分段,然后对分段进行加锁,每个分段里的节点就对应这个分段的锁,1.8之后,使用的是的加上CS来进行的加锁,然后它是对哈希表的每个统节点都进行了加锁,使这个锁的力度更小,主要原因就是JVM虚拟机对S的锁进行了进一步的优化

1
ConcurrentHashMap 在 HashMap 基础上,依靠锁与 CAS 机制保证线程安全。 JDK1.7 采用 Segment 分段锁,将哈希表划分为多个分段,对每个分段加锁,锁作用于该分段内全部节点,不同分段之间可以并发访问。 JDK1.8 废弃 Segment 分段锁,采用**CAS + synchronized**,对哈希桶的头节点加锁,仅锁定当前操作的桶,锁粒度变得更小。得益于 JVM 对 synchronized 锁(偏向锁、轻量级锁)的优化,加锁开销较低。同时使用 LongAdder 统计元素数量,进一步提升并发性能。

9、说一下什么是CAS

CAS就是compare and swap, 属于乐观锁。它主要有三个值嘛,一个值是原来的值,一个只是预期值,一个是只是修改后的值。通过比较原来的值和预期值是否相同,如果相同的话,就把原来的值改为修改后的值。然后他可能会出现三个问题吧,第一个问题就是ABA问题,就是假如将这个比较这个原来值的时候,在比较的时候它还是A,然后在修改之前它也还是A,但是在这个比较和修改过程中,可能会被别的线程修改为B,但是他最终还是A。这个可能不会影响结果,但是也无法捕捉到中间这个过程的变化,然后其次就是有CAS,它是乐观锁,它就会不断的进行自旋,然后自权就可能会造成一些资源浪费,性能性能消耗。然后第三个问题就是CAS它只能比较单个变量的值,如果有多个变量,他就不能实现了

1
2
3
4
5
6
CAS 即 Compare And Swap,比较并交换,属于乐观锁。包含内存实际值、预期值、修改后的新值。比较内存实际值与预期值,如果相等,则把内存值更新为新值;不相等则更新失败。

CAS 存在的问题:
① **ABA 问题**:CAS 只校验当前值,无法感知中间过程。例如变量从 A 改为 B,又改回 A,CAS 无法识别发生过修改。可通过增加版本号`AtomicStampedReference`解决。
② **自旋带来 CPU 开销**:自旋 CAS 不断重试,竞争激烈时持续空循环,消耗 CPU。
③ **仅支持单个变量原子更新**,无法实现多个变量的原子操作。

10、synchronized和Reentrantlock区别

Second的锁是非公平锁,而return lock锁它可以是公平锁也可以是非公平锁。Second的它通过monitor互斥锁来实现,而return lock它是通过aqs来实现,aqs它就是根据一个volatile修饰的state和一个非否双向队列来维护的,通过这个state来记录和重入次数,通过链表来实现等待队列,然后synchronized锁是隐式锁,在线程进入虚拟机后自动加锁。而Return lock它是显示锁,必须要手动调用lock方法来进行加锁。Second的它不支持绑定多个条件啊,必须用wait notify notify all方法啊,只能支持单个条件的啊,加锁和唤醒,而return lock可以和condition条件那个组合,然后有多种唤醒情况,可以实现分组唤醒,return lock的锁粒度也更小,性能开销也更小。最后还有second的锁,它不支持中断响应,而return look它有个有个加速的可以支持终端响应,就是线程在等待锁的期间收到了中断命令,可以退出锁的等待

11、Reentrantlock里有公平锁和非公平锁,他们区别是什么

公平锁和非公平锁区别就是在等待队列中竞争锁。公平锁它就是可以按照一定的规则来进行获取锁,比如说先到先得。非公平锁就是每一次锁释放之后,在等待队列里的所有线程都要去随机竞争,锁就是非公平的

1
2
3
**公平锁**:遵循先到先得。线程按进入等待队列的先后顺序获取锁,锁释放后优先唤醒队列头部等待的线程,不允许插队。

**非公平锁**:允许线程插队。锁释放时,**新到来的线程可以直接尝试抢占锁,不必进入等待队列排队**;只有抢锁失败,才会进入等待队列。队列内部唤醒依旧按顺序,不是随机竞争。非公平锁可能造成队列里老线程**饥饿**,一直抢不到锁。减少线程阻塞、切换,**吞吐量更高**

12、线程池的主要参数

线程池的主要参数主要有7个:
第一是核心线程数
第二是最大线程数
第三是空闲线程存活时间
第四是嗯时间单位
第五是工作队列
第六是线程创建工厂
第七是拒绝策略

13、提交任务时,假如核心线程数为10,最大线程数100,队列1000,线程变化是什么样的,假如超过最大线程数,有哪些拒绝策略可以选择

提交任务时,先看当前核心线程有没有达到核心线程数。如果没有达到的话,就创建核心线程来执行任务。如果达到的话,就将任务放到等待队列中。如果等待队列满了的话,就会看这个核心线程和非核心线程有没有达到最大线程数。如果没有达到的话,就创建非核心线程来执行任务。如果满了的话,就会执行拒绝策略

14、如果队列没有限制大小会出现什么问题

如果队列没有限制大小的话,就会让这个任务无限地排在队列中,可能会造成内存溢出。然后同时会让这个核心线程一直运,一直这个执行全部的任务都交给核心线程,给这个核心线程造成压力,然后利用率也低

1
2
3
4
1. **容易发生 OOM 内存溢出**:大量任务不断提交,任务会持续存入队列,队列无限膨胀,占用大量堆内存,最终内存溢出。
2. **最大线程数失效**:队列永远不会满,线程池不会创建非核心线程,`maximumPoolSize`参数不起作用。
3. **并发能力受限**:所有任务只能由核心线程执行,无法通过增加非核心线程提升处理能力,任务大量堆积,响应变慢。
4. **拒绝策略永远不会触发**,即使配置了拒绝策略也不会执行。

15、介绍一下ThreadLocal是干什么用的

Trade local是给每个线程创建一个独立的私有站,在trade local里的存储的数据只有当前线程私有,不会共享给其他线程

1
2
ThreadLocal 叫做线程本地变量,用于实现**线程间的数据隔离**。
它会为每个使用它的线程创建一份独立的副本,不同线程操作的是线程自己的私有数据,各个线程之间数据互不干扰,实现线程私有,避免多线程共享变量带来的并发问题。

16、ThreadLocal底层原理

Threadlocal底层是一个threadlocalmap,存放entry对象,key也就是创建的threadlocal的对象的弱引用。Value就是我们存放数据的强引用。存放弱引用是为了防止,那个当把trade lock trade lock复制为null时,这个如果是强引用的话,它就不会被GC,如果是弱引用的话,当这个trade lock为no时会被JC掉,可以,减缓内存泄露

17、在多线程里加锁的方式有哪些

可以加synchronized锁,reentrantlock锁,还有读写锁,还有乐观锁、悲观锁以及自旋锁

读写锁是Linux接口中的一个read write lock锁,它只支持一个线程写入,支持多个线程读取。说读多写少的情况,可以避免多线程同时写入造成的并发问题

乐观锁就是通过版本号来维持多个线程,在修改数据的时候,通过这个版本号,然后修改不同版本的数据,也可以确认就是线程修改的是它要修改的那个版本的那个数据,就不会因为别的线程修改数据而导致并发问题

18、如果用Redis实现一个分布式锁你会怎么实现

用Redis实现分布式锁,主要用到Redis的setnx. Setnx就是不存在时才创建,就可以确保这个多个线程在修改数据修改同一个数据的时候,可以用setnx保证只有一个线程可以修改数据,然后同时要给这个Redis设置TTL时间防止死锁。然后要用uuid防止那个业务防止这个误删别的线程的锁,就是当这个业务时间大于TTL时间时,可能业务还没有执行完,但是就TTL了,然后锁被别的线程持有,然后当业务执行完的时候,它会删掉别的线程持有的锁。然后在这个判断uuid是否为当前线程时,当前线程持有这个锁时,为了让这个判断和这个删除是一个原子操作,还可以用到Lua脚本

19、Redission底层原理以及怎么续期的

Redisson的底层就是封装了Redis的这个setnx+Lua脚本,它传入3个它的try lock方法要传入3个参数,一个是wait time, 一个list time, 一个单位时间。时间单位如果不是啊,它这个wait time就是当前线程等待的时间啊,就是竞争所等待的时间,如果超过这个时间,在这个时间范围内它就会竞争不到锁,就会进行重试,超过这个时间它就放弃,然后list time就是它这个设置的这个持有锁的有效期,如果设置值的话就是30秒,它就会在30秒后释放,如果不设置值的话,它就会其中有有一个看门狗机制,就是假如它默认是30秒,然后到了30秒之后,这个看门狗机制会检查当前线程的任这个是否还持有这个锁。如果还持有的话,就会给这个线程进行续期,续期到这个30秒

1
2
3
4
5
6
7
8
9
10
11
默认锁 30s,看门狗**每 10 秒(30/3)就主动检查一次**,提前续期,不是等到快过期才处理。
Redisson 的看门狗,是**获取锁成功后启动的一个后台异步定时任务**。

>
> 前提:调用`tryLock`**没有手动指定 leaseTime**,看门狗才会启动;一旦手动设置 leaseTime,看门狗直接不生效。

1. 默认锁有效期是 30 秒。看门狗定时任务**每 10 秒(30/3)执行一次**。
2. 任务发送一段 Lua 脚本到 Redis,原子校验:**当前锁是否还属于当前业务线程**。
3. 如果业务还在运行、锁仍然是当前线程持有 → 执行续期,把锁过期时间重新重置回 30 秒。
4. 如果业务执行完毕,手动调用`unlock()`释放锁,看门狗定时任务就会被取消,不再续期。
5. 极端场景:业务卡死,并且看门狗线程本身宕机,不再续期。锁到 30 秒自动过期释放,避免死锁。

20、Redis基础数据类型和使用场景

string 分布式锁

set 无序不可重复 运算 共同好友,去重,黑名单

list 有序可重复 消息队列

hash 购物车

zset 排行榜,排序

21、介绍下缓存穿透、缓存击穿、缓存雪崩以及他们的解决方案

缓存穿透就是每次都访问数据库和Redis中都不存在数据,这样它每次访问的时候Redis中不存在,然后就直接打到数据库,然后数据库中也不存在啊,就会造成数据库压力。解决方法有两个,第一个就是给控制也设置缓存,这样就算那个他第二次查询这个不存在的数据,就会在Redis这里给给它拦截,就不会再打到MYSQL里。然后但是它不能解决这个随机穿透的问题。要解决随机穿透的问题可以用布隆过滤器。布隆过滤器它底层是一个位图吧,就是通过多个哈希函数,然后计算出这个桶的下标,然后在过滤的时候它就会再次通过这个相同的哈希函数来进行计算,如果它全部唯一的话,说明它可能存在,如果有一个不为一,说明它肯定不存在,因为它可能会发生哈气冲突的原因,导致导致这个算出来的同下标相同

缓存击穿就是,当这个弱点数据在TTL时间过后,那个过期之后它会重建嘛,就是在这个重建的过程中可能会有大量的数据来进行访问,然后这个时候它就可能会全部都打到数据库,然后对数据库造成压力啊,为了解决这个就可以用到分布式锁嘛,就是在确保,这个只有一个线程对,就是在重建的这个过程中只能用一个线程访问MYSQL,然后其他线程等待,等到重建完Redis之后,再来访问

缓存雪崩就是大量的这个K设置了相同的过期时间,然后导致这个相同的一段时间内大量的这个K都过期了,然后同时这个访问就都打到了,这个MYSQL会对它造成较大的压力。这个的解决方法也比较简单,就把这些K的过期时间设置不一样嘛,可以在过期时间中加上随机值

22、Redis集群部署的时候,集群模式有哪些

Redis集群分为主从复制、哨兵模式和Redis cluster集群。主从复制是一主多从,一个主机用来写数据,多个从机用来读数据。但是如果主机宕了宕机了的话,它无法,它需要手动那个切换,然后哨兵模式是在主从复制的基础上,用哨兵来检测主机状态。如果主机宕机的话,会自动挑选一个从机,把它作为新的主机,但是它不能水平扩展。第三个是cluster集群,它可以有多个主机来进行写操作,然后也支持水平扩容

23、哨兵作用是什么

哨兵就是在主机和从机之间,如果主机它会创建一个守护线程嘛,就是如果主机宕机了的话,它会挑选一个从机,然后来作为主机来进行写操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
```



24、介绍一下MySQL的数据结构

MYSQL的数据结构就是B加数嘛,B加数它有特有几个特点啊:
首先就是它的非叶子节点只存放索引,然后叶子节点存放索引加数据,嗯,所以在访问的时候,在查找数据的时候,他就必须要查到,叶子节点就可以提高它的稳定性。然后就是它这个叶子页面之间用链表相连,所以在用这个~~等值查询或者~~在那个范围查询的时候啊,当你查到范围这个第一个数据,就可以通过链表来查询后面的数据

25、MySQL隔离级别有哪些,默认是哪个

MYSQL的隔离级别有4种,分别是读未提交、读已提交、可重复读和串行化。默认的话是可重复读

26、介绍一下可重复读

可重复读它主要就解决了脏读和不可重复读的问题。然后脏读它就是读到了其他事物还没有提交的数据。不可重复读就是两次查询,同一个事物两次查询返回的这个结果不一致。然后它还可以通过MACC加上零件锁来解决换读问题。换读就是同一个事物两次查询返回的结果行数不同啊,就说明这个同一个事物两次查询之间过程之间发生了这个数据插入,然后就可以通过,就是在单线程的情况下就可以通过零件锁啊,零件锁它是由行锁加上间隙锁,来锁住这个两个数据之间的这个区间,然后MACC就是多版本并发控制,它通过一个版本链,Redo log的~~redo log~~的版本链,加上一个读视图read view啊,通过读视图判断当前版本是否可见,然后去~~read redo log~~里面查询这个版本的数据。这样的话,就算是有这个别别的事物提交,也可以通过版本来查询,就是这个版本的数据,避免了这个插入的问题

  • MVCC(多版本并发控制):依靠undo log版本链 + 读视图ReadView。查询时用 ReadView 判断数据版本是否可见,读取 undo log 中的历史快照版本。普通 select 快照读,依靠 MVCC 解决快照读场景下的幻读。
  • 临键锁 Next-Key Lock:行锁 + 间隙锁,锁住记录以及记录左右区间,阻止其他事务向区间插入数据,用来解决当前读的幻读。
1
2
3
4
5
6
7



27、讲一下MySQL的主从复制

嗯,MYSQL主从复制主要是通过二进制日志和这个中继日志。就是先在主机通过二进制日志来记录,主机的修改,然后从机创建线程,读取这个二进制日志,然后再再把这个读取到的信息写回到从机的中继日志上,然后在访问中继日志来进行一个从机和主机的数据同步

MySQL 主从复制依靠 binlog 二进制日志与 relay log 中继日志。

  1. 主库执行写事务,提交后将数据变更记录到 binlog。
  2. 从库 IO 线程连接主库,拉取主库 binlog,写入本地 relay log 中继日志。
  3. 从库 SQL 线程读取 relay log,解析并回放日志事件,在从库执行,实现和主库的数据同步。
    默认是异步复制,主库写完 binlog 直接返回,不等从库同步完成。

从机两个线程!!一个读二进制日志,写入中继日志,另一个读中继日志,解析

1
2
3
4
5
6
7
8
9
10
11



28、Binlog日志有哪些数据格式

有statement语句肉行和那个自动statement,它是会记录这个修改的语句。它会修就是这个具体的这个SQL语句。它的优点是存储空间小,缺点就是,修改语句可能会造成结果不一致。然后肉行它记录的是这个发生变化的这个数据的行。它的缺点是存储空间大,优点是不会发生数据不一致,然后自动就是让这个MYSQL自动选择

29、Mybatis里为了防止SQL注入应该用什么范式

#{}

#{}底层会使用 JDBC 的 PreparedStatement 预编译机制,参数会被当作占位符,先编译 SQL 模板,之后传入参数,参数不会参与 SQL 编译,避免 SQL 注入。
${}是直接字符串拼接,参数直接拼进 SQL 语句,存在 SQL 注入风险。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247



30、在MyBatis中$和#有什么区别
31、Mybatis的缓存机制
32、Spring的AOP作用是什么,以及底层是通过什么方式去实现的

Spring AOP它是面向切面编程,就是把这个重复的代码在不修改原来代码的前提下,把这个重复的操作抽取为一个切面啊,比如说登录校验呀,写日志这些啊。它底层是通过动态代理来实现的啊,动态代理又分为JDK动态代理和cglib动态代理啊,JDK动态代理它要求实现接口,它会创建一个实现这个接口的,那个一个一个类实现实现了这个相同接口,然后在这个类里面进行方法增强,然后cglib它是要求不,它是在这个直接在这个原来类下面创建一个子类,然后在子类中进行方法增强

33、介绍一下双亲委派机制

双亲委派机制就是在这个类加载过程中的机制,就是当类加载器收到这个类加载请求的时候,会先将请求传递给父类啊,如果父类无法完成这个类加载的话,然后才由这个子类来进行加载啊。主要有这个应用程序类加载器、扩展类加载器和启动类加载器吗?它在这个应用程序类加载器收到这个请求的时候,它会先**委派给**扩展类加载器,然后突然类加载器又会传给启动类加载器,然后先看启动类加载器有没有这个这个类能不能进行类加载,如果不能的话,再传给由这个拓展类加载,如果还没有的话,再由它这个应用程序类加载啊,它的好处呢,主要第一就是,避免类重复加载嘛。就是如果启动类可以加载的话,这些其他的子类就可以复用这个启动类加载器加载的类,就避免了重复加载。然后第二就是安全性保证啊,避免这个下面的子类啊,修改这个核心类里面的这个对象,比如说避免这个子类修改string对象string类

34、JVM垃圾回收机制、垃圾回收器有哪些、垃圾回收算法有哪些

JVM的垃圾回收机制,它是靠这个可达性分析机制,判断垃圾是否回收,就是会选取一个根节点,然后访通过引用来访问这个根节点的对象,查找这个对象能否到达根节点,然后再查找这个到达这个节点的其他节点啊,就会形成这个节点链嘛。然后如果,这个节点就通过节点链还是访问不了啊,根节点就对它进行GC。垃圾回收算法可以有这个标记清除复制还有标记移动。标记清除就是有两次访问嘛:
第一次是对这个存活对象进行标记
然后第二次就是对这个没有标记的对象进行清除。缺点就是会这个两次要要要进行两次访问,然后开销可能会有点大,然后还会造成这个内存碎片。然后第二个就是复制算法,复制算法在这个新生代的幸存者区就用的复制算法啊,它就是将内存空间分为大小相等的两份,然后每次将这个保持一份是空的,然后将另一份中的存留对象移动到另一份中,然后再清空那个清空原来那一份的内存空间啊,它的缺点就是就是这个内存只有一半可以使用,然后它的优点就是只需要一次访问嘛,操作比较省节省消耗,然后也不会造成内存碎片。然后第三个标记整理算法就是标记这个存活对象,然后将存活对象向这个内存空间的一端进行移动,然后再清除另一端的对象,另一端的就剩下了这个垃圾了嘛?标记移动主要在这个老年区中使用啊

垃圾回收器有server,它是个串行的这个垃圾收集器,然后还有个server new, 它是对这个server的并行化版本,然后还有CMS和G1

35、介绍一下你最熟悉的垃圾回收器

Serial垃圾收集器是新生代的复制算法的垃圾收集器,在它执行期间要暂停所有工作线程,然后new是的穿并行版本,它是创新化的。然后老年代采用的标记清除算法在JDK1.7之前是CMS,它分为四步,分别是初始标记、并发标记、重新标记和并发清除初始标记,它会STW,停止掉所有的工作线程。标记可以直达根节点的对象。并发标记它会开启工作线程和GC线程,然后标记所有可达对象,然后重新标记它是会再次STW来标记在重新标记在并发标记阶段中,由于线程开启线程状态改变而没有标记到的节点清除标记,它会清除掉**未标记**的节点,然后,清除阶段产生的垃圾会在下一次GC时清除

建议垃圾收集器在JDK1.9之后替代掉了CMS垃圾收集器。它也分为四部,分别是初始标记、并发标记、最终标记和**筛选回收** 初始标记它会停顿,先标记所有可达可以直接达到根节点的对象,然后并发标记是让这个这个线程和工作线程启动,然后标记可达节点,然后重新然后最终标记,它也是再次标记前面并发标记没有标记到的节点,然后筛选回收,它会这一收集器它会维护一个优先级列表啊,它会把这个内存划分为多个region。嗯,然后它会优先根据这个优先级,就是回收效率,然后来,优先清除然后筛选回收阶段,它会用复制算法来防止产生内存碎片

36、你用过哪些前端框架,能独立开发前端吗
37、vue的那些基础知识,比如生命周期你有了解过吗
38、我们这边前端主要用的是React,如果让你系统性学习了解一下基本的知识,你觉得你会需要多长时间
39、你项目部署是用的Docker吗
40、介绍一下你在最新一段实习参与的项目都做了哪些东西,遇到哪些难点,怎么克服的
41、我看你实习做了个Redission实现IOT古树养护闭环并实现延迟队列能详细介绍一下吗

我用的是RabbitMQ的死信队列来实现支付。30分钟未支付,自动取消订单。就是,给这个消息发送消息,先到这个正常队列中,然后经过30分钟把消息放到死信队列,然后有死信监听器对死信进队列进行监听。如果他支付成功的话就不做处理,如果他支付失败的话,就把它有那个支嗯支付状态由未支付改为已取消

42、那你部署的这个项目突然CPU飙得很高,你会怎么排查这个问题
43、假如说现在有个需求给你,从整个流程一直到上线所有工作都有你来做,可以用AI辅助,你会怎么做
44、反问:几轮面试(四轮)
45、反问:岗位需求,过去做哪些工作(面试官说还得会c++)
46、反问:面试评价







mysql方向
讲一下 Mysql 存储引擎有哪几种?

MYSQL存储引擎主要有inno DB myisam和memory吧,1诺DB它支持这个外界约束呀,支持事物还有**行级锁** 然后myisam它是有较低的内存消耗,适合存储这个大量数据。然后Memory它是将数据存到内存中,然后发生宕机的话,它的数据可能会丢失

我们平常有一些业务数据比较多,我们建立索引有没有什么方法?



B 树和 B 加树有什么区别?

java
问中间件和 Redis相关
Spring Boot 自动装配的原理
jdk相关的问题

docker部署用过k8s吗介绍一下





# 9.5 科大讯飞(线下)—一面过,二面挂

南京线下面试,凉经

一面:
先自我介绍,后面完全对着简历问

1、用jwt为什么还要做redis的白名单
2、Spring Data JPA是怎么做一对一或者多对多的映射的
3、如果用户在多个窗口或者平台登陆,其中一个登出,相应其他的窗口怎么做登出
4、你的支付的完整交易链路怎么做的
5、幂等性验证怎么做的



6、库存扣减是怎么做

库存扣减的话,首先就是可以用数据库的**乐观锁**,但是啊,这个在这个数据量情况,数据量比较小的情况下可以用,如果数据量较大的话,这个数据库乐观锁非常不敢说,达到这个MYSQL数据库,我会对数据库造成很大,然后也可以用的**分布式锁**,但是Redis分布式锁它也有手机操作,还是在这个程序中频繁的这个唤醒方式,就是可以用把这个**数据复制到Redis中**,然后在Redis中用**Lua脚本**来对操作,让他原则,让它变成原子操作这个查库存,减库存,查库存,然后判断库存防止其他他这个Redis的需要保证他场景操作是原子原子的,然后也可以加上这个学习中间件的MQ来实现异步,然后通过这个直方山和panel来保证这个MYSQL和数据一致性然后同时要保证消息不丢失,可以用确认模式确保消息达到到达交换机,用返回模式确认消息达到队列,然后用手动ack确认消息被消费,然后,如果消息发生丢失的话,要进行重试机制,然后重试一定次数。不行的话,要对这个就是修改MYSQL失败的话,要对这个Redis里的数据来进行这个回补

7、MVCC是什么,能详细讲一讲吗

MVCC是多版本并发控制,解决的是不加锁的快照读。快照读,主要运用于这个并发场景下的,解决这个并发场景下的换读问题。换读就是同一个事物两次查询返回的结果行数不一致啊,说明这两次查询之间被其他事物插入的数据,然后就可以通过MVCC来解决,MVCC它是通过undo log日志和一个read view读视图。通过Read view读视读视图判断版本是否可见,对当前事物是否是否可见,然后通过undo log来回滚来选择这个对当前版本可见的,对当前事物可见的版本。Read view它里面主要存4个参数,一个是当前事物ID,一个是活跃ID列活跃事物列表,然后一个是活跃事物的最小列表最小ID,还有一个是即将进行的这个事物ID,也就是最大事物ID+1,然后它会判断当前事物是否小于最小事物ID。如果小于的话,说明当前事物是在这个版本提交之前呢,说明当前版本对该事物可见啊。如果是大于这个下一次要提交事物的话,说明当前版本是在这个活跃事物之后,所以是不可见的。然后如果在他们两个之间的话,就要进一步判断啊,当前事物ID是否在活跃ID列表中,如果在的话,就说明当前事务由这个活跃事务来提交,所以不可见然后如果不在的话,就说明它是已经被一个已提交的事物提交,可见

8、MySQL的事务

MYSQL的事务就是这个SQL,一组SQL语句,它是要么全部成功,要么全部失败,进行回滚

主要就是acid,原子性、一致性、隔离性、持久性。原子性就是说啊,要么全部成功,要么全部失败,回滚主要通过unlock日志来实现一致性,就是说这个,MYSQL语句操作前后的这个属性数据变化要符合那个逻辑,它主要是靠其他3个属性来实现的。然后隔离性就是说事物之间相互隔离,互不影响,持久性就是数据,只要发生修改,就要永久保存,主要通过redo log日志来实现

9、Spring Boot是怎么启动的

Spring Boot启动流程主要有两步:
启动流程主要分两步:
第一步是初始化apply spring application对象
第二步是执行装方法。初始化Application对象它要先获取应用类型,如果是so类的话,说明它是外部类型,后面要用到他们开的服务器,然后加载初始化器和监听器。然后执行装方法的话,它会先开启计时器,然后加载监听器,初始化监听器,然后会创建一个环境对象,用于加载系统属性、环境变量和配置文件等。然后会传入20字的参数,传入命令行参数,就是在启动时传入那个20字嘛,然后传入到spring application和环境对象中,然后下一步会执行会那个启动那个banner图,然后就是会创建初始化并且刷新这个上下文。对象,在这里它会扫描那个该类以及子类下子子包下的那个配置文件啊,的那个注解,加载这个自动配置类,然后完成并的创建和初始化注册初始化,然后如果是外部的话会在这里启动他们开的服务器,然后就,还有一个后置的一个刷新方法用于这个后置处理,然后就是最后就是调用一个application runners或者command line runners接口,然后完成启动发布通知

10、Spring Boot的事务注解是怎么实现的

Spring Boot事务注解是transactional注解,它的底层是通过AOP动态代理。当你给这个类加上transactional注解时,它会创建一个动态代理对象,然后对它进行一个AOP的环绕操作,在这个事务在这个业务代码之前获取数据库连接关闭自动提交,然后开启事务,然后执行完执行业务方法完之后会提交事务,然后如果发生了报错它会回滚

11、事务注解什么时候失效(这里我情况没有说全)

事物注解失效:
第一就是方法用try catch捕获,但是没有抛出,就会导致事物接收不到异常不能回滚
第二就是事物进行了嵌套,然后,可能那个传递传递设置没有设置好,它就也可能会导致事物失效。然后,第三就是用this方法调用,你用this方法调用这个对象的时候,它会跳过动态代理类对象,动态代理对象也会导致事物失效,然后就是多个数据源操作,由于这个事物它也是对于这个单个数据库来进行事物回滚的嘛,就是多个数据源也可能导致事物失效,然后还有它默认只会对运行时异常和error进行回滚,如果不手动设置它的rollback for是exception class的话,对于这些IO和circle异常它就不会进行回滚,然后就是朱姐在私有方法上也不会进行,它只有在public上才能生效

12、oss上传的图片,外部应该是不能访问的,这个要怎么做

buckets设置为private

加密url,设置过期时间

12、Redis除了做缓存,还能做分布式锁,这个能讲一讲吗

setnx+ttl+uuid+lua

Redis做分布式锁主要是用到它的set NX嘛,Set NX就是不存在时才设置,然后多个客户端只能有一个获取到锁,然后为了防止死锁要给它设置TTL,然后为了防止它误删别人的锁,要给它设置这个uuid,给每个线程创建一个uuid,然后在这个删锁过程中的这个判断和删除它要保证是一个原子操作,要用这个Lua脚本

13、docker会用吗,一些命令
14、agent的三种引擎怎么创建和使用的
15、RAG知识库怎么做的
16、记忆系统,上下文过长怎么处理的
17、skill怎么在对话中使用的
18、平时用过哪些ai,使用心得,觉得哪个使用最好,怎么编排工作流,有没有用过什么skill等问题,不再过多描述,感觉基本上每次面试都问,问的都差不多
(补:开头和结尾各问了几个我实习的问题)

反问部门工作,惊奇的发现和我的项目,以及实习有很大的重叠,我都感觉有了

在门外等候5min,通知去下一个二面

二面:
无自我介绍,说明前面java已经问完通过了,所以他要问agent方面的(我投的java开发,你问我这么多python干嘛)

1、了解deepresearch吗(回答不了解)
2、多引擎怎么的
3、知识库怎么做的
4、工具注册是怎么做的
5、记忆怎么做的
6、我问下来感觉跟deepresearch很想啊,你为什么不用现有框架呢
7、文献查阅工具的api用的是哪几个
8、我还是不明白你为什么不用现有框架呢
9、我还是不明白你为什么不用现有框架呢。。
10、如果现在有后端/全栈开发、agent开发、还有个啥开发我忘了,三个岗位给你,你会选择哪个

反问无

门外再次等候5min,通知让我回去了

已心碎,本来感觉面的贼好的,纯java开发岗真的会被二面问的这种问题卡掉吗,本科依旧不配上桌





# 京东后端一面复盘

面试时间:9.23 时常:20分钟 岗位:京东后端开发
本来以为主要拷打Java八股,结果一半时间都在深挖项目和Agent

1. 自我介绍,简单介绍自己的技术背景

2.项目部分:RAG知识库的业务场景是什么?
3.针对高并发写热点问题,你有哪些优化思路?
4.向量库为什么选Milvus?
5.你了解过 Code Graph 这类开源项目吗?
6.知识库更新后,怎么保证内容及时同步?
7.Agent怎么识别用户意图?
8.项目的核心创新点是什么?
9.简历写了性能提升,具体怎么测出来的?
10.如果线上检索结果不准,怎么定位问题?

11. Java八股:CAS是什么,有什么问题?

12.ABA问题怎么解决?

版本号

13.线程池核心参数怎么设置?

cpu密集还是io密集

cpu密集cpu核心数+1;io密集cpu核心数*2

14.ThreadLocal底层原理
15.B+树为什么适合做MySQL索引?
16.慢SQL怎么排查和优化?

explain -- type、key、key_len、extra

17.平时是否会使用 AI 工具辅助开发?你认为目前 AI Coding 最大的问题是什么?



18.缓存击穿怎么解决?
19.Kafka如何保证消息顺序?
20.后续有什么实习或者职业规划?
21.反问环节







# 9.20 得物后端一面

[#秋招面试记录#]() 半小时无手撕

1. 请做一下自我介绍。

2. 介绍一个你觉得有挑战性的项目。

3. 聊天机器人项目中引入了哪些 AI 模型能力?接 AI 进来帮助做什么决策?

4. 这个项目类似于监控平台吗?它起辅助定位还是什么作用?

5. 它可能给出错误信息吗?如何优化或防止幻觉?

6. 简单介绍 Java 内存模型。

7. 线程间数据交换有什么手段?

8. 如何保证原子性?

9. volatile 的机制能简单介绍一下吗?

头是轻量级的同步机制主要用在这个变量上,保证变量的可见性以及禁止指令重排序

10. volatile 底层如何保证可见性?

保证可见性的话,就是我老头会给每个修改这个变量的线程创作一个工作线程,然后,如果这个工作线程对变量进行修改的话,会立即把这个工作线程的修改同步到主线程中,然后强制其他线程重新从主线主线程中读取数据,这样就保证了这个变量的可见性

11. volatile 如何防止指令重排

指令重排的话主要是通过内存屏障volatile,它有4个内存屏障,第一个是Store,store内存屏障,它会在volatile写之前加这个store,store内存,会防止这个普通鞋与这个高头血发生重排序
第二个是,store内存故障,它会在volatile写之后添加store的内存屏障,为了防止volatile写与后面的这个欢乐头读写操作发生重排序。第3个是load store内存屏障,是在这个volatile读之后加load store内存屏障,防止这个volatile读操作与后面的写操作发生排序。第4个load load内存屏障就是加在这个网络头,也是加在我的读之后,完了,他读之后,防止这个网络头读与后面的普通读发生重排序

❌错误点:

1. 不是创建工作线程,是 JVM 内存模型:**每个线程拥有自己的工作内存(缓存副本),不是新创建线程**。
2. 不是单纯 JVM 刷主内存,底层依赖**CPU MESI 缓存一致性协议**。
3. 不是强制其他线程主动去读主存;volatile 写会让其他 CPU 的缓存行直接失效,其他线程读的时候发现缓存无效,被迫从主内存加载。

1. Store‑Store 屏障,放在 volatile 写之前,阻止前面普通写重排到 volatile 写之后;
2. Store‑Load 屏障,放在 volatile 写之后,阻止 volatile 写和后面的读写重排序;
3. Load‑Load 屏障,放在 volatile 读之前,阻止前面普通读重排到 volatile 读之后;
4. Load‑Store 屏障,放在 volatile 读之后,阻止 volatile 读和后面普通写重排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15



12. Spring Boot 在项目中主要是当微服务用,还是普通 Java 分模块开发?

用的是微服务用的Spring Cloud阿里巴巴多个把整个程序拆分为多个微服务,微服务之间通过OpenFeign来进行调用配置和这个服务配置和注册到Nacos中。然后有的服务之间通过RabbitMQ中间件来进行消息转发。将来异步调流量削峰

13. Spring Boot 自动装配机制你清楚吗?

Spring Boot自动装配主要是通过Spring Boot application这个复合注解里的enable auto configuration, enable auto configuration注解里面通过import注解导入了一个叫做auto configuration import selector的类,它会自动扫描mate INF包下的配置文件,在SpringBoot3之前它叫做spring.factories SpringBoot3之后叫做auto configuration imports, 然后会通过条件注解,比如说conditional On class对这个配置文件里的配置类进行一个**去重**、筛选、排序之类的,然后spring容器就会把这些筛选后的配置类进行初始化,加载到并容器中

14. Bean 的生命周期能介绍一下吗?

首先呢,会创建这个上下文对象,还有这个变容器之类的,创建变容器的时候,它会然后先通过XML或者注解的方式来读取这个病的定义。然后将这个病定义封装为B对象,封装到这个factory对象里,然后要对并进行实例化,开车来给他,然后是要进行一个依赖注入吗?通过这个set或者do的变量进行赋值,然后下一步就是回调avail。如果他实现了这个b name, 最后的话要给他传入内幕然后呢,factory报表的话啊,对不对,不要传入这个上下文对象,然后有是这个前置方法,如果他实现了processor接口的话,要执行一个前置方法叫做process or in a lazy处理完之后,要是通过这个post construct Julia或者是XML里init method这个方法呀,而对他进行出手,他要是有实现一个叫inner light接口的话,要给他就是InitializingBean的afterPropertiesSet方法;然后这个初始化方法执行完之后,这个后置处理也是这个猜猜里面的一个叫做process after ization方法。然后完了这个对象就这个病对象就使用完之后要对它进行销毁。通过这个**@PreDestry**注解或者是AML里这个stream method定义的方法,如果要是有这个disable的话,也要实现这个接口里的这个distry方法

首先,它会初始化容器会创建一个方向,In fact对象网页初始化并bay注解或者XML方式来读取这个病的定义 然后将它注册到并FAY对象里,然后第二步就是使通过反射来对这个病对象来实例化,然后实例化之后要进行一个依赖注入,通过set或者autowired对这个成员变量进行赋值;复制完之后要有一个avail回调,分别有3个接口,分别是这个be name aware,要传入这个名的内幕,beanfactoryaware要传入beanfactory对象,还有application context aware要传入这个上下文对象,然后就是要有一个前置的一个,如果它实现了postprocessor接口的话,在这个初始化方法之前,要执行一个post process before initialization, 然后要完成之后就要执行这个初始化,通过post construct或者XML里的init method定义的方法看来对他能对并进行一个初始化。如果他实现了In the lazy bean接口的话,要执行里面的这个after properties set方法,完了之后就是一个后置处理,和前面前置处理一样,也是post processor哦,实现的是里面的post the process after in, 这个完了之后并实例就可以被这个用完之后要进行销毁。下个月是通过pre出去或者这个XML里这个destroy method定义的方法。来进行销毁,不过也是有这个DisabNBean接口,要执行这个接口里的destory方法 ```
  1. 如何设计高并发秒杀活动,保证不超卖?

    来说一下秒杀场景要注意的点,首先秒杀场景要解决的两个最重要的点,一个点就是秒杀时会有大量的请求来,这个时候要进行一些负载均衡来,这个减小压力,然后另一个点就是要防止这个订单超卖,库存扣减问题,然后先说这个减小压力这一块吧,首先在前端界面要对这个,要把这个秒杀界面作为一个静态界面存到CDN中,这样秒杀活动开始的时候,用户就这个加载静态界面,让这个服务端专心去处理这个秒杀请求,然后第二就是通过这个用户在这个秒杀活动开始之前,把这个按键设置为不可点击,防止这个到这个秒杀时间就用户发出发出这些无效请求。然后在这个前端解决超卖问题的话,可以把这个按键在点击完之后设置成不可点击,就从前端界面先啊,防止这个用户点不生成订单之类的问题,然后这是前端界面,前端界面完了之后,到这个Nginx用这个词,它要做一个负载均衡,把这个前端大量分到多个Nginx服务器上,然后可以进行一个流量削峰。除此之外,Nginx还可以在网关这一层进行一些过滤,拉黑呀,然后限制IP这个操作,现有这个每秒请求量和这个每个IP的这个总都可以进行一个限制,防止恶意的刷单之类的,然后Nginx完了之后就是要到到缓存界缓存缓存首先就是要把这个秒杀开,活动开始之前要把这个预估从这个MYSQL数据库复制到Redis中,提前复制好,这样秒杀活动开始的时候就可以直接从Redis中然后。除此之外,在Redis上还要做一个防止超卖的一个解决防止超卖的问题,可以用这个令牌机制吧,就是给每个这个订单请求这个设置一个唯一的流水号,然后到Redis中,然后每次生成订单的时候都要查询一下这个Redis是否是否有这个流水号。每次这个请求业务的时候要查询一下有没有这个流水号,如果有的话就重复啊,这个重复请求就拒绝掉,然后在这个扣减库存的时候,它这个要首先要判断这个库存是否大于零,然后再对这个库存进行更更新嘛,然后这两个操作它是两个操作,所以需要用到这个Lua脚本来保证它这个操作的一个原子性。除此之外,也可以用分布式锁来实现啊,这个防止防虫,但是分布式锁它会让这个请求串行化,而且可能也要有一些那个IO操作性能较低,所以直接使用Lua脚本,然后从这个缓存到数据库中间可以用消息中间件用RabbitMQ来进行一个转发,同样它也是首先就是有一个异步转发的功能,把这个实现这个生成订单和这个修改数据库的这个功能,就是请求后端,后端这个实现和这个修改数据库分为这个两个部分,然后在这个后端,后端这个操作完成之后,就可以先返回前端,这个正在修改,然后然后由另一个这个异步操作来来修改这个数据库内容。然后同时也可以对这个流量进行一个限制,可以有这个削峰填谷的这个可以在这个,高并发时期把这些修改数据库的操作先暂留,然后存到到这个流量低谷的时候再进行这个修改,这样的话它就不,它就会有一个这个不会立即同步的 情况出现,所以要注意保证数据一致性,要用延时双删和canal监听来保证数据一致性,然后也要设置定时任务来定时比较redis和mysql数据库的库存是否一致,如果不一致要有兜底回补方案。最后在mysql数据库也要有兜底防超卖,比如设置版本号或者乐观锁来保证只有一个线程修改数据库,再用唯一索引确保只能添加一个订单

  2. 秒杀场景下,如何兼顾性能与防超卖?

  3. Lua 脚本中的 Key 如何设计?

    可以用userid+商品url

  4. Redis 不可用时,系统有自愈能力吗?

    不可用,首先先考虑它有没有这个哨兵模式或者集群
    然后,如果有这个哨兵模式或集群的话,可以让它这个通过哨兵模式来重新选择主节点这个就是Redis内部的一个自愈,如果他这个主主节点和从节点全部挂掉的话,然后可以把这个请求再进一步限流,限流到这个MYSQL数据库可以接受的范围之内,然后让他去查询MYSQL数据库,当然这个就可能会对这个数据库造成很大压力嘛,哦,如果要是这个实在承受不了,可以对他进行进行一个熔断处理,就前端返回一个当前活动火爆这种,然后再重试这种,然后让

  5. 如何保障 Redis 高可用?

    Redis高可用性有三种,第一种是组同复制,然后哨兵模式和cluster集群,然后复制就是读写分离,将这个主机作为写操作,然后多个从机来进行操作,防止这个读操作对这个写造成的影响,提高这个主机的性能,然后它有一个不足,就是当这个主节点挂掉之后,从节点需要这个手动,再次这个从这个从节点选择一个从节点作为主节点,然后这个时候可以用这个,哨兵模式它有这个哨兵节点,可以选择一个leader,然后用这个leader来选择一个,当这个主节点挂机之后,由leader从从节点选择一个作为这个主节点,然后还有这个cluster集群,Cluster集群是将这个数据将这个K分为16384个这个166384个槽,然后在这个把这164816384个槽分给这个主节点,然后,主节点越多,每个节点分的槽就越少。然后,如果要查找K的时候,直接查找这个,算出这个K对应的还有槽位,然后再根据这个槽位找到对应的节点地址,直接访问这个节点即可,这个cluster集群它有这个高可用性、高性能和易拓展可以进行这个分片,然后要查找数据,直接访问这个对应哈希槽的这个节点即可。然后拓展的时候,它也可以直接动态添加节点

  6. 稳定性设计方面有什么经验?