springboot利用Redisson 实现缓存与数据库双写不一致问题

使用了 Redisson 来操作 Redis 分布式锁,主要功能是从缓存和数据库中获取商品信息,以下是针对并发时更新缓存和数据库带来不一致问题的解决方案

1. 基于读写锁和删除缓存策略

在并发更新场景下,使用读写锁可以控制对共享资源(缓存和数据库)的访问,更新操作加写锁,读取操作加读锁。同时采用先更新数据库,再删除缓存的策略。

java 复制代码
import org.example.seckilldemo.entity.Product;
import org.example.seckilldemo.mapper.ProductMapper;
import org.example.seckilldemo.redis.RedisUtil;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ProductService {

    @Autowired
    private ProductMapper productDao;
    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisUtil redisUtil;

    // 假设的缓存操作方法
    private void setProductToCache(String cacheKey, Product product) {
        // 实际的缓存写入逻辑,例如使用RedisTemplate

    }

    private Product getProductFromCache(String cacheKey) {

        // 实际的缓存读取逻辑,例如使用RedisTemplate
        Product product= (Product) redisUtil.get(cacheKey);
        return product;
    }

    // 读取商品信息
    public Product get(Long productId) {
        String productCacheKey = "PRODUCT_CACHE_" + productId;
        //从缓存中获取数据,没有再从数据库中获取
        Product product = getProductFromCache(productCacheKey);
        if (product != null) {
            return product;
        }

        // 分布式锁防止缓存击穿
        RLock productHotCacheLock = redissonClient.getLock("LOCK_PRODUCT_HOT_CACHE_CREATE_PREFIX_" + productId);
        productHotCacheLock.lock();
        try {
            product = getProductFromCache(productCacheKey);
            if (product != null) {
                return product;
            }

            // 使用读写锁的读锁
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("LOCK_PRODUCT_UPDATE_PREFIX_" + productId);
            RLock rLock = readWriteLock.readLock();
            rLock.lock();
            try {
                product = productDao.get(productId);
                if (product != null) {
                    setProductToCache(productCacheKey, product);
                }
                return product;
            } finally {
                rLock.unlock();
            }
        } finally {
            productHotCacheLock.unlock();
        }
    }

    // 更新商品信息
    public void update(Product product) {
        Long productId = product.getId();
        String productCacheKey = "PRODUCT_CACHE_" + productId;

        // 使用读写锁的写锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("LOCK_PRODUCT_UPDATE_PREFIX_" + productId);
        RLock writeLock = readWriteLock.writeLock();
        writeLock.lock();
        try {
            // 先更新数据库
            productDao.update(product);
            // 再删除缓存
            // 假设的删除缓存方法
            deleteProductFromCache(productCacheKey);
        } finally {
            writeLock.unlock();
        }
    }

    // 假设的删除缓存方法
    private void deleteProductFromCache(String cacheKey) {
        // 实际的缓存删除逻辑,例如使用RedisTemplate
    }
}

异步缓存更新补偿机制

有时候因为网络抖动等原因,删除缓存操作可能失败。可以引入异步任务来进行缓存补偿更新。

java 复制代码
public void update(Product product) {
    Long productId = product.getId();
    String productCacheKey = "PRODUCT_CACHE_" + productId;

    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("LOCK_PRODUCT_UPDATE_PREFIX_" + productId);
    RLock writeLock = readWriteLock.writeLock();
    writeLock.lock();
    try {
        productDao.update(product);
        if (!deleteProductFromCache(productCacheKey)) {
            // 假设删除缓存失败返回false
            cacheUpdateAsyncTask.updateCacheAfterDBUpdate(productId);
        }
    } finally {
        writeLock.unlock();
    }
}
java 复制代码
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

    @Component
    public class CacheUpdateAsyncTask {

        @Autowired
        private ProductService productService;

        // 异步更新缓存任务
        @Async
        public void updateCacheAfterDBUpdate(Long productId) {
            Product product = productService.get(productId);
            if (product != null) {
                // 重新写入缓存逻辑
                productService.setProductToCache("PRODUCT_CACHE_" + productId, product);
            }
        }
    }  
、

缓存版本控制

为缓存数据添加版本号,每次更新数据库时,同时更新版本号。读取数据时,比较缓存版本和数据库版本,如果不一致则更新缓存。

Product 实体类中添加版本字段:

java 复制代码
public class Product {
    private Long id;
    // 其他属性
    private int version;

    // 省略getter和setter
}

ProductService 中更新相关逻辑:

java 复制代码
public Product get(Long productId) {
    String productCacheKey = "PRODUCT_CACHE_" + productId;
    Product cachedProduct = getProductFromCache(productCacheKey);
    if (cachedProduct != null) {
        Product dbProduct = productDao.get(productId);
        if (cachedProduct.getVersion() == dbProduct.getVersion()) { 
            return cachedProduct;
        }
    }

    // 后续逻辑同之前的get方法
}

public void update(Product product) {
    Long productId = product.getId();
    String productCacheKey = "PRODUCT_CACHE_" + productId;

    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("LOCK_PRODUCT_UPDATE_PREFIX_" + productId);
    RLock writeLock = readWriteLock.writeLock();
    writeLock.lock();
    try {
        // 更新数据库时同时更新版本号
        product.setVersion(product.getVersion() + 1); 
        productDao.update(product);
        deleteProductFromCache(productCacheKey);
    } finally {
        writeLock.unlock();
    }
}

通过上述多种策略结合,可以有效降低并发时更新缓存和数据库带来的不一致问题 。

相关推荐
Flittly2 分钟前
【SpringAIAlibaba新手村系列】(13)Tool Calling 函数工具调用技术
java·spring boot·spring·ai
my_styles36 分钟前
linux系统下安装 tengine / 宝兰德等国产信创中间件和闭坑
linux·运维·服务器·spring boot·nginx·中间件
一个有温度的技术博主41 分钟前
Redis集群实战:如何实现节点的弹性伸缩与数据迁移?
redis·分布式·缓存·架构
coder阿龙1 小时前
基于SpringAI+Qdrant+Ollama本地模型和向量数据库开发问答和RAG检索
java·数据库·spring boot·ai·数据库开发
小江的记录本1 小时前
【JEECG Boot】 JEECG Boot——数据字典管理 系统性知识体系全解析
java·前端·spring boot·后端·spring·spring cloud·mybatis
张元清1 小时前
不用 Server Components 也能做 React 流式 SSR —— 实战指南
前端·javascript·面试
Jul1en_1 小时前
【Redis】常用命令及定时器实现思想
数据库·redis·缓存
杰克尼1 小时前
redis(day02-短信登录)
数据库·redis·缓存
却话巴山夜雨时i2 小时前
互联网大厂Java面试:从Spring到微服务的全栈挑战
java·spring boot·redis·微服务·面试·kafka·技术栈
杰克尼2 小时前
springCloud(day10-面试篇)
redis·spring cloud·面试