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

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

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

1.SETNX实现锁

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

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

  1. SETNX key value

参数说明:

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

返回值:

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

获取锁:

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

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

 

释放锁:

  1. DEL lock_key

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

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

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

 

2.Set实现锁

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

  1. SET lock_key value EX expire_time NX

其中:

lock_key :是锁的名称;

value :是锁的值;

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

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

示例:

  1. import redis.clients.jedis.Jedis;
  2.  
  3. public class RedisSetLockExample {
  4. private static final int LOCK_EXPIRY_TIME = 10; // 锁的过期时间,单位为秒
  5.  
  6. private Jedis jedis;
  7. private String lockKey;
  8. private String lockValue;
  9.  
  10. public RedisSetLockExample() {
  11. // 创建Redis连接
  12. jedis = new Jedis("localhost", 6379);
  13. lockKey = "mylock";
  14. lockValue = "locked";
  15. }
  16.  
  17. public boolean acquireLock() {
  18. // 使用SET命令设置键值对,并设置锁的过期时间
  19. String result = jedis.set(lockKey, lockValue, "NX", "EX", LOCK_EXPIRY_TIME);
  20. return "OK".equals(result);
  21. }
  22.  
  23. public void releaseLock() {
  24. // 使用DEL命令删除锁的键
  25. jedis.del(lockKey);
  26. }
  27.  
  28. public static void main(String[] args) {
  29. RedisSetLockExample redisSetLockExample = new RedisSetLockExample();
  30.  
  31. if (redisSetLockExample.acquireLock()) {
  32. try {
  33. // 锁获取成功,执行业务逻辑
  34. System.out.println("锁获取成功");
  35.  
  36. // 执行业务逻辑代码
  37. // ...
  38. } finally {
  39. // 释放锁
  40. redisSetLockExample.releaseLock();
  41. System.out.println("锁已释放");
  42. }
  43. } else {
  44. System.out.println("锁获取失败");
  45. }
  46.  
  47. // 关闭Redis连接
  48. redisSetLockExample.jedis.close();
  49. }
  50. }

 

 

3.Redlock实现锁

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

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

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

示例:

  1. import redis.clients.jedis.Jedis;
  2. import redis.clients.jedis.JedisPool;
  3. import redis.clients.jedis.JedisPoolConfig;
  4.  
  5. import java.util.ArrayList;
  6. import java.util.List;
  7.  
  8. public class RedlockExample {
  9. private static final int LOCK_EXPIRY_TIME = 30000; // 锁的过期时间,单位为毫秒
  10. private static final int LOCK_WAIT_TIME = 1000; // 获取锁的等待时间,单位为毫秒
  11. private static final int RETRY_COUNT = 3; // 获取锁的重试次数
  12.  
  13. private List<JedisPool> jedisPools;
  14.  
  15. public RedlockExample() {
  16. // 初始化Redis连接池
  17. jedisPools = new ArrayList<>();
  18. jedisPools.add(createJedisPool("localhost", 6379));
  19. jedisPools.add(createJedisPool("localhost", 6380));
  20. jedisPools.add(createJedisPool("localhost", 6381));
  21. }
  22.  
  23. private JedisPool createJedisPool(String host, int port) {
  24. JedisPoolConfig config = new JedisPoolConfig();
  25. return new JedisPool(config, host, port);
  26. }
  27.  
  28. public boolean acquireLock(String lockKey, String uniqueIdentifier) throws InterruptedException {
  29. int acquiredCount = 0;
  30.  
  31. for (int i = 0; i < RETRY_COUNT; i++) {
  32. long currentTime = System.currentTimeMillis();
  33. boolean lockAcquired = true;
  34.  
  35. for (JedisPool jedisPool : jedisPools) {
  36. try (Jedis jedis = jedisPool.getResource()) {
  37. // 尝试在当前Redis节点上获取锁
  38. String result = jedis.set(lockKey, uniqueIdentifier, "NX", "PX", LOCK_EXPIRY_TIME);
  39.  
  40. if (result == null) {
  41. lockAcquired = false;
  42. break;
  43. }
  44. }
  45. }
  46.  
  47. if (lockAcquired) {
  48. acquiredCount++;
  49. }
  50.  
  51. long elapsedTime = System.currentTimeMillis() - currentTime;
  52.  
  53. // 判断是否成功获取锁
  54. if (acquiredCount >= (jedisPools.size() / 2) + 1 && elapsedTime < LOCK_EXPIRY_TIME) {
  55. return true;
  56. }
  57.  
  58. // 等待一段时间后重试
  59. Thread.sleep(LOCK_WAIT_TIME);
  60. }
  61.  
  62. return false;
  63. }
  64.  
  65. public void releaseLock(String lockKey) {
  66. for (JedisPool jedisPool : jedisPools) {
  67. try (Jedis jedis = jedisPool.getResource()) {
  68. jedis.del(lockKey);
  69. }
  70. }
  71. }
  72.  
  73. public static void main(String[] args) {
  74. RedlockExample redlockExample = new RedlockExample();
  75.  
  76. String lockKey = "mylock";
  77. String uniqueIdentifier = "client001";
  78.  
  79. try {
  80. if (redlockExample.acquireLock(lockKey, uniqueIdentifier)) {
  81. System.out.println("锁获取成功");
  82. // 执行业务逻辑
  83. // ...
  84. } else {
  85. System.out.println("锁获取失败");
  86. }
  87. } catch (InterruptedException e) {
  88. System.err.println("获取锁时发生中断异常:" + e.getMessage());
  89. } finally {
  90. redlockExample.releaseLock(lockKey);
  91. System.out.println("锁已释放");
  92. }
  93. }
  94. }
评论交流
    说说你的看法
欢迎您,新朋友,感谢参与互动!