当前位置: 首页 > news >正文

分享一种实用redis原子锁的方式

1. setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向2。

2. get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向3。

3. 计算newExpireTime=当前时间+过期超时时间,然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。

4. 判断currentExpireTime与oldExpireTime 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。

5. 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行delete释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。

代码如下:

/**

* 带超时时间的锁

*

* @param lockKey 锁的键值

* @param timeUnit 锁的时间单位

* @param timeout 锁的时间长度

* @param relyOnRedisAvailable 是否依赖redis可用性

* true则redis不可用或异常情况下返回加锁失败;

* false则redis不可用或异常情况下返回加锁成功。

* @return 是否添加锁成功

*/

@Override

public boolean lock(String lockKey, TimeUnit timeUnit, long timeout, boolean relyOnRedisAvailable) {

//获取当前时间

long currentTimeMillis = System.currentTimeMillis();

String repeatSign = UUID.randomUUID().toString().replace("-", "");//重复标记

//获取超时时间的ms

long lockTimeMs = timeUnit.toMillis(timeout);

long setNx;

try {

//这里我们以 超时时间为value+标记

String setLockValue = String.valueOf(currentTimeMillis + lockTimeMs)+repeatSign;

setNx = redisClientUtil.setnx(lockKey, setLockValue);

logger.info("键值[{}]设置的setNx为:{}, setLockValue为:{}", lockKey, setNx, setLockValue);

} catch (Exception e) {

logger.error("{}设置锁异常",lockKey,e);

pubEvent(lockKey,e);

return true;

}

//如果成功了,那么我们就设置超时时间

if (setNx == 1 ) {

logger.info("键值{}获取redis锁成功",lockKey);

try {

redisClientUtil.expire(lockKey, (int)timeUnit.toSeconds(timeout));

} catch (Exception e) {

logger.error("{}设置超时时间异常,异常信息为",lockKey,e);

}

return true;

}

//以下是不成功的处理情况,即 setNx 等于0的时候,我们先获取旧的值

String lockValue = redisClientUtil.getValueByKey(lockKey);

logger.info("键值[{}],设置的lockValue为:{},重复标记为:{}", lockKey, lockValue, repeatSign);

if (StrUtil.isBlank(lockValue)) {

//如果锁value为空,直接返回成功

return true;

}

if(lockValue.contains(repeatSign)){

//如果锁value包含重复标记,则说明其为本次设置,直接返回成功

return true;

}

String lockTime = lockValue.substring(0, lockValue.indexOf("|"));//锁超时时间

if (null == lockTime) {

logger.warn("键值[{}]获取到时间戳为空, 按照获取成功返回!", lockKey);

return true;

} else {

try {

logger.info("键值[{}]redis存储的时间戳:{}, 格式化后:{}", lockKey, lockTime, DateUtil.dateToString(new Date(Long.valueOf(lockTime)), DateUtil.DEFAULT_TIMESTAMP_FORMAT));

} catch (Exception e) {

logger.warn("键值[{}]redis存储的时间戳:{}, 格式化时出现异常,不影响流程", lockKey, lockTime, e);

}

}

if (lockTime != null && Long.valueOf(lockTime)

< System.currentTimeMillis() ) {

String oldValue = redisClientUtil.getSet(lockKey,

String.valueOf(currentTimeMillis + lockTimeMs)+ "|" + repeatSign);

String oldLockTime = oldValue.substring(0, lockValue.indexOf("|"));//锁超时时间

//将两次获取的值对比,这里也就是文章第五条,如果相等说明没有被其他线程修改

if (oldValue != null && oldLockTime.equals(lockTime)) {

//这里用该还要在设置一次超时时间,可以用lua 脚本保持一致性

return true;

}

}

return false;

}

http://www.lryc.cn/news/24542.html

相关文章:

  • 【华为OD机试】 字符串解密(C++ Java JavaScript Python)
  • 金三银四,助力你的大厂梦,2023年软件测试经典面试真题(1)(共3篇)
  • 假如面试官要你手写一个promise
  • 【leetcode】寻找重复数
  • LeetCode 1247. Minimum Swaps to Make Strings Equal【数学,贪心,字符串】
  • pid控制加热算法,附代码仓库
  • 一文看懂预训练和自训练模型
  • (五十四)大白话索引的页存储物理结构,是如何用B+树来实现的?.md
  • 前端Vue代码风格指南
  • 「TCG 规范解读」基础设施架构和协议 (2)
  • NodeJs 中的 HTML 模板
  • 3.ffmpeg命令行环境搭建、ffmpeg命令行初步了解
  • Kubernetes初始化容器
  • leetcode: Swapping Nodes in a Linked List
  • Nydus 在约苗平台的容器镜像加速实践
  • 企业对不同形态CRM系统价格需求不同
  • 「JVM 高效并发」线程安全
  • 微信扫码登录
  • Unity协程的简单应用
  • LeetCode 1250. Check If It Is a Good Array【数论】
  • ETHDenver 2023
  • React架构演变
  • 安全认证--JWT介绍及使用
  • 【计算机组成原理】计算机硬件的基础组成、认识各个硬件部件
  • 使用ChIPSeeker进行ChIP-seq, ATAC-seq,cuttag等富集峰的基因组注释
  • 第九届蓝桥杯省赛——7缩位求和
  • 【c++】STL常用容器5—list容器
  • 【牛客刷题专栏】0x0D:JZ5 替换空格(C语言编程题)
  • 聚观早报 | 苹果2024年放弃高通;腾讯回应进军类 ChatGPT
  • Elasticsearch:如何正确处理 Elasticsearch 摄取管道故障