在分布式架构中,MyBatis的本地二级缓存会因多实例部署导致数据不一致,而将Redis作为共享缓存层是唯一能兼顾性能与一致性的方案,通过自定义缓存实现,开发者可以无缝切换,无需改动业务代码。
为什么分布式环境下MyBatis本地缓存不够用
本地缓存的数据不一致问题
当系统部署多个节点时,每个节点拥有独立的MyBatis二级缓存,一个节点上的写操作只更新自己的缓存,其他节点依然返回旧数据,这种“一写多读”的错位在用户请求量大的场景下会频繁引发数据回滚和业务异常,行业共识认为,本地缓存无法在分布式网络中维持全局一致性,这是推动缓存改造的首要原因。
本地缓存的性能瓶颈
本地缓存占用JVM堆内存,随着数据量增大,GC压力上升,影响应用响应速度,而Redis基于内存存储,读写性能极高,且支持横向扩展,能支撑更大并发,多数情况下,单台Redis实例的吞吐量就远超应用节点本地缓存的上限。
mybatis二级缓存与Redis缓存方案对比
将两者并列对比,可以清晰看到各自短板与长处,MyBatis二级缓存实现简单,但在分布式场景下几乎不可用;Redis缓存方案需要额外配置,但能解决数据一致性和扩展性问题。mybatis二级缓存和redis哪个好这个问题的答案不是二选一,而是整合:利用Redis作为二级缓存的存储引擎,既保留MyBatis的缓存机制,又获得Redis的分布式能力。
| 对比项 | MyBatis本地二级缓存 | Redis缓存方案 |
|---|---|---|
| 数据一致性 | 多实例下不一致 | 全局共享,自动一致 |
| 扩展性 | 受限于节点内存 | 支持集群,弹性伸缩 |
| 持久化 | 无 | 提供RDB/AOF持久化 |
| 实现复杂度 | 简单 | 需要自定义缓存实现 |
MyBatis Redis缓存配置步骤详解
引入Redis客户端依赖
在pom.xml或build.gradle中添加Jedis或Lettuce库,推荐使用Lettuce,因为它支持异步和集群模式,性能更稳定,示例依赖:

<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.3.2.RELEASE</version>
</dependency>
实现MyBatis的Cache接口
创建一个类继承org.apache.ibatis.cache.Cache,重写putObject、getObject、removeObject、clear等方法,在putObject和getObject中调用Redis命令,关键代码片段:
- 使用Redis的
SET和GET命令存储和读取缓存数据。 - 序列化方式选用JSON或Protobuf,避免Java原生序列化带来的性能开销和兼容问题。
- 设置过期时间,防止缓存无限增长。
配置二级缓存为自定义实现
在Mapper XML文件中添加<cache>标签,type属性指向自定义缓存类的全限定名:
<cache type="com.example.cache.MyRedisCache" eviction="LRU" flushInterval="60000" size="1024" readOnly="false"/>
注意:flushInterval和size等参数由自定义缓存内部处理,此处配置仅作参考。
配置Redis连接参数
通过mybatis-config.xml或Spring的@Configuration类设置Redis连接信息,建议使用连接池,并指定超时时间。
RedisClient client = RedisClient.create("redis://password@host:6379/0");
StatefulRedisConnection<String, String> connection = client.connect();
实际部署时,将连接信息抽离到配置文件,通过环境变量注入。
验证缓存生效
启动应用后,观察Redis键空间是否出现以namespace:sqlId形式命名的Key,执行同一条查询语句两次,第二次不命中数据库,则说明缓存生效。mybatis redis缓存配置步骤至此完成,整个过程无需修改业务代码,仅依赖配置和自定义实现。
如何解决MyBatis缓存失效问题
缓存更新策略的选择
缓存失效主要源于数据变更,解决方案包括:
- 设置合理的过期时间:根据业务数据的更新频率配置TTL,例如配置信息设置1小时,用户信息设置5分钟。
- 主动清除:在插入、更新、删除操作对应的Mapper中,通过
flushCache属性或显式调用Redis的DEL命令清除相关缓存。 - 使用Redis的pub/sub:当数据变更时,通知所有节点清除本地缓存引用,适用于一致性要求较高的场景。

数据一致性保障
在读写分离或主从架构中,数据同步延时可能导致缓存与数据库不一致,业内专家指出,可以引入缓存版本号或分布式锁来保证写操作的原子性,具体做法:写操作先获取锁,更新数据库后再更新缓存;读操作只读取缓存版本号与数据库版本号匹配的数据,多数情况下,使用“先更新数据库,再删除缓存”的策略,配合延迟双删,即可满足大部分业务需求。
序列化与集群兼容性
Redis存储的是字节数组,因此对象序列化必须统一,推荐使用JSON格式,因为可读性强,且跨语言兼容,如果使用Redis集群,需要注意:
- 键的哈希分布要均匀,避免热点Key集中在同一个节点。
- 批量操作使用Pipeline或Lua脚本,减少网络往返。
- 自定义缓存中的
getObject方法应处理连接异常,兜底返回null,避免缓存雪崩。
高并发场景下MyBatis缓存的优化实践
缓存穿透、击穿、雪崩应对
高并发下,缓存问题会成倍放大,针对缓存穿透(查询不存在的数据),可以在Redis布隆过滤器中预加载有效Key,拦截无效请求,对于缓存击穿(热点Key过期),使用互斥锁,只有一个线程查询数据库并重建缓存,其他线程等待,对于缓存雪崩(大量Key同时过期),将过期时间均匀分布,比如基础时间加上随机数。mybatis缓存优化的核心是让缓存层成为流量缓冲器,而不是瓶颈。
读写分离下的缓存一致性
当使用主从复制时,主节点写库,从节点读库,缓存更新存在延迟,解决方案:
- 写操作后,主动使缓存失效,避免从库读到旧数据。
- 利用Redis的事务或Lua脚本,将“删除缓存”和“更新数据库”合并为一个原子操作。
- 对于弱一致性场景,可以接受短暂不一致,只设置较短的过期时间来自动修复。

异地多活下的缓存同步
在跨地域部署中,各个数据中心的Redis实例需要同步缓存数据。分布式mybatis缓存方案推荐使用Redis的集群模式(如Redis Cluster)或一致性哈希分片,确保同一Key的读写落在同一节点,在应用层实现缓存路由,根据请求来源选择最近的数据中心,减少跨地域延迟,写操作通过消息队列广播,保证所有数据中心最终一致。
分布式MyBatis缓存常见问题解答
问题1:mybatis二级缓存和redis哪个好?
这是理解上的误区,MyBatis二级缓存是框架层面的缓存机制,而Redis是具体的缓存存储引擎,整合后,二级缓存实际上由Redis提供数据存储,你既不用抛弃MyBatis的缓存管理,又能获得Redis的分布式特性,所以不存在“哪个好”,而是“如何结合更好”。
问题2:MyBatis缓存配置后为什么不生效?
先检查缓存作用域:二级缓存默认对同一个Mapper生效,且需要事务提交后才写入,确认cacheEnabled设置为true,并且Mapper XML中正确配置了<cache>,如果使用自定义Redis缓存,检查Redis连接是否正常,序列化是否成功,在多表关联查询时,需要手动设置flushCache,否则父表缓存不会因子表变更而失效。
问题3:如何保证MyBatis缓存与数据库数据一致?
最直接的方法是指定极短的过期时间,例如1秒,让缓存自动失效,对于写操作频繁的场景,采用“先更新数据库,再删除缓存”的套路,并配合延迟双删:先删除缓存,等待一段时间后再次删除,避免并发读写导致的不一致,对于强一致性需求,使用Redis分布式锁将写操作串行化,同时将缓存版本号与数据库版本号绑定,读操作时验证版本号,不一致则回源数据库。mybatis缓存一致性的最终保障在于业务层对数据准确性的容忍度,以及缓存更新策略的严格设计。