type
Post
status
Published
date
Mar 11, 2026
slug
interview-qa
summary
八股进行时
tags
开发
category
技术分享
titleIcon
password
icon
insider
暑期八股
Redis
单线程模型
核心
Redis 是“核心执行单线程 + 周边能力多线程/子进程”的模型。
缺点
- 大 Key、慢命令、Lua 脚本、阻塞命令都会卡主线程,导致延迟抖动。
什么不是单线程
- 多线程
- unlink - 异步删除
- AOF - fsync
- 网络socket IO(小包很多,网络读写开销大)
- 多进程
- bgsave - RDB
- bgrewriteaof - AOF重写
性能 - 为什么快
- 基于内存,CPU 不是主要瓶颈
- 数据结构实现简单高效
- IO多路复用epoll
- 单线程执行命令,没有锁竞争、线程切换成本低
缓存异常与治理
缓存治理
├─ 穿透
│ ├─ 参数校验
│ ├─ BloomFilter
│ └─ 空值缓存
├─ 击穿
│ ├─ 分布式锁
│ ├─ 互斥重建
│ ├─ 逻辑过期
│ └─ 异步刷新
├─ 雪崩
│ ├─ TTL 抖动
│ ├─ 预热
│ ├─ 多级缓存
│ ├─ 限流降级
│ └─ Redis 高可用
├─ 热点 key
│ ├─ 逻辑过期
│ ├─ 永不过期+主动刷新
│ ├─ 本地缓存
│ └─ key 拆分
└─ 一致性
├─ Cache Aside
├─ 写库后删缓存
├─ MQ/binlog 失效
└─ 强一致场景慎用缓存
- 大 Key - String value > 1KB | 集合 > 5k元素 总和 > 100MB
问题:慢(大key阻塞读写,网络带宽,内存占用,主从,持久化)
异常 - 穿透 | 击穿 | 雪崩 | 热key | 大key | 一致性
穿透:查不存在的数据,请求一直打DB
击穿:热点key失效并发回源
雪崩:大量 key 同一时间失效/redis宕机,整体回源流量冲垮后端
热点 key:个别 key QPS 极高,缓存本身或重建路径被打爆
一致性与更新治理:缓存和数据库如何更新、回源、重建、失效
协商缓存 - 客户端数据 + 版本号 读取时协商
击穿与热key治理
- 逻辑过期 + 互斥重建(DB限时)| 本地缓存 | 拆分热点key(读时聚合,写时分散)
雪崩治理
- TTL抖动 + 限流降级 + 预热 + 高可用(主从 哨兵)+ 本地缓存
穿透治理
- 参数校验(订单号长度,商品id格式)+ 限流(IP,用户) + BloomFilter + 空值缓存(TTL可短些)
一致性治理
- 延迟双删 | CDC/MQ
IO多路复用
- select | poll | epoll
数据结构
- String | List | Set | Hash | Zset
- 底层实现 listpack(7/ziplist(
- SDS 压缩列表 跳表 复杂度
- 应用场景 排行榜
大热key
- 大key
- 热key
- 治理
数据淘汰
- lru lfu
集群
- 脑裂
持久化
- AOF AOF膨胀 AOF重写
- RDB
- 混合
应用
- 分布式锁(Redisson实现原理,红锁替代)
- 布隆过滤器(误判率)
- 一致性(双删,CDC)
- lua原子执行业务逻辑(读 判断 写)| Redis 事务:解决“多条命令一起执行”
MySQL
逻辑SQL顺序
- FROM -> ON -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT
- from on join 表形成
- where 筛选
- group by 分组 having 分组筛选
- select 结果集
- distinct去重 order by 排序 limit 限制
变长字段格式
- compact(旧,留768) | dynamic(新)
页碎片、分裂、合并
- 索引非自增(二级索引) | 更新变长字段 - 插入中间 - 页分裂
- 删除 - 有效数据低于阈值 - 相邻页合并
实践
- 逻辑删除 - 更新is_deleted字段,查询带字段
- 避免了 B+ 树结构的破坏和页合并;保留了历史数据供审计。
- 依然是更新操作(可能引发页分裂);表会无限膨胀,查询越来越慢;导致二级索引体积庞大。
- 适合数据量不会随时间无限增长的核心业务表。
- 冷热数据分离 / 定期归档(Archiving)
- 定时任务在业务低峰期(如凌晨 3 点),把 3 个月前的老数据批量
INSERT INTO 历史表,然后从原表中批量DELETE。 - 配套动作: 批量
DELETE产生大量碎片后,必须定期(如每周/每月)执行OPTIMIZE TABLE table_name;(或ALTER TABLE table_name ENGINE=InnoDB;)。 - 原理:
OPTIMIZE TABLE会锁表(虽然现阶段有 Online DDL,但仍有开销),然后重新整齐地编排整张表的数据,重建 B+ 树,回收所有碎片空间。
- 使用表分区(Partitioning)
- 做法: 针对日志、流水、订单等按时间无限增长的数据,不采用传统的
DELETE,而是按月或按天建分区表(Partition Table)。 - 如何删除? 当需要清理一年前的数据时,直接执行
ALTER TABLE drop PARTITION p_202201;。 - 直接在文件系统层面删掉对应的底层
.ibd数据文件。瞬间完成,不产生任何碎片,不触发页合并,不影响其他分区的 B+ 树结构
存储引擎
- InnoDB vs MyISAM(事务不完整)
事务
ACID
- A - undo log + 2PC(redo|bin)
- C - 一致性实现 A/I/D + 约束 + 正确业务逻辑共同保证
- I - 隔离性实现
MVCC + 锁
- D - redo log WAL + doublewrite(恢复半写入页) + 落盘fsync(bin|redo)
隔离级别
- (RU RC RR Serializable 脏读 不可重复 幻读 - MVCC + gap/nextkey锁索引)
- 锁 2PL 行锁 间隙锁 Next-Key锁
MVCC
- 一行数据留多个版本,读历史版本减少快照读与写冲突;
- 行隐藏信息:最后修改trx_id,undo log指针
ReadView
- 快照读时,事务提交与活跃状态的快照,决定当前事务可见版本
- min [active] max
WAL
- 内存更新页,日志落盘后commit,随后刷盘
- 随机写改顺序写,延迟数据页刷盘(结合doublewrite保障刷盘完整,先顺序写doublewrite,落盘,然后根据doublewrite(LSN redo log更新)落实际位置,查checksum确认是否需要)
- 性能更高 更安全
索引
B+树优势
- 支持范围查询;
- 天然有序,支持
order by/group by/ 区间扫描;
- 更适合磁盘/页存储,层高低,IO 次数少;
- 叶子层顺序扫描效率高,适合大量连续数据读取;
- 支持最左前缀匹配,复合索引利用率高。
对比b树
- B+ 树非叶子节点只存键,不存行数据,单页能放更多 key,树更矮;
- 所有数据都在叶子节点,更适合范围扫描。
建索引原则
- 索引优先建在:
where / join / order by / group by的高频字段。
- 优先给区分度高的字段建索引,低基数字段(如性别)通常价值低。
- 遵守 最左前缀原则,尽量做覆盖索引,减少回表。
索引失效
- 索引列函数计算
- 联合索引不满足最左前缀原则
- %开头模糊匹配
- 隐式类型转换
索引下推
- Server层 WHERE 条件中包含的索引条件,下推到存储引擎层,在扫描索引时就先过滤一部分数据,减少回表次数。
explain - extra - Using index condition索引下推
SQL调优
深分页
LIMIT offset, size 要先扫前 offset+size 条,再丢掉前 offset 条。- 游标分页(where排序键的最后一项)
- 延迟关联(索引覆盖扫完拿主键再最后join回表)
- 预计算页锚点(单调可预测)
join分析
1)看 EXPLAIN ANALYZE
<TEXT>
-> Nested loop inner join
-> Table scan on t
-> Single-row index lookup on p using PRIMARY (id=t.id)
在 Nested Loop Join 里:
第一个子节点 = 外层表 / 驱动表
第二个子节点 = 内层表 / 被驱动表
所以这里很明确:
Table scan on t → t 是驱动表
Single-row index lookup on p using PRIMARY (id=t.id) → p 是被驱动表
2)看传统 EXPLAIN
<TEXT>
1,PRIMARY,<derived2>,...,ALL,...,Using temporary; Using filesort
1,PRIMARY,p,...,eq_ref,PRIMARY,PRIMARY,8,t.id,...
同一个 id=1,表示同一个 SELECT 块。
在 MySQL 传统 EXPLAIN 里:
同一个 id 下,从上到下就是 join 顺序
第一行先访问,后面的表被前面的结果驱动
所以这里:
<derived2> 先访问
p 后访问
也说明:
<derived2> / t 是驱动表
p 是被驱动表
另外:
<TEXT>
ref = t.id
也已经直接告诉你:
p 是拿着前面 t 产生的 id 去查的
这就是被驱动表的典型特征。
TODO:热点账号高并发付款,加行锁导致堆积甚至超时,怎么解决 一个账号请求串行化 说一下
explain analyse
定位流程
###
SQL 查询性能急剧降低,可能有什么原因
SQL 突然变慢,先看计划有没有变,再看是不是被锁住了。
- 看慢查询日志
Query_timeLock_timeRows_examined
- 跑
EXPLAIN/EXPLAIN ANALYZE - 看有没有全表扫描
- 看估算行数和实际行数差异
- 看锁
SHOW ENGINE INNODB STATUSperformance_schema
- 看统计信息
- 必要时
ANALYZE TABLE
- 看资源
- CPU / IO / Buffer Pool / 连接数
explain
-id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
- type - 访问方式 - const(唯一索引常量比较) | eq_ref(唯一索引等值join) | ref(索引常量比较) | range(范围比较)| index(全索引) | all(全表)
- possible_keys - 索引候选
- key - 索引
- key_len - 索引长度 - 联合索引
- rows - 预估扫描行数
filtered - 过滤留存百分比
- extra - Using index | index condition | where | filesort | temporary | join buffer
- type - key - key_len - rows - extra
- 用
EXPLAIN FORMAT=TREE或EXPLAIN ANALYZE把 join 结构打印成树(推荐):对 Nested loop join:Nested loop节点下
- 第一个 child 是驱动表(outer input)
- 第二个 child 是被驱动表(inner input,通常是 index lookup)
EXPLAIN ANALYZE 还能看 loops:- 驱动表通常
loops=1
- 被驱动表
loops≈驱动表实际输出行数(或按块/批次)
日志
- bin log - Server 层、偏逻辑、用于主从复制/恢复
- undo log - MVCC,事务回滚
- redo log - InnoDB 层、偏物理、用于崩溃恢复
innodb_flush_log_at_trx_commit
- 2PC - InnoDB redo log prepare | MySQL Server binlog | InnoDB redo log commit
- 崩溃恢复 prepare [binlog] commit
- group commit
JVM
内存模型/运行时数据
- 线程独占:栈(HotSpot合并native)PC 线程共享:堆 方法区(旧永久代在堆里内存受限,元空间实现动态)
JVM调优
- 堆大小
- 直接内存
类加载生命周期
- 加载(Loading) → 链接(Linking:验证、准备、解析) → 初始化(Initialization) → 使用(Using) → 卸载(Unloading)
- 加载 - 1. 通过类的全限定名获取二进制字节流 2.将字节流转成运行时数据结构 3.在堆中生成一个 java.lang.Class 对象
- 链接 - 验证:确保这个类的字节码是合法、安全、符合 JVM 规范的,不会危害虚拟机。 准备:为类变量(也就是 static 变量)分配内存,并设置默认零值。解析:把常量池中的符号引用,替换成直接引用。解析可以在初始化之后按需进行,也就是延迟解析。
- 只有“主动使用”类时才会初始化。常见触发条件:
(1)创建类实例
(2)访问类的静态变量
(3)调用类的静态方法
(4)通过反射使用类
(5)初始化子类时,先初始化父类
(6)JVM 启动时的主类
双亲委派
- 防重复 防篡改
- Bootstrap → Platform → Application → 自定义
- 类加载机制 流程
- 类加载器层次
- 打破双亲委派(SPI - JDBC|SLF4j ContextClassLoader,热部署热更新,新的cl加载新版本旧的类与cl一起回收,Tomcat多版本依赖)
- findClass() | getParent().loadClass() // 业务类/插件类优先自己加载(child-first)
GC
CMS G1 ZGC
维度 | CMS | G1 | ZGC |
设计目标 | 最小化老年代停顿 | 可预测停顿 + 大堆通用 | 极低停顿 |
堆结构 | 传统分代:新生代/老年代 | Region 分区,逻辑分代 | 分区化(ZPages/Regions),新版本支持分代 |
回收方式 | 并发标记 + 清除 | 标记 + 复制整理(Evacuation) | 并发标记 + 并发搬迁 |
停顿特点 | 初始标记/重新标记短停顿,但 Full GC 风险高 | Young/Mixed GC 有停顿,可调但不是硬保证 | 停顿通常极短,目标基本与堆大小弱相关 |
屏障机制 | 主要写屏障,增量更新 | SATB + RSet 写后屏障 | 着色指针 + Load Barrier(分代版再加 store barrier) |
典型问题 | 碎片、浮动垃圾、concurrent mode failure | RSet 开销、Humongous 对象、仍有 STW | CPU/内存开销更高,吞吐可能略低 |
版本地位 | JDK9 弃用,JDK14 移除 | JDK9 起默认 | JDK11 引入,JDK15 生产可用,JDK21 有分代 ZGC |

第一段直接访问到的GC Roots 第二段重标记
GC Roots
- 活跃线程栈帧中的局部变量引用 - Object obj
- 已加载类的静态字段引用 - static
- JNI 引用 - native (Object obj)
- 活跃线程对象 - new Thread().start()
- 监视器锁相关引用 - synchronized(obj)
- JVM 内部保留引用 - 常量池
GC算法
- Young - 复制
- Old - 标记清除 | 标记整理
- Mixed
GC种类
- CMS G1 ZGC
- 分代理论 各自特点 算法 是否STW
- 读写屏障
手撕
- 多线程交替打印ABC
- 反转区间链表 x2
- 环形链表
- 链表反转
- topK x2
- 并发安全单例模式
- LRU - ttl
- 课程表(拓扑排序)
- 出现次数超过一半的数字
- 岛屿数量
- 合并两个有序数组 x2
- 最接近的三数之和
- LRUCache
- 一道回溯 已知元素:1 2 3 4,查出所有形如这样的组合:1 12 123 124 1234 13 134 14 2 23 234 24 3 34 4
- 二叉树高度
- 删除链表的倒数第 N 个结点
- 反转二叉树(迭代和层序遍历)
JUC
逃逸分析
- 对象会不会逃出当前方法?return | 字段赋值 | 传参其他方法保存 | 集合
- 会不会被别的线程访问,那就属于线程逃逸。
- 用于锁消除(如果某个锁对象根本不会被别的线程看到,那加锁就没意义,可以去掉。)
- 标量替换(如果一个对象不会逃逸,JVM 甚至可能连对象都不真创建,直接把它拆成几个普通变量用。)
- 栈上分配
JMM
JMM(Java Memory Model)规定的是:
- 哪些重排序是允许的
volatile、synchronized、final各自提供什么保证
happens-before成立时,线程间应该看到什么结果
可见性问题由来:
- 1)编译器/JIT 会重排
- 2)CPU 会乱序执行
- 3)多核各自有缓存
Synchronized
锁升级
- JVM 根据锁竞争强度,动态选择“偏向线程、CAS 自旋、阻塞唤醒”三种不同成本的同步方式。
偏向锁(17移除) | 基本只有一个线程反复进出 | “这把锁偏向某个线程” |
轻量级锁 | 多线程交替访问,但竞争不激烈 | CAS + 自旋,尽量不阻塞 |
重量级锁 | 多线程激烈竞争 | 进入 ObjectMonitor,线程阻塞挂起 |
无锁
↓
偏向锁
- 适合单线程反复进入
- 对象头记录线程ID
- 同一线程重入几乎零成本
↓(其他线程来竞争,撤销偏向)
轻量级锁
- 线程栈创建 Lock Record(旧mark word)
- CAS 抢锁
- 抢不到先自旋
↓(自旋失败 / 竞争激烈)
重量级锁
- 膨胀为 ObjectMonitor(重入次数 | owner | 阻塞队列 | wait队列)
- 线程阻塞挂起
- 涉及上下文切换,用户内核态转换,成本最高
作用
- 实现原理 - monitorenter monitorexit ACC_SYNCHRONIZED字节码 信息存在对象头的Mark Word(hashCode | GC年龄 | 偏向线程ID | 栈帧LockRecord指针 |ObjectMonitor指针)
- 方法锁 this(实例)/class(static) 自定义锁控制粒度+封装
JVM 是通过对象头的 Mark Word 来记录“这个对象现在是什么锁状态”的。
对比volatile
- volatile的happens-before: volatile写 - 类似monitorexit | volatile读 - 类似monitorenter,最后写,最先读,保障其他变量的可见性
volatile:可见性 + 有序性
synchronized:可见性(同锁的happens before) + 有序性 + 原子性(复合操作count++) + 互斥
- 双重检查单例(DCL)里必须给
instance加volatile,因为第一次if (instance == null)是在锁外读的;只靠synchronized,这个锁外读没有和初始化线程建立happens-before,可能读到半初始化对象
对比ReentrantLock
- 超时、可中断、公平、多个条件队列时选
Lock
CAS
- 自旋锁 - 空转浪费CPU
- ABA问题 - +时间戳,版本号等单调序列区分
AQS框架
ThreadLocal
- 弱引用Key - 内存泄露
- 应用场景 - MDC,用户信息
- 用完一定
remove(),最好放在finally里。
- 每个
Thread对象内部都有一个ThreadLocalMap。
ThreadLocal作为 key,线程隔离的数据作为 value。
线程池
线程的三种创建方式
- 继承
Thread
- 实现
Runnable
- 实现
Callable+Future/FutureTask/ExecutorService
Runnable 和 Callable 区别?
Runnable无返回值,不能抛受检异常;
Callable有返回值,可抛异常。
参数与流程
- 参数 - 核心 最大 时间 单位 队列 拒绝策略 线程工厂
- 流程 - 核心 → 队列 → 最大 → 拒绝
- 设置考虑 - 1 + W/C(W-等待,DB RPC Lock 阻塞 IO | C- CPU火焰图 Arthas)
N = Ncpu × Ucpu × (1 + W/C)。这个公式的推导逻辑是:一个线程并不是一直在用 CPU,它在一个任务周期里只有C/(W+C)的时间真正占用 CPU。为了让 CPU 达到目标利用率,就需要更多线程去填补等待时间,于是推导出1 + W/C这个放大系数。
- 拒绝策略 - CallerRuns Abort Discard DiscardOldest
- 阻塞队列 - ArrayBlockingQueue可控
核心业务拒绝:
- 拒绝时打日志、埋点、告警
- 必要时落库 / 发 MQ / 延迟重试 / 降级补偿
任务异常如何捕获
- execute submit
- 方式 1:任务内部自己 try-catch
- 方式 2:submit + Future.get()
- 方式 3:重写线程池 afterExecute() - 统一日志;统一告警;对 execute/submit 都能兜底
数据结构
- ConcurrentHashMap 并发原理 1.7 Segment 1.8 不支持null key/value,无法从返回值稳定地推断状态,get containsKey中间可put remove
- HashMap非并发安全 | ConcurrentHashMap - put覆盖数据 1.7头插成环
设计模式
- 代理 AOP的动态代理 - cglib jdk
- 策略 支付
- 单例 Bean 双重校验
- 工厂 三层 循环依赖
- 模板 AQS
JDK
- 值传递 引用传递 - 值传递,基本类型,对象引用
- 面向对象 封装 继承|组合 多态
21新特性
异常
受检异常(Checked Exception)
编译器强制你处理的异常,比如:
IOException
SQLException
ClassNotFoundException
你必须:
try-catch捕获,或者
- 在方法上
throws声明继续往外抛
非受检异常(Unchecked Exception)
运行时异常,不强制处理,比如:
NullPointerException
ArithmeticException
IllegalArgumentException
它们属于
RuntimeException 及其子类。IO
- BIO AIO NIO
数据结构
HashMap
- 底层 - 数组 链表 红黑
put:算 hash 扰动 高 16 ^ 低 16 → 定位桶(n - 1) & hash → 无冲突直接放→有冲突链表追加;当64链表长度8时可能树化→扩容容量一般按 2 倍扩,阈值 =capacity * 0.75
Spring
bean生命周期
- 实例化
- 依赖注入
Aware回调
4.BeanPostProcessor#postProcessBeforeInitialization5.初始化方法
6.BeanPostProcessor#postProcessAfterInitialization7.Bean 可用
8.容器关闭时执行销毁回调(单例)
循环依赖
IOC
- 控制反转:对象的创建、依赖组装、生命周期管理不再由你
new,而是交给容器(Spring IoC 容器)完成;你只“声明”依赖关系。
IoC 容器做什么
- 扫描/注册 Bean | 实例化 Bean、注入依赖(DI) | 生命周期回调(
Aware、BeanPostProcessor、init/destroy) | 织入增强(AOP 代理通常也发生在创建阶段)
- DI(依赖注入)是 IoC 的实现方式 - 构造器注入 | Setter/字段注入,可循环依赖
AOP
- 把“横切逻辑”(日志、监控、事务、权限、重试、限流…)从业务逻辑里剥离出来,以统一方式织入
- Spring AOP 是基于代理(proxy-based)的运行时 AOP(不是AspectJ编译期织入)。
- 代理方式:基于接口JDK动态代理 基于继承CGLIB
两个常见坑
- 自调用失效:同一个类内部
this.xxx()调用不会走代理,@Transactional/@Cacheable可能不生效解决:拆到别的 bean;或通过AopContext.currentProxy()
- final 类/方法:CGLIB 不能覆盖 final 方法(某些增强无法应用)。
循环依赖
- @Lazy延迟拿对象,启动时先不形成硬依赖
- 拆成第三个 Bean,把公共逻辑抽出去,A/B 都依赖 C,不再互相依赖
- 字段/Setter 注入 + allow-circular-references,让 Spring 走三级缓存兜底
自动装配
- 核心链路:
@SpringBootApplication- → 包含
@EnableAutoConfiguration - → 导入
AutoConfigurationImportSelector - → 加载候选自动配置类
- → 根据条件注解决定是否生效
- → 注册 Bean
- Spring Boot 3
- 候选自动配置类主要来自
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports - 老版本常问的是
spring.factories,要区分开。
- 自动装配的核心不是“自动”,而是条件装配
- 典型条件:
@ConditionalOnClass@ConditionalOnMissingBean@ConditionalOnProperty@ConditionalOnWebApplication
- 一句话总结
- Spring Boot 自动装配 =“把一堆可能需要的配置类先准备好,再根据当前 classpath、配置项、已有 Bean 按条件决定装不装。”
自定义starter
Starter 本质:
依赖聚合 + 自动装配- 一般拆成两个模块:
xxx-spring-boot-autoconfigure:放自动配置代码xxx-spring-boot-starter:空壳依赖聚合
- 写属性类:
@ConfigurationProperties(prefix="xxx")
- 写自动配置类:
@AutoConfiguration+@EnableConfigurationProperties+ 条件注解常用:@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty
- 注册自动配置:
- Spring Boot 3:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports - Spring Boot 2:
META-INF/spring.factories
- 用户一引入 starter,就能自动注入 Bean;用户自己定义同名/同类型 Bean 时,要能让位。
- 💡 关键概念展开:
- 核心设计目标:开箱即用,但不过度侵入。所以一定要加
@ConditionalOnMissingBean。 - 命名细节:第三方一般命名
xxx-spring-boot-starter,不要抢spring-boot-*前缀。
- 最小记忆骨架就 3 个点:
@ConfigurationProperties、@AutoConfiguration、AutoConfiguration.imports(Boot 2 用spring.factories)。
- 怎么自定义 starter?
- 配置属性类ConfigurationProperties
- 写自动配置类(条件装配);
注解
- 原理
- 常用注解
Spring的架构原理
其他
如果你的第一个项目qps × 10或者100,你会有哪些优化?
2.操作系统基础:线程调度的算法。
5.数据库:分布式数据库如何分表
6.了解编译器优化指令重排吗
说说对 DDD 架构的理解
滑动窗口限流和令牌桶限流有什么区别,各自的优劣
4.HashMap线程不安全的原因及CurrentHashMap线程安全的原因
9.redis哈希槽
8.简单介绍ES
- 讲讲 HashMap 的扩容机制,还有 ConcurrentHashMap 的原理和线程安全是怎么保证的?
- 注解的实现原理是啥?
- 一个类调用自己内部类上的注解,这个注解会生效吗?
- 遇到高并发页面访问,有哪些解决方案?
Web
输入 URL 到显示网页,发生了什么
- 浏览器解析 URL → 协议、域名、端口、路径、参数 查缓存
- 浏览器缓存、DNS 缓存、可能还有 Service Worker / HSTS DNS 解析域名 -> IP
- 建立连接发送请求
3.http和tcp在tcp\ip模型的哪一层
4.tcp的三次握手过程
5.浏览器的跨域访问,跨域限制,原因
TCP | UDP
- TCP - 面向连接 | 可靠传输 | 有序 | 有重传、确认、流量控制、拥塞控制 | 面向字节流
- UDP - 无连接 | 不保证可靠 | 不保证顺序 | 没有重传/确认 | 面向报文
OS
进程、线程、协程
- 区别
死锁
- 条件
- 避免
rpc如果发送超时怎么解决
怎么保证数据一致性
分布式事务怎么实现的
线上jvm是怎么设置的
4.红黑树特性
5.红黑树使用场景、b树使用场景
- 3. 抽象类和接口的应用场景
java中sort用的是什么排序
快排
Redis 和数据库的更新策略是啥?怎么保证数据一致性?
问了实习中用到的设计模式(责任链和策略模式有啥区别)
- 4. 如何设计银行存取款和转账业务的具体实现,除了加锁还有什么?
- 5. 本地测试没问题,上线之后出问题了,怎么排查是哪方面的问题
- 6. 索引优化
1、你怎么理解Java和Go这两种语言的区别?
2、你个人更喜欢哪种语言?
3、谈谈Java和Go在GC(垃圾回收)方面的区别?
4、Java为什么要设计成分代回收这种机制?
5、新生代和老年代分别用的是什么清除算法?
6、新生代和老年代的默认比例是多少?
7、xx项目中,任务切片的具体细节是怎样的?
8、分库分表的分表键是怎么设计的?为什么分128张表?
9、为什么使用RocketMQ而不是Kafka?
10、除了异步和削峰,使用MQ还有什么考量?
11、关于自研限流器:为什么公司没有现成的组件需要自己写?
12、设置令牌桶参数时,有没有考虑“预热”?
13、限流和熔断的区别是什么?分别作用在服务端还是客户端?
14、本地缓存(LocalCache)如何保证集群一致性?(即怎么通知所有机器清除缓存)雪花算法(Snowflake)生成的ID结构?存在什么问题?
15、分布式锁的超时时间设为多少?为什么?
16、锁超时了怎么办?
17、看门狗(Watchdog)机制,如果有1000个线程,需要开1000个守护线程吗?
18、乐观锁和悲观锁的区别?乐观锁(CAS)有什么问题?
19、Java中如何在不重启JVM的情况下修改一个类的结构?(HotSwap)关于热更新:
21、Java线程的状态有哪些?IO操作时线程处于什么状态?
22、数据库聚簇索引和非聚簇索引的区别?
23、MySQL有哪些日志?Redo Log是在存储引擎层还是服务层?
redis大key是什么,你有什么解决方案吗,你认为多大是大,会有什么影响,如何拆分呢,(完全不会)
redis实现滑动窗口的方法,做什么的知道吗
使用mq的注意事项,幂等性的实现方法(要两种以上)
策略模式的演化,是从什么模式演变过来的
Kafka的消息重平衡
explain怎么分析,看什么关键指标
springboot使用到的设计模式,哪里用到了设计模式
1 aop是怎么做的
2 zset的底层数据结构是什么样 跳表如何完成一次查询? 时间复杂度?
3 你这里说点赞流程加锁控制并发加的是什么锁 答基于redis的分布式锁 问 那这样不会有什么问题么 再答这种实现的缺点 然后介绍后面采用了redission 顺便说了redission源码
4 @async有什么问题 和自定义线程池怎么选 你用过线程池么 一般如何考虑参数 异步返回值如何处理
1 spring用到了哪些设计模式?责任链设计模式介绍下?
2 redis的常用数据结构和底层实现
1.怎么删除redis的大key
2.分布式锁是怎么实现的
3.消息队列是否支持读写分离?
4.mysql的事务和并发控制,mvcc是怎么实现的
5.如果服务器中有大量的time_wait状态,是怎么引起的,要怎么解决
6.怎么防止sql注入
7.负载均衡算法有哪些
8.cpu使用率飙升怎么排查
9.数据库和缓存一致性要怎么保证
10.如何设计一个短链系统
11.联合索引设计题,有A、B、C三个字段,有两个查询语句(select * from XXX where B = 200 and C > 100,select * from XXX where A = 300 and B = 200 and C > 100)大量 执行,问怎么设计联合索引?
12.4TB的文件中全是int32的数值,如何在2GB的内存中快速找出某一个数是否存在?用bitmap的话,需要占用多少内存
13.2GB的文件中全是int64的数值,如何在只有128MB的内存中找出前100大的数字
3.redis 大key问题 如何在不拆分大key的情况下读取key scan命令
4.审批流程 A->B->C ABC可以互换 适合用什么设计模式设计 责任链模式
5.线程池八股
6.caffeine原理
7.数据库缓存一致性
第三次挥手后客户端是什么状态?为什么?
Redis数据不一致的问题
用Redis分布式锁做了什么
如果使用分布式锁的客户端挂掉了,会发生什么
Redis分布式锁底层是怎样的
Redission原理
HashMap
如果HashMap非常大,扩容时候要耗费长时间,你自己设计一种方案来解决这个问题
Redis集群了解过吗,让你设计一个Redis集群,怎样做
2.RAG有哪些复杂索引?有哪些类型,原理是什么
3.大模型微调技术有哪些,LoRA的原理是什么,为什么可以降秩
4.虚拟内存是什么
5.哲学家就餐问题是什么,如何解决这个问题
6.计算机执行1+1打印出2背后做了些什么
7. 怎么保证发出一个文件,就是最终能够发送成功,而且能够恢复,且所有的数据都不丢失
8.Go协程和线程本质区别
9.索引为什么用B+树
10.B+树与红黑树的区别
- 🔍 慢查询定位与优化 线上遇到过慢查询吗?怎么定位问题的?怎么优化的?
- 📂 MySQL索引建立原则 一般你们怎么建Mysql索引,基于什么原则,遇到过索引失效的情况么,怎么优化的?
- 🔄 自增ID设计 你们建表会定义自增ID吗?为什么?自增ID用完了怎么办?
- 📝 MySQL写入流程 介绍一下Mysql写入数据的流程,越详细越好。
- 💾 Redis持久化策略 Redis线上的持久化策略是怎么样的?你们是多久同步一次的?
- 📊 Redis Set与ZSet Redis的Set有了解过么?底层的数据结构是怎么样的?和Zset有啥差别?
- ⏳ Redis崩溃与数据恢复 系统在11:05 设置一个值,并给出5分钟的过期时间,系统刚刚Set完之后redis集群崩溃,11:11分系统重启成功,那么Redis中Set的值是否还存在?
- 📦 消息队列模型对比 项目中用到的消息队列的消息模型是怎么样的?Kafka、RabbitMQ有啥区别么?
- 🚀 消息队列必要性 项目中这些场景不用队列行不行?会有什么问题吗?
- 🔍 Kafka高吞吐与一致性 Kafka一致性、可靠性,为什么能做到高吞吐,消息积压怎么处理,如何保证消费有序?
- 🛠️ RPC调用流程 RPC框架调用的流程是怎么样的?讲一下核心步骤。
- 🔥 热榜功能设计 一个热榜功能怎么设计,怎么设计缓存,如何保证缓存和数据库的一致性?
- 🎨 工厂模式实现 写一个工厂模式。
- 💡 四则运算求值 口述算法,四则运算表达式怎么求值。
- 前后端如何交互
- 服务之间如何交互
- 登录业务怎么做的
- 高并发怎么做的
- mysql索引依然很慢怎么办
- MySQL的一页怎么在Redis里面
- 从前端url到显示的整个流程
- jvm回收策略,设置过参数吗?
- spring的自动装配等注解
- springAOP
- 乐观锁分布式锁讲一讲
- 实习时遇到的项目难点如何解决?
- 实习时遇到的同事压力甚至刁难如何应对?
- 库存不足怎么解决的
- 还有更好的办法吗
- 分库分表的思路
- 为什么会有缓存击穿的问题?如何解决
- ArrayList扩容机制,存在哪些问题
- contains方法的执行原理
- 多线程你平时怎么使用
- mysql清空一张表有几种方式
- 遍历分页查询一个非常大的表,有几种比较快的
- 联合索引怎么用
- 强制使用索引的关键字
- 乐观锁的sql
- 用redis做分布式锁一般怎么用
- hashmap是怎么执行contains的
- 16 多线程场景下,主线程需要同步所有子线程执行结果需要怎么做
- 写sql语句的注意事项
- mysql的事务特性
4、Redis的基础数据结构
5、如何解决秒杀系统异步下单导致redis和MySQL数据不一致的问题(我答的是秒杀系统主要关注的是超卖和一人一单问题,对Redis和MySQL的强一致性要求不高,只需要最终结果保持一致性就行。然后他又问如果高并发下kafka消息堆积导致CPU飙升怎么办 ,我就答调整kafka配置或者优化MySQL扣库存语句,但是他好像不是很满意)
6、介绍一下本地缓存 (答caffine 但是因为我技术栈没用到所以没太看本地缓存的相关知识 所以他也没有追问)
7、介绍一下RPC协议(因为技术栈没用到分布式 所以也没看)
问我是否了解GC(新生代/老年代,标记清除算法,Minor/Full GC)
问我了解java内存模型吗?(这块没说清除,应该回答JMM那一套)
CAS(我说了下原理)
本地缓存(我大概了解caffeine)
问我了解哪些ai技术(我说了RAG\Function call\MCP,然后让我说下RAG,我说了下RAG的原理)
- ==和 equals 的区别 - 常见字符串操作集合有哪些,有什么区别 - hashmap 底层 - set list map 区别,顺序和能否元素重复 - nio bio aio 区别 - bio:同步阻塞 IO 模型,对 于 IO 读写操作必须阻塞在一个线程中完成 - nio:同步非阻塞 IO 模型,NIO中引入了Channel(通道)和Buffer(缓冲区)的概念。面向缓冲区的读取和写入,都是与Buffer进行交互,多用于高并发高负载的网络 - aio:异步非阻塞 IO 模型,基于事件和回调机制实现,IO 处理后会直接返回不会阻塞,当后台操作结束后,操作系统通知相应线程进行后续处理 - 保证线程安全的集合 - currenthashmap - vector - CopyOnWriteArrayList - 读操作不加锁,写操作加锁 - ConcurrentLinkedQueue - 非阻塞队列,通过 cas 实现线程安全 - BlockingQueue - 实现类ArrayBlockingQueue(容量不可变)、LinkedBlockingQueue(单向链表容量可变)、PriorityBlockingQueue - sleep 和 wait 的区别 - spring 中 bean 的作用域 - 异常的类型 - 1/0 什么异常 ArithmeticException
TCP连接泄露有什么现象
- (项目相关)动态刷新线程池有什么需要注意的吗
- 创建一个线程要多少内存
- 如果是虚拟线程的话,做动态线程池设置参数要考虑什么
- Redis怎么设计更新与失效策略
- 缓存与数据库的一致性设计方案
- 说一下延迟双删
- ES 的倒排索引
- 给定一个关键词进行ES查询,结果不好,怎么优化查询
MQ
消息异常
丢失
打包一系列SQL语句为一个整体,关注原子性、一致性、隔离性和持久性
IOC/AOP、自动配置快速开发、web应用开发和微服务管理
url从输入到响应的全流程
http、DNS、TCP/UDP、IP、ARP和FCS
电商场景的秒杀库存系统设计一下
前端请求拦截/CDN、网关限流/身份验证、Redis缓存热点数据和库存进行预扣减、MQ异步、乐观锁更新数据库数据
跨平台运行、内存管理优化;虚拟机栈、本地方法栈、程序计数器、堆和元空间
JVM垃圾回收机制
G1分代垃圾回收
6、讲一下JUC并发编程
7、谈谈synchronized 和reentrantlock 的它的一个核心区别
9、你熟悉那个 spring boot 那个执行流程吗?就是能说一下 spring boot 启动时它的一些核心的步骤,了解不?
10、自己实现一个 spring boot 的那个 start 该如何Z做?
11、spring 的这个 AOP 跟 IOC 这一块了解吗?那它的应用场景一般有哪些容器?就是什么情况下会用到这个项目里面?
13、k8s了解吗?
14、如何来设计实现一个短链接系统
15、RAG了解吗?工作流程是什么?
16、Function call、MCP了解么
17、谈谈你对agent的了解和看法
为什么“商户详情”这类数据容易出现缓存击穿问题?
解决缓存击穿时,为什么不直接使用 Redis 的物理过期(TTL),而是采用逻辑过期?
项目

- 防止超卖 - 条件更新原子SQL
- 当系统真正去执行“超时取消订单”这个逻辑时,需要校验哪些事情才能最终取消?
- 你的项目中使用了延迟队列来实现订单超时取消,相比于轮询方案,它的优点在哪里?
- 轮询方案除了增大数据库压力外,还有什么缺点?
- 如果用 Redis 实现互斥锁,具体可以使用什么命令? 使用 SET NX 命令实现分布式锁会有什么弊端?(涉及主从一致性等问题)
snowflake
- 1 bit 符号位
- 41 bits 时间戳差值 69年
- 10 bits workerId 1024节点
- 12 bits sequence 4096/ms/节点
- (timestamp,workerId,sequence)
- 时间不能倒退、节点不能撞号、同一毫秒内序号不能溢出
- 回拨幅度很小,比如
<= 5ms或<= 10ms,那就直接等待到lastTimestamp。
- 回拨较大:直接报错,不发号,报警,把节点摘掉
- 预留回拨标识位
dubbo
- 长连接复用
- 二进制协议/高效序列化
TCC
[无记录]
| Try
v
[TRIED] -------- Confirm --------> [CONFIRMED]
|
| Cancel
v
[CANCELED]
[无记录] -- Cancel --> [CANCELED] // 空回滚标记
[CANCELED] -- Try --> 拒绝执行 // 防悬挂
[TRIED] / [CONFIRMED] / [CANCELED]
重复收到同阶段请求 --> 幂等返回
- 三个幂等 - “重复调用”;空回滚和悬挂 - “乱序调用”。
AI
架构
Transformer
- 自注意力 - 并行计算;长距离建模;
- QKV - 视频查询案例
- d_k - 方差缩放,稳定梯度
MOE
- 少量推理计算增加,增大模型总参
- 门控计算token与各个FFN部分权重决定激活
- 训练损失带负载均衡
训练流程
Pretraining
- 无监督文本块进行next token predication
- 数据清洗与配比
- Scale - 参数 数据量
SFT
- 对话对文本,指令遵循,QA(仅对A loss)
- 关注数据质量 多样性
- 小学习率
DPO
- 不需Reward Model
- CE优化好坏对,对基模KL约束
- 数据靠同prompt好坏配对(网页app要求用户反馈),控制排版与长度
LoRA微调
- 冻结原始权重,只在特定层旁路训练低rank矩阵,降低优化状态显存占用
- 推理部署可以将权重加回去
蒸馏 (Distillation)
- 能力迁移 - 多黑盒蒸馏,调优秀模型API获取生成结果(回答,思维链)作GT来SFT学生模型
推理优化
量化
- 降低存储体积,提高读取速度
KV Cache
- 旧上下文内容的prefill复用
- GQA(多q共享kv)降低显存
- 拆kv为块减少碎片
响应优化
WebSocket与SSE
- SSE - HTTP单向长连接,支持文本流推送
- 可短线重连(Last-Event-ID)
工程实现
回复中断
- 发TCP RST
多租户
- 前缀匹配
断线重连
- 自动重连带上SSE last-event-id
- 一致性哈希打到同一台机器
1. Transformer
- 核心:Self-Attention 建模 token 间关系
- 优势:并行强、长依赖好、可扩展
- 瓶颈:长序列 O(n2)
O(n2)O(n^2)
- 追问点:RoPE、PreNorm、FlashAttention、GQA
2. MoE
- 核心:总参数大,但每个 token 只激活少数 expert
- 一般替换 FFN,不替换 attention
- 难点:Router、负载均衡、all-to-all 通信
- 优势:提高容量;缺点:工程复杂
3. Pretraining
- 核心:海量语料自监督学习,下一个 token 预测
- 重点:数据清洗、去重、tokenizer、scaling law
- 作用:决定模型能力天花板
4. SFT
- 核心:把基础模型变成“会按指令回答”的助手
- 数据比量更重要
- 重点:assistant loss、多轮格式、行为一致性
5. DPO
- 核心:用 chosen/rejected 做偏好优化
- 相比 RLHF/PPO 更简单稳定
- 难点:偏好数据质量、beta、reference model
6. LoRA
- 核心:冻结原模型,只训练低秩增量
- 优点:省显存、省参数、适合多任务
- 拓展:QLoRA
7. 量化
- 核心:低比特表示,省显存和带宽
- PTQ vs QAT
- 不一定一定更快,依赖硬件和 kernel
8. KV Cache
- 核心:缓存历史 K/V,避免重复计算
- 主要加速 decode,不是 prefill
- 难点:显存占用、长上下文、调度管理
9. WebSocket / SSE
- SSE:单向,适合文本流
- WebSocket:双向,适合实时交互
- 流式输出优化的是体验,不是模型本体速度
7.分布式锁怎么实现的?锁的过期时间设置?下游接口响应慢锁过期怎么办?超时时间有考虑过full gc吗?(goland特性stw时间极短,没考虑gc)
8.其他的分布式锁方案?(mysql,Zookeeper)
9.ZK做分布式锁了解吗?
11.SpringBoot常见注解?
声明式事务失效
编程式事务
12.Transaction注解失效情况?
@autowired @resources
13.Ioc和aop通俗易懂描述一下?
14.哪些注解实现aop了?
16.是否遇到mysql主从延迟情况?
乐观锁如何实现
17.主从延迟如何获取的?(没测过)
19.三次握手是否可以改成两次或四次?
20.Tcp和udp使用场景
9. Redis 数据结构
10. Redis zset 结构 和 应用场景
4.为什么不能全用快照读,或者不能全用当前读
6.一个事务还没提交,其他事务能看见吗
7.varchar(20)和varchar(10000)都存”woshishabi“的时候会发生什么区别
8.用violatile是怎么避免指令重排的,举个代码例子说明下,这段代码的字节码是怎么样的,执行顺序是怎么样的,用了只会,顺序可能会变成什么样
9.你设计一个服务,你会涉及成rpc,还是涉及成mq,怎么权衡
10.手撕,判断一个数组中,有没有一个元素是另外一个元素的两倍
- 2. Object类有哪些方法
- 3. 责任链模式使用场景
- 4. 线程池怎么使用?
- 5. 线程池,实现1加到100
Redis主要使用过哪些数据结构,常用的指令有哪些
你在项目中使用的淘汰策略是什么
Mysql的优化有哪些
Mysql的底层的数据结构
手写一下简单工厂
消息队列使用场景,消息队列流量削峰值原理,
redis过期底层原理实现
一人一单的下单实现 set key lua脚本,订单id
JVM调优会不会,CPU爆掉了怎么排查?
线程池参数和怎么分配
rag,transformer
redis类型和使用业务场景
怎么aicoding
多个mq怎么保证有序性传输
redis持久化
项目复盘
核心流程
- 交易主链先用一次性买入 Token + Redis+Lua 做高并发前置预扣
- 本地事务沉淀订单事实,并通过 Outbox + Canal CDC + RocketMQ 顺序消息,把 Redis 预扣收敛成数据库库存与订单状态事实
CONFIRM之后分成两条主分支:- 支付成功链用 Seata AT 统一收敛订单、支付、库存与持仓,再在事务提交后触发上链;
- 超时/取消链用 RocketMQ 事务消息驱动关单与库存回补,最后再靠对账、补偿与限流把整条链治理收口。
流程
入口filter
- 放filter消费一次性token防请求重放(redis lua get del)→ 幂等身份放threadlocal
预扣减
- /trade/buy → 预扣减(redis Hash流水幂等,藏品id作Key,一次token作Field,Value为流水json字符串 String库存 校验流水 校验库存 扣减库存 记录流水)
建单
- 幂等or成功就本地事务建单(订单主表unique(buyer_id, token),订单流水unique(order_id, stream_type, sourceEventId) ,outbox unique(event_no - 订单id+事件类型),buyer_id为分片(outbox与订单)键,撞了查单),返回订单id
- CDC回调relay,定时兜底扫,发MQ(orderId顺序键串行重复投递)
确认
- 消费者校验订单状态,create推进/paid补齐confirm
- 插库存流水unique(collection_id, order_id, stream_type),扣DB可售库存
- 查订单,跑状态机,启事务插订单流水unique(order_id, stream_type, sourceEventId),CAS改订单状态
支付
- /trade/pay → 插新支付单to_pay unique(biz_type, biz_id),幂等冲突查单按状态返回 → 支付渠道取url,推进状态paying
- 支付回调 → 支付单paid状态+渠道+渠道流水幂等重复通知(不同的走退款)→ 推进订单支付状态 → 增加已售DB库存 + 建持仓 → 支付单更新状态 → aftercommit上链(定时任务兜底)
退款
- 落推款单 → 调外部渠道 → 回调推进退款单
超时关单(?)
- 定时任务 → 事务消息 → 回补DB 回补Redis
outbox定时任务补偿
上链
- 限流 → 幂等插unique(biz_id, biz_type, out_biz_id)操作记录 → 调用上链 → 更新操作表状态
- 定时查询 → 重试/成功发MQ → 改持仓状态
超时关单回补
表相关
改进汇总
选型
- redis + lua 而不是 分布式锁 → 避开 rtt开销 与 服务器阻塞
- 减小redis压力 1.拦无效流量 - 本地缓存标识位 2.库存分片(多个key)3.削峰填谷(网关Sentinel限流,MQ排队)
TCC空回滚 悬挂
如果预扣后宕机 - 扫 Redis 预扣流水里存在但 DB 中没有对应订单的进行回补 Redis
订单
流水
outbox
• 订单状态机吸收乱序
- 主路径 Canal:NEW/RETRY -> SENDING -> SENT;失败:SENDING -> RETRY/DEAD,并 ack 位点
- fallback 路径:NEW/RETRY/(超时 SENDING) -> SENDING -> SENT/RETRY/DEAD
- NEW 不是“准备发”,而是“这条订单事实已经提交成功,并且存在一条待传播事件”;只要还没确认发到 MQ,它就可以一直停在 NEW。
- SENDING 不是最终业务状态,也不是主路径事务状态;它只是 fallback 补发时的抢占中间态,相当于“这条 outbox 先被某个 worker 领走处理了”。
- RETRY 不是主路径每失败一次都马上改出来的,而是 fallback 补发时明确失败后,才进入 RETRY 并带上下一次重试时间。
关单回补
- redis lua反向操作
下单
- 是否还在售,是否价格无变化

表 幂等键
流水 幂等键
redis 幂等键
DDL
减可售与推CONFIRM - - RocketMQ 消费失败重投(主路径)
调整库存减库存DB后redis前宕机 - 超单
改进:
去锁create避免每单固定引入一笔 Redisson 加锁成本
- | 步骤 | 域/动作 | 业务幂等键 | 流水幂等键(落库/落Redis) | 兜底裁决点 | |------|--------|------------|----------------------------|------------| | 0 | 一次性买入 Token 消费(网关/交易入口) | tokenKey | - | Redis GET+DEL(一次性) | | 1 | Redis 预扣(Lua decrease) | buyRequestId(token 中的幂等号) | Redis Hash field = buyRequestId(inventory:stream:{goodsId}) | HEXISTS 判重 | | 2 | 建单(insert-first) | (buyerId, buyRequestId) | trade_order.identifier = buyRequestId | 订单表唯一约束(buyerId+identifier) | | 3 | 建单流水(CREATE) | buyRequestId | (orderId, CREATE, buyRequestId) | 订单流水唯一约束(orderId+type+identifier) | | 4 | Outbox 事件落库(订单事务内) | eventNo = orderId:ORDER_CREATED | outbox.event_no = eventNo | outbox 唯一约束(event_no) | | 5 | relay/CDC 投递 MQ | eventNo + orderKey | 消费侧靠各自流水键吸收 | 重投 + 消费幂等 | | 6 | 扣减可售库存(trySale,DB) | orderId | (collectionId, orderId, TRY_SALE) | 库存流水唯一约束 + 条件更新(saleable>=qty) | | 7 | 订单确认(CONFIRM) | orderId | (orderId, CONFIRM, orderId) | 订单流水唯一约束 + 状态机/条件更新 | | 8 | 生成支付链接(payUrl) | payDedupKey = bizNo+payerId+payChannel | pay_order.pay_dedup_key | 支付单唯一约束(pay_dedup_key) | | 9 | 支付成功回调入账 | payCallbackId 或 payChannel#channelStreamId | pay_order / pay_order_stream | 渠道流水唯一约束 + 状态判断 | | 10 | 推进订单已支付(PAY->PAID) | payChannel#channelStreamId | (orderId, PAY, payChannel#channelStreamId) | 订单流水唯一约束 + 状态机 | | 11 | 最终占用库存与建持仓(confirmSale) | orderId | (collectionId, orderId, CONFIRM_SALE) | 库存流水唯一约束 | | 12 | 链上触发(afterCommit) | outBizId = heldCollectionId 或 orderId | chain_operate_info 幂等键 | 过程表唯一约束(biz_id+biz_type+out_biz_id) | | 13 | 链结果回写持仓(INIT->ACTIVED) | operateInfoId/txHash | held_collection 状态推进键 | 状态机 + 幂等更新 | | 14 | 持仓激活驱动订单完结(FINISH) | heldCollectionId | (orderId, FINISH, heldCollectionId) | 订单流水唯一约束 | | 15 | 超时关单(TIME_OUT) | orderId | (orderId, TIME_OUT, orderId) | 订单流水唯一约束 + 状态机 | | 16 | 用户取消关单(CANCEL) | orderId | (orderId, CANCEL, orderId) | 订单流水唯一约束 + 状态机 | | 17 | 关单回补库存(DB+Redis) | orderId | (collectionId, orderId, CANCEL_SALE) + Redis 回补 Lua 流水 | 库存流水唯一约束 + Lua 幂等 | | 18 | 对账/补偿任务 | diffKey(按场景) | 复用以上各流水键 | 所有补偿动作都必须幂等化 |
改进
- 前端刷新详情页 - 请求一个一次性token
- 下单一次 - 请求一个一次性token - 置灰跳转等待order_id轮询状态
- 移除一次性token在预扣减的分布式锁 → 每个请求都吃一次rtt → 重复请求实际走不到(一次性token在filter处lua脚本get del,不会用到第二次)
- redis预扣减流水 - 原版Field:buyerId + "" + identifier + "" + itemCount;
hset(streamKey, identifier, {
action = "decrease",
from = current,
to = new,
change = qty,
by = identifier,
timestamp = now
})
问题:
- itemCount 拼进幂等键,语义被放大
- value 里重复塞 identifier,但没有分片路由键buyer_id → Redis 孤儿预扣对账时,只拿 identifier 查订单会广播
改进版:identifier
preInventoryDeduct(collectionId, itemCount, preDeductIdentifier, buyerId);
hset(streamKey, identifier, {
action = "decrease",
identifier = identifier,
buyerId = buyerId,
from = current,
to = new,
change = qty,
timestamp = now
})
改进点:
- field = identifier,Redis 幂等裁决最干净
- value 冗余 buyerId,对账可按 (buyerId, identifier) 单点路由
- 和订单唯一键 unique(buyer_id, identifier) 完全收敛
- 不再把 itemCount 拼进幂等键
临时
UT | 日志排错 | code review
ipv4 32 ipv6 128 报头精简 取消分片 取消校验和 不需DHCP
双栈 隧道
Java仅值传递 - 基本的值 对象地址的值
B+ 非叶仅主键id 仅叶子节点放数据 双向链表串联 单页可更多索引 更矮胖磁盘IO少 范围查询只需链表遍历不需要走中序遍历
vs Hash 范围查询 | 模糊匹配 | 哈希碰撞 (且也有AHI,不完全冲突)
职业规划 - AI相关 | 业务理解
从设计模式角度,创建对象的方法
工厂方法:屏蔽具体实现类创建。
建造者模式:复杂对象分步骤创建,避免构造器参数爆炸。
用栈模拟队列 - 双栈 一入一出,读时导出入到出的里面(容量需要注意)
- 责任链
- 模板方法
- 策略 + 工厂
- 建造者
- Facade(门面)
- 状态机
- 观察者 / 事件监听
- 代理 / AOP
- 过滤器链
【5】 讲一下 put 的实际操作
- 🎯 核心作答:
- 先对 key 做 hash 扰动。
- 如果 table 还没初始化,先初始化。
- 根据下标找到桶:
- 桶为空:直接插入;
- 桶不空:判断是否同 key;
- 同 key:覆盖旧值;
- 不同 key:链表尾插或红黑树插入。
- 若链表长度达到树化条件,执行树化。
- 插入后
size++,超过阈值则扩容。
- 💡 关键概念展开:
- JDK8 扩容是 2 倍扩容。
- 节点迁移时不需要重新算完整 hash,只需要看:
- 留在原索引;
- 或移动到
原索引 + oldCap。
- ⚡ 追问预判:
- 追问1:key 相同是怎么判断的? -> 先比较 hash,再比较
==或equals()。 - 追问2:为什么 JDK8 用尾插? -> 避免 JDK7 头插在并发扩容下形成环链问题。
•
◦ 追问2:shutdown 和 shutdownNow 区别? ->
shutdown 不再接新任务,但会处理已提交任务;shutdownNow 尝试中断正在执行的任务并返回队列中未执行任务。- 作者:CamelliaV
- 链接:https://camelliav.netlify.app/article/interview-qa
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章

_long_hair_rope_shirakami_fubuki_white_hair.png?table=block&id=2dfca147-5df8-808a-a962-f2738172bca6&t=2dfca147-5df8-808a-a962-f2738172bca6)

_crying_dress_fire_long_hair_magic_pink_eyes_silver_palace_sword_tears_tiara_torn_clothes_weapon_white_hair.jpg?table=block&id=330ca147-5df8-8016-bfab-c65b121b06d9&t=330ca147-5df8-8016-bfab-c65b121b06d9)





![[2026.3.29]暑期笔试复盘](https://www.notion.so/image/attachment%3A3276fd3d-2a21-49b7-a5e7-4fb867dc7b06%3AG9BRMXrb0AMWXYp.jpg?table=block&id=338ca147-5df8-804b-b1b4-f0bb4ec2b823&t=338ca147-5df8-804b-b1b4-f0bb4ec2b823)
![[2026.4.3]暑期面试复盘](https://www.notion.so/image/attachment%3Ab7aa5da1-bd4b-4428-8931-1ca5096cf7a8%3AKonachan.com_-_399937_clouds_no_humans_original_signed_sky_tree_yu_jing.png?table=block&id=338ca147-5df8-80d6-b053-fcbedb3bc649&t=338ca147-5df8-80d6-b053-fcbedb3bc649)
![[2026.3.27]优质blog笔记](https://www.notion.so/image/attachment%3A663f9378-6675-43c0-b353-001be947f796%3AKonachan.com_-_399921_aqua_eyes_black_hair_braids_brown_eyes_brown_hair_flowerscur_gloves_gun_mask_pointed_ears_school_uniform_sketch_skirt_weapon.jpg?table=block&id=2b8ca147-5df8-8071-a9be-d74acf98e923&t=2b8ca147-5df8-8071-a9be-d74acf98e923)