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<Long> 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、导入依赖

<!-- redisson -->
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.11.2</version>
</dependency>

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{
    /**
     * 缓存key的前缀
           * @return
     */
    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;

/**
 * @author mqx
 * 处理环绕通知
 * @date 2020-11-11 09:30:29
 */
@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());
    }
    /**
     *  表示从缓存中获取数据
     * @param key 缓存的key
     * @param signature 获取方法的返回值类型
     * @return
     */
    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);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Java分布式锁实现方式有多种,常见的包括: 1. 基于Redis分布式锁:利用Redis单线程的特性,使用SETNX命令创建锁,利用EXPIRE设置锁的过期时间,同时使用DEL命令释放锁,确保锁的释放是原子的。 2. 基于Zookeeper的分布式锁:通过创建临时节点实现分布式锁,当某个服务占用了锁,其它服务将无法创建同名节点,从而保证同一时间只有一个服务占用该锁。 3. 基于数据库的分布式锁:使用数据库表中的一行记录来表示锁状态,使用事务确保锁的获取和释放是原子的。 4. 基于Redisson的分布式锁Redisson是一个开源的Java分布式框架,提供了对分布式锁的支持,使用SETNX和EXPIRE命令实现锁的创建和过期,同时还提供了自旋锁、可重入锁等高级特性。 以上是Java分布式锁实现方式的几种常见方式,不同的实现方式有着各自的特点和适用场景,需要根据实际需求进行选择。 ### 回答2: Java分布式锁分布式系统中实现数据同步和控制的关键技术之一,它用于保证多个分布式进程并发访问共享资源时的数据一致性和安全性。分布式锁与普通的锁相比,需要解决跨进程、跨节点的同步和并发控制问题。 Java分布式锁的实现方式有以下几种: 1. 基于Zookeeper实现分布式锁 Zookeeper是一个高性能的分布式协调服务,它可以被用来实现分布式锁。Zookeeper的实现原理是基于它的强一致性和顺序性,可以保证多个进程访问同一个分布式锁时的数据同步和控制。 通过创建一个Zookeeper的持久节点来实现分布式锁,使用create()方法来创建节点,如果创建成功则说明获取锁成功。当多个进程同时请求获取锁时,只有一个进程能够创建节点成功,其它进程只能等待。当持有分布式锁的进程退出时,Zookeeper会自动删除对应的节点,其它进程就可以继续请求获取锁。 2. 基于Redis实现分布式锁 Redis是高性能的内存数据库,可以使用它的setnx()命令来实现分布式锁。setnx()命令可以在指定的key不存在时设置key的值,并返回1;如果key已经存在,则返回0。通过这个原子性的操作来实现分布式锁。 当多个进程同时请求获取锁时,只有一个进程能够成功执行setnx()命令,其它进程只能等待。进程在持有锁期间,可以利用Redis的expire()命令来更新锁的过期时间。当持有分布式锁的进程退出时,可以通过delete()命令来删除锁。 3. 基于数据库实现分布式锁 数据库通过ACID特性来保证数据的一致性、并发性和可靠性,可以通过在数据库中创建一个唯一索引来实现分布式锁。当多个进程同时请求获取锁时,只有一个进程能够成功插入唯一索引,其它进程只能等待。当持有分布式锁的进程退出时,可以通过删除索引中对应的记录来释放锁。 不同的实现方式各有优劣。基于Zookeeper的实现方式可以保证分布式锁的一致性和可靠性,但是需要引入额外的依赖;基于Redis可以实现较高性能的分布式锁,但是在高并发条件下可能会存在死锁等问题;基于数据库的实现方式简单,但在高并发条件下也可能会有锁争抢等问题。 总之,在选择分布式锁的实现方式时,需要根据业务场景和需求来综合考虑各种因素,选择最适合自己的方式。 ### 回答3: 分布式系统中的并发控制是解决分布式系统中竞争资源的重要问题之一,而分布式锁作为一种并发控制工具,在分布式系统中被广泛采用。Java作为一种常用的编程语言,在分布式锁的实现方面也提供了多种解决方案。下面就分别介绍Java分布式锁的实现方式。 1. 基于ZooKeeper的分布式锁 ZooKeeper是分布式系统中常用的协调工具,其提供了一套完整的API用于实现分布式锁。实现分布式锁的过程中需要创建一个Znode,表示锁,同时用于控制数据的访问。在这个Znode上注册监听器用于接收释放锁的成功/失败事件,从而控制加锁/解锁的过程。 2. 基于Redis分布式锁 Redis作为一种高性能的Key-Value数据库,其提供了完整的API用于实现分布式锁。实现分布式锁的过程中需要在Redis中创建一个Key,利用Redis的SETNX命令进行加锁,同时设置过期时间保证锁的生命周期。在解锁时需要判断是否持有锁并删除对应的Key。 3. 基于数据库的分布式锁 数据库作为分布式系统中常用的数据存储方式,其提供了事务机制用于实现分布式锁。在实现分布式锁的过程中需要在数据库中创建一个表,利用数据库的事务机制实现加锁/解锁,同时需要设置过期时间保证锁的生命周期。 总之,以上三种方式都是常用的Java分布式锁的实现方式。选择合适的方法需要综合考虑锁的使用场景、性能需求、可靠性要求等因素。同时,在实现分布式锁的过程中需要注意锁的加锁/解锁的正确性和过期时间的设置,保证分布式系统的并发控制的正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值