分布式锁2-Redis实现分布式锁

  • 分布式锁核心需求

  • redis分布式锁常见场景

  • redis分布式锁方案设计与实现

 

 

分布式锁核心需求

  • 互斥性

同一时刻只能有一个客户端加锁,不可出现多个客户端同时持有锁的情况

  • 防止死锁

防止一台机器出现 宕机,没有释放锁,导致其他机器无法加锁的情况。此处可通过锁超时机制来实现,给锁设置超时时间,超过某个时长则自动释放锁。

  • 高性能

分布式锁应该具备高并发的能力,对于访问量大的资源,需要考虑减少锁等待的时间,减少线程阻塞的情况。故在锁设计考虑:

1、粒度尽可能小:加锁尽量到最细的业务维度。比如牌价信息,可加锁到具体产品id上

2、锁范围尽可能小:加锁lock和unlock内部核心代码控制在有效范围内

  • 可重入性

类似 ReentrantLock的可重入锁,其特点是:同一个线程可以重复拿到同一资源的锁,有利于资源的高效利用。

 

redis分布式锁常见场景

  • 基于服务级别的分布式锁

即多机器启动后,同时抢锁;一台机器抢到锁,其它机器不断等待,如果持有锁的机器宕机,其它机器重新抢锁,知道有一台抢到为止。

  • 基于用户级别的分布式锁

用户在做交易的过程当中,同一用户在同一时刻做交易(此交易会引起资金变动);此时只能让每笔交易通过同步方式一笔一笔处理完成,不可同时处理。

 

 

redis分布式锁方案设计

  • redisson处理redis分布式锁分布式锁

解决基于服务级别的分布式锁

 

设计原理:

 

分布式锁2-Redis实现分布式锁_第1张图片

  • 加锁机制

线程去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。

线程去获取锁,获取失败: 一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。

  • watch dog自动延期机制

在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放,你也可以设置锁的有效时间(不设置默认30秒),这样的目的主要是防止死锁的发生。

  • 可重入加锁机制

redisson可以实现可重入加锁机制。

 

代码实现:

引入依赖:

   org.redisson

   redisson-spring-boot-starter

   3.12.1

 

配置服务:单机配置,也可配置集群

spring:

  redis:

    host: 118.190.173.250

    port: 6379

    password: zyj

 

逻辑实现:

1、加锁逻辑:

//redisson默认就是加锁30秒,建议也是30秒以上,默认的lockWatchdogTimeout会每隔10秒观察一下,

// 待到20秒的时候如果主进程还没有释放锁,就会主动续期30秒

// a、lock.tryLock(5, 30, TimeUnit.SECONDS);  有锁等待5s,加锁延迟一次30s,只延迟一次

// b、lock.tryLock(5, TimeUnit.SECONDS);  有锁等待5s

// c、lock.lock();

//a、b、c三种方式的结果是用a方式没有实现lockwatchdong机制,用c机制类似于普通加锁,无加锁等待,用c机制有实现lockwatchdog机制

public boolean lock(Object obj, String lockKey, String requestId, int expireTime) {

    RedissonClient redissonClient= (RedissonClient) obj;

    RLock lock = redissonClient.getLock(lockKey);

    System.out.println(Thread.currentThread()+"==========是否已加锁1:"+lock.isLocked());

    boolean b=false;

    try {

        //有锁等待5s,加锁延迟一次30s

      //  b = lock.tryLock(5, 30, TimeUnit.SECONDS);

        //有锁等待5s

        b = lock.tryLock(5,TimeUnit.SECONDS);

        System.out.println(Thread.currentThread()+"加锁操作=====加锁结果:"+b+"=====是否已加锁2:"+lock.isLocked());

        while(!b){

            System.out.println(Thread.currentThread()+"==========已被加锁,重新开始获取:"+b);

            //等待3s后重新加锁

            b = lock.tryLock(5,TimeUnit.SECONDS);

        }

        System.out.println(Thread.currentThread()+"==========获取锁结果:"+b);

    }catch (Exception e){

        lock.unlock();

    }

    return b;

}

2、解锁逻辑

public boolean unLock(Object obj, String lockKey, String requestId) {

    RedissonClient redissonClient= (RedissonClient) obj;

    RLock lock = redissonClient.getLock(lockKey);

    if(lock.isLocked()){

        lock.unlock();

        return true;

    }

    return false;

}

 

3、业务测试:

@RequestMapping("/redisson/{id}")

public long testRedisDemo(@PathVariable String id ) throws InterruptedException {

    long startTime=System.currentTimeMillis();

 

 

    boolean lock= redisLockService.lock(redissonClient,id,id,3000);

    System.out.println(Thread.currentThread()+"===========锁定redis:"+lock);

 

 

    System.out.println(Thread.currentThread()+"==========处理业务逻辑===等待3s开始========");

    List list=new ArrayList<>();

    list.add("test1");

    list.add("test2");

    RSortedSet sortedSet = redissonClient.getSortedSet("mySortedSet");

    if(null==sortedSet||sortedSet.size()==0){

        sortedSet.addAll(list);

    }

    System.out.println(Thread.currentThread()+"==========获取有序集合========:"+sortedSet);

    TimeUnit.SECONDS.sleep(50);

    System.out.println(Thread.currentThread()+"==========处理业务逻辑===等待3s结束========");

 

 

 

 

    boolean unLock=  redisLockService.unLock(redissonClient,id,id);

    System.out.println(Thread.currentThread()+"===========解锁redis:"+unLock);

 

 

    long endTime=System.currentTimeMillis();

    return (endTime-startTime);

}

 

4、测试结果:  线程1,5和线程2,5 。线程1,5线抢到锁,线程2,5 进入等待;直至线程1,5释放锁后,线程2,5 持有锁

Thread[http-nio-18081-exec-1,5,main]==========是否已加锁1:false

Thread[http-nio-18081-exec-1,5,main]加锁操作=====加锁结果:true=====是否已加锁2:true

Thread[http-nio-18081-exec-1,5,main]==========获取锁结果:true

Thread[http-nio-18081-exec-1,5,main]===========锁定redis:true

Thread[http-nio-18081-exec-1,5,main]==========处理业务逻辑===等待3s开始========

Thread[http-nio-18081-exec-1,5,main]==========获取有序集合========:[test1, test2]

Thread[http-nio-18081-exec-2,5,main]==========是否已加锁1:true

Thread[http-nio-18081-exec-2,5,main]加锁操作=====加锁结果:false=====是否已加锁2:true

Thread[http-nio-18081-exec-2,5,main]==========已被加锁,重新开始获取:false

Thread[http-nio-18081-exec-2,5,main]==========已被加锁,重新开始获取:false

Thread[http-nio-18081-exec-2,5,main]==========已被加锁,重新开始获取:false

Thread[http-nio-18081-exec-2,5,main]==========已被加锁,重新开始获取:false

Thread[http-nio-18081-exec-2,5,main]==========已被加锁,重新开始获取:false

Thread[http-nio-18081-exec-1,5,main]==========处理业务逻辑===等待3s结束========

Thread[http-nio-18081-exec-1,5,main]===========解锁redis:true

Thread[http-nio-18081-exec-2,5,main]==========获取锁结果:true

Thread[http-nio-18081-exec-2,5,main]===========锁定redis:true

Thread[http-nio-18081-exec-2,5,main]==========处理业务逻辑===等待3s开始========

Thread[http-nio-18081-exec-2,5,main]==========获取有序集合========:[test1, test2]

Thread[http-nio-18081-exec-2,5,main]==========处理业务逻辑===等待3s结束========

Thread[http-nio-18081-exec-2,5,main]===========解锁redis:true

 

 

  • jedis处理redis分布式锁

解决基于用户级别的分布式锁

 

1、引入依赖:

   redis.clients

   jedis

   2.9.0

 

2、逻辑实现:加锁、解锁

public class JedisLockServiceImpl implements RedisLockService {

    //返回结果

    private static final String LOCK_SUCCESS = "OK";

    //是否存在

    private static final String SET_IF_NOT_EXIST = "NX";

    //过期时间:px毫秒  ex秒

    private static final String SET_WITH_EXPIRE_TIME = "PX";

    private static final Long RELEASE_SUCCESS = 1L;

    @Override

    public boolean lock(Object obj, String lockKey, String requestId, int expireTime) {

        Jedis jedis= (Jedis) obj;

        String result =jedis.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,3000);

        if(LOCK_SUCCESS.equals(result)){

            return true;

        }

        return false;

    }

 

 

    @Override

    public boolean unLock(Object obj,  String lockKey, String requestId) {

        Jedis jedis= (Jedis) obj;

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        System.out.println("=========:"+result);

        if (RELEASE_SUCCESS.equals(result)) {

            return true;

        }

        return false;

    }

}

 

3、业务测试

@RequestMapping("/jedis/{id}")

public long testDemo(@PathVariable String id ) throws InterruptedException {

    long startTime=System.currentTimeMillis();

    jedis.auth("zyj");

 

 

   boolean lock= jedisLockService.lock(jedis,"redis",id,3000);

    System.out.println(Thread.currentThread()+"===========锁定redis:"+lock);

 

 

    System.out.println(Thread.currentThread()+"==========处理业务逻辑===等待3s开始========");

    jedis.zadd("id",10,id);

    Set sets= jedis.zrange("id",0,100);

    System.out.println(Thread.currentThread()+"==========获取有序集合========:"+sets);

    TimeUnit.SECONDS.sleep(3);

    System.out.println(Thread.currentThread()+"==========处理业务逻辑===等待3s结束========");

 

    boolean unLock=  jedisLockService.unLock(jedis,"redis",id);

    System.out.println(Thread.currentThread()+"===========解锁redis:"+unLock);

 

 

    long endTime=System.currentTimeMillis();

    return (endTime-startTime);

}

 

4、测试结果:线程9,5和线程10,5 。线程9,5线抢到锁,线程10,5 进入等待;直至线程9,5释放锁后,线程10,5 持有锁

Thread[http-nio-18081-exec-9,5,main]===========锁定redis:true

Thread[http-nio-18081-exec-9,5,main]==========处理业务逻辑===等待3s开始========

Thread[http-nio-18081-exec-9,5,main]==========获取有序集合========:[test, test1]

Thread[http-nio-18081-exec-9,5,main]==========处理业务逻辑===等待3s结束========

=========:0

Thread[http-nio-18081-exec-9,5,main]===========解锁redis:false

Thread[http-nio-18081-exec-10,5,main]===========锁定redis:true

Thread[http-nio-18081-exec-10,5,main]==========处理业务逻辑===等待3s开始========

Thread[http-nio-18081-exec-10,5,main]==========获取有序集合========:[test, test1]

Thread[http-nio-18081-exec-10,5,main]==========处理业务逻辑===等待3s结束========

=========:0

Thread[http-nio-18081-exec-10,5,main]===========解锁redis:false

 

 

参考资料:

https://www.cnblogs.com/yorkd/p/12793101.html(redisson 设计原理)

https://blog.csdn.net/weixin_43691942/article/details/107591137(redisson)

https://www.cnblogs.com/moxiaotao/p/10829799.html(jdis分布式锁)

https://blog.csdn.net/zhanglong_4444/article/details/105795104?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5.channel_param(redis分布式锁的5个坑)

https://www.cnblogs.com/qdhxhz/p/11046905.html(看门狗源码解析)

 

 

 

 

 

 

你可能感兴趣的:(微服务,redis,分布式)