业务方案总结
Q:Redis和数据库的缓存一致性保证
A: 方案如下:
更新数据库之后删除缓存(旁路缓存模式)
并发情况下可采用延迟双删策略
或者实现canal客户端订阅数据变更,更新缓存
适合:商品详情页场景,读多写少的场景
先更新缓存再更新数据库(异步更新)
- 更新缓存成功采用消息队列更新数据(消费幂等性保证)
- 场景:秒杀
双写操作
- 同时更新Redis和数据库,可以使用事务保证(分布式事务方案,事务补偿机制)
- 场景:积分
数据回写
- 优先更新缓存,缓存数据定期更新到数据库(Redis集群和持久化机制)
- 场景:广告计费系统,点赞
Q:分布式锁实现方案
A:
- 本地同步锁
- 分布式锁setNX 设置超时时间
- redission看门狗机制,读写锁
- redisssion redlock 解决锁的高一致性问题
Q:如何快速定位线上OOM
A:出现的原因:
- 一次性申请的对象数量过多:如查询返回结果过大
- 内存资源耗尽未释放:如何jdbc连接、文件资源
- 本身资源不够:
jmap -heap PID
查看堆信息
方案:
- 预先设置
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./java_logs
参数保证发生OOM时有堆信息输出 - 导出dump文件
jmap -dump:format=b,file=xushu.hprof PID
结合jvisualvm和idea进行查看,找过GCRooot 查看线程栈线程
Q:如何定位和避免死锁
A:
定位:
jstack PID
- 业务日志
方案
- 破坏产生死锁的四大因素之一(除互斥)
- 银行家算法也能有效避免【预先计算分配资源】
Q:SpringBoot或Tomcat可以同时处理的最大请求数
A:accept-count + max-connections
1 | server: |
Q:Sentinel限流算法
A:
滑动窗口计数法(Sliding Window Counting)
核心思想:将时间划分为多个小的时间窗口(如 500ms),统计每个窗口内的请求数量,并动态滑动窗口,避免固定窗口的“突刺问题”。
- 固定窗口计数法:将时间划分为固定大小的窗口(如 1s),统计窗口内的请求总数。缺点是可能出现“突刺”(例如前 1ms 内请求过多,后 999ms 被拒绝)
- 滑动窗口计数法:通过多个子窗口(如 20 个 50ms 的子窗口)动态统计流量,避免突刺问题。例如,Sentinel 默认使用 1s 的窗口,划分为 2 个 500ms 的子窗口。
- 优点:
- 避免固定窗口的突刺问题,流量统计更平滑。
- 适用于 QPS 限流、热点参数限流等场景。
令牌桶算法(Token Bucket)
核心思想:以固定速率生成令牌,存入令牌桶。请求必须获取令牌才能被处理。如果令牌桶满,多余令牌被丢弃;如果令牌桶空,请求被拒绝或等待
**Sentinel 实现:**令牌桶算法:允许突发流量(桶容量决定突发流量大小)。预热模式(Warm Up):通过令牌桶的动态调整,实现系统冷启动时的流量预热。例如,从低阈值逐渐增加到设定阈值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class WarmUpController {
private final double count; // 最大 QPS 阈值
private final long warmUpPeriodInSec; // 预热时间(秒)
private final double coldFactor; // 冷却因子
private final long warningToken; // 警戒令牌数
private final long maxToken; // 最大令牌数
private final double slope; // 斜率
public boolean canPass(int acquireCount) {
long currentTokens = storedTokens.get(); // 当前令牌数
if (currentTokens >= warningToken) {
// 超过警戒线,按预热公式计算允许的 QPS
double allowedQps = 1.0 / (aboveToken * slope + 1.0 / count);
return passQps + acquireCount <= allowedQps;
} else {
// 正常模式,按令牌桶算法处理
return passQps + acquireCount <= count;
}
}
}
- 优点:
- 支持突发流量(令牌桶容量决定)。
- 预热模式避免冷启动时系统被压垮。
漏桶算法(Leaky Bucket)
核心思想:请求进入漏桶后,以固定速率处理。如果漏桶满,新请求被拒绝或排队等待。
**Sentinel 实现:**排队等待模式(RateLimiter):允许请求排队等待,直到令牌桶有空间。例如,设置最大等待时间,超时则拒绝。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class RateLimiterController {
private final int maxQueueingTimeMs; // 最大等待时间
private final double count; // QPS 阈值
private final AtomicLong latestPassedTime = new AtomicLong(-1);
public boolean canPass(int acquireCount) {
long currentTime = System.currentTimeMillis();
long costTime = Math.round(1.0 * acquireCount / count * 1000); // 计算间隔时间
long expectedTime = costTime + latestPassedTime.get();
if (expectedTime <= currentTime) {
latestPassedTime.set(currentTime);
return true; // 立即通过
} else {
long waitTime = expectedTime - currentTime;
if (waitTime <= maxQueueingTimeMs) {
Thread.sleep(waitTime); // 等待
latestPassedTime.set(currentTime + waitTime);
return true;
} else {
return false; // 超时拒绝
}
}
}
}- 优点:
- 严格限制流量速率,适合对延迟敏感的场景(如数据库访问)。
- 优点:
快速失败模式(DefaultController)
基于滑动窗口统计 QPS,如果当前 QPS 超过阈值,直接拒绝请求。
Q:SpringBoot如何优化启动速度
A:
- 延迟初始化Bean:懒加载,或异步执行初始化任务
- 创建扫描索引,引入index依赖
@Indexed索引
,编译打包时Spring在项目自动生成META-INF/spring.componets文件维护索引,Spring应用启动时会执行ComponentScan扫描时,这个文件将会被CandidateComponentsIndexLoader读取并加载,转为CandidateComponentsIndex对象; - 去除不必要的自动配置和扫描范围
- 关闭Spring Boot的JMX监控
spring.jmx.enabled=false
- JVM参数关闭类验证
- AOP尽量不使用注解方式标记,会对所有方法进行扫描
- 排除不需要的依赖
- JDK17 新的特性支持更好的垃圾回收期,SpringBoot3的GraalVM支持编译成本地镜像文件,启动速度更快,内存占用更少
Q:SqlSessionFactory(线程安全)和SqlSession(不安全)是线程安全的吗
A:判断一个类是否是线程安全的
- 是否有共享的可变状态
- 是否使用了线程安全的数据结构
- 是否使用了同步机制
- 如果类中的共享资源是不可变的
Q:如何解决线程CPU飙高
A:
top
命令查询CPU占用最高的进程top -H -p PID
找到CPU占用最高的线程printf ‘0x%x\n’ PID
将线程ID转为16进制jstack 进程PID | grep 16进制线程PID -A 20
找到进程中指定线程执行的状态和代码
Q:布隆过滤器解决缓存穿透的原理
A:Redis实现布隆过滤器功能 setbit bitmap原理
误判几率:
延迟位数长度
增加hash法的个数
Q:内存200M读取1G文件,统计文件内容
A:
- 使用缓冲区分块读取 map缓存次数,如果存在大量不重复的情况 可考虑使用hash取模存储到多个文件中,逐个读取细分的文件综合统计
Q:new String(“abc”)会创建几个对象
A:两个对象
- new String() 预先创建一个对象引用,然后判断常量池中是否存在,创建常量池对象。然后拿到常量池对象初始化预先创建的对象引用
- String s = “abc” 直接在常量池中创建对象
Q:百万计数据插入优化
A:
方案:多线程分批导入,线程池 IO密集型 cpu空闲时间长 可以将线程数据设置 (核心线程数=cpu个数 * 2, 最大线程数 = cpu个数 * 4)
Q:单表多少数据量需要分表
A:阿里巴巴推荐:一般来说推荐单表行数超过500万行或者单表容量超过2G,推荐进行分表;(如果三年内数据量达不到这个级别,创建时不建议进行分表)
分析: B+树索引由一页一页数据(16KB)组成,结构如下 , 所以有效的数据空间 16384 -200 = 16184字节
名称 | 中文名 | 占用空间大小 | 简单描述 |
---|---|---|---|
File Header | 文件头部 | 38字节 | 页的通用信息 |
Page Header | 页面头部 | 56字节 | 数据页专有信息 |
Infimum + Supremum | 最小记录和最大记录 | 26字节 | 两个虚拟行记录 |
User Records | 用户记录 | 不确定 | 实际存储的行记录内容 |
Free Space | 空闲空间 | 不确定 | 页中尚未使用的空间 |
Page Directory | 页面目录 | 不确定 | 记录的相对位置 |
File Trailer | 文件尾部 | 8字节 | 校验页完整性 |
所以一般来说:所有磁盘页的数量 * 一页的数据行数 (数据行数 = 有效页大小 / 一行设计数据大小 ) == 三层的B+树的总数据量