• 分布式锁核心需求
  • redis分布式锁常见场景
  • redis分布式锁方案设计与实现

 

 

分布式锁核心需求

  • 互斥性

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

  • 防止死锁

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

  • 高性能

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

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

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

  • 可重入性

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

 

redis分布式锁常见场景

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

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

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

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

 

 

redis分布式锁方案设计

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

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

 

设计原理:

 

redis分布式锁 单机版 redis分布式锁设计_redis分布式锁 单机版

  • 加锁机制

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

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

  • watch dog自动延期机制

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

  • 可重入加锁机制

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

 

代码实现:

引入依赖:

<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson-spring-boot-starter</artifactId>
   <version>3.12.1</version>
</dependency>

 

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

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<String> list=new ArrayList<>();
    list.add("test1");
    list.add("test2");
    RSortedSet<String> 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、引入依赖:

<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>2.9.0</version>
</dependency>

 

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<String> 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