结合redis和redisson依赖设计的分布式实现,达到读写互斥,且避免了缓存穿透和雪崩
涉及的依赖
<!--json依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- Spring AOP依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<!-- Spring 事务管理依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<!-- 如果你使用AspectJ的注解方式,还需要添加AspectJ的依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.2</version>
</dependency>
代码实现
package com.chenling.redis;
import com.alibaba.fastjson.JSON;
import com.chenling.Util.RedisUtil;
import com.chenling.dao.ProductDao;
import com.chenling.vo.Product;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
/**
* @Desc: 分布式锁实现
* @Auther:chenLing
* @Date: 2024/8/9 10:43
*/
@Service
public class DclLock {
static final String CACHE_PRODUCT = "cache:product:";
static final String CACHE_PRODUCT_UPDATE = "cache:product:update:";
static final String PRODUCT_EMPTY = "-1";
@Autowired
private RedisUtil redisUtil;
@Autowired
private Redisson redisson;
@Autowired
private ProductDao productDao;
public Product get(Long productId) {
Product product = null;
String cacheKey = CACHE_PRODUCT + productId;
product = getProductFromCache(cacheKey);
if (product != null) {
return product;
}
// DCL
RLock getLock = redisson.getLock(cacheKey);
// 加锁
getLock.lock();
try {
// 因为已经加锁,所以并发时的查询到此会变成串行,此时第一个的查询结果会缓存到数据库,所以再查一次缓存,
product = getProductFromCache(cacheKey);
if (product != null) {
return product;
}
// 增加写锁,避免查询和更新并发,造成数据的不一致
RReadWriteLock readWriteLock = redisson.getReadWriteLock(CACHE_PRODUCT_UPDATE + productId);
RLock rLock = readWriteLock.readLock();
rLock.lock(); try {
product = productDao.get(productId);
if (product == null) {
// 空值设置-1的缓存,避免缓存击穿
redisUtil.set(cacheKey, PRODUCT_EMPTY, 100);
} else {
// 查到则保存缓存,建议缓存时间设置一个具体时间的随机时间,避免同一时间缓存大量失效,造成缓存雪崩
redisUtil.set(cacheKey, JSON.toJSONString(product), 1000);
}
} finally {
rLock.unlock();
}
} finally {
getLock.unlock();
}
return product;
}
@Transactional
public Product update(Product product) {
// 更新前上锁,如果此锁已经在查询,则更新滞后
RReadWriteLock readWriteLock = redisson.getReadWriteLock(CACHE_PRODUCT_UPDATE + product.getProductId());
RLock updateLock = readWriteLock.writeLock();
updateLock.lock();
try {
productDao.update(product);
redisUtil.set(CACHE_PRODUCT + product.getProductId(), JSON.toJSONString(product), 1000);
} finally {
updateLock.unlock();
}
return new Product();
}
public Product getProductFromCache(String cacheKey) {
String productStr = redisUtil.get(cacheKey);
if (!StringUtils.hasLength(productStr)) {
if (productStr.equals(PRODUCT_EMPTY)) {
return new Product();
} else {
return JSON.parseObject(productStr, Product.class);
}
}
return null;
}
}
代码到这里初步实现了读写互斥,但是锁加的过多,且,在缓存不存在而多个读并发时,并发的每个 读都需要加一次锁,存在锁重复问题,有继续优化的空间。