分布式锁从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 分布式锁的开发全部完成
相关推荐
剑胆琴心静水深流2 小时前
全栈之路6---web集成与呈现
前端·vue.js·spring boot·分布式·spring·前端框架·npm
阮胜昌3 小时前
MySQL 中的动态数据脱敏:无需更改应用程序即可保护敏感数据
android·mysql·adb
小白说大模型5 小时前
AI驱动的个性化学习路径:知识图谱与知识点关联的存储与推理
大数据·人工智能·学习·mysql·机器学习·prompt·知识图谱
这个DBA有点耶5 小时前
多模数据库深度解读:从“多库拼装”到“一库多能”的架构演进
数据库·mysql·dba
程序员夏洛6 小时前
MySQL 默认的事务隔离级别是什么?为什么选择这个级别?
数据库·mysql
卓怡学长6 小时前
w180springboot基于Java的悠扬乐器管理
java·spring boot·mysql·spring·maven·intellij-idea
KAIWEILIUCC6 小时前
Ray:高性能、易扩展的Python分布式计算框架
分布式·llm
( •̀∀•́ )9207 小时前
MySQL 自动备份与恢复方案
数据库·mysql·oracle
这个DBA有点耶7 小时前
大事务的“事前预防”:监控、拦截、Kill,四层防线一次讲透
数据库·mysql·代码规范
nvd119 小时前
LiteLLM 与 Redis 响应缓存机制:从精确哈希匹配到模型底层 KV-Cache 边界
redis·缓存·哈希算法