欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

在分布式環(huán)境下正確使用MyBatis二級緩存的最佳實踐

 更新時間:2025年08月27日 09:37:59   作者:一葉飄零_sweeeet  
緩存就是內(nèi)存中的數(shù)據(jù),常常來自對數(shù)據(jù)庫查詢結(jié)果的保存,使用緩存,我們可以避免頻繁與數(shù)據(jù)庫進(jìn)行交互,從而提高響應(yīng)速度,這篇文章主要介紹了在分布式環(huán)境下正確使用MyBatis二級緩存的最佳實踐,需要的朋友可以參考下

前言

在分布式環(huán)境下使用 MyBatis 二級緩存,核心挑戰(zhàn)是解決多節(jié)點緩存一致性問題。單機(jī)環(huán)境中,二級緩存是內(nèi)存級別的本地緩存,而分布式環(huán)境下多節(jié)點獨立部署,本地緩存無法跨節(jié)點共享,易導(dǎo)致 “緩存孤島” 和數(shù)據(jù)不一致。本文從底層原理出發(fā),提供一套完整的分布式二級緩存解決方案,包含實戰(zhàn)配置與最佳實踐。

一、分布式環(huán)境下二級緩存的核心問題

在分布式架構(gòu)(如微服務(wù)集群)中,默認(rèn)的 MyBatis 二級緩存(本地內(nèi)存緩存)會暴露三個致命問題:

  • 緩存孤島:每個節(jié)點維護(hù)獨立緩存,同一查詢在不同節(jié)點可能命中不同緩存數(shù)據(jù)(如節(jié)點 A 更新數(shù)據(jù)后,節(jié)點 B 的緩存仍是舊值)。
  • 數(shù)據(jù)不一致:跨節(jié)點更新數(shù)據(jù)時,無法通知其他節(jié)點同步清空緩存,導(dǎo)致部分節(jié)點返回臟數(shù)據(jù)。
  • 序列化風(fēng)險:本地緩存可直接存儲 Java 對象引用,而分布式緩存需網(wǎng)絡(luò)傳輸,若對象未序列化會導(dǎo)致緩存失敗。

二、解決方案:基于集中式緩存的二級緩存改造

分布式環(huán)境下的核心解決方案是:用集中式緩存(如 Redis、Memcached)替代本地內(nèi)存緩存,讓所有節(jié)點共享同一緩存源,實現(xiàn)緩存數(shù)據(jù)全局一致。

2.1 技術(shù)選型:MyBatis + Redis(最常用組合)

Redis 作為高性能的分布式緩存中間件,支持?jǐn)?shù)據(jù)持久化、過期策略和集群模式,是 MyBatis 二級緩存的理想選擇。實現(xiàn)思路是:

  • 讓 MyBatis 的二級緩存數(shù)據(jù)存儲到 Redis,而非本地內(nèi)存。
  • 所有節(jié)點通過 Redis 訪問緩存,確保緩存數(shù)據(jù)全局唯一。

三、實戰(zhàn):MyBatis 集成 Redis 實現(xiàn)分布式二級緩存

3.1 環(huán)境準(zhǔn)備

  • 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下創(chuàng)建redis.properties,配置 Redis 連接信息:

# Redis服務(wù)器地址
redis.host=192.168.1.100
# Redis端口
redis.port=6379
# 連接超時時間(毫秒)
redis.timeout=2000
# Redis密碼(無密碼則留空)
redis.password=your_redis_password
# 數(shù)據(jù)庫索引(默認(rèn)0)
redis.database=1
# 緩存默認(rèn)過期時間(毫秒,30分鐘)
redis.default.expiration=1800000

3.4 改造實體類:實現(xiàn)序列化

分布式緩存中,對象需在網(wǎng)絡(luò)中傳輸,必須實現(xiàn)Serializable接口,否則會導(dǎo)致緩存失敗。

User.java

package com.example.entity;

import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 用戶實體類(必須實現(xiàn)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,替代默認(rèn)的本地緩存。

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緩存實現(xiàn)類 -->
        eviction="LRU"  <!-- 緩存淘汰策略:最近最少使用 -->
        flushInterval="300000"  <!-- 自動刷新間隔(5分鐘) -->
        size="1000"  <!-- 最大緩存對象數(shù)量 -->
        readOnly="false"/>  <!-- 非只讀(需序列化) -->

    <!-- 查詢語句:默認(rèn)使用二級緩存 -->
    <select id="selectById" resultType="com.example.entity.User">
        SELECT id, username, email, create_time AS createTime
        FROM t_user
        WHERE id = #{id}
    </select>

    <!-- 更新語句:默認(rèn)觸發(fā)緩存清空(flushCache=true) -->
    <update id="update">
        UPDATE t_user
        SET username = #{username}, email = #{email}
        WHERE id = #{id}
    </update>
</mapper>

關(guān)鍵配置說明

  • type="org.mybatis.caches.redis.RedisCache":指定 MyBatis 使用 Redis 存儲緩存數(shù)據(jù)。
  • eviction="LRU":當(dāng)緩存滿時,移除最久未使用的對象,避免內(nèi)存溢出。
  • flushInterval="300000":5 分鐘自動刷新一次緩存,作為數(shù)據(jù)一致性的兜底策略。

3.6 全局啟用二級緩存

在 MyBatis 配置文件(或 Spring Boot 配置)中確保二級緩存全局開啟(默認(rèn)開啟,建議顯式配置)。

application.yml

mybatis:
  configuration:
    cache-enabled: true  # 全局啟用二級緩存(默認(rèn)true)
  mapper-locations: classpath:mapper/*.xml  # 指定Mapper.xml路徑

3.7 驗證分布式緩存效果

部署兩個服務(wù)節(jié)點(Node1 和 Node2),通過測試驗證緩存一致性:

測試代碼(Service 層)

@Slf4j
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    /**
     * 查詢用戶:優(yōu)先從Redis緩存獲取
     */
    public User getUserById(Long id) {
        if (Objects.isNull(id)) {
            log.warn("用戶ID為空");
            return null;
        }
        User user = userMapper.selectById(id);
        log.info("查詢用戶結(jié)果:{}", user);
        return user;
    }

    /**
     * 更新用戶:觸發(fā)Redis緩存清空
     */
    @Transactional
    public void updateUser(User user) {
        if (Objects.isNull(user) || Objects.isNull(user.getId())) {
            log.warn("用戶信息不完整");
            return;
        }
        int rows = userMapper.update(user);
        log.info("更新用戶影響行數(shù):{}", rows);
        // 事務(wù)提交后,MyBatis會自動清空Redis中該Mapper的緩存
    }
}

測試步驟與預(yù)期結(jié)果

  • Node1 首次查詢用戶 ID=1:未命中緩存,查詢數(shù)據(jù)庫,結(jié)果存入 Redis。
  • Node2 查詢用戶 ID=1:命中 Redis 緩存,直接返回結(jié)果(無需查庫)。
  • Node1 更新用戶 ID=1:事務(wù)提交后,Redis 中該用戶的緩存被清空。
  • Node2 再次查詢用戶 ID=1:未命中緩存,查詢數(shù)據(jù)庫獲取最新數(shù)據(jù),并存入 Redis。

通過 Redis 客戶端(如redis-cli)可觀察到緩存鍵值的創(chuàng)建與刪除,證明所有節(jié)點共享同一緩存。

四、分布式緩存的高級優(yōu)化策略

4.1 緩存鍵設(shè)計:避免命名沖突

MyBatis 默認(rèn)的緩存鍵由namespace + SQL語句 + 參數(shù)組成,在分布式環(huán)境下需確保唯一性??赏ㄟ^自定義RedisCache實現(xiàn)類優(yōu)化鍵名:

CustomRedisCache.java

package com.example.cache;

import org.mybatis.caches.redis.RedisCache;
import java.util.UUID;

/**
 * 自定義Redis緩存,添加應(yīng)用前綴避免鍵沖突
 */
public class CustomRedisCache extends RedisCache {
    // 應(yīng)用唯一標(biāo)識(避免多應(yīng)用共用Redis時鍵沖突)
    private static final String APP_PREFIX = "myapp:";

    public CustomRedisCache(String id) {
        super(id);
    }

    /**
     * 重寫緩存鍵,添加應(yīng)用前綴
     */
    @Override
    public Object getObject(Object key) {
        String cacheKey = APP_PREFIX + key.toString();
        return super.getObject(cacheKey);
    }

    @Override
    public void putObject(Object key, Object value) {
        String cacheKey = APP_PREFIX + key.toString();
        super.putObject(cacheKey, value);
    }

    @Override
    public Object removeObject(Object key) {
        String cacheKey = APP_PREFIX + key.toString();
        return super.removeObject(cacheKey);
    }
}

在 Mapper.xml 中使用自定義緩存:

<cache type="com.example.cache.CustomRedisCache"/>

4.2 緩存失效策略:主動 + 被動結(jié)合

分布式環(huán)境下,單一的自動失效可能存在延遲,需結(jié)合主動失效策略:

  • 被動失效:依賴flushInterval自動刷新(如 30 分鐘),適合非核心數(shù)據(jù)。
  • 主動失效:更新數(shù)據(jù)后,通過代碼手動刪除緩存(極端場景):
/**
 * 手動刪除指定用戶的緩存
 */
public void deleteUserCache(Long userId) {
    // 獲取UserMapper的緩存對象
    Cache cache = sqlSessionFactory.getConfiguration().getCache("com.example.mapper.UserMapper");
    if (Objects.nonNull(cache)) {
        // 構(gòu)造緩存鍵(需與MyBatis生成規(guī)則一致)
        // 鍵格式:namespace + "::" + SQLID + "::" + 參數(shù)
        String cacheKey = "com.example.mapper.UserMapper::selectById::" + userId;
        cache.removeObject(cacheKey);
        log.info("手動刪除用戶緩存,key: {}", cacheKey);
    }
}

4.3 處理緩存與數(shù)據(jù)庫一致性:延遲雙刪

在高并發(fā)場景,更新數(shù)據(jù)庫后立即刪除緩存可能仍有風(fēng)險(刪除緩存前已有請求讀取舊緩存)??刹捎?“延遲雙刪” 策略:

@Transactional
public void updateUserWithDelayDelete(User user) {
    // 1. 更新數(shù)據(jù)庫
    userMapper.update(user);
    // 2. 第一次刪除緩存(事務(wù)提交后執(zhí)行)
    transactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
        @Override
        public void afterCommit() {
            // 事務(wù)提交后刪除緩存
            deleteUserCache(user.getId());
            
            // 3. 延遲1秒后第二次刪除(避免更新前的請求仍讀取舊緩存)
            CompletableFuture.runAsync(() -> {
                try {
                    Thread.sleep(1000);
                    deleteUserCache(user.getId());
                } catch (InterruptedException e) {
                    log.error("延遲刪除緩存失敗", e);
                }
            });
        }
    });
}

4.4 緩存序列化優(yōu)化:使用 JSON 替代 Java 序列化

默認(rèn)情況下,MyBatis-Redis 使用 Java 序列化存儲對象,存在性能差、可讀性低的問題??勺远x序列化方式(如 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);
    }

    @Override
    public String getId() { return id; }

    @Override
    public 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);
        }
    }

    @Override
    public Object getObject(Object key) {
        try {
            String jsonValue = (String) redisTemplate.opsForValue().get(key.toString());
            if (StringUtils.hasText(jsonValue)) {
                // 根據(jù)實際類型反序列化(簡化示例)
                return objectMapper.readValue(jsonValue, User.class);
            }
        } catch (Exception e) {
            log.error("緩存反序列化失敗", e);
        }
        return null;
    }

    @Override
    public Object removeObject(Object key) {
        redisTemplate.delete(key.toString());
        return null;
    }

    @Override
    public void clear() {
        // 清空當(dāng)前namespace的所有緩存(需批量刪除匹配鍵)
    }

    @Override
    public int getSize() { return 0; }

    @Override
    public ReadWriteLock getReadWriteLock() { return readWriteLock; }
}

使用 JSON 序列化后,Redis 中緩存的數(shù)據(jù)可讀性更強(qiáng),且序列化效率更高。

五、分布式二級緩存的適用場景與禁忌

5.1 適用場景

  • 查詢頻繁、更新極少的數(shù)據(jù):如字典表、地區(qū)表、系統(tǒng)配置表(更新頻率低,緩存命中率高)。
  • 非核心業(yè)務(wù)數(shù)據(jù):如商品詳情、歷史訂單(允許短時間不一致,優(yōu)先保證性能)。
  • 數(shù)據(jù)一致性要求不高的場景:如用戶瀏覽記錄、熱門商品排行(可接受分鐘級延遲)。

5.2 禁忌場景

  • 實時性要求極高的數(shù)據(jù):如庫存數(shù)量、賬戶余額(緩存延遲可能導(dǎo)致超賣、余額顯示錯誤)。
  • 高頻更新數(shù)據(jù):如秒殺商品狀態(tài)、實時在線人數(shù)(緩存命中率低,反而增加 Redis 負(fù)擔(dān))。
  • 超大對象:如包含大量字段的報表數(shù)據(jù)(序列化 / 傳輸成本高,不如直接查庫)。

六、監(jiān)控與調(diào)優(yōu)

  • 緩存命中率監(jiān)控:通過 Redis 的INFO stats命令查看keyspace_hits(命中數(shù))和keyspace_misses(未命中數(shù)),命中率低于 70% 需優(yōu)化緩存策略。
  • 過期鍵清理:避免緩存鍵永久有效,結(jié)合業(yè)務(wù)設(shè)置合理過期時間(如 30 分鐘~24 小時)。
  • Redis 集群:高并發(fā)場景下,使用 Redis Cluster 保證緩存服務(wù)的高可用。

七、總結(jié)

分布式環(huán)境下正確使用 MyBatis 二級緩存的核心是用集中式緩存(如 Redis)替代本地緩存,關(guān)鍵步驟包括:

  • 集成 MyBatis-Redis 適配器,讓緩存數(shù)據(jù)存儲到 Redis。
  • 實體類實現(xiàn)序列化,確??绻?jié)點傳輸正常。
  • 配置合理的緩存淘汰策略與過期時間,平衡性能與一致性。
  • 結(jié)合業(yè)務(wù)場景選擇緩存對象,實時性數(shù)據(jù)禁用緩存。

通過這套方案,既能保留二級緩存的性能優(yōu)勢,又能解決分布式環(huán)境的數(shù)據(jù)一致性問題,實現(xiàn) “高性能 + 高可靠” 的平衡。

到此這篇關(guān)于在分布式環(huán)境下正確使用MyBatis二級緩存的文章就介紹到這了,更多相關(guān)分布式環(huán)境使用MyBatis二級緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot登錄、退出、獲取用戶信息的session處理方案

    SpringBoot登錄、退出、獲取用戶信息的session處理方案

    這篇文章主要介紹了SpringBoot登錄、退出、獲取用戶信息的session處理,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-08-08
  • SpringBoot3.x集成nacos并實現(xiàn)多環(huán)境配置的操作步驟

    SpringBoot3.x集成nacos并實現(xiàn)多環(huán)境配置的操作步驟

    本文詳細(xì)介紹了如何在Springboot3.x中集成Nacos2.x版本,包括nacos的安裝、配置更改,以及在集成過程中遇到的問題,如端口設(shè)置、依賴版本調(diào)整等,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2024-10-10
  • mybatis框架入門學(xué)習(xí)教程

    mybatis框架入門學(xué)習(xí)教程

    MyBatis是一個支持普通SQL查詢,存儲過程和高級映射的優(yōu)秀持久層框架。這篇文章主要介紹了mybatis框架入門學(xué)習(xí)教程,需要的朋友可以參考下
    2017-02-02
  • Java基于Scanner對象的簡單輸入計算功能示例

    Java基于Scanner對象的簡單輸入計算功能示例

    這篇文章主要介紹了Java基于Scanner對象的簡單輸入計算功能,結(jié)合實例形式分析了Java使用Scanner對象獲取用戶輸入半徑值計算圓形面積功能,需要的朋友可以參考下
    2018-01-01
  • Java9 Stream Collectors新增功能(小結(jié))

    Java9 Stream Collectors新增功能(小結(jié))

    這篇文章主要介紹了Java9 Stream Collectors新增功能(小結(jié)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • java泛型常用通配符實例解析

    java泛型常用通配符實例解析

    這篇文章主要介紹了java泛型常用通配符實例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-01-01
  • Java8之Stream流代替For循環(huán)操作

    Java8之Stream流代替For循環(huán)操作

    這篇文章主要介紹了Java8之Stream流代替For循環(huán)操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • springboot tomcat最大線程數(shù)與最大連接數(shù)解析

    springboot tomcat最大線程數(shù)與最大連接數(shù)解析

    這篇文章主要介紹了springboot tomcat最大線程數(shù)與最大連接數(shù)解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • 基于SpringBoot后端導(dǎo)出Excel文件的操作方法

    基于SpringBoot后端導(dǎo)出Excel文件的操作方法

    這篇文章給大家介紹了基于SpringBoot后端導(dǎo)出Excel文件的操作方法,文中通過代碼示例給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-02-02
  • Java多態(tài)成員訪問的特點是什么?

    Java多態(tài)成員訪問的特點是什么?

    在上一篇文章中介紹了方法重載和方法重寫的區(qū)別,但是在多態(tài)情況下發(fā)現(xiàn)程序的執(zhí)行結(jié)果和我們預(yù)期的不太一樣,這篇將繼續(xù)介紹多態(tài)場景下,Java成員訪問的特點,需要的朋友可以參考下
    2021-06-06

最新評論