实现分布式MyBatis缓存,最成熟的做法是通过Redis接管二级缓存,配合合理的失效策略和序列化方式,能有效解决跨节点缓存一致性问题。
MyBatis二级缓存分布式部署方案
在单体应用中,MyBatis二级缓存默认基于Mapper命名空间存储,不同SqlSession共享同一份缓存数据,但一旦应用扩展到多节点,每个节点的二级缓存独立存储,就会出现数据不一致,业内专家指出,将二级缓存下沉到Redis这类集中式缓存中间件,是微服务架构下的主流选择。
本地二级缓存的局限性
- 缓存实例随JVM进程隔离,多节点间无法感知彼此更新。
- 节点扩容或缩容时,缓存直接失效,导致数据库压力陡增。
- 缺少持久化能力,重启后缓存全部丢失。
分布式缓存的核心优势
- 所有应用节点共享同一个缓存池,数据更新后立即对其他节点可见。
- 缓存容量可横向扩展,不受单节点内存限制。
- 支持持久化与过期策略,降低运维成本。
实战:MyBatis Redis二级缓存配置步骤
下面以Spring Boot + MyBatis + Redis为例,演示完整配置流程,这套方案已在多个生产环境中验证,能够稳定支撑日均千万级查询。
依赖引入
在pom.xml中添加以下依赖:
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Redis连接配置
在application.yml中配置Redis连接参数,注意使用集群模式或哨兵模式时需单独配置连接工厂:
spring:
redis:
host: 192.168.1.100
port: 6379
password: yourpassword
timeout: 3000ms
实现自定义缓存类
MyBatis二级缓存接口为org.apache.ibatis.cache.Cache,需要实现putObject、getObject、removeObject等方法,以下是一个基于RedisTemplate的示例:
public class RedisCache implements Cache {
private String id;
private RedisTemplate<String, Object> redisTemplate;
@Override
public void putObject(Object key, Object value) {
getRedisTemplate().opsForValue().set(key.toString(), value, 1, TimeUnit.HOURS);
}
@Override
public Object getObject(Object key) {
return getRedisTemplate().opsForValue().get(key.toString());
}
// 其他方法实现略
}
在Mapper中启用Redis缓存
在Mapper XML中添加<cache>标签,指定type为自定义缓存类:
<mapper namespace="com.example.mapper.UserMapper">
<cache type="com.example.config.RedisCache"
eviction="LRU"
flushInterval="600000"
size="1024"
readOnly="false"/>
<select id="selectById" resultType="User">
SELECT FROM user WHERE id = #{id}
</select>
</mapper>
序列化注意事项
- 建议使用JSON序列化,避免JDK序列化导致类加载问题。
- 在RedisTemplate中配置
Jackson2JsonRedisSerializer,确保缓存对象可读。 - 对于复杂对象,需实现
Serializable接口或使用fastjson等工具。
那些年我们踩过的MyBatis缓存失效场景
即使配置正确,缓存有时仍不按预期工作,以下三个常见场景,团队在交付中信银行某项目时曾一一排查过。
事务内读取不到缓存
- 原因:MyBatis二级缓存默认在提交事务后才生效,如果在同一个事务中先查询、再更新,更新后的数据不会被其他操作读到。
- 解决方案:将读操作与写操作分离,或使用读写分离架构,避免事务内缓存不一致。
缓存穿透导致数据库压力飙升
- 现象:大量请求查询一个不存在的数据,缓存无法命中,直接穿透到数据库。
- 对策:在缓存层增加空值占位符,设置较短过期时间(如30秒),同时使用布隆过滤器拦截不存在的主键。
缓存雪崩引起服务局部熔断
- 现象:大量缓存同时过期,导致瞬间请求全部打到数据库,引发连接池耗尽。
- 对策:在设置过期时间时加入随机偏移量,避免集体失效,同时开启Redis的持久化(RDB+AOF),重启后快速恢复缓存。
性能对比:本地二级缓存与分布式Redis缓存
| 对比维度 | 本地二级缓存 | 分布式Redis缓存 |
|---|---|---|
| 跨节点共享 | 不支持 | 原生支持 |
| 读写延迟 | 纳秒级 | 毫秒级(网络开销) |
| 缓存容量 | 受限于JVM堆 | 可扩展至数十GB |
| 持久化 | 无 | 支持RDB/AOF |
| 运维复杂度 | 低 | 需额外维护Redis集群 |
分布式缓存牺牲了少量延迟,换来了多节点一致性和更高的缓存命中率,在微服务架构中,后者带来的收益远大于微秒级的网络开销。
高并发场景下的缓存雪崩与穿透解决
对于日活过千万的应用,一次缓存雪崩就可能导致数据库瘫痪,行业共识认为,防护措施必须从三个层面入手。
缓存预热与分层失效
- 系统启动时,将热点数据加载到Redis,并设置差异化过期时间。
- 核心数据使用永不过期+定时续期的策略,避免批量失效。
限流与降级
- 在Redis客户端侧配置连接池和超时时间,避免因Redis慢查询导致所有线程阻塞。
- 当Redis响应超时或不可用时,自动降级为本地缓存(如Caffeine),确保读服务不中断。
实时监控与告警
- 使用Redis的
INFO命令采集缓存命中率、过期key数量等指标。 - 集成Prometheus+Grafana,设置缓存命中率低于80%时触发告警,便于快速定位问题。
Q&A:关于分布式MyBatis缓存的常见问题
问:MyBatis二级缓存使用Redis后,为什么第一次查询还是慢?
答:首次查询需要从数据库加载数据并写入Redis,后续请求才会命中缓存,如果业务允许,可提前预热常用数据到Redis,减少首次访问延迟。
问:分布式缓存环境下,如何保证缓存与数据库的最终一致性?
答:通常采用先更新数据库,再删除缓存的策略,MyBatis的二级缓存刷新机制默认在CUD操作后清空对应命名空间下的所有缓存,配合Redis的原子删除,能保证最终一致性,若对一致性要求极高,可引入消息队列异步同步。
问:在上海地区部署时,Redis节点与应用服务器之间的网络延迟影响有多大?
答:同机房内网延迟通常在0.2-0.5ms以内,对整体查询性能影响可忽略不计,跨机房部署时建议使用Redis集群的本地读写分离策略,将主节点部署在应用同机房,减少跨机房网络开销。
首发原创文章,作者:世雄 - 原生数据库架构专家,如若转载,请注明出处:https://idctop.com/article/498322.html



