Redis锁详解(3种常见锁实现)

Redis锁详解(3种常见锁实现)-mikechen

Redis锁经常都会使用到,下面给大家详解3种常见的Redis锁实现@mikechen

1.SETNX实现锁

基于SETNX命令的简单锁,使用SETNX命令可以设置一个键的值,只有当键不存在时才会设置成功。

SETNX是”SET if Not eXists”的缩写,命令格式:

SETNX key value

参数说明:

  • key:要设置的键名。
  • value:要设置的值。

返回值:

  • 当键被成功设置时,返回1。
  • 当键已经存在,无法设置时,返回0。

获取锁:

SETNX lock_key unique_identifier
  • lock_key是用作锁的键名。
  • unique_identifier是一个唯一标识符,可以是客户端ID或者其他具有唯一性的值。

如果SETNX命令执行成功(返回1),表示锁获取成功,可以执行对应的业务逻辑。如果SETNX命令返回0,表示锁已被其他客户端持有,获取锁失败。

 

释放锁:

DEL lock_key

使用DEL命令删除锁的键名,释放锁资源。

需要注意的是,在获取锁后,执行业务逻辑时应设定合理的超时时间,以避免锁被长时间占用。

这种方式实现的锁存在一定的缺陷,当 Redis 服务器故障或者出现网络分区时,可能会导致锁无法正常释放,从而导致死锁的问题。

 

2.Set实现锁

为了解决死锁的问题,我们可以使用 SET 命令结合过期时间来实现分布式锁,当一个线程需要获取锁时,它可以执行如下 Redis 命令:

SET lock_key value EX expire_time NX

其中:

lock_key :是锁的名称;

value :是锁的值;

expire_time:是锁的过期时间,NX 表示只有在锁不存在的情况下才设置锁。

如果命令执行成功,说明当前线程成功获取到了锁,可以执行相关操作,否则说明锁已经被其他线程占用,当前线程需要等待一段时间后再次尝试获取锁。

示例:

import redis.clients.jedis.Jedis;

public class RedisSetLockExample {
    private static final int LOCK_EXPIRY_TIME = 10; // 锁的过期时间,单位为秒

    private Jedis jedis;
    private String lockKey;
    private String lockValue;

    public RedisSetLockExample() {
        // 创建Redis连接
        jedis = new Jedis("localhost", 6379);
        lockKey = "mylock";
        lockValue = "locked";
    }

    public boolean acquireLock() {
        // 使用SET命令设置键值对,并设置锁的过期时间
        String result = jedis.set(lockKey, lockValue, "NX", "EX", LOCK_EXPIRY_TIME);
        return "OK".equals(result);
    }

    public void releaseLock() {
        // 使用DEL命令删除锁的键
        jedis.del(lockKey);
    }

    public static void main(String[] args) {
        RedisSetLockExample redisSetLockExample = new RedisSetLockExample();

        if (redisSetLockExample.acquireLock()) {
            try {
                // 锁获取成功,执行业务逻辑
                System.out.println("锁获取成功");

                // 执行业务逻辑代码
                // ...
            } finally {
                // 释放锁
                redisSetLockExample.releaseLock();
                System.out.println("锁已释放");
            }
        } else {
            System.out.println("锁获取失败");
        }

        // 关闭Redis连接
        redisSetLockExample.jedis.close();
    }
}

 

 

3.Redlock实现锁

Redlock锁它是由Redis的作者Salvatore Sanfilippo提出的,旨在提供一个可靠的分布式锁方案。

以下是Redlock算法的基本步骤:

  1. 获取当前时间戳:所有Redis实例使用相同的时间源,例如:NTP获取当前时间戳。
  2. 尝试在多个Redis实例上获取锁:在每个Redis实例上尝试使用SET命令获取锁,设置一个带有唯一标识符的键,设置的键名应该是全局唯一的,以避免与其他锁冲突。
  3. 计算获取锁所花费的时间:计算从第一步获取时间戳到成功获取锁所花费的时间,记为elapsed_time
  4. 判断锁是否获取成功:如果获取锁的时间elapsed_time小于设定的锁超时时间,并且大多数(例如大于一半)的Redis实例成功获取了锁,那么认为锁获取成功。
  5. 释放锁:在所有成功获取锁的Redis实例上执行释放锁的操作,使用DEL命令删除对应的键。

示例:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.ArrayList;
import java.util.List;

public class RedlockExample {
    private static final int LOCK_EXPIRY_TIME = 30000; // 锁的过期时间,单位为毫秒
    private static final int LOCK_WAIT_TIME = 1000; // 获取锁的等待时间,单位为毫秒
    private static final int RETRY_COUNT = 3; // 获取锁的重试次数

    private List<JedisPool> jedisPools;

    public RedlockExample() {
        // 初始化Redis连接池
        jedisPools = new ArrayList<>();
        jedisPools.add(createJedisPool("localhost", 6379));
        jedisPools.add(createJedisPool("localhost", 6380));
        jedisPools.add(createJedisPool("localhost", 6381));
    }

    private JedisPool createJedisPool(String host, int port) {
        JedisPoolConfig config = new JedisPoolConfig();
        return new JedisPool(config, host, port);
    }

    public boolean acquireLock(String lockKey, String uniqueIdentifier) throws InterruptedException {
        int acquiredCount = 0;

        for (int i = 0; i < RETRY_COUNT; i++) {
            long currentTime = System.currentTimeMillis();
            boolean lockAcquired = true;

            for (JedisPool jedisPool : jedisPools) {
                try (Jedis jedis = jedisPool.getResource()) {
                    // 尝试在当前Redis节点上获取锁
                    String result = jedis.set(lockKey, uniqueIdentifier, "NX", "PX", LOCK_EXPIRY_TIME);

                    if (result == null) {
                        lockAcquired = false;
                        break;
                    }
                }
            }

            if (lockAcquired) {
                acquiredCount++;
            }

            long elapsedTime = System.currentTimeMillis() - currentTime;

            // 判断是否成功获取锁
            if (acquiredCount >= (jedisPools.size() / 2) + 1 && elapsedTime < LOCK_EXPIRY_TIME) {
                return true;
            }

            // 等待一段时间后重试
            Thread.sleep(LOCK_WAIT_TIME);
        }

        return false;
    }

    public void releaseLock(String lockKey) {
        for (JedisPool jedisPool : jedisPools) {
            try (Jedis jedis = jedisPool.getResource()) {
                jedis.del(lockKey);
            }
        }
    }

    public static void main(String[] args) {
        RedlockExample redlockExample = new RedlockExample();

        String lockKey = "mylock";
        String uniqueIdentifier = "client001";

        try {
            if (redlockExample.acquireLock(lockKey, uniqueIdentifier)) {
                System.out.println("锁获取成功");
                
                // 执行业务逻辑
                // ...
            } else {
                System.out.println("锁获取失败");
            }
        } catch (InterruptedException e) {
            System.err.println("获取锁时发生中断异常:" + e.getMessage());
        } finally {
            redlockExample.releaseLock(lockKey);
            System.out.println("锁已释放");
        }
    }
}

陈睿mikechen

十余年BAT架构经验,资深技术专家,就职于阿里、淘宝、百度等一线互联网大厂。

关注「mikechen」公众号,获取更多技术干货!

后台回复面试即可获取《史上最全阿里Java面试题总结》,后台回复架构,即可获取《阿里架构师进阶专题全部合集

评论交流
    说说你的看法