RocketMQ
架构图
三种消息队列对比
使用配置文件
发送在业务中注入即可使用
12@Autowiredprivate RocketMQTemplate rocketMqTemplate;
三种发送方式同步发送==可靠性最高==:
异步发送:
单向发送:
五种消息类型顺序消息:
延时消息:
批量消息:
事务消息:
过滤消息:
接收使用示例:
消费者类型:
Pull模式手动拉取:
一、消息队列的核心作用1. 异步
主业务快速返回:将非核心业务(如发短信、写日志)放入消息队列异步处理,主链路无需等待。
提升响应速度:例如订单完成后发送积分通知,用户无需等待积分处理完毕即可看到下单成功。
2. 解耦
只关心消息到达队列:生产者只负责将消息发送到队列,不关心下游具体由谁处理、如何处理。
下游灵活扩展:新增消费者或修改消费逻辑不影响上游业务,降低系统间的依赖性。
3. 削峰填谷
削峰:高并发场景下(如秒杀),将突发请求暂存到消息队列,避免直接冲击后端系统。
填谷:消费者按照自身处理能力拉取消息,平滑处理流量峰值,防止系统过载。
二、Rock ...
线程池+ CompletableFuture
Executor框架
继承关系图
被框架执行的任务需要实现的 Runnable 接口 或 Callable接口
框架能执行任务需要实现ExecutorService接口
任务执行完毕的结果是 FutureTask 对象
三种线程池主要就是上面类图的三个主要实现类
1. ScheduledThreadPoolExecutor核心特征:ThreadPoolExecutor的子类,专为定时/周期性任务设计
使用示例:构造线程池,提交任务
主要有三种任务:
schedule(task, delay, unit):在指定延迟后执行一次任务。
scheduleAtFixedRate(task, initialDelay, period, unit):固定频率执行。任务开始时间严格按initialDelay, initialDelay+period, ...推进,不受任务自身执行时间影响。
scheduleWithFixedDelay(task, initialDelay, delay, unit):固定间隔执行。下次任务开始时间 = 上次任务执行结束的时间 + de ...
ThreadLocal
使用规范在拦截器中进行设置和清理操作
封装成工具类进行调用
为什么工具类,不直接注入容器?ThreadLocal:它的设计目的是为每一个线程提供一个独立的变量副本。每个线程访问 get()时,拿到的是自己线程里的那个值,和其他线程互不干扰;容器是单例,所有的线程拿到的都是同一个了
为什么要是私有静态的?
每个线程内部有一个 ThreadLocalMap,它以 ThreadLocal 实例本身作为 Key 来存取对应的 Value;
ThreadLocal在ThreadLocalMap的Entry中是一个弱应用的key,为什么外部还要用一个强引用呢,不是自相矛盾了吗?
设计成弱引用的必要性;看下面的情况,如果没有设计成弱引用,用户又错误使用,那么ThreadLocalMap的一直是强引用,会一直持有,永远无法回收
外部强引用,如果不规范使用,忘记remove,确实还是会造成无法回收,外部引用让用户可以自主的去控制,
value肯定不能设计成弱引用,不然正常情况下去获取,value被回收可能就直接回去为NULL了
跨线程传递ThreadLocal首先,如果你只是 ...
Java锁
创建线程
继承 Thread类
实现 Runnable接口(最常用 ✅)
实现 Callable+ FutureTask(有返回值)
使用线程池
线程状态1. NEW(新建状态)
定义:线程对象已被创建(new Thread()),但尚未调用 start()方法。
2. RUNNABLE(可运行状态)
定义:线程正在 Java 虚拟机中执行。但这个状态涵盖了两个层面:Ready(就绪) 和 Running(运行中)。
Ready:线程已准备好运行,等待 CPU 调度。
Running:线程正在 CPU 上执行。
==Thread.yield()暂时释放CPU使用,运行变为就绪,但可能马上获取到CPU又变为运行,但总的来说都是Java的可运行状态==
3. BLOCKED(阻塞状态)
定义:线程被阻塞,正在等待获取一个监视器锁(Monitor Lock)。通常发生在试图进入一个被 synchronized修饰的代码块或方法时,且该锁已被其他线程持有。
进入此状态的操作:
线程尝试进入一个 synchronized同步块 ...
JMM
一、JMM内存模型JMM是一种抽象规范,并不对应物理内存,而是定义了线程和内存之间的交互规则。它的核心设计如下:
概念
作用
主内存(Main Memory)
所有共享变量(实例字段、静态字段等)存储的地方,是所有线程共享的公共区域。
工作内存(Working Memory)
每个线程私有的内存区域,存储该线程使用的共享变量的副本(从主内存拷贝)。
并发三特性:
原子性
可见性 (读)
有序性
二、JMM解决的三大核心问题1. 可见性(Visibility)问题:一个线程修改了共享变量,其他线程可能无法立即感知。
JMM解决方案:
volatile关键字:保证变量的修改会立即刷新到主内存,且其他线程读取时会强制从主内存重新加载(禁止缓存副本)。
**synchronized/Lock**:解锁前必须将变量写回主内存,加锁时会清空工作内存并从主内存重新加载。
面试考点:volatile不保证原子性,仅保证可见性和有序性(部分场景)。
2. 原子性(Atomicity)问题:某些操作(如 i++)看似一步,实际分为“读取-修改-写入”三步,多线程下可 ...
MySQL日志详解
一、MySQL日志类型MySQL主要有以下日志:
错误日志(Error Log)
二进制日志(Binary Log)
中继日志(Relay Log)
慢查询日志(Slow Query Log)
一般查询日志(General Query Log)
重做日志(Redo Log)
回滚日志(Undo Log)
二、错误日志作用记录MySQL启动、运行、停止过程中的错误信息。
配置12345-- 查看错误日志位置SHOW VARIABLES LIKE 'log_error';-- 查看错误日志级别SHOW VARIABLES LIKE 'log_error_verbosity';
使用场景
排查启动失败
定位运行时错误
查看警告信息
三、二进制日志作用记录所有修改数据的SQL语句,用于主从复制和数据恢复。
格式STATEMENT:记录SQL语句ROW:记录行数据变化MIXED:混合模式
12345-- 查看binlog格式SHOW VARIABLES LIKE 'binlog_format';-- 设置格式SET binlog ...
树形结构查询
树形结构查询
存储结构
一般来说,树形结构存的就是当前节点和父节点,例如:
1234567CREATE TABLE `sys_dept` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '部门ID', `parent_id` bigint(20) DEFAULT 0 COMMENT '父部门ID(0表示根节点)', `dept_name` varchar(50) NOT NULL COMMENT '部门名称', `order_num` int(11) DEFAULT 0 COMMENT '显示顺序', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
实体类
我们只需要一个简单的查询,把所有的部门都查出来。不需要写复杂的递归 SQL,把组装的工作留给 Java 程序
使用 “Map 两次遍历法”。这种方法性能最好,时间复杂度接近 O(n)
第一次遍历:把所有查出来的节 ...
过期删除与缓存淘汰
一、过期键删除策略(针对“带 TTL 的 Key”)Redis 采用 “惰性删除 + 定期删除” 的组合策略,在 CPU 开销和内存占用之间取得平衡。
1. 惰性删除(Lazy Expiration)
触发时机:访问 Key 时(如 GET、SET、DEL等命令执行前)。
逻辑:在 db.c/expireIfNeeded()函数中,检查 Key 是否带有过期时间且已过期。如果是,则直接删除该 Key,然后视为 Key 不存在。
优点:CPU 友好,不浪费资源在没人访问的过期 Key 上。
缺点:内存不友好。如果过期 Key 永远不被访问,它们会一直占用内存(内存泄漏)。这就是为什么需要配合“定期删除”。
2. 定期删除(Active Expiration)
触发时机:时间事件触发(Redis 内部定时任务 serverCron,默认每 100ms 执行一次)。
逻辑(重点,面试常考):
随机抽样:从设置了过期时间的 Key 字典中,随机选出 20 个 Key(数量由 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_CRON定义)。
删除过期键:遍历这 20 个,删除其 ...
线程模型+持久化
线程模型Redis线程模型:
“关于 Redis 的线程模型,我是这样理解的:
首先,Redis 的核心命令执行是单线程的。它采用 Reactor 模式,基于 I/O 多路复用(如 epoll)来监听海量客户端连接。因为数据主要在内存中操作,且避免了多线程的上下文切换和锁竞争,所以单线程效率很高。
其次,Redis 6.0 引入了多线程 I/O。这是因为随着网络带宽增加,单线程处理网络读写成了瓶颈。现在的多线程主要用于并行处理网络数据的读写和协议解析,而命令的执行依然是单线程串行执行的,这样既提升了性能,又保证了命令的原子性。
此外,Redis 还有一些后台线程(BIO),用于处理像大 Key 删除、AOF 刷盘这样的耗时操作,防止这些操作阻塞主线程。”
追问准备:面试官可能会问:“既然单线程这么好,为什么不用多线程执行命令?”
回答:“主要是因为 Redis 的操作都是内存级的,CPU 通常不是瓶颈。如果改成多线程执行命令,为了保证数据一致性,需要引入复杂的锁机制,这会带来巨大的开销,而且会让代码变得非常复杂,得不偿失。目前的方案(单线程执行 + 多线程 I& ...
Redis集群
搭建
主从复制模式架构:1 个 Master + N 个 Slave,Master 负责读写,Slave 只读并异步复制 Master 数据
核心原理
全量同步:Slave 首次连接或主从断连过久,Master 执行 BGSAVE生成 ==RDB== 发给 Slave,再补发复制积压缓冲区写命令
复制积压缓冲区:如果是主节点写的时候还会把命令写到这个缓冲区里,用于快照RDB之后的数据同步
增量同步(PSYNC):正常连接后,Master 将写命令异步发送给 Slave(使用复制积压缓冲区)。
复制是异步的,Slave 数据存在延迟,有短暂不一致。
优缺点
✅ 数据冗余(备份)、可读写分离减轻 Master 读压力。
❌ Master 宕机需手动切换(人工 SLAVEOF NO ONE),恢复时间长。
❌ 写能力和容量仍受限于单机 Master。
哨兵模式(Sentinel)架构:在主从复制基础上,部署 Sentinel 集群(通常 ≥3 个奇数节点),专门负责监控、选主、通知客户端。
1234┌─ Sentinel1Client ...
