JedisLock基于Redis实现分布式锁

JedisLock基于Redis实现分布式锁

一、 背景

实际的项目开发过程中我们常常遇到高并发访问,容易导致数据不同步,例如:库存的增加, 故此我们可以通过Redis提供的特性实现分布式锁,以达到数据的同步性。本文主要通过客户端JedisLock来实现分布式锁。

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

二、 JedisLock实现分布式锁的原理

1、实现思想:

(1)获取锁的时候,使用setnx加锁,锁的value值为一个超时时间,通过超时时间进行判断锁的占有与释放。

(2)释放锁的时候,判断是不是占有锁,若是占有锁,则执行delete进行锁释放。

三、添加Maven依赖

<dependency>
    <groupId>com.github.jedis-lock</groupId>
    <artifactId>jedis-lock</artifactId>
    <version>1.0.0</version>
</dependency>

四、代码示例

package com.lance.net.server;

import com.github.jedis.lock.JedisLock;
import redis.clients.jedis.Jedis;

public class JedisLockTest {

    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<10;i++){
            int num = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Jedis jedis=new Jedis("127.0.0.1",6379);
                    lock(jedis,"key", num);
                }
            }).start();
        }

    }


   public static void lock(Jedis jedis, String key,int i){
       JedisLock jedisLock=new JedisLock(jedis,key,2000,2000);
       try {
           //获得锁
           jedisLock.acquire();
           System.out.println(i+"获得锁");
           if(i==2){
               //该代码块是为了验证效果
               System.out.println(i+"睡眠2秒....");
               Thread.sleep(2000);
           }
       }catch (Exception e){
            e.printStackTrace();
       }finally {
           System.out.println(i+"释放锁");
           if(jedisLock!=null){
               //释放锁
               jedisLock.release();
           }
           if(jedis!=null){
               jedis.close();
           }
       }
   }

}

效果图:
在这里插入图片描述

对于JedisLock如何基于Redis来实现锁的效果,本文就不一一描述,各位可自行看源码理解!

以下贴上源码以供学习:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.github.jedis.lock;

import redis.clients.jedis.Jedis;

public class JedisLock {
    Jedis jedis;
    String lockKey;
    int expireMsecs;
    int timeoutMsecs;
    boolean locked;

    public JedisLock(Jedis jedis, String lockKey) {
        this.expireMsecs = 60000;
        this.timeoutMsecs = 10000;
        this.locked = false;
        this.jedis = jedis;
        this.lockKey = lockKey;
    }

    public JedisLock(Jedis jedis, String lockKey, int timeoutMsecs) {
        this(jedis, lockKey);
        this.timeoutMsecs = timeoutMsecs;
    }

    public JedisLock(Jedis jedis, String lockKey, int timeoutMsecs, int expireMsecs) {
        this(jedis, lockKey, timeoutMsecs);
        this.expireMsecs = expireMsecs;
    }

    public JedisLock(String lockKey) {
        this((Jedis)null, lockKey);
    }

    public JedisLock(String lockKey, int timeoutMsecs) {
        this((Jedis)null, lockKey, timeoutMsecs);
    }

    public JedisLock(String lockKey, int timeoutMsecs, int expireMsecs) {
        this((Jedis)null, lockKey, timeoutMsecs, expireMsecs);
    }

    public String getLockKey() {
        return this.lockKey;
    }

    public synchronized boolean acquire() throws InterruptedException {
        return this.acquire(this.jedis);
    }
    
    /**
     * 获得 lock.
     * 实现思路: 主要是使用了redis 的setnx命令,缓存了锁.
     * reids缓存的key是锁的key,所有的共享, value是锁的到期时间(注意:这里把过期时间放在value了,没有时间上设置其超时时间)
     * 执行过程:
     * 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁
     * 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值
     *
     */
     
    public synchronized boolean acquire(Jedis jedis) throws InterruptedException {
        int timeout = this.timeoutMsecs;

        while(timeout >= 0) {
            long expires = System.currentTimeMillis() + (long)this.expireMsecs + 1L;
            String expiresStr = String.valueOf(expires);  //锁到期时间
            if (jedis.setnx(this.lockKey, expiresStr) == 1L) {
                this.locked = true;
                return true;
            }
          //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
          //获取上一个锁到期时间,并设置现在的锁到期时间,
          //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
            String currentValueStr = jedis.get(this.lockKey);
            if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
                String oldValueStr = jedis.getSet(this.lockKey, expiresStr);
                if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                    this.locked = true;
                    return true;
                }
            }

            timeout -= 100;
            Thread.sleep(100L);
        }

        return false;
    }

    public synchronized void release() {
        this.release(this.jedis);
    }

    public synchronized void release(Jedis jedis) {
        if (this.locked) {
            jedis.del(new String[]{this.lockKey});
            this.locked = false;
        }

    }
}

五、注意点

  1. 查看源码发现jedis.setnx(this.lockKey, expiresStr)设置值的时候value是超时时间,是通过value为超时时间计算出锁的到期时间,但是并没有设置key的过期时间,如果运行过程中Redis怠机,失去连接,就无法删除key,该key就会永久存在Redis中,直到下次运行命中该key。
  2. 由于是通过当前服务器时间来判断,故对于多台服务器时间需要强一致。

1、Redission实现分布式锁(官网推荐)
https://blog.csdn.net/weixin_43947588/article/details/90064064
2、Curator基于zookeeper实现分布式锁
https://blog.csdn.net/weixin_43947588/article/details/84893777
3、Jedis实现分布式锁
https://blog.csdn.net/weixin_43947588/article/details/98631392

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值