重点八股总结
感恩牛客的各位大佬给我的各种面经,把我自己整理的重点面经也发出来给后来人看看。
其实,到后面才发现这些不过是非常简单的问题,所以不妨把战线拉长一点。切不要为暂时的得失而胆怯!
重点的知识
CMS(Concurrent Mark-Sweep)垃圾回收器
CMS(Concurrent Mark-Sweep)是一种以最小化停顿时间为目标的垃圾回收器,适用于低延迟的应用场景。它主要用于老年代的垃圾回收。CMS 的工作过程分为四个阶段:
- 初始标记(Initial Mark):标记直接与 GC Roots 相连的对象,这个阶段会触发“Stop The World(STW)”,但时间很短。
- 并发标记(Concurrent Mark):在该阶段,GC 线程和应用程序线程并发运行,标记从 GC Roots 可达的对象。应用程序可以继续运行,因此不会有明显的停顿。
- 重新标记(Remark):为了处理并发标记过程中新增的对象或修改的引用关系,需要一次重新标记,这个阶段也会触发 STW,不过时间较短。
- 并发清除(Concurrent Sweep):在该阶段,GC 线程并发清理未被标记的对象空间,不会影响应用线程。
优点:
- 并发标记和清除阶段几乎不会停止应用程序运行,停顿时间较短。
缺点:
- 清理时不会对内存进行整理,容易产生碎片,可能导致老年代无法分配大对象。
- 需要更多的 CPU 资源,特别是在并发阶段,GC 线程和用户线程争夺资源。
G1垃圾回收器
G1 收集器不采用传统的新生代和老年代物理隔离的布局方式,仅在逻辑上划分新生代和老年代,将整个堆内存划分为2048个大小相等的独立内存块Region,每个Region是逻辑连续的一段内存,具体大小根据堆的实际大小而定。
G1收集器通过跟踪Region中的垃圾堆积情况,每次根据设置的垃圾回收时间,回收优先级最高的区域,避免整个新生代或整个老年代的垃圾回收,使得stop the world的时间更短、更可控,同时在有限的时间内可以获得最高的回收效率。
三色标记算法
三色标记算法是并发收集阶段的重要算法,它是描述追踪式回收器的一种有用的方法,利用它可以推演回收器的正确性。 首先,我们将对象分成三种类型的。
黑色:根对象,或者该对象与它的子对象都被扫描了灰色:对象本身被扫描,但还没扫描完该对象中的子对象白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象
MVCC 在 InnoDB 的实现方案
MVCC(多版本并发控制)是 InnoDB 用于实现事务隔离的技术。其核心是通过保存数据的多个版本来避免读写冲突。具体实现包括:
- 为每行记录添加两个隐藏字段:一个表示数据版本的创建时间戳,一个表示删除时间戳。
- 当读数据时,根据事务的时间戳判断当前事务能否看到该数据版本,来实现快照读和当前读。
- MVCC 主要用于实现
READ COMMITTED
和REPEATABLE READ
两种隔离级别。
Redis 除了缓存,还有什么应用场景
- 消息队列:通过列表或发布订阅模式实现消息队列。
- 分布式锁:通过
setnx
和过期时间实现分布式锁。 - 计数器:Redis 的自增操作非常适合实现高并发场景下的计数器。
- 会话管理:通过 Redis 存储会话,保证分布式系统中会话的共享。
- 排行榜:使用有序集合存储用户排名数据。
Redis 实现超时订单取消
可以使用 Redis 的延时队列,或通过 setnx
设置订单的超时时间,当订单超时后通过定时任务扫描未支付订单并取消。
Redis 实现分布式锁
1. setnx
Redis 实现分布式锁常用 SET key value NX EX time
命令:
NX
保证锁只会被一个客户端获取。EX
设置锁的过期时间,防止死锁。
2. Redisson 分布式锁
Redisson 是一个基于 Redis 的高级客户端库,提供了许多实用的 Redis 功能,包括分布式锁。相比于原生的 SETNX,Redisson 实现了一个更加复杂和完善的分布式锁机制。
- 高抽象封装:Redisson 提供了类似 Java 中的 ReentrantLock 的分布式锁实现,API 简单且易用,隐藏了底层细节。
- 自动续期:Redisson 实现了锁的自动续期功能,当持有锁的线程仍在执行时,Redisson 会不断延长锁的过期时间,防止锁过期失效导致其他线程误拿锁。
- 公平锁和非公平锁:支持公平锁(多个线程按照获取锁的顺序排队)和非公平锁。
- 可重入锁:Redisson 支持可重入锁(Reentrant Lock),即同一个线程可以多次获取同一把锁,而不会发生死锁。
3. Redlock 分布式锁
目的是在一个分布式环境中,使用多个 Redis 实例来实现一个高可用的分布式锁。
- 多实例加锁:假设有 5 个 Redis 节点(推荐奇数个节点,至少 3 个)。在每个节点上执行 SETNX 操作。
- 多数派锁定:如果客户端能在大多数节点(至少 3 个节点)上成功加锁,并且加锁操作的总耗时小于锁的过期时间,则认为加锁成功。
- 解锁:当任务执行完毕后,客户端需要在所有节点上解锁
- 优点: 容错性强:即使部分 Redis 节点宕机,只要大多数节点成功加锁,仍可以认为加锁成功。 可靠性高:相比单个 Redis 实例的锁,在 Redis 实例出现网络分区或节点故障时,Redlock 能提供更高的可靠性。
- 缺点: 实现复杂,需要维护多个 Redis 实例。 因为需要在多个节点上执行锁定操作,性能较 SETNX 和 Redisson 略低。
Spring 什么时候初始化 Bean
Spring 默认在容器启动时(应用启动时)初始化单例 Bean,使用懒加载时则在第一次使用时才初始化。
AOP 的底层原理
AOP(面向切面编程)通过动态代理机制实现。Spring 使用 JDK 动态代理(基于接口)和 CGLIB 动态代理(基于类)来实现切面逻辑,将横切关注点(如日志、事务)分离出来。
计网的分层
计算机网络通常分为五层:应用层、传输层、网络层、数据链路层和物理层。在 OSI 七层模型中,多了表示层和会话层。
HTTP 和 HTTPS 属于哪一层
HTTP 和 HTTPS 属于应用层。
TCP 和 UDP 属于哪一层,TCP 如何实现可靠连接
TCP 和 UDP 属于传输层。TCP 通过三次握手建立连接,通过确认应答机制、超时重传、流量控制、拥塞控制等手段保证连接的可靠性。
以下是对你提供问题的简要解答:
线程池拒绝策略
当线程池中的任务队列已满且所有线程都在忙碌时,会触发拒绝策略,主要有四种常见的拒绝策略:
- AbortPolicy:默认策略,直接抛出
RejectedExecutionException
异常,阻止系统正常工作。 - CallerRunsPolicy:由提交任务的线程来执行该任务,降低任务的提交速度,减轻线程池的负担。
- DiscardPolicy:直接丢弃无法处理的任务,不抛异常。
- DiscardOldestPolicy:丢弃任务队列中最旧的任务,然后尝试重新提交当前任务。
开发者也可以通过实现
RejectedExecutionHandler
接口自定义拒绝策略。
让我做一个订单搜索怎么做,通过商品名搜索订单
在 MySQL 中可以通过 全文索引(Full-Text Index)来提高文本搜索效率。创建全文索引的步骤:
- 首先,确保数据库表的存储引擎为 InnoDB 或 MyISAM。
- 对 商品名 字段创建全文索引:
- 然后使用 MATCH() 和 AGAINST() 函数来搜索商品名匹配的订单:通过这种方式,MySQL 会使用索引加速商品名的模糊匹配搜索。
G1 怎么设置它的区大小
G1 GC(Garbage First Garbage Collector)中的堆内存分成了多个相同大小的 Region
。这些 Region
的大小可以通过 JVM 参数配置:
-XX:G1HeapRegionSize=n
:设置每个Region
的大小,取值范围是 1MB 到 32MB,必须是 2 的幂。默认大小根据堆的大小自动确定。 G1 的区大小越大,垃圾回收的开销越小,但可能会造成内存浪费。
单机为什么用redis缓存?
- redis的读写比mysql快很多,基于内存、数据结构优化、基于单线程;
- 对于变动少、查询时间长的数据是比较合适放在redis中
如何解决redis和mysql双写一致性问题
- 旁路缓存策略(CashAside方法)
一般在项目中都是此案用旁路缓存策略,具体思想是先更新数据库,再删除缓存。那么就有两个问题
(1)为什么不是先更新数据库,再更新缓存。假设我们有两个线程A和B,分别执行更新数据库、更新缓存,两个操作,如果是顺序执行,自然是没有问题;如果是两个线程同时执行,考虑网络等问题,会出现
A更新数据库,B更新数据库,B更新缓存,A更新缓存,这样数据库就是B的数据,缓存是A的数据。这样读取的就是脏数据,如果是删除缓存,就不会出现这个问题。而且写的操作消耗IO也比较多,不推荐这个。
(2)为什么不能先删除缓存,再更新数据库尼?继续考虑这样的场景,A线程:写操作,需要1.删除缓存;2.更新数据库; B线程:读操作,1.查询缓存 2. 若缓存未命中,查询数据库并更新缓存。这样的话,A删除缓存后,B执行完,读取缓存,缓存未命中,更新缓存,A再更新数据库。这样数据库和缓存的数据就不一致了。
(3)当然先更新数据库、再删除缓存也存在很多问题:
- A:更新数据库,删除缓存;B:查询缓存,缓存未命中查询数据库,更新缓存。B查询缓存未命中,查询数据库为10,准备写入缓存时,A线程来了,更新数据库为20,删除缓存一气呵成,然后B再更新缓存为之前的10,也有数据不一致的问题,但是写入缓存只有几毫秒的时间,这种情况下需要更新数据库和删除缓存,很难得。所以是弱一致性。
- 如果删除数据库失败了怎么办?引入延迟双删操作,就是1.更新数据库,2.删除数据库3.定时任务,过几百毫秒后再次尝试删除数据库。目的是删除缓存中可能出现的旧数据。或者失败重试机制,或者监听binlog数据变更情况,异步删除。也就是引入多次删除的策略,强制刷新缓存。
缓存击穿、缓存穿透、缓存雪崩分别对应什么场景?
- 缓存击穿:hot key失效,由于采用先更新数据库,再删除缓存的操作,所以大量的操作会查询数据库,更新缓存,所以我们可以加锁,让第一个获得互斥锁的线程,查询数据库,更新缓存。其他的线程重试,看看缓存是否更新了,但是这样就会有大量的线程空转,浪费资源。所以可以采用逻辑删除+异步更新。给hot key设置逻辑过期时间,每次获取hot key都检查是否过期,如果过期,开启一个线程异步更新,这个线程和其他线程正常返回老的数据即可,但是这样也有数据不一致的情况,但是资源消耗很少。
- 缓存穿透:查询的数据,压根在缓存和数据库中不存在,属于恶意访问。所以最简单的直接记录key和value,返回null值,但是这样会增加缓存压力。所以可以在参数层做校验,将所有可能的参数的hash到布隆过滤器,这样可以极大的防止无效的访问,但是也存在的误判率,比如你的布隆过滤器开的长度很小,参数又多,直接全为1了,布隆过滤器就失效了。所以在分布式架构中,对这种高QPS的请求做一定的限流,做好预警的工作。
- 缓存雪崩:大量key同时失效或者redis服务宕机。减少key,采用更多的静态化页面。给key设置不同的预期过期时间。采用多级缓存机制,L2Cache机制(本地缓存之王Caffine+redis)。构建redis服务集群。
redis为什么快
- 内存
- 高效的数据结构
- 单线程
- IO多路复用
https://www.cnblogs.com/coderacademy/p/18099027
redis的数据结构和持久化策略
接口幂等性
https://www.cnblogs.com/coderacademy/p/18082540
- UUID,雪花算法,自定义唯一的标识符
- 业务判断,更新论文的审核状态,某个时间是从未批改改为已经审核,update 强制让status=1
- 兜底的话,就是定时查询,及时更新状态
MQ如何解决消息堆积问题
- 引入惰性队列,接收到消息后直接存入磁盘而非内存,消费者消费需要从磁盘加载到内存中
- 支持百万级别的消息存储
超时消费
- 延迟队列
ES
什么是倒排索引
- 正排索引就是根据文档查找词条,判断文档中是否有该词条,不适合模糊查询。
- 倒排索引就是根据分词的结果(词条)来查询文档,拿到文档id后到正向索引中查找文档
- 常用的分词器是IK分词器,支持扩展词典,