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算法的基本步骤:
- 获取当前时间戳:所有Redis实例使用相同的时间源,例如:NTP获取当前时间戳。
- 尝试在多个Redis实例上获取锁:在每个Redis实例上尝试使用SET命令获取锁,设置一个带有唯一标识符的键,设置的键名应该是全局唯一的,以避免与其他锁冲突。
- 计算获取锁所花费的时间:计算从第一步获取时间戳到成功获取锁所花费的时间,记为
elapsed_time
。 - 判断锁是否获取成功:如果获取锁的时间
elapsed_time
小于设定的锁超时时间,并且大多数(例如大于一半)的Redis实例成功获取了锁,那么认为锁获取成功。 - 释放锁:在所有成功获取锁的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面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》