基于Redis实现分布式锁

Posted wy697495

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Redis实现分布式锁相关的知识,希望对你有一定的参考价值。

1.  分布式锁介绍

  什么是锁?在多线程(多进程)应用程序中,当需要访问到共同的资源时,尤其涉及到写操作的时候,如果不对资源访问做同步处理,会发生无法预料的情况。锁就是在程序中对资源访问做同步处理的,把异步变同步。java,数据库等都有锁的概念。

  那么什么是分布式锁呢?程序开发直至今日,许多的项目,尤其是互联网项目,单个服务已经无法满足需求了。许多的项目都是同时部署多个机器节点。这个时候如果要对同个资源做同步控制时,传统的锁已经无法解决问题了。比如java,我们知道java应用中的锁只能局限于单个jvm才能生效,但是多个jvm的情况下,锁就失去了作用。这个时候就要用到分布式锁了,保证某一时刻只能有一个客户端获取到锁。一般情况下,分布式锁在应用程序层面已经实现不了,这个时候需要借助到三方中间应用来实现分布式锁。比如,数据库,redis,zookeeper。本文就是讲如何使用redis实现分布式锁。

2.  Redis实现分布式锁原理

  实现分布式锁最重要的是确保多个客户端同时获取锁只能有一个客户端能获取到锁,其他的客户端都获取不到锁。redis提供了许多很好用的命令来确保当并发情况下操作时只有一个连接能操作成功,比如setnx命令,下面就以这个命令来实现分布式锁。

  nx的含义:当redis中不存在key就设置成功,如果存在key就设置失败,那么当多个连接同时使用nx命令来设置key时只有一个连接会操作成功,这个时候我们就可以认为该连接获取到了锁,其它连接获取锁失败。当获取到锁的连接用完后释放锁就把对应的key删除掉,这样其它的连接就可以去竞争获取锁了。为了防止获取到锁的连接没能正常关闭,导致其它连接无法获取到锁从而出现死锁的情况,我们可以给key设置一个过期时间,以防止死锁。下面是代码。

redis jar包依赖

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

代码实现

public class RedisLock {
    private static JedisPool jedisPool;

    //uuid,用于区分不同客户端
    private String uid;

    //锁的名称(key)
    private String lockName;

    //锁过期时间(防止死锁)
    private Integer expireTime;

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        // 设置最大连接数
        config.setMaxTotal(200);
        // 设置最大空闲数
        config.setMaxIdle(8);
        // 设置最大等待时间
        config.setMaxWaitMillis(1000 * 100);
        // 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的
        config.setTestOnBorrow(true);
        jedisPool = new JedisPool(config, "127.0.0.1", 6379, 3000, "123456");
    }

    public RedisLock(String lockName, Integer expireTime) {
        this.lockName = lockName;
        this.expireTime = (expireTime == null || expireTime <= 0) ? 100 : expireTime;
        this.uid = UUID.randomUUID().toString();
    }

    /**
     * @描述: 获取锁
     * @作者:
     * @时间:
     */
    public Boolean getLock() {
        Jedis jedisClient = jedisPool.getResource();
        try {
            //用uid加上线程id表示value,用于锁释放判断是否是当前线程获取到的锁
            String lockValue = uid + Thread.currentThread().getId();
            //用nx命令设置key并加上过期时间
            String result = jedisClient.set(lockName, lockValue, "nx", "ex", expireTime);
            return "ok".equalsIgnoreCase(result);
        } catch (Exception e) {
            //操作异常
            e.printStackTrace();
            return false;
        } finally {
            if (jedisClient != null) {
                jedisClient.close();
            }
        }
    }

    /**
     * @描述: 释放锁
     * @作者:
     * @时间:
     */
    public void releaseLock() {
        String lockValue = uid + Thread.currentThread().getId();
        Jedis jedisClient = jedisPool.getResource();
        try {
            String result = jedisClient.get(lockName);
            if (lockValue.equalsIgnoreCase(result)) {
                //是当前线程获取到的锁,释放
                jedisClient.del(lockName);
            }
        } catch (Exception e) {

        } finally {
            if (jedisClient != null) {
                jedisClient.close();
            }
        }

    }
}

 总结:以上虽然实现了锁的基本要求,但是可以看出来有以下几个缺点:

(1)没有实现可重入锁的概念,当前线程无法多次获取锁,这种实现可以通过改造key的value来实现重入锁;

(2)过期时间不好控制,如果在过期时间内当前获取到锁的线程还没执行完业务,锁就自动释放了,可能会导致其它线程获取到锁了。这个就得需要在获取到锁后加上监听事件了,当锁即将自动过期时更新过期时间,Redisson框架实现分布式锁就用到了这种监听;

(3)锁的释放不是原子性操作,这个解决方法可以通过lua脚本来解决;

总之redis的这种实现分布式锁还是存在一定的问题,在高并发的情况下很容易出现一直获取不到锁的情况,无法实现有序获取锁,这应该是用redis实现分布式锁最大的一个弊端。

 

注意:本文仅代表个人理解和看法哟!和本人所在公司和团体无任何关系!

以上是关于基于Redis实现分布式锁的主要内容,如果未能解决你的问题,请参考以下文章

Redis实现分布式锁(设计模式应用实战)

redis基于redis实现分布式并发锁

基于 Redis 分布式锁实现“秒杀”(含代码)

基于Redis实现分布式锁

基于redis和zookeeper的分布式锁实现方式

分布式锁的两种实现方式(基于redis和基于zookeeper)