分布式锁从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 分布式锁的开发全部完成
相关推荐
jerry6094 小时前
7天用Go从零实现分布式缓存GeeCache(改进)(未完待续)
分布式·缓存·golang
锐策4 小时前
〔 MySQL 〕数据库基础
数据库·mysql
日月星宿~5 小时前
【MySQL】summary
数据库·mysql
古人诚不我欺5 小时前
jmeter常用配置元件介绍总结之分布式压测
分布式·jmeter
希忘auto7 小时前
详解MySQL安装
java·mysql
运维佬7 小时前
在 MySQL 8.0 中,SSL 解密失败,在使用 SSL 加密连接时出现了问题
mysql·adb·ssl
Runing_WoNiu7 小时前
MySQL与Oracle对比及区别
数据库·mysql·oracle
sam-1237 小时前
k8s上部署redis高可用集群
redis·docker·k8s
天道有情战天下7 小时前
mysql锁机制详解
数据库·mysql
看山还是山,看水还是。7 小时前
Redis 配置
运维·数据库·redis·安全·缓存·测试覆盖率