分布式锁从0到1落地实现03(mysql/redis/zk)redisLock终极版

1 分布式锁从0到1落地实现 redis终极版

    //1:独占排他预防栈内存溢出
    //2:预防死锁 集群中客户端的某个节点挂了,自己的锁没释放,导致其他客户端没法获取到锁
    //3:原子性
    //4:防止误删
    //5:可重入
    //6:自动续期
在之前的文章中已经处理了各种 情况下的 redis 的锁 现在还剩下两个问题
    //5:可重入
    //6:自动续期
   下面就来集合 lua 实现一个完整的分布式锁
   1>创建一个分布式锁工厂类 用于创建不同的分布式锁  redis zk mysql 的,现在暂时只要有 redis 的
   =========================================================
   import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.wl.study.lock.DistributedRedisLock;

import java.util.UUID;

@Component
public class DistributedLockFactory {
    //单例对象 使用uuid 每个服务生成的是一样的,后面再加上当前线程的 id 来作为分布式锁的filed
    @Autowired
    private StringRedisTemplate redisTemplate; //redis 模板工具类也需要时同一个

    private String uuid;

    public DistributedLockFactory() {
            this.uuid = UUID.randomUUID().toString();
    }

    public DistributedRedisLock getRedisLock(String lockName) {
        return new DistributedRedisLock(lockName, redisTemplate,uuid);
    }
}
===========================================================   
锁对象及其方法实现
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class DistributedRedisLock implements Lock {


    private final String lockName;
    private StringRedisTemplate redisTemplate;

    private String uuid;
    private Long expireTime = 10L;


    public DistributedRedisLock(String lockName, StringRedisTemplate redisTemplate, String uuid) {
        this.lockName = lockName;
        this.redisTemplate = redisTemplate;
        this.uuid = uuid;
    }

    @Override
    public void lock() {
        this.tryLock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        try {
            this.tryLock(-1L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return false;
    }

    //加锁
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time != -1) {
            this.expireTime = unit.toSeconds(time);
        }
        // KEYS[1] 代表第一个key ,ARGV[1] 第一个KEY的值
        // ARGV[2] 代表过期时间
        String script = "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
                "then " +
                "redis.call('hincrby',KEYS[1],ARGV[1],1) " +
                "redis.call('expire',KEYS[1],ARGV[2]) " +
                "return 1 " +
                "else " +
                "return 0 " +
                "end";
        while (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), getLockFiled(), String.valueOf(expireTime))) {
            Thread.sleep(50);
        }
        return true;
    }

    //解锁
    @Override
    public void unlock() {
        String script = "if redis.call('hexists',KEYS[1],ARGV[1]) == 0 " +
                "then " +
                "return nil " +
                "elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 " +
                "then " +
                "   return redis.call('del',KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";
        Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), getLockFiled());
        if (flag == null) {
            throw new IllegalMonitorStateException("this lock is not belong to you");
        }

    }

    @Override
    public Condition newCondition() {
        return null;
    }

    private String getLockFiled(){
        return uuid+":"+Thread.currentThread().getId();
    }
}

================================================
service 调用
    //自己手动完成
    @Autowired
    private StringRedisTemplate redisTemplate;
    //1:独占排他预防栈内存溢出
    //2:预防死锁 集群中客户端的某个节点挂了,自己的锁没释放,导致其他客户端没法获取到锁
    //3:原子性
    //4:防止误删
    //5:可重入
    //6:自动续期
    public void reduce() {
        DistributedRedisLock redisLock = distributedLockFactory.getRedisLock("lock");
        redisLock.lock();
        try {
            String stock = this.redisTemplate.opsForValue().get("stock");
            if (stock!= null && stock.length() != 0) {
                Integer st = Integer.parseInt(stock);
                if (st > 0) {
                    this.redisTemplate.opsForValue().set("stock", String.valueOf(--st));
                }
            }
            this.testReEnter();  //测试重入
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            redisLock.unlock();
        }
    }

    public void testReEnter(){
        DistributedRedisLock redisLock = distributedLockFactory.getRedisLock("lock");
        redisLock.lock();
        System.out.println(".......................测试可重入..............");
        redisLock.unlock();
    }
执行结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/30150ae7f0fb48e584e3f0d90ce4393e.png)

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a62eb2fc21bb4dbeadeadd3e3eaefc59.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/92447dad6ebc44debb93b36771bf8aa4.png)


2 自动续期以及带来的问题

package org.wl.study.lock;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class DistributedRedisLock implements Lock {


    private final String lockName;
    private StringRedisTemplate redisTemplate;

    private String uuid;
    private Long expireTime = 10L;


    public DistributedRedisLock(String lockName, StringRedisTemplate redisTemplate, String uuid) {
        this.lockName = lockName;
        this.redisTemplate = redisTemplate;
        this.uuid = uuid;
    }

    @Override
    public void lock() {
        this.tryLock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        try {
            this.tryLock(-1L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return false;
    }

    //加锁
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time != -1) {
            this.expireTime = unit.toSeconds(time);
        }
        // KEYS[1] 代表第一个key ,ARGV[1] 第一个KEY的值
        // ARGV[2] 代表过期时间
        String script = "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
                "then " +
                "redis.call('hincrby',KEYS[1],ARGV[1],1) " +
                "redis.call('expire',KEYS[1],ARGV[2]) " +
                "return 1 " +
                "else " +
                "return 0 " +
                "end";
        while (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), getLockFiled(), String.valueOf(expireTime))) {
            Thread.sleep(50);
        }
        this.reNewExpire();//重置过期时间
        return true;
    }

    //解锁
    @Override
    public void unlock() {
        String script = "if redis.call('hexists',KEYS[1],ARGV[1]) == 0 " +
                "then " +
                "return nil " +
                "elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 " +
                "then " +
                "   return redis.call('del',KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";
        Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), getLockFiled());
        if (flag == null) {
            throw new IllegalMonitorStateException("this lock is not belong to you");
        }

    }

    @Override
    public Condition newCondition() {
        return null;
    }

    //这个方法在自动续期的时候 会有问题
    private String getLockFiled(){
        return uuid+":"+Thread.currentThread().getId();//当前线程
    }

    private void reNewExpire(){
        String script ="if redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
                "then " +
                "   return redis.call('expire',KEYS[1],ARGV[2]) " +
                "else " +
                "  return 0 " +
                "end";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                //子线程 getLockFiled()  拿到的时子线程已经不能给原来的线程操作的锁做自动续期了
                if(redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class),Arrays.asList(lockName),getLockFiled())){
                    reNewExpire();
                }
            }
        },this.expireTime * 1000 / 2,this.expireTime * 1000 / 2);
    }
}


需要修改代码
这个方法去掉
    private String getLockFiled(){
        return uuid+":"+Thread.currentThread().getId();//当前线程
    }
锁的构造方法
 public DistributedRedisLock(String lockName, StringRedisTemplate redisTemplate, String uuid) {
        this.lockName = lockName;
        this.redisTemplate = redisTemplate;
        this.uuid = uuid +":"+Thread.currentThread().getId();  //这里来生产uuid
    }

替换加锁 解锁 和续期的 使用到uuid 的 地方
package org.wl.study.lock;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class DistributedRedisLock implements Lock {


    private final String lockName;
    private StringRedisTemplate redisTemplate;

    private String uuid;
    private Long expireTime = 10L;


    public DistributedRedisLock(String lockName, StringRedisTemplate redisTemplate, String uuid) {
        this.lockName = lockName;
        this.redisTemplate = redisTemplate;
        this.uuid = uuid +":"+Thread.currentThread().getId();
    }

    @Override
    public void lock() {
        this.tryLock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        try {
            this.tryLock(-1L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return false;
    }

    //加锁
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time != -1) {
            this.expireTime = unit.toSeconds(time);
        }
        // KEYS[1] 代表第一个key ,ARGV[1] 第一个KEY的值
        // ARGV[2] 代表过期时间
        String script = "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
                "then " +
                "redis.call('hincrby',KEYS[1],ARGV[1],1) " +
                "redis.call('expire',KEYS[1],ARGV[2]) " +
                "return 1 " +
                "else " +
                "return 0 " +
                "end";
        while (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expireTime))) {
            Thread.sleep(50);
        }
        this.reNewExpire();//重置过期时间
        return true;
    }

    //解锁
    @Override
    public void unlock() {
        String script = "if redis.call('hexists',KEYS[1],ARGV[1]) == 0 " +
                "then " +
                "return nil " +
                "elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 " +
                "then " +
                "   return redis.call('del',KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";
        Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);
        if (flag == null) {
            throw new IllegalMonitorStateException("this lock is not belong to you");
        }

    }

    @Override
    public Condition newCondition() {
        return null;
    }

    //这个方法在自动续期的时候 会有问题
//    private String getLockFiled(){
//        return uuid+":"+Thread.currentThread().getId();//当前线程
//    }

    private void reNewExpire(){
        String script ="if redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
                "then " +
                "   return redis.call('expire',KEYS[1],ARGV[2]) " +
                "else " +
                "  return 0 " +
                "end";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                //子线程 getLockFiled()  拿到的时子线程已经不能给原来的线程操作的锁做自动续期了
                if(redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class),Arrays.asList(lockName),uuid)){
                    reNewExpire();
                }
            }
        },this.expireTime * 1000 / 2,this.expireTime * 1000 / 2);
    }
}

到这里redis 分布式锁的开发全部完成
  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值