【redis】[实战]---- MySQL 与 Redis 延迟双删实现方案实例

延迟双删是解决缓存与数据库一致性的常用方案,主要流程是:

先删除缓存
更新数据库
延迟一段时间后再次删除缓存

在这里插入图片描述

下面是基于 Spring Boot 3 + MyBatis Plus + Redis 的核心实现代码:

缓存服务类 CacheService

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 缓存服务类,封装Redis缓存的基本操作
 */
@Component
@RequiredArgsConstructor
public class CacheService {

    private final RedisTemplate<String, Object> redisTemplate;

    /**
     * 从缓存获取数据
     * 
     * @param key 缓存键
     * @return 缓存值,如果不存在则返回null
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 向缓存设置数据
     * 
     * @param key 缓存键
     * @param value 缓存值
     * @param timeout 过期时间
     * @param unit 时间单位
     */
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 删除缓存
     * 
     * @param key 缓存键
     * @return 是否删除成功
     */
    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }
}

延迟双删服务类 DelayDoubleDeleteService

import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 延迟双删服务类,提供延迟删除缓存的能力
 */
@Component
@RequiredArgsConstructor
public class DelayDoubleDeleteService {

    private final CacheService cacheService;
    private final ThreadPoolTaskScheduler taskScheduler;

    /**
     * 延迟删除缓存
     * 
     * @param key 要删除的缓存键
     * @param delay 延迟时间
     * @param unit 时间单位
     */
    public void delayDelete(String key, long delay, TimeUnit unit) {
        taskScheduler.schedule(() -> {
            // 执行第二次删除
            boolean result = cacheService.delete(key);
            // 可以根据需要记录日志
            // log.info("延迟删除缓存 {},结果: {}", key, result);
        }, System.currentTimeMillis() + unit.toMillis(delay));
    }
}

Redis配置类 RedisConfig

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis配置类,配置RedisTemplate和相关序列化方式
 * 确保Redis数据正确序列化和反序列化
 */
@Configuration
public class RedisConfig {

    /**
     * 配置RedisTemplate,设置key和value的序列化方式
     * 
     * @param connectionFactory Redis连接工厂
     * @return 配置好的RedisTemplate实例
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        
        // 使用StringRedisSerializer序列化key
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        
        // 使用Jackson2JsonRedisSerializer序列化value
        GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

线程池配置类 ThreadPoolConfig

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池配置类,配置定时任务线程池
 */
@Configuration
public class ThreadPoolConfig {

    /**
     * 配置定时任务线程池,用于执行延迟删除操作
     * 
     * @return 配置好的ThreadPoolTaskScheduler实例
     */
    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        // 核心线程数
        scheduler.setCorePoolSize(5);
        // 最大线程数
        scheduler.setMaxPoolSize(10);
        // 队列容量
        scheduler.setQueueCapacity(20);
        // 线程名前缀
        scheduler.setThreadNamePrefix("delay-delete-");
        // 线程空闲时间
        scheduler.setKeepAliveSeconds(60);
        // 拒绝策略:当线程池和队列都满了,让提交任务的线程执行任务
        scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        scheduler.initialize();
        return scheduler;
    }
}

用户服务类 UserService

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

/**
 * 用户服务类,实现用户相关业务逻辑,包含延迟双删缓存策略
 */
@Service
@RequiredArgsConstructor
public class UserService extends ServiceImpl<UserMapper, User> {

    private final CacheService cacheService;
    private final DelayDoubleDeleteService delayDoubleDeleteService;

    /**
     * 缓存键前缀
     */
    private static final String CACHE_KEY_PREFIX = "user:";

    /**
     * 根据ID获取用户信息(先查缓存,再查数据库)
     * 
     * @param id 用户ID
     * @return 用户信息,如果不存在则返回null
     */
    public User getUserById(Long id) {
        // 1. 先查询缓存
        String cacheKey = getCacheKey(id);
        User user = (User) cacheService.get(cacheKey);
        if (user != null) {
            return user;
        }

        // 2. 缓存不存在,查询数据库
        user = getById(id);
        
        // 3. 将查询结果存入缓存,设置过期时间
        if (user != null) {
            cacheService.set(cacheKey, user, 30, TimeUnit.MINUTES);
        }
        
        return user;
    }

    /**
     * 更新用户信息(实现延迟双删策略)
     * 
     * @param user 要更新的用户信息
     * @return 是否更新成功
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean updateUser(User user) {
        if (user == null || user.getId() == null) {
            return false;
        }
        
        String cacheKey = getCacheKey(user.getId());
        
        // 1. 第一次删除缓存
        cacheService.delete(cacheKey);
        
        // 2. 更新数据库
        boolean result = updateById(user);
        
        // 3. 延迟一段时间后再次删除缓存(根据业务场景调整延迟时间)
        // 这里设置500毫秒延迟,确保数据库更新完成后再删除可能存在的脏数据
        delayDoubleDeleteService.delayDelete(cacheKey, 500, TimeUnit.MILLISECONDS);
        
        return result;
    }

    /**
     * 构建缓存键
     * 
     * @param id 用户ID
     * @return 缓存键字符串
     */
    private String getCacheKey(Long id) {
        return CACHE_KEY_PREFIX + id;
    }
}

实现说明

核心原理:延迟双删通过两次删除缓存来保证数据一致性,第一次删除避免脏读,第二次删除解决可能存在的并发问题。
关键配置:
    RedisTemplate 配置确保序列化正确
    线程池配置用于执行延迟删除任务
    缓存键设计采用前缀 + ID 的方式,便于管理
业务流程:
    查询:先查缓存,缓存未命中再查数据库并更新缓存
    更新:先删缓存,再更新数据库,最后延迟删缓存
延迟时间设置:
    延迟时间应根据实际业务场景设置,通常为几百毫秒到几秒
    确保能覆盖数据库事务提交的时间,避免二次删除时数据库更新尚未完成

如果文章对你有一点点帮助,欢迎【点赞、留言、+ 关注】
您的关注是我持续创作的重要动力!有问题欢迎随时交流!多一个朋友多一条路!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值