在分布式环境下使用 MyBatis 二级缓存,核心挑战是解决多节点缓存一致性问题。单机环境中,二级缓存是内存级别的本地缓存,而分布式环境下多节点独立部署,本地缓存无法跨节点共享,易导致 “缓存孤岛” 和数据不一致。本文从底层原理出发,提供一套完整的分布式二级缓存解决方案,包含实战配置与最佳实践。

一、分布式环境下二级缓存的核心问题

在分布式架构(如微服务集群)中,默认的 MyBatis 二级缓存(本地内存缓存)会暴露三个致命问题:

  1. 缓存孤岛:每个节点维护独立缓存,同一查询在不同节点可能命中不同缓存数据(如节点 A 更新数据后,节点 B 的缓存仍是旧值)。
  2. 数据不一致:跨节点更新数据时,无法通知其他节点同步清空缓存,导致部分节点返回脏数据。
  3. 序列化风险:本地缓存可直接存储 Java 对象引用,而分布式缓存需网络传输,若对象未序列化会导致缓存失败。

二、解决方案:基于集中式缓存的二级缓存改造

分布式环境下的核心解决方案是:用集中式缓存(如 Redis、Memcached)替代本地内存缓存,让所有节点共享同一缓存源,实现缓存数据全局一致。

2.1 技术选型:MyBatis + Redis(最常用组合)

Redis 作为高性能的分布式缓存中间件,支持数据持久化、过期策略和集群模式,是 MyBatis 二级缓存的理想选择。实现思路是:

  • 让 MyBatis 的二级缓存数据存储到 Redis,而非本地内存。
  • 所有节点通过 Redis 访问缓存,确保缓存数据全局唯一。

三、实战:MyBatis 集成 Redis 实现分布式二级缓存

3.1 环境准备
  • JDK 17
  • MyBatis 3.5.10+
  • Redis 6.2+
  • Spring Boot 2.7.x(简化配置)
3.2 依赖配置(Maven)
<!-- MyBatis核心依赖 -->
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.0</version>
</dependency><!-- Redis缓存依赖(MyBatis官方适配) -->
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0-beta2</version>
</dependency><!-- Redis客户端 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.3 配置 Redis 连接

src/main/resources下创建redis.properties,配置 Redis 连接信息:

# Redis服务器地址
redis.host=192.168.1.100
# Redis端口
redis.port=6379
# 连接超时时间(毫秒)
redis.timeout=2000
# Redis密码(无密码则留空)
redis.password=your_redis_password
# 数据库索引(默认0)
redis.database=1
# 缓存默认过期时间(毫秒,30分钟)
redis.default.expiration=1800000
3.4 改造实体类:实现序列化

分布式缓存中,对象需在网络中传输,必须实现Serializable接口,否则会导致缓存失败。
User.java

package com.example.entity;import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;/*** 用户实体类(必须实现Serializable)*/
@Data
public class User implements Serializable {// 序列化版本号(避免反序列化冲突)private static final long serialVersionUID = 1L;private Long id;private String username;private String email;private LocalDateTime createTime;
}
3.5 配置 Mapper 使用 Redis 缓存

在 Mapper.xml 中指定缓存类型为 Redis,替代默认的本地缓存。
UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mapper.UserMapper"><!-- 配置Redis作为二级缓存 --><cache type="org.mybatis.caches.redis.RedisCache"  <!-- 指定Redis缓存实现类 -->eviction="LRU"  <!-- 缓存淘汰策略:最近最少使用 -->flushInterval="300000"  <!-- 自动刷新间隔(5分钟) -->size="1000"  <!-- 最大缓存对象数量 -->readOnly="false"/>  <!-- 非只读(需序列化) --><!-- 查询语句:默认使用二级缓存 --><select id="selectById" resultType="com.example.entity.User">SELECT id, username, email, create_time AS createTimeFROM t_userWHERE id = #{id}</select><!-- 更新语句:默认触发缓存清空(flushCache=true) --><update id="update">UPDATE t_userSET username = #{username}, email = #{email}WHERE id = #{id}</update>
</mapper>

关键配置说明

  • type="org.mybatis.caches.redis.RedisCache":指定 MyBatis 使用 Redis 存储缓存数据。
  • eviction="LRU":当缓存满时,移除最久未使用的对象,避免内存溢出。
  • flushInterval="300000":5 分钟自动刷新一次缓存,作为数据一致性的兜底策略。
3.6 全局启用二级缓存

在 MyBatis 配置文件(或 Spring Boot 配置)中确保二级缓存全局开启(默认开启,建议显式配置)。
application.yml

mybatis:configuration:cache-enabled: true  # 全局启用二级缓存(默认true)mapper-locations: classpath:mapper/*.xml  # 指定Mapper.xml路径
3.7 验证分布式缓存效果

部署两个服务节点(Node1 和 Node2),通过测试验证缓存一致性:

测试代码(Service 层)

@Slf4j
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;/*** 查询用户:优先从Redis缓存获取*/public User getUserById(Long id) {if (Objects.isNull(id)) {log.warn("用户ID为空");return null;}User user = userMapper.selectById(id);log.info("查询用户结果:{}", user);return user;}/*** 更新用户:触发Redis缓存清空*/@Transactionalpublic void updateUser(User user) {if (Objects.isNull(user) || Objects.isNull(user.getId())) {log.warn("用户信息不完整");return;}int rows = userMapper.update(user);log.info("更新用户影响行数:{}", rows);// 事务提交后,MyBatis会自动清空Redis中该Mapper的缓存}
}

测试步骤与预期结果

  1. Node1 首次查询用户 ID=1:未命中缓存,查询数据库,结果存入 Redis。
  2. Node2 查询用户 ID=1:命中 Redis 缓存,直接返回结果(无需查库)。
  3. Node1 更新用户 ID=1:事务提交后,Redis 中该用户的缓存被清空。
  4. Node2 再次查询用户 ID=1:未命中缓存,查询数据库获取最新数据,并存入 Redis。

通过 Redis 客户端(如redis-cli)可观察到缓存键值的创建与删除,证明所有节点共享同一缓存。

四、分布式缓存的高级优化策略

4.1 缓存键设计:避免命名冲突

MyBatis 默认的缓存键由namespace + SQL语句 + 参数组成,在分布式环境下需确保唯一性。可通过自定义RedisCache实现类优化键名:

CustomRedisCache.java

package com.example.cache;import org.mybatis.caches.redis.RedisCache;
import java.util.UUID;/*** 自定义Redis缓存,添加应用前缀避免键冲突*/
public class CustomRedisCache extends RedisCache {// 应用唯一标识(避免多应用共用Redis时键冲突)private static final String APP_PREFIX = "myapp:";public CustomRedisCache(String id) {super(id);}/*** 重写缓存键,添加应用前缀*/@Overridepublic Object getObject(Object key) {String cacheKey = APP_PREFIX + key.toString();return super.getObject(cacheKey);}@Overridepublic void putObject(Object key, Object value) {String cacheKey = APP_PREFIX + key.toString();super.putObject(cacheKey, value);}@Overridepublic Object removeObject(Object key) {String cacheKey = APP_PREFIX + key.toString();return super.removeObject(cacheKey);}
}

在 Mapper.xml 中使用自定义缓存:

<cache type="com.example.cache.CustomRedisCache"/>
4.2 缓存失效策略:主动 + 被动结合

分布式环境下,单一的自动失效可能存在延迟,需结合主动失效策略:

  1. 被动失效:依赖flushInterval自动刷新(如 30 分钟),适合非核心数据。
  2. 主动失效:更新数据后,通过代码手动删除缓存(极端场景):
/*** 手动删除指定用户的缓存*/
public void deleteUserCache(Long userId) {// 获取UserMapper的缓存对象Cache cache = sqlSessionFactory.getConfiguration().getCache("com.example.mapper.UserMapper");if (Objects.nonNull(cache)) {// 构造缓存键(需与MyBatis生成规则一致)// 键格式:namespace + "::" + SQLID + "::" + 参数String cacheKey = "com.example.mapper.UserMapper::selectById::" + userId;cache.removeObject(cacheKey);log.info("手动删除用户缓存,key: {}", cacheKey);}
}
4.3 处理缓存与数据库一致性:延迟双删

在高并发场景,更新数据库后立即删除缓存可能仍有风险(删除缓存前已有请求读取旧缓存)。可采用 “延迟双删” 策略:

@Transactional
public void updateUserWithDelayDelete(User user) {// 1. 更新数据库userMapper.update(user);// 2. 第一次删除缓存(事务提交后执行)transactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCommit() {// 事务提交后删除缓存deleteUserCache(user.getId());// 3. 延迟1秒后第二次删除(避免更新前的请求仍读取旧缓存)CompletableFuture.runAsync(() -> {try {Thread.sleep(1000);deleteUserCache(user.getId());} catch (InterruptedException e) {log.error("延迟删除缓存失败", e);}});}});
}
4.4 缓存序列化优化:使用 JSON 替代 Java 序列化

默认情况下,MyBatis-Redis 使用 Java 序列化存储对象,存在性能差、可读性低的问题。可自定义序列化方式(如 JSON):

JsonRedisCache.java(简化版)

package com.example.cache;import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class JsonRedisCache implements Cache {private final String id;private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();private final RedisTemplate<String, Object> redisTemplate;private final ObjectMapper objectMapper = new ObjectMapper();public JsonRedisCache(String id) {this.id = id;// 注入RedisTemplate(实际需通过Spring上下文获取)this.redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);}@Overridepublic String getId() { return id; }@Overridepublic void putObject(Object key, Object value) {try {String jsonValue = objectMapper.writeValueAsString(value);redisTemplate.opsForValue().set(key.toString(), jsonValue, 30, TimeUnit.MINUTES);} catch (Exception e) {log.error("缓存序列化失败", e);}}@Overridepublic Object getObject(Object key) {try {String jsonValue = (String) redisTemplate.opsForValue().get(key.toString());if (StringUtils.hasText(jsonValue)) {// 根据实际类型反序列化(简化示例)return objectMapper.readValue(jsonValue, User.class);}} catch (Exception e) {log.error("缓存反序列化失败", e);}return null;}@Overridepublic Object removeObject(Object key) {redisTemplate.delete(key.toString());return null;}@Overridepublic void clear() {// 清空当前namespace的所有缓存(需批量删除匹配键)}@Overridepublic int getSize() { return 0; }@Overridepublic ReadWriteLock getReadWriteLock() { return readWriteLock; }
}

使用 JSON 序列化后,Redis 中缓存的数据可读性更强,且序列化效率更高。

五、分布式二级缓存的适用场景与禁忌

5.1 适用场景
  • 查询频繁、更新极少的数据:如字典表、地区表、系统配置表(更新频率低,缓存命中率高)。
  • 非核心业务数据:如商品详情、历史订单(允许短时间不一致,优先保证性能)。
  • 数据一致性要求不高的场景:如用户浏览记录、热门商品排行(可接受分钟级延迟)。
5.2 禁忌场景
  • 实时性要求极高的数据:如库存数量、账户余额(缓存延迟可能导致超卖、余额显示错误)。
  • 高频更新数据:如秒杀商品状态、实时在线人数(缓存命中率低,反而增加 Redis 负担)。
  • 超大对象:如包含大量字段的报表数据(序列化 / 传输成本高,不如直接查库)。

六、监控与调优

  1. 缓存命中率监控:通过 Redis 的INFO stats命令查看keyspace_hits(命中数)和keyspace_misses(未命中数),命中率低于 70% 需优化缓存策略。
  2. 过期键清理:避免缓存键永久有效,结合业务设置合理过期时间(如 30 分钟~24 小时)。
  3. Redis 集群:高并发场景下,使用 Redis Cluster 保证缓存服务的高可用。

七、总结

分布式环境下正确使用 MyBatis 二级缓存的核心是用集中式缓存(如 Redis)替代本地缓存,关键步骤包括:

  1. 集成 MyBatis-Redis 适配器,让缓存数据存储到 Redis。
  2. 实体类实现序列化,确保跨节点传输正常。
  3. 配置合理的缓存淘汰策略与过期时间,平衡性能与一致性。
  4. 结合业务场景选择缓存对象,实时性数据禁用缓存。

通过这套方案,既能保留二级缓存的性能优势,又能解决分布式环境的数据一致性问题,实现 “高性能 + 高可靠” 的平衡。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/96883.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96883.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/96883.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

血缘元数据采集开放标准:OpenLineage Integrations Apache Spark Quickstart with Jupyter

OpenLineage 是一个用于元数据和血缘采集的开放标准&#xff0c;专为在作业运行时动态采集数据而设计。它通过统一的命名策略定义了由作业&#xff08;Job&#xff09;、运行实例&#xff08;Run&#xff09;和数据集&#xff08;Dataset&#xff09; 组成的通用模型&#xff0…

人工智能之数学基础:离散随机变量和连续随机变量

本文重点 随机变量是概率论与统计学中的核心概念,用于将随机现象的抽象结果转化为可量化的数值。根据取值特性的不同,随机变量可分为离散型和连续型两大类。 在前面的课程中我们学习了随机变量,随机变量可以理解为一个函数,通过这个函数我们就可以将随机试验中的结果数值…

SQL语句(查询)

单表查询 常量查询 让我们来看一个具体的 SQL 代码和结果示例&#xff0c;假设有一张名为 orders 的数据表&#xff0c;它存储了订单信息&#xff0c;包括订单编号&#xff08;order_id&#xff09;、商品单价&#xff08;unit_price&#xff09;、购买数量&#xff08;quantit…

Java 大视界 -- Java 大数据机器学习模型在金融市场波动预测与资产配置动态调整中的应用

Java 大视界 -- Java 大数据机器学习模型在金融市场波动预测与资产配置动态调整中的应用引言&#xff1a;正文&#xff1a;一、Java 构建的金融数据处理架构1.1 多源数据实时融合与清洗1.2 跨市场数据关联&#xff08;风险传导分析&#xff09;二、Java 驱动的市场波动预测模型…

基于muduo库的图床云共享存储项目(一)

基于muduo库的图床云共享存储项目&#xff08;一&#xff09;项目简介整体架构项目依赖基础组件muduo库Channel类Poller / EpollPoller 类EventLoopAcceptor类FastDfsJSON的使用项目简介 当前所实现的项目是一个基于muduo库的图床云共享存储项目&#xff0c;他的主要的功能就是…

数字化转型三阶段:从信息化、数字化到数智化的战略进化

企业的数字化转型包括信息化、数字化、数智化三个阶段&#xff0c;并非一个阶段结束才能进入到下一个阶段。01信息化→业务数据化信息化是将企业在生产经营过程中产生的业务信息进行记录、储存和管理&#xff0c;通过电子终端呈现&#xff0c;便于信息的传播与沟通。信息化是对…

SpringBoot如何获取系统Controller名称和方法名称

这种代码里面的Controller和里面的方法怎么获取代码&#xff1a;/*** 获取所有Controller名称*/ApiDescription("获取所有Controller名称")PostMapping("/getControllerNames")public Result getControllerNames() {return dataDesensitizationRulesServic…

(二十二)深入了解AVFoundation-编辑:视频变速功能-实战在Demo中实现视频变速

一. 引言视频变速&#xff08;Speed Ramp&#xff09;是视频编辑中最常见的特效之一&#xff1a;慢动作&#xff08;Slow Motion&#xff09;&#xff1a;强调细节&#xff0c;让观众捕捉到肉眼难以察觉的瞬间&#xff1b;快动作&#xff08;Fast Motion&#xff09;&#xff1…

MCP零基础学习(7)|实战指南:构建论文分析智能体

在之前的教程中&#xff0c;我们已经介绍了 MCP&#xff08;Model Context Protocol&#xff09;的基本概念及其核心组件。在本篇教程中&#xff0c;我们将通过一个实际案例&#xff0c;演示如何运用 MCP 构建一个能够分析学术论文的智能体。这个智能体将具备读取 PDF 文件、提…

Unity URP半透明物体自身交叠解决方案

前言 在 Unity 的通用渲染管线&#xff08;URP&#xff09;中&#xff0c;处理半透明物体的自身交叠是一个常见挑战。当半透明物体&#xff08;如玻璃、水或透明材质&#xff09;的某些部分相互重叠时&#xff0c;可能会出现渲染顺序问题&#xff0c;导致视觉瑕疵。 对惹&…

哈希算法入门:深入浅出讲明白HASH哈希算法

一、先搞懂&#xff1a;哈希算法到底是 “啥玩意儿”&#xff1f;咱们先别碰复杂概念&#xff0c;从你每天都会遇到的事说起 —— 你会发现&#xff0c;“哈希思维” 其实早就藏在生活里了。&#xff08;一&#xff09;生活中的 “哈希例子”&#xff1a;给东西 “贴标签、找位…

Vuex 和 Pinia 各自的优点

核心总结&#xff08;一句话概括&#xff09; Vuex&#xff1a;Vue 官方曾经的状态管理标准解决方案&#xff0c;成熟稳定&#xff0c;概念清晰&#xff0c;但语法稍显冗长。Pinia&#xff1a;Vue 官方推荐的新一代状态管理库&#xff0c;API 设计极其简洁&#xff0c;完美支持…

几种方式实现文件自动上传到服务器共享文件夹

文章目录一、方案核心逻辑二、详细实现步骤&#xff08;以Windows系统为例&#xff09;1. 确认服务器共享文件夹的“访问权限”&#xff08;前提&#xff09;2. 选择“传输触发方式”&#xff08;按需求选实时/周期&#xff09;&#xff08;1&#xff09;周期传输&#xff08;如…

Milvus介绍及多模态检索实践

1、核心组件 1.1 Collection (集合) 可以用一个图书馆的比喻来理解 Collection&#xff1a; Collection (集合): 相当于一个图书馆&#xff0c;是所有数据的顶层容器。一个 Collection 可以包含多个 Partition&#xff0c;每个 Partition 可以包含多个 Entity。 Partition (分区…

第二十三天-LCD液晶显示实验

一、LCD结构体定义LCD为LCD_TypeDef类型的指针&#xff0c;指向0x6C000000的地址空间&#xff08;bank1分区4的地址范围&#xff09;。为什么需要并上0x000007FE呢&#xff1f;因为虽然驱动SRAM的时序和16位8080接口时序&#xff08;驱动LCD时序&#xff09;很像&#xff0c;但…

SQL性能调优

MySQL出现性能差的原因有哪些? 可能是 SOL查询使用了全表扫描&#xff0c;也可能是查询语句过于复杂&#xff0c;如多表 IOIN 或嵌套子查询。 也有可能是单表数据量过大。 通常情况下&#xff0c;添加索引就能解决大部分性能问题。对于一些热点数据&#xff0c;还可以通过增加…

dapo:开源大规模llm强化学习系统的突破与实现

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; ✨ 1. dapo概述&#xff1a;开源llm强化学习系统的重要突破 dapo&…

【车载开发系列】ParaSoft集成测试环境配置(五)

【车载开发系列】ParaSoft集成测试环境配置(五) 【车载开发系列】ParaSoft集成测试环境配置(五) 【车载开发系列】ParaSoft集成测试环境配置(五) 一. 剥离硬件环境的设置 二. 灵活使用编译开关 三. 导入修改后的bdf文件 四. 自动生成底层桩函数 五. 开始跑集成测试用例 六…

大模型(一)什么是 MCP?如何使用 Charry Studio 集成 MCP?

目录一、什么是 MCP&#xff1f;1.1 &#x1f914; 开始之前的思考1.2 MCP 的定义1.3 MCP 结构二、MCP 的使用2.1 uv 的安装2.2 MCP 广场2.3 MCP 的配置2.4 MCP 的依赖安装2.5 Charry Studio2.6 测试结果背景&#xff1a; MCP 这个概念大概是 2025 年上半年火起来的&#xff0c…

源码导航页

一、Python捕捉动作发送到Unity驱动模型跟着动&#xff08;获取源码&#xff09; 二、AI输入法源码&#xff08;获取源码&#xff09; 三、Java企业级后台管理系统-登录授权角色菜单&#xff08;获取源码&#xff09; 四、Jetson实现纯视觉导航&#xff08;获取源码&#xff09…