Jedis基于Redis实现分布式锁

注:该文是本博主记录学习之用,没有太多详细的讲解,敬请谅解!

一、背景

之前写过《JedisLock基于Redis实现分布式锁》的文章,从它的实现逻辑中你会发现存在一些问题(具体问题可以跳转过去看,故这里不作详细说明),所以本文基于Redis的官方文档的实现逻辑实现分布式锁。

二、实现分布式锁的原理

1、实现思想:

(1)获取锁的时候,使用Set命令加锁,这个命令仅在不存在key的时候才能被执行成功(NX选项),并且这个key有一个自动失效时间expire(PX属性)。这个key的值是“my_random_value”(一个随机值),这个值在所有的客户端必须是唯一的,所有同一key的获取者(竞争者)这个值都不能一样。
官方命令:SET resource_name my_random_value NX PX expire

(2)第二步是为了防止操作超时,锁过期,别人获得了锁,导致业务不一致。实现逻辑是通过key获取到value值跟当前的value值对比是否一致。一致则没有超时,不一致则表示操作超时导致了锁过期,别人获得锁,可以解决业务一致性问题。

(3)释放锁的时候,通过lua脚本告诉Redis只有key存在并且存储的值和我指定的值一样才能告诉我删除成功。
Lua脚本:
if redis.call('GET',KEYS[1]) == ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end

三、Maven依赖

注意这里使用的是3.1.0版本,每个版本实现不同。

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

四、代码实现

JedisLock锁工具类

package com.test.demo;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.util.UUID;

/**
 * @date: 2019/8/2 18:49
 * @description: 实现分布式锁工具类
 */
public class JedisLock {

    /**
     * 客户端jedis
     */
    private Jedis jedis;
    /**
     * 锁key
     */
    private String key;
    /**
     * 锁超时时间,毫秒为单位
     */
    private int expire = 5000;
    /**
     * 获取锁等待时间,毫秒为单位
     */
    private int timeout = 3000;
    /**
     * 是否占有锁
     */
    private volatile boolean locked = false;
    /**
     * 唯一标识
     */
    private UUID uuid;

    /**
     * 线程等待时间
     */
    private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;

    /**
     * 删除key的lua脚本
     */
    private static final String LUA_DEL_SCRIPT = "if redis.call('GET',KEYS[1]) == ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end";


    public JedisLock(Jedis jedis,String key,int timeout,int expire){
        this.jedis=jedis;
        this.key=key;
        this.timeout=timeout;
        this.expire=expire;
        this.uuid=UUID.randomUUID();
    }

    /**
     * set值
     * 说明:这个命令仅在不存在key的时候才能被执行成功(NX选项),并且这个key有一个自动失效时间(PX属性)
     *      这个key的值是一个唯一值,这个值在所有的客户端必须是唯一的,所有同一key的获取者(竞争者)这个值都不能一样。
     * @param value
     * @return
     */
    public String setNX(final String value){
        SetParams setParams=new SetParams();
        setParams.nx();
        setParams.px(this.expire);
        return this.jedis.set(this.key,value,setParams) ;
    }

    /**
     * 获取锁
     * @return
     */
    public synchronized boolean lock() throws InterruptedException {
        long timeout=this.timeout;
        while (timeout>0){
            //获取锁,返回OK则代表获取锁成功
            if("OK".equals(this.setNX(this.getLockValue(Thread.currentThread().getId())))){
                System.out.println("#######获得锁#######");
                this.locked=true;
                return true;
            }
            timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
            Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
        }
        return false;
    }

    /**
     * 释放锁
     * 说明:通过key和唯一value值删除
     * @return
     */
    public synchronized void release(){
        if(this.locked){
            System.out.println("#######释放锁#######");
            long result= (long) this.jedis.eval(LUA_DEL_SCRIPT,1,this.key,this.getLockValue(Thread.currentThread().getId()));
            if(result>0){
                this.locked=false;
            }
        }

    }

    /**
     * 判断当前线程是否还继续拥有锁
     * 说明:该方法主要用来判断操作时间已经超过key的过期时间,可以用来做业务过滚
     * @return
     */
    public  boolean checkTimeOut(){
        if(this.locked){
            String value=this.jedis.get(this.key);
            if(this.getLockValue(Thread.currentThread().getId()).equals(value)){
                return true;
            }
        }
        return false;
    }

    /**
     * 锁的value值(唯一值)
     * 说明:value的值必须是唯一主要是为了更安全的释放锁,释放锁的时候使用脚本告诉Redis:只有key存在并且存储的值和我指定的值一样才能告诉我删除成功
     * @param threadId
     * @return
     */
    public String getLockValue(Long threadId){
        return this.uuid.toString()+"_"+threadId;
    }

}

调用实现方法

package com.test.demo;

import redis.clients.jedis.Jedis;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test {


    public static volatile int num=0;

    public static void main(String[] args) {

        ThreadPoolExecutor executor=new ThreadPoolExecutor(10, 20, 20L,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), new ThreadPoolExecutor.AbortPolicy());
        for (int i=0;i<10;i++){
            executor.execute(()->{testLock();});
        }

    }

    public static void testLock(){
        Jedis jedis=new Jedis("127.0.0.1",6379);
        JedisLock jedisLock=new JedisLock(jedis,"test",8000,3000);
        try {
            //第一步:获取锁
            if(jedisLock.lock()){
                num+=1;
                //该步骤是为了验证操作超时
                if(num==3){
                    Thread.sleep(5000);
                }
                System.out.println(num);
                //第二步:验证是否操作超时
                if(!jedisLock.checkTimeOut()){
                    System.out.println("######操作超时#######");
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //第三步:释放锁
            if(null!=jedisLock){
                jedisLock.release();
            }
            if(jedis!=null){
                jedis.close();
            }
        }
    }
}

五、注意点

1、该分布式实现是基于单例Redis,依然无法解决Redis集群下出现数据同步问题。若想集群下实现分布式锁,可以利用Redis推荐的Redlock算法实现。

1、Redission实现分布式锁(官网推荐)
https://blog.csdn.net/weixin_43947588/article/details/90064064

2、Curator基于zookeeper实现分布式锁
https://blog.csdn.net/weixin_43947588/article/details/84893777

3、JedisLock实现分布式锁
https://blog.csdn.net/weixin_43947588/article/details/84977357

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值