1. MyBatis二级缓存深度解析
作为Java持久层框架的标配方案,MyBatis的二级缓存机制在实际项目中既能显著提升性能,也可能成为灾难性问题的源头。我在电商系统的高并发实践中发现,90%的二级缓存使用问题都源于配置不当。今天我们就来彻底拆解这些关键参数背后的设计哲学和实战要点。
二级缓存不同于一级缓存(SqlSession级别),它的作用域是Mapper Namespace,可以被多个SqlSession共享。这种共享特性带来了性能提升的可能性,也引入了线程安全、数据一致性等复杂问题。下面这个简单的配置示例包含了所有核心参数:
xml复制<cache
eviction="LRU"
flushInterval="60000"
size="1024"
readOnly="true"/>
2. 缓存回收策略的工程抉择
2.1 四种淘汰算法对比
eviction属性决定了内存不足时缓存对象的淘汰机制,这直接关系到系统在压力下的稳定性表现:
-
LRU(最近最少使用)
默认策略,基于访问时间排序。在内存受限场景下,它能有效保留热点数据。实测在百万级商品库的查询中,相比FIFO能提升约30%的缓存命中率。实现原理是维护一个访问顺序链表,每次访问数据时将其移到链表头部,淘汰时从尾部开始。 -
FIFO(先进先出)
简单队列结构,适合数据均匀访问的场景。但在电商这类存在明显热点数据的系统中表现较差,可能误伤高频访问的商品信息。它的优势是实现成本低,JDK的LinkedHashMap通过accessOrder参数即可控制。 -
SOFT(软引用)
基于JVM软引用机制,在内存不足时会被GC回收。这种策略看似智能但存在隐患:当缓存突然被大量回收时,会导致数据库瞬时压力激增。建议仅在缓存数据可丢失的统计类场景使用。 -
WEAK(弱引用)
比SOFT更激进的回收策略,只要发生GC就会被清理。除非是极其临时性的数据,否则不建议生产环境使用。
关键选择建议:常规业务选LRU,定时任务类批处理考虑FIFO,报表统计可用SOFT。内存敏感型系统需要配合size参数严格限制缓存大小。
2.2 参数调优实战
在日活百万的用户系统中,我们通过以下步骤确定了最优配置:
- 使用Arthas监控缓存命中率
- 逐步调整size直到内存占用稳定在70%水位线
- 对比不同eviction策略的QPS变化
- 最终确定组合:LRU + 2048个对象上限
3. 刷新间隔的精细控制
3.1 flushInterval的双刃剑
这个以毫秒为单位的参数控制着缓存自动刷新的频率:
xml复制<!-- 每分钟自动刷新 -->
<flushInterval="60000">
未设置时的表现:缓存仅在执行insert/update/delete操作时失效。这保证了强一致性,但高并发读场景下可能产生大量重复查询。
设置后的风险:定时刷新会导致缓存雪崩——所有缓存同时失效,数据库瞬时压力暴增。我们在会员系统曾因此引发过数据库连接池耗尽。
3.2 最佳实践方案
- 对实时性要求高的核心业务(如库存),不配置flushInterval
- 对可容忍短暂不一致的辅助数据(商品描述),设置随机化间隔:
java复制flushInterval="${30000 + random.nextInt(30000)}" - 结合Redis的过期时间分散策略,避免集中失效
4. 缓存大小的内存博弈
size属性指定缓存对象的最大数量,这个看似简单的参数需要精细计算:
4.1 容量规划公式
code复制建议size = (可用JVM内存 * 0.3) / 单个缓存对象平均大小
例如:8G堆内存,保留30%给缓存,平均每个订单对象50KB:
code复制(8 * 1024 * 0.3) / 0.05 ≈ 4915
4.2 避坑指南
- 避免使用Integer.MAX_VALUE这种危险值
- 监控缓存命中率,当低于60%时应考虑扩容
- 大对象要特别处理,比如分离LOB字段
- 使用WeakHashMap辅助监控缓存对象大小
5. 只读模式的性能玄机
readOnly属性涉及深拷贝与浅拷贝的经典权衡:
5.1 true模式(共享实例)
xml复制<readOnly="true"/>
优势:
- 零拷贝开销,性能提升约40%
- 适合配置类等不变对象
风险:
- 任何修改都会污染缓存
- 必须确保对象不可变
5.2 false模式(拷贝实例)
xml复制<readOnly="false"/>
实现机制:
- 序列化缓存对象
- 反序列化返回副本
- 需要对象实现Serializable
适用场景:
- 需要修改返回值的业务
- 线程安全要求高的环境
6. 生产环境全配置示例
结合电商系统的实战经验,推荐以下配置组合:
xml复制<cache
eviction="LRU"
size="2048"
flushInterval="180000"
readOnly="false"
blocking="true"
type="com.xxx.MyCustomCache"/>
关键增强点:
- 添加blocking防止缓存击穿
- 自定义缓存实现接入Redis
- 设置合理的刷新间隔
- 启用安全拷贝模式
7. 性能监控与问题排查
7.1 监控指标清单
| 指标项 | 健康阈值 | 监控工具 |
|---|---|---|
| 缓存命中率 | >70% | Prometheus |
| 平均加载时间 | <50ms | SkyWalking |
| 内存占用比 | <30%堆内存 | VisualVM |
| 淘汰频次 | <5次/分钟 | Arthas |
7.2 典型问题解决方案
缓存雪崩:
- 现象:数据库CPU突然100%
- 解决:阶梯式过期时间 + Hystrix熔断
脏读问题:
- 现象:看到别人未提交的数据
- 解决:设置statement级别的useCache="false"
内存泄漏:
- 现象:Full GC频繁
- 解决:限制size + 避免大对象
8. 高级定制方案
对于千万级以上的高并发系统,建议:
-
实现Cache接口接入Redis
java复制public class RedisCache implements Cache { // 实现所有接口方法 } -
使用二级缓存装饰器
java复制public class MetricsCache implements Cache { private Cache delegate; // 添加监控逻辑 } -
开发热加载机制
java复制void reloadOnChange() { // 监听配置中心变更 }
经过多个百万DAU系统的验证,合理的二级缓存配置能使数据库负载下降60%以上。但切记:任何缓存策略都要配套完善的监控体系,没有监控的缓存就像没有刹车的跑车。