基于redis实现分布式锁
发布日期:2022-02-25 01:17:42 浏览次数:26 分类:技术文章

本文共 3407 字,大约阅读时间需要 11 分钟。

基于 Redis 做分布式锁基于 REDIS 的 SETNX()、EXPIRE() 方法做分布式锁setnx()setnx 的含义就是 SET if Not Exists,其主要有两个参数 setnx(key, value)。该方法是原子的,如果 key 不存在,则设置当前 key 成功,返回 1;如果当前 key 已经存在,则设置当前 key 失败,返回 0。expire()expire 设置过期时间,要注意的是 setnx 命令不能设置 key 的超时时间,只能通过 expire() 来对 key 设置。使用步骤1、setnx(lockkey, 1) 如果返回 0,则说明占位失败;如果返回 1,则说明占位成功2、expire() 命令对 lockkey 设置超时时间,为的是避免死锁问题。3、执行完业务代码后,可以通过 delete 命令删除 key。这个方案其实是可以解决日常工作中的需求的,但从技术方案的探讨上来说,可能还有一些可以完善的地方。比如,如果在第一步 setnx 执行成功后,在 expire() 命令执行成功前,发生了宕机的现象,那么就依然会出现死锁的问题,所以如果要对其进行完善的话,可以使用 redis 的 setnx()、get() 和 getset() 方法来实现分布式锁。基于 REDIS 的 SETNX()、GET()、GETSET()方法做分布式锁这个方案的背景主要是在 setnx() 和 expire() 的方案上针对可能存在的死锁问题,做了一些优化。getset()这个命令主要有两个参数 getset(key,newValue)。该方法是原子的,对 key 设置 newValue 这个值,并且返回 key 原来的旧值。假设 key 原来是不存在的,那么多次执行这个命令,会出现下边的效果:getset(key, “value1”) 返回 null 此时 key 的值会被设置为 value1getset(key, “value2”) 返回 value1 此时 key 的值会被设置为 value2依次类推!使用步骤setnx(lockkey, 当前时间+过期超时时间),如果返回 1,则获取锁成功;如果返回 0 则没有获取到锁,转向 2。get(lockkey) 获取值 oldExpireTime ,并将这个 value 值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向 3。计算 newExpireTime = 当前时间+过期超时时间,然后 getset(lockkey, newExpireTime) 会返回当前 lockkey 的值currentExpireTime。判断 currentExpireTime 与 oldExpireTime 是否相等,如果相等,说明当前 getset 设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行 delete 释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。import cn.com.tpig.cache.redis.RedisService;import cn.com.tpig.utils.SpringUtils;//redis分布式锁public final class RedisLockUtil {

   private static final int defaultExpire = 60;
private RedisLockUtil() {
   //
}
/**
 * 加锁
 * @param key redis key
 * @param expire 过期时间,单位秒
 * @return true:加锁成功,false,加锁失败
 */
public static boolean lock(String key, int expire) {
   RedisService redisService = SpringUtils.getBean(RedisService.class);
long status = redisService.setnx(key, "1");
if(status == 1) {
   redisService.expire(key, expire);
return true;
}
return false;
}
public static boolean lock(String key) {
   return lock2(key, defaultExpire);
}
/**
 * 加锁
 * @param key redis key
 * @param expire 过期时间,单位秒
 * @return true:加锁成功,false,加锁失败
 */
public static boolean lock2(String key, int expire) {
   RedisService redisService = SpringUtils.getBean(RedisService.class);
long value = System.currentTimeMillis() + expire;
long status = redisService.setnx(key, String.valueOf(value));
if(status == 1) {
   return true;
}
long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
if(oldExpireTime < System.currentTimeMillis()) {
   //超时
long newExpireTime = System.currentTimeMillis() + expire;
long currentExpireTime = Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));
if(currentExpireTime == oldExpireTime) {
   return true;
}
}
return false;
}
public static void unLock1(String key) {
   RedisService redisService = SpringUtils.getBean(RedisService.class);
redisService.del(key);
}
public static void unLock2(String key) {
   RedisService redisService = SpringUtils.getBean(RedisService.class);
long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
   if(oldExpireTime > System.currentTimeMillis()) {
   redisService.del(key);
}   }}public void drawRedPacket(long userId) {
   String key = "draw.redpacket.userid:" + userId;
boolean lock = RedisLockUtil.lock2(key, 60);
if(lock) {
   try {
   //领取操作
} finally {
   //释放锁
RedisLockUtil.unLock(key);
}
} else {
   new RuntimeException("重复领取奖励");
}}

转载地址:https://blog.csdn.net/qq_37741426/article/details/122553562 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:关于sed and grep的几个练习题
下一篇:java 异步调用处理

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2023年02月03日 01时10分08秒