关于分布式锁的Aop(这里没写延时双删)
1.首先关于分布式锁
分布式锁主流的实现机制(都是为了跨jvm的互斥机制来控制共享资源)
基本思路
1. 获取锁
步骤:
- 构建锁的键:根据业务需求生成一个唯一的锁键,例如
lock:sku:123
。- 设置锁:使用Redis的
SETNX
命令(Set if Not Exists)尝试设置锁键的值。如果键不存在,则设置成功,表示获取锁。- 设置过期时间:为了防止锁持有者崩溃导致锁无法释放,设置一个合理的过期时间(TTL),例如30秒。
2. 检查锁状态
步骤:
- 判断是否获取锁:如果
SETNX
返回true
,表示成功获取锁。- 未获取锁时重试:如果
SETNX
返回false
,表示锁已被其他线程持有,可以选择自旋等待或重试。3. 释放锁
步骤:
- 删除锁键:在完成业务逻辑后,删除锁键以释放锁。
- 避免误删:为了防止误删其他线程的锁,可以使用Lua脚本确保只有锁的持有者才能删除锁。
1.1redis实现
try {// 缓存存储数据:key-value// 定义key sku:skuId:infoString skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;// 获取里面的数据? redis 有五种数据类型 那么我们存储商品详情 使用哪种数据类型?// 获取缓存数据skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);// 如果从缓存中获取的数据是空if (skuInfo==null){// 直接获取数据库中的数据,可能会造成缓存击穿。所以在这个位置,应该添加锁。// 第一种:redis ,第二种:redisson// 定义锁的key sku:skuId:lock set k1 v1 px 10000 nxString lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKULOCK_SUFFIX;// 定义锁的值String uuid = UUID.randomUUID().toString().replace("-","");// 上锁Boolean isExist = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);if (isExist){// 执行成功的话,则上锁。System.out.println("获取到分布式锁!");// 真正获取数据库中的数据 {数据库中到底有没有这个数据 = 防止缓存穿透}skuInfo = getSkuInfoDB(skuId);// 从数据库中获取的数据就是空if (skuInfo==null){// 为了避免缓存穿透 应该给空的对象放入缓存SkuInfo skuInfo1 = new SkuInfo(); //对象的地址redisTemplate.opsForValue().set(skuKey,skuInfo1,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);return skuInfo1;}// 查询数据库的时候,有值redisTemplate.opsForValue().set(skuKey,skuInfo,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);// 解锁:使用lua 脚本解锁String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";// 设置lua脚本返回的数据类型DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();// 设置lua脚本返回类型为LongredisScript.setResultType(Long.class);redisScript.setScriptText(script);// 删除key 所对应的 valueredisTemplate.execute(redisScript, Arrays.asList(lockKey),uuid);return skuInfo;}else {// 其他线程等待Thread.sleep(1000);return getSkuInfo(skuId);}}else {return skuInfo;}} catch (InterruptedException e) {e.printStackTrace();}// 为了防止缓存宕机:从数据库中获取数据return getSkuInfoDB(skuId);}
1.2通过redission
try {//定义获取sku信息的keyString skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;//判断里面是否有skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);//判断skuInfo是否有if(skuInfo==null){//设置skuLockString skuLock = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKULOCK_SUFFIX;//先注入redissonRLock rlLock= redissonClient.getLock(skuLock);//设置锁boolean res = rlLock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);//判断是否拥有锁if(res){//拥有锁,查询数据skuInfo = getSkuInfoDB(skuId);try {//判断是否为空if(skuInfo==null){//new一个skuInfo = new SkuInfo();redisTemplate.opsForValue().set(skuKey,skuInfo,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);}else{//不为空,存储到redis中redisTemplate.opsForValue().set(skuKey,skuInfo,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);//释放锁}} finally {//释放锁rlLock.unlock();}}else{//没有获得锁则自旋Thread.sleep(1000);getSkuInfoByRedisson(skuId);}}else{//有就直接返回return skuInfo;}} catch (InterruptedException e) {throw new RuntimeException(e);}return getSkuInfoDB(skuId);
2.关于使用AOp来实现
为什么要使用aop的思想:因为我们不可能只缓存一个,有多个类似的,
依旧是基本的思想
可以看看spring官网,上面有
https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/at-aspectj.html
package com.atguigu.gmall.common.cache;import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.RedisConst;
import lombok.SneakyThrows;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.util.Arrays;
import java.util.concurrent.TimeUnit;/*** @author ChenZhou* @date 2025-04-01 18:35** 高并发* 定义Key* 根据key获取尝试从redis获取数据* 判断是否有缓存 有直接返回* 没有的话定义锁的key--->尝试加锁* ------>睡眠重试* --->成功的话 又查询* --->失败的话,赋值成null,* 无论有没有值都需要释放锁*** 释放锁lua脚本*/
/*需要能顾被扫描到*/
@Component
//这是一个切面
@Aspect
public class GmallCacheAspect {@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate RedisTemplate redisTemplate;//定义一个环绕通知@Around("@annotation(com.atguigu.gmall.common.cache.GmallCache)")@SneakyThrows//需不需要添加什么参数 需要加入连接点public Object mallCacheAspectMethod(ProceedingJoinPoint point){//创建一个新对象Object object = new Object();//拦截MethodSignature signature = (MethodSignature) point.getSignature();//现在我需要获取方法的注解,因为我需要获取里面的参数和方法名//获取 GmallCache 注解实例GmallCache annotation = signature.getMethod().getAnnotation(GmallCache.class);//获取前缀组成skuKeyString prefix = annotation.prefix();//SkuKey:prefix+skuId(这个是方法里面的参数)//point.getArgs().toString(); 这个样子的不对吧String key = prefix+ Arrays.asList(point.getArgs()).toString();// 从缓存中获取数据// 类似于skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);//我做的是切面,所以定义一个方法object = this.getRedisDate(key,signature);//判断是否获取到了缓存if(object ==null){//没有缓存,需要加锁RLock lock = redissonClient.getLock(key + "lock");//获取锁boolean res = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);if(res){try {//获取到了锁//执行业务逻辑:直接从数据库获取数据//如何查询数据库---》方法体里面 就是查询数据object = point.proceed(point.getArgs());//如果object为空就得防止缓存穿透问题if(object==null){//Object obj = new Object();//反射Class aClass = signature.getReturnType();Object obj = aClass.newInstance();//放入//不知道类型// 将缓存的数据变为 Json 的 字符串this.redisTemplate.opsForValue().set(key, JSON.toJSONString(object),RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);return obj;}//不为空的话就直接存储到redis中,但是还是需要转换成jsonthis.redisTemplate.opsForValue().set(key, JSON.toJSONString(object),RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);return object;//释放锁} finally {lock.unlock();}}else{//没有获取到锁//睡眠try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}else {return object;}//兜底return point.proceed(point.getArgs());}private Object getRedisDate(String key, MethodSignature signature) {// 从缓存中获取数据//类似于skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);//返回类型--多种String string = (String) this.redisTemplate.opsForValue().get(key);//判断缓存中有没有数据if(!StringUtils.isEmpty(string)){//不为空,转换成jsonreturn JSON.parseObject(string, signature.getReturnType());}return null;}
}