使用了 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();
}
}
通过上述多种策略结合,可以有效降低并发时更新缓存和数据库带来的不一致问题 。