顺丰一面:缓存、索引、消息与 Agent 基础八股
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 你对 AI 全栈的理解是什么?
- 缓存三剑客(穿透、击穿、雪崩)分别是什么?怎么解决?
- Redis 的内存过期策略有哪些?
- MySQL 索引哪些情况下会失效?
- 消息重试和消息丢失怎么处理?
- 你对 Agent 的理解是什么?
- CAS 是什么?有什么问题?
- 线程池的原理和核心参数?
- 事务的隔离机制有哪些?
- 拷打实习和项目。
《参考解析》
缓存三剑客:① 穿透——查询根本不存在的数据,缓存永远不命中,请求直打数据库。解法是布隆过滤器拦截非法 Key,或对空结果缓存一个短 TTL 的空值(注意攻击者每次换 Key 会让空值缓存膨胀,需要限制 key 的生成规则或对来源限流)。② 击穿——某个热点 Key 过期瞬间大量并发同时打到数据库。解法是互斥锁(只让一个请求去重建)或逻辑过期(不设物理过期,异步刷新,读到旧值时返回旧值)。③ 雪崩——大量 Key 同时过期或缓存整体不可用。解法是过期时间加随机抖动、多级缓存、以及 Redis 高可用(哨兵/集群)。
Redis 过期策略:惰性删除(访问时检查是否过期,过期就删)+ 定期删除(每 100ms 随机抽取一批设置了过期时间的 Key 检查并删除)。两者结合的原因是纯惰性会让内存长期不释放,纯定期扫描又太耗 CPU。内存达到 maxmemory 时的淘汰策略有八种:noeviction(写入报错)、allkeys-lru/lfu/random/ttl、volatile-lru/lfu/random/ttl。做缓存一般用 allkeys-lru,做”有过期时间的会话”用 volatile-*。注意 LRU 在 Redis 里是近似实现(采样后淘汰),LFU 更适合有明显热点的场景。
消息重试与不丢:不丢要三段保证——生产者确认(Kafka acks=all + 重试、RocketMQ 同步发送)、Broker 持久化与多副本、消费端手动 ACK(处理成功再提交位点)。重试要有纪律:指数退避 + 随机抖动,避免重试风暴;设置最大重试次数,超过进死信队列并告警;重试必须幂等(否则会重复扣款、重复发货),靠业务唯一 ID + 去重表/唯一索引 + 状态机式条件更新实现。另外注意重试的语义边界:网络超时场景下”重试”可能造成重复执行,所以幂等键要在第一次请求前就生成并复用。
CAS 及其问题:Compare-And-Swap 是一条 CPU 原子指令,比较内存值与期望值,相等才写新值,用来实现无锁并发(AtomicInteger、AQS 的入队)。三个经典问题:① ABA——值从 A 变 B 又变回 A,CAS 会误判为没变,解法是加版本号 AtomicStampedReference;② 自旋开销——高竞争下大量线程空转,LongAdder 用分段降低竞争;③ 只能保证单个变量的原子性,多变量需要借助锁或封装成对象。
线程池与隔离级别:线程池七参数——核心线程数、最大线程数、空闲存活时间、时间单位、工作队列、线程工厂、拒绝策略;执行流程是”核心线程未满就新建 → 队列未满就入队 → 最大线程未满就新建 → 触发拒绝策略”。生产上强调用有界队列(LinkedBlockingQueue 默认容量是 Integer.MAX_VALUE,会把内存吃满),并按业务隔离线程池。事务隔离级别是读未提交(脏读)、读已提交(不可重复读)、可重复读(幻读,MySQL 默认)、串行化;MySQL 靠 MVCC 实现一致性读、靠 next-key lock 压住当前读的幻读。