栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java分布式锁

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java分布式锁

使用redis锁使用分布式锁

可能存在的问题:

问题:setnx刚好获取到锁,业务逻辑出现异常,导致锁无法释放
解决:设置过期时间,自动释放锁。 在set时指定过期时间(保证原子性)
问题:可能会释放其他服务器的锁。比如,某一个业务正常执行需要3秒,但由于某种原因执行了7s,3s后锁自动释放,其他进程进入,7s后该进程del释放的是其他进程的锁
解决:setnx获取锁时,设置一个指定的唯一值(例如:uuid);释放前获取这个值,判断是否自己的锁
问题:删除操作缺乏原子性 进程1经过了uuid的判断,但此时刚好过了3秒,锁自动释放,此时进程2获得了锁,进程1的del释放的是进程2的锁
解决:使用lua脚本保证删除的原子性 lua在集群的情况下就不能保证删除的原子性
问题:redis集群状态下,进程1从master获取到锁,在master将锁同步到slave之前,master宕掉了,slaver节点晋升为master节点,这时进程2可以从新的master节点获取到锁。
解决:使用redlock 通过redisson实现redlock
 public void testLock() {
     //  设置uuId
     String uuid = UUID.randomUUID().toString();
     //  缓存的lock 对应的值 ,应该是index2 的uuid
     Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
     //  判断flag index=1
     if (flag){
         //  说明上锁成功! 执行业务逻辑
         String value = redisTemplate.opsForValue().get("num");
         //  判断
         if(StringUtils.isEmpty(value)){
             return;
         }
         //  进行数据转换
         int num = Integer.parseInt(value);
         //  放入缓存
         redisTemplate.opsForValue().set("num",String.valueOf(++num));

         //  定义一个lua 脚本
         String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

         //  准备执行lua 脚本
         DefaultRedisscript redisscript = new DefaultRedisscript<>();
         //  将lua脚本放入DefaultRedisscript 对象中
         redisscript.setscriptText(script);
         //  设置DefaultRedisscript 这个对象的泛型
         redisscript.setResultType(Long.class);
         //  执行删除
         redisTemplate.execute(redisscript, Arrays.asList("lock"),uuid);

     }else {
         //  没有获取到锁!
         try {
             Thread.sleep(1000);
             //  睡醒了之后,重试
             testLock();
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }
 }
使用redission实现分布式锁

1、导入依赖


   org.redisson
   redisson
   3.11.2

2、配置redission

//配置redisson
package com.atguigu.gmall.common.config;

@Data
@Configuration
@ConfigurationProperties("spring.redis")
public class RedissonConfig {

    private String host;
    private String password;
    private String port;
    private int timeout = 3000;
    private static String ADDRESS_PREFIX = "redis://";

    
    @Bean
    RedissonClient redissonSingle() {
        Config config = new Config();

        if(StringUtils.isEmpty(host)){
            throw new RuntimeException("host is  empty");
        }
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(ADDRESS_PREFIX + this.host + ":" + port)
                .setTimeout(this.timeout);
        if(!StringUtils.isEmpty(this.password)) {
            serverConfig.setPassword(this.password);
        }
        return Redisson.create(config);
    }
}

3、redisson分布式锁实现

@Autowired
private RedissonClient redissonClient;

@Override
public void testLock() {
    // 创建锁:
    String skuId="25";
    String locKey ="lock:"+skuId;
    // 锁的是每个商品
    RLock lock = redissonClient.getLock(locKey);
    // 开始加锁
    lock.lock();
    // 业务逻辑代码
    // 获取数据
    String value = redisTemplate.opsForValue().get("num");
    if (StringUtils.isBlank(value)){
        return;
    }
    // 将value 变为int
    int num = Integer.parseInt(value);
    // 将num +1 放入缓存
    redisTemplate.opsForValue().set("num",String.valueOf(++num));
    // 解锁:
    lock.unlock();
}
分布式锁 + AOP实现缓存

1、定义一个注解

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GmallCache{
    
    String prefix() default "cache";
}

2、 定义一个切面类加上注解

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.RedisConst;
import lombok.SneakyThrows;
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 org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;


@Component
@Aspect
public class GmallCacheAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    //  切GmallCache注解
    @SneakyThrows
    @Around("@annotation(com.atguigu.gmall.common.cache.GmallCache)")
    public Object cacheAroundAdvice(ProceedingJoinPoint joinPoint){
        //  声明一个对象
        Object object = new Object();
        //  在环绕通知中处理业务逻辑 {实现分布式锁}
        //  获取到注解,注解使用在方法上!
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        GmallCache gmallCache = signature.getMethod().getAnnotation(GmallCache.class);

        //  获取到注解上的前缀
        String prefix = gmallCache.prefix(); // sku

        //  方法传入的参数
        Object[] args = joinPoint.getArgs();

        //  组成缓存的key 需要前缀+方法传入的参数
        String key = prefix+ Arrays.asList(args).toString();

        //  防止redis ,redisson 出现问题!
        try {
            //  从缓存中获取数据
            //  类似于skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);
            object = cacheHit(key,signature);
            //  判断缓存中的数据是否为空!
            if (object==null){
                //  从数据库中获取数据,并放入缓存,防止缓存击穿必须上锁
                //  perfix = sku  index1 skuId = 32 , index2 skuId = 33
                //  public SkuInfo getSkuInfo(Long skuId)
                //  key+":lock"
                String lockKey = prefix + ":lock";
                //  准备上锁
                RLock lock = redissonClient.getLock(lockKey);
                boolean result = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
                //  上锁成功
                if (result){
                    try {
                        //  表示执行方法体 getSkuInfoDB(skuId);
                        object = joinPoint.proceed(joinPoint.getArgs());
                        //  判断object 是否为空
                        if (object==null){
                            //  防止缓存穿透
                            Object object1 = new Object();
                            redisTemplate.opsForValue().set(key, JSON.toJSONString(object1),RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                            //  返回数据
                            return object1;
                        }
                        //  放入缓存
                        redisTemplate.opsForValue().set(key, JSON.toJSONString(object),RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);

                        //  返回数据
                        return object;
                    } finally {
                        lock.unlock();
                    }
                }else{
                    //  上锁失败,睡眠自旋
                    Thread.sleep(1000);
                    return cacheAroundAdvice(joinPoint);
                    //  理想状态
                    //  return cacheHit(key, signature);
                }
            }else {
                return object;
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        //  如果出现问题数据库兜底
        return joinPoint.proceed(joinPoint.getArgs());
    }
    
    private Object cacheHit(String key, MethodSignature signature) {
        //  通过key 来获取缓存的数据
        String strJson = (String) redisTemplate.opsForValue().get(key);
        //  表示从缓存中获取到了数据
        if (!StringUtils.isEmpty(strJson)){
            //  字符串存储的数据是什么?   就是方法的返回值类型
            Class returnType = signature.getReturnType();
            //  将字符串变为当前的返回值类型
            return JSON.parseObject(strJson,returnType);
        }
        return null;
    }
}

3、使用注解完成缓存

@GmallCache(prefix = RedisConst.SKUKEY_PREFIX)
@Override
public SkuInfo getSkuInfo(Long skuId) {

    return getSkuInfoDB(skuId);
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/694798.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号