温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Java中怎么使用Redis 实现分布式锁

发布时间:2021-06-30 17:59:32 来源:亿速云 阅读:408 作者:Leah 栏目:大数据

今天就跟大家聊聊有关Java中怎么使用Redis 实现分布式锁,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

  1. 支持立即获取锁方式,如果获取到返回true,获取不到则返回false;

  2. 支持等待获取锁方式,如果获取到,直接返回true,获取不到在等待一小段时间,在这一小段时间内反复尝试,如果尝试成功,则返回true,等待时间过后还获取不到则返回false;

  3. 不能产生死锁的情况;

  4. 不能释放非自己加的锁;

下面我们用实例来演示在 Java 中利用 redis 实现分布式锁服务

加锁

通过 redis 来实现分布式锁的加锁逻辑如下所示:

Java中怎么使用Redis 实现分布式锁

redis 分布式锁上锁实现逻辑

根据这个逻辑,实现上锁的核心代码如下所示:

jedis.select(dbIndex); String key = KEY_PRE + key; String value = fetchLockValue(); if(jedis.exists(key)){   jedis.set(key,value);   jedis.expire(key,lockExpirseTime);   return value; }

表面上看这段代码好像没有什么问题,实际上并不能在分布式环境中正确的实现加锁的操作。要能够正确的实现加锁操作,“判断 key 是否存在”“保存 key-value”“设置 key 的过期时间”这三步操作必须是原子操作。如果不是原子操作,那么可能会出现下面两种情况:

  1. “判断 key 是否存在”得出 key 不存在的结果步骤后,“保存 key-value”步骤前,另一个客户端执行同样的逻辑,并且执行到了“判断 key 是否存在”步骤,同样得出了 key 不存在的结果。这样回导致多个客户端获得了同一把锁;

  2. 在客户端执行完“保存 key-value” 步骤后,需要设置一个 key 的过期时间,防止客户端因为代码质量未解锁,在或者进程崩溃未解锁导致的死锁情况。在“保存 key-value”步骤之后,“设置 key 的过期时间”步骤之前,可能进程崩溃,导致“设置 key 的过期时间”步骤失败;

redis 在2.6.12版本之后,对 set 命令进行了扩充,能够规避上面的两个问题。新版的 redis set 命令的参数如下

SET key value [EX seconds] [PX milliseconds] [NX|XX]

新版的 set 命令增加了 EX 、 PX 、 NX|XX 参数选项。他们的含义如下

EX seconds – 设置键 key 的过期时间,单位时秒 PX milliseconds – 设置键 key 的过期时间,单位时毫秒 NX – 只有键 key 不存在的时候才会设置 key 的值 XX – 只有键 key 存在的时候才会设置 key 的值

这样,原来的三步操作就可以在一个 set 的原子操作里面来完成,规避了上面我们提到的两个问题。新版的 redis 加锁核心代码修改如下所示:

jedis = redisConnection.getJedis(); jedis.select(dbIndex); String key = KEY_PRE + key; String value = fetchLockValue(); if ("OK".equals(jedis.set(key, value, "NX", "EX", lockExpirseTime))) {     return value; }

解锁

解锁的基本流程如下:

redis 分布式锁解锁实现逻辑

根据这个逻辑,在 Java 中解锁的核心代码如下所示:

jedis.select(dbIndex); String key = KEY_PRE + key; if(jedis.exists(key) && value.equals(jedis.get(key))){     jedis.del(key);     return true; } return false;

和加锁的时候一样,key 是否存在判断是否自己持有锁删除 key-value 这三步操作需要是原子操作,否则当一个客户端执行完“判断是否自己持有锁”步骤后,得出自己持有锁的结论,此时锁的过期时间到了,自动被 redis 释放了,同时另一个客户端又基于这个 key 加锁成功,如果第一个客户端还继续执行删除 key-value的操作,就将不属于自己的锁给释放了。这显然是不运行的。在这里我们利用 redis 执行 Lua 脚本的能力来解决原子操作的问题。修改后的解锁核心代码如下所示:

jedis.select(dbIndex); String key = KEY_PRE + key; String command = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; if (1L.equals(jedis.eval(command, Collections.singletonList(key), Collections.singletonList(value)))) {     return true; }

另外,判断是否自己持有锁的机制是用加锁的时候的 key-value 来判断当前的 key 的值是否等于自己持有锁时获得的值。所以加锁的时候的 value 必须是一个全局唯一的字符串。

完整的代码如下所示

package com.x9710.common.redis.impl; import com.x9710.common.redis.LockService; import com.x9710.common.redis.RedisConnection; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import redis.clients.jedis.Jedis; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.UUID; /**  * 分布式锁 redis 实现  *  * @author 杨高超  * @since 2017-12-14  */ public class LockServiceRedisImpl implements LockService { private static Log log = LogFactory.getLog(LockServiceRedisImpl.class); private static String SET_SUCCESS = "OK"; private static String KEY_PRE = "REDIS_LOCK_"; private DateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS"); private RedisConnection redisConnection; private Integer dbIndex; private Integer lockExpirseTime; private Integer tryExpirseTime; public void setRedisConnection(RedisConnection redisConnection) {     this.redisConnection = redisConnection; } public void setDbIndex(Integer dbIndex) {     this.dbIndex = dbIndex; } public void setLockExpirseTime(Integer lockExpirseTime) {     this.lockExpirseTime = lockExpirseTime; } public void setTryExpirseTime(Integer tryExpirseTime) {     this.tryExpirseTime = tryExpirseTime; } public String lock(String key) {     Jedis jedis = null;     try {         jedis = redisConnection.getJedis();         jedis.select(dbIndex);         key = KEY_PRE + key;         String value = fetchLockValue();         if (SET_SUCCESS.equals(jedis.set(key, value, "NX", "EX", lockExpirseTime))) {             log.debug("Reids Lock key : " + key + ",value : " + value);             return value;         }     } catch (Exception e) {         e.printStackTrace();     } finally {         if (jedis != null) {             jedis.close();         }     }     return null; } public String tryLock(String key) {     Jedis jedis = null;     try {         jedis = redisConnection.getJedis();         jedis.select(dbIndex);         key = KEY_PRE + key;         String value = fetchLockValue();         Long firstTryTime = new Date().getTime();         do {             if (SET_SUCCESS.equals(jedis.set(key, value, "NX", "EX", lockExpirseTime))) {                 log.debug("Reids Lock key : " + key + ",value : " + value);                 return value;             }             log.info("Redis lock failure,waiting try next");             try {                 Thread.sleep(100);             } catch (InterruptedException e) {                 e.printStackTrace();             }         } while ((new Date().getTime() - tryExpirseTime * 1000) < firstTryTime);     } catch (Exception e) {         e.printStackTrace();     } finally {         if (jedis != null) {             jedis.close();         }     }     return null; } public boolean unLock(String key, String value) {     Long RELEASE_SUCCESS = 1L;     Jedis jedis = null;     try {         jedis = redisConnection.getJedis();         jedis.select(dbIndex);         key = KEY_PRE + key;         String command = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";         if (RELEASE_SUCCESS.equals(jedis.eval(command, Collections.singletonList(key), Collections.singletonList(value)))) {             return true;         }     } catch (Exception e) {         e.printStackTrace();     } finally {         if (jedis != null) {             jedis.close();         }     }     return false; } /**  * 生成加锁的唯一字符串  *  * @return 唯一字符串  */ private String fetchLockValue() {     return UUID.randomUUID().toString() + "_" + df.format(new Date()); } }

测试代码

package com.x9710.common.redis.test; import com.x9710.common.redis.RedisConnection; import com.x9710.common.redis.impl.LockServiceRedisImpl; public class RedisLockTest { public static void main(String[] args) {     for (int i = 0; i < 9; i++) {         new Thread(new Runnable() {             public void run() {                 RedisConnection redisConnection = RedisConnectionUtil.create();                 LockServiceRedisImpl lockServiceRedis = new LockServiceRedisImpl();                 lockServiceRedis.setRedisConnection(redisConnection);                 lockServiceRedis.setDbIndex(15);                 lockServiceRedis.setLockExpirseTime(20);                 String key = "20171228";                 String value = lockServiceRedis.lock(key);                 try {                     if (value != null) {                         System.out.println(Thread.currentThread().getName() + " lock key = " + key + " success! ");                         Thread.sleep(25 * 1000);                     }else{                         System.out.println(Thread.currentThread().getName() + " lock key = " + key + " failure! ");                     }                 } catch (Exception e) {                     e.printStackTrace();                 } finally {                     if (value == null) {                         value = "";                     }                     System.out.println(Thread.currentThread().getName() + " unlock key = " + key + " " + lockServiceRedis.unLock(key, value));                 }             }         }).start();     } } }

测试结果

Thread-1 lock key = 20171228 failure!  Thread-2 lock key = 20171228 failure!  Thread-4 lock key = 20171228 failure!  Thread-8 lock key = 20171228 failure!  Thread-7 lock key = 20171228 failure!  Thread-3 lock key = 20171228 failure!  Thread-5 lock key = 20171228 failure!  Thread-0 lock key = 20171228 failure!  Thread-6 lock key = 20171228 success!  Thread-1 unlock key = 20171228 false Thread-2 unlock key = 20171228 false Thread-4 unlock key = 20171228 false Thread-8 unlock key = 20171228 false Thread-3 unlock key = 20171228 false Thread-5 unlock key = 20171228 false Thread-0 unlock key = 20171228 false Thread-7 unlock key = 20171228 false Thread-6 unlock key = 20171228 true

看完上述内容,你们对Java中怎么使用Redis 实现分布式锁有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI