Q:Redis和数据库的缓存一致性保证

A: 方案如下:

  1. 更新数据库之后删除缓存(旁路缓存模式

    并发情况下可采用延迟双删策略

    或者实现canal客户端订阅数据变更,更新缓存

    适合:商品详情页场景,读多写少的场景

  2. 先更新缓存再更新数据库(异步更新

    1. 更新缓存成功采用消息队列更新数据(消费幂等性保证)
    2. 场景:秒杀
  3. 双写操作

    1. 同时更新Redis和数据库,可以使用事务保证(分布式事务方案,事务补偿机制
    2. 场景:积分
  4. 数据回写

    1. 优先更新缓存,缓存数据定期更新到数据库(Redis集群和持久化机制)
    2. 场景:广告计费系统,点赞

Q:分布式锁实现方案

A:

  1. 本地同步锁
  2. 分布式锁setNX 设置超时时间
  3. redission看门狗机制,读写锁
  4. redisssion redlock 解决锁的高一致性问题

Q:如何快速定位线上OOM

A:出现的原因:

  1. 一次性申请的对象数量过多:如查询返回结果过大
  2. 内存资源耗尽未释放:如何jdbc连接、文件资源
  3. 本身资源不够:jmap -heap PID 查看堆信息

方案:

  1. 预先设置-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./java_logs 参数保证发生OOM时有堆信息输出
  2. 导出dump文件 jmap -dump:format=b,file=xushu.hprof PID

结合jvisualvm和idea进行查看,找过GCRooot 查看线程栈线程

Q:如何定位和避免死锁

A:

定位:

  1. jstack PID
  2. 业务日志

方案

  1. 破坏产生死锁的四大因素之一(除互斥)
  2. 银行家算法也能有效避免【预先计算分配资源】

Q:SpringBoot或Tomcat可以同时处理的最大请求数

A:accept-count + max-connections

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 12600
tomcat:
threads:
max: 200
min-spare: 10
# 最大等待数 当所有可能的请求处理线程都在使用时,传入连接请求的最大队列长度。
accept-count: 100
# 最大连接数 服务器在任何给定时间接受和处理的最大连接数。一旦达到限制,操作系统仍然可以根据“acceptCount”属性接受连接
max-connections: 8192
# 连接超时时间
connection-timeout: 3000

Q:Sentinel限流算法

A:

  1. 滑动窗口计数法(Sliding Window Counting)

    核心思想:将时间划分为多个小的时间窗口(如 500ms),统计每个窗口内的请求数量,并动态滑动窗口,避免固定窗口的“突刺问题”。

    1. 固定窗口计数法:将时间划分为固定大小的窗口(如 1s),统计窗口内的请求总数。缺点是可能出现“突刺”(例如前 1ms 内请求过多,后 999ms 被拒绝)
    2. 滑动窗口计数法:通过多个子窗口(如 20 个 50ms 的子窗口)动态统计流量,避免突刺问题。例如,Sentinel 默认使用 1s 的窗口,划分为 2 个 500ms 的子窗口。
    3. 优点
      1. 避免固定窗口的突刺问题,流量统计更平滑。
      2. 适用于 QPS 限流、热点参数限流等场景。
  2. 令牌桶算法(Token Bucket)

    核心思想:以固定速率生成令牌,存入令牌桶。请求必须获取令牌才能被处理。如果令牌桶满,多余令牌被丢弃;如果令牌桶空,请求被拒绝或等待

    **Sentinel 实现:**令牌桶算法:允许突发流量(桶容量决定突发流量大小)。预热模式(Warm Up):通过令牌桶的动态调整,实现系统冷启动时的流量预热。例如,从低阈值逐渐增加到设定阈值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public 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;
    }
    }
    }
  • 优点
    • 支持突发流量(令牌桶容量决定)。
    • 预热模式避免冷启动时系统被压垮。
  1. 漏桶算法(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
    24
    public 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; // 超时拒绝
    }
    }
    }
    }
    • 优点
      • 严格限制流量速率,适合对延迟敏感的场景(如数据库访问)。
  2. 快速失败模式(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:

  1. top命令查询CPU占用最高的进程
  2. top -H -p PID 找到CPU占用最高的线程
  3. printf ‘0x%x\n’ PID 将线程ID转为16进制
  4. jstack 进程PID | grep 16进制线程PID -A 20 找到进程中指定线程执行的状态和代码

Q:布隆过滤器解决缓存穿透的原理

A:Redis实现布隆过滤器功能 setbit bitmap原理

误判几率:

延迟位数长度

增加hash法的个数

Q:内存200M读取1G文件,统计文件内容

A:

  • 使用缓冲区分块读取 map缓存次数,如果存在大量不重复的情况 可考虑使用hash取模存储到多个文件中,逐个读取细分的文件综合统计

Q:new String(“abc”)会创建几个对象

A:两个对象

  1. new String() 预先创建一个对象引用,然后判断常量池中是否存在,创建常量池对象。然后拿到常量池对象初始化预先创建的对象引用
  2. 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+树的总数据量