redis-lock

  • redis setnx cmmand
  • java object condition queue 条件队列
  • retrycount 带有重试次数限制
  • object wait time 带有超时时间的wait
  • delete lock 删除远程锁
  • acquire lock 申请lock
  • release lock 释放lock
  • demo 演示
  • 锁的粒度问题,锁分解、锁分段
  • github https://github.com/Plen-wang/redis-lock

redis setnx 命令

redis setnx 命令特性

当指定key不存在时才设置。也就是说,如果返回1说明你的命令被执行成功了,redis服务器中的key是你之前设置的值。如果返回0,说明你设置的key在redis服务器里已经存在。

            status = jedis.setnx(lockKey, redisIdentityKey);/**设置 lock key.*/
if (status > 0) {
expire = jedis.expire(lockKey, lockKeyExpireSecond);/**set redis key expire time.*/
}

如果设置成功了,才进行过期时间设置,防止你的retry lock重复设置这个过期时间,导致永远不过期。

java object condition queue 条件队列

这里有一个小窍门,可以尽可能的最大化cpu利用率又可以解决公平性问题。

当你频繁retry的时候,要么while(true)死循环,然后加个Thread.sleep,或者CAS。前者存在一定线程上下文切换开销(Thread.sleep是不会释放出当前内置锁),而CAS在不清楚远程锁被占用多久的情况会浪费很多CPU计算周期,有可能一个任务计算个十几分钟,CPU不可能空转这么久。

这里我尝试使用condition queue条件队列特性来实现(当然肯定还有其他更优的方法)。

if (isWait && retryCounts < RetryCount) {
retryCounts++;
synchronized (this) {//借助object condition queue 来提高CPU利用率
logger.info(String.
format("t:%s,当前节点:%s,尝试等待获取锁:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey));
this.wait(WaitLockTimeSecond); //未能获取到lock,进行指定时间的wait再重试.
}
} else if (retryCounts == RetryCount) {
logger.info(String.
format("t:%s,当前节点:%s,指定时间内获取锁失败:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey));
return false;
} else {
return false;//不需要等待,直接退出。
}

使用条件队列的好处就是,它虽然释放出了CPU但是也不会持有当前synchronized,这样就可以让其他并发进来的线程也可以获取到当前内置锁,然后形成队列。当wait时间到了被调度唤醒之后才会重新来申请synchronized锁。

简单讲就是不会再锁上等待而是在队列里等待。java object每一个对象都持有一个条件队列,与当前内置锁配合使用。

retrycount 带有重试次数限制

等待远程redis lock肯定是需要一定重试机制,但是这种重试是需要一定的限制。

    /**
* 重试获取锁的次数,可以根据当前任务的执行时间来设置。
* 需要时间=RetryCount*(WaitLockTimeSecond/1000)
*/
private static final int RetryCount = 10;

这种等待是需要用户指定的, if (isWait && retryCounts < RetryCount) ,当isWait为true才会进行重试。

object wait time 带有超时时间的wait

object.wait(timeout),条件队列中的方法wait是需要一个waittime。

    /**
* 等待获取锁的时间,可以根据当前任务的执行时间来设置。
* 设置的太短,浪费CPU,设置的太长锁就不太公平。
*/
private static final long WaitLockTimeSecond = 2000;

默认2000毫秒。

this.wait(WaitLockTimeSecond); //未能获取到lock,进行指定时间的wait再重试.

注意:this.wait虽然会blocking住,但是这里的内置锁是会立即释放出来的。所以,有时候我们可以借助这种特性来优化特殊场景。

delete lock 删除远程锁

释放redis lock比较简单,直接del key就好了

long status = jedis.del(lockKey);
if (status > 0) {
logger.info(String.
format("t:%s,当前节点:%s,释放锁:%s 成功。", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey));
return true;
}

一旦delete 之后,首先wait唤醒的线程将会获得锁。

acquire lock 申请lock

/**
* 带超时时间的redis lock.
*
* @param lockKeyExpireSecond 锁key在redis中的过去时间
* @param lockKey lock key
* @param isWait 当获取不到锁时是否需要等待
* @throws Exception lockKey is empty throw exception.
*/
public Boolean acquireLockWithTimeout(int lockKeyExpireSecond, String lockKey, Boolean isWait) throws Exception {
if (StringUtils.isEmpty(lockKey)) throw new Exception("lockKey is empty."); int retryCounts = 0;
while (true) {
Long status, expire = 0L;
status = jedis.setnx(lockKey, redisIdentityKey);/**设置 lock key.*/
if (status > 0) {
expire = jedis.expire(lockKey, lockKeyExpireSecond);/**set redis key expire time.*/
}
if (status > 0 && expire > 0) {
logger.info(String.
format("t:%s,当前节点:%s,获取到锁:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey));
return true;/**获取到lock*/
} try {
if (isWait && retryCounts < RetryCount) {
retryCounts++;
synchronized (this) {//借助object condition queue 来提高CPU利用率
logger.info(String.
format("t:%s,当前节点:%s,尝试等待获取锁:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey));
this.wait(WaitLockTimeSecond); //未能获取到lock,进行指定时间的wait再重试.
}
} else if (retryCounts == RetryCount) {
logger.info(String.
format("t:%s,当前节点:%s,指定时间内获取锁失败:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey));
return false;
} else {
return false;//不需要等待,直接退出。
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
## release lock 释放lock
/**
* 释放redis lock。
*
* @param lockKey lock key
* @throws Exception lockKey is empty throw exception.
*/
public Boolean releaseLockWithTimeout(String lockKey) throws Exception {
if (StringUtils.isEmpty(lockKey)) throw new Exception("lockKey is empty."); long status = jedis.del(lockKey);
if (status > 0) {
logger.info(String.format("当前节点:%s,释放锁:%s 成功。", getRedisIdentityKey(), lockKey));
return true;
}
logger.info(String.format("当前节点:%s,释放锁:%s 失败。", getRedisIdentityKey(), lockKey));
return false;
}

demo 演示

2017-06-18 13:57:43.867 INFO 1444 --- [nio-8080-exec-1] c.plen.opensource.implement.RedisLocker : t:23,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,获取到锁:product:10100101:shopping

2017-06-18 13:57:47.062 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:57:49.063 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:57:51.064 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:57:53.066 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:57:55.068 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:57:57.069 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:57:59.070 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:01.071 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:03.072 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:05.073 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:07.074 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,指定时间内获取锁失败:product:10100101:shopping

2017-06-18 13:58:23.768 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:25.769 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:27.770 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:29.772 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:31.773 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:33.774 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:35.774 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,获取到锁:product:10100101:shopping

thread 23 优先获取到对商品ID 10100101 进行修改,所以先锁住当前商品。

t:23,当前节点:843d3ec0-9c22-4d8a-bcaa-745dba35b8a4,获取到锁:product:10100101:shopping

紧接着,thread 25也来对当前商品 10100101进行修改,所以在尝试获取锁。

2017-06-18 13:50:11.021 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:946b7250-29f3-459b-8320-62d31e6f1fc4,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:50:13.023 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:946b7250-29f3-459b-8320-62d31e6f1fc4,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:50:15.026 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:946b7250-29f3-459b-8320-62d31e6f1fc4,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:50:17.028 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:946b7250-29f3-459b-8320-62d31e6f1fc4,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:50:19.030 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:946b7250-29f3-459b-8320-62d31e6f1fc4,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:50:21.031 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:946b7250-29f3-459b-8320-62d31e6f1fc4,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:50:23.035 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:946b7250-29f3-459b-8320-62d31e6f1fc4,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:50:25.037 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:946b7250-29f3-459b-8320-62d31e6f1fc4,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:50:27.041 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:946b7250-29f3-459b-8320-62d31e6f1fc4,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:50:29.042 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:946b7250-29f3-459b-8320-62d31e6f1fc4,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:50:35.289 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:946b7250-29f3-459b-8320-62d31e6f1fc4,指定时间内获取锁失败:product:10100101:shopping

在进行了retry10次(2000毫秒,2秒)之后,获取失败,直接返回,等待下次任务调度开始。

2017-06-18 13:58:07.074 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,指定时间内获取锁失败:product:10100101:shopping

2017-06-18 13:58:23.768 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:25.769 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:27.770 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:29.772 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:31.773 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:33.774 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,尝试等待获取锁:product:10100101:shopping

2017-06-18 13:58:35.774 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,当前节点:5f81f482-295a-4394-b8cb-d7282e51dd6e,获取到锁:product:10100101:shopping

thread 28 发起对商品 10100101 进行修改,retry6次之后获取到lock。

锁的粒度问题,锁分解、锁分段

这里的例子比较简单。如果在并发比较大的情况下是需要结合锁分解、锁分段来进行优化的。

修改商品,没有必要锁住整个商品库,只需要锁住你需要修改的指定ID的商品。也可以借鉴锁分段思路,将数据按照一定维度进行划分,然后加上不同维度的锁,可以提升CPU性能。可以根据商品catagory来设计段锁或者batch来设计段锁。

github

源码已提交gihub,代码如有不对请多指教。

github地址:https://github.com/Plen-wang/redis-lock

redisLock redis分布式锁的更多相关文章

  1. spring boot redis分布式锁

    随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁.分布式锁的实现有很多种,比如基于数据库. zookeeper 等,本文主要介绍使用 Redis 做分布式锁的方式,并封装成spring b ...

  2. redis分布式锁实践

    分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...

  3. Redis分布式锁的try-with-resources实现

    Redis分布式锁的try-with-resources实现 一.简介 在当今这个时代,单体应用(standalone)已经很少了,java提供的synchronized已经不能满足需求,大家自然 而 ...

  4. springMVC 实现redis分布式锁

    1.先配置spring-data-redis 首先是依赖 <dependency> <groupId>org.springframework.data</groupId& ...

  5. Redis 分布式锁及缓存注释的使用方法

    使用工具:Apache an 测压命令: ab -n 100 -c 100 http://www.baidu.com -n代表模拟100个请求,-c代表模拟100个并发,相当于100个人同时访问 ab ...

  6. RedLock.Net - 基于Redis分布式锁的开源实现

    工作中,经常会遇到分布式环境中资源访问冲突问题,比如商城的库存数量处理,或者某个事件的原子性操作,都需要确保某个时间段内只有一个线程在访问或处理资源. 因此现在网上也有很多的分布式锁的解决方案,有数据 ...

  7. redis分布式锁的具体应用

    1.关于redis分布式锁,有个setIfAbsent: 即如果没有设置,会添加分布式锁,并返回true; 2.redis分布式锁有个轮询过程: / * @param key redis键 * @pa ...

  8. redis分布式锁小试

    一.场景 项目A监听mq中的其他项目的部署消息(包括push_seq, status, environment,timestamp等),然后将部署消息同步到数据库中(项目X在对应环境[environm ...

  9. SpringBoot集成Redis分布式锁以及Redis缓存

    https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...

随机推荐

  1. 【Spark2.0源码学习】-5.Worker启动

         Worker作为Endpoint的具体实例,下面我们介绍一下Worker启动以及OnStart指令后的额外工作   一.脚本概览      下面是一个举例: /opt/jdk1..0_79/ ...

  2. 基于jqUI的日期选择(‘yy-mm-dd’)

    今天看某公司的网页,其中有个筛选条件是选择一个时间区间,从而选择出符合条件的项.什么也不说了,先看图左边的输入框,点击具体的日期,这里我选择的是2017-3-9,然后右边的输入框就只能选择这个日期以后 ...

  3. Windows 10 碎片整理程序使用

    1.打开 此电脑 2.右击任意一个磁盘,这里以C盘为例,点击 属性 3.点击 工具  选项卡--->优化 4.选中其中的一个盘  优化 5.然后静等结束,就好啦. 虽然不知道有什么用,...

  4. spoj 694. Distinct Substrings 后缀数组求不同子串的个数

    题目链接:http://www.spoj.com/problems/DISUBSTR/ 思路: 每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数.如果所有的后缀按照su ...

  5. SSO单点登录的研究

    一.单点登录的概述       单点登录(Single Sign On),简称为 SSO,SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统. 用以解决同一公司不同子产 ...

  6. .Net Core中使用ref和Span<T>提高程序性能

    一.前言 其实说到ref,很多同学对它已经有所了解,ref是C# 7.0的一个语言特性,它为开发人员提供了返回本地变量引用和值引用的机制. Span也是建立在ref语法基础上的一个复杂的数据类型,在文 ...

  7. 【JAVAWEB学习笔记】17_jsp

    动态页面技术(JSP/EL/JSTL) 学习目标 案例:完成商品的列表的展示 一.JSP技术 1.jsp脚本和注释 jsp脚本: 1)<%java代码%> ----- 内部的java代码翻 ...

  8. DDD理论学习系列(3)-- 限界上下文

    1. 引言 限界上下文可以拆分为两个词,限界和上下文. 限界:是指一个界限,具体的某一个范围. 上下文:个人理解就是语境. 比如我们常说的段子: "我想静静." 这个句子一般是想表 ...

  9. xml 和html 语言区别

    都是标记语言(ML),一个是超文本标记语言,一个是扩展标记语言. 不同之处: 1可扩展性:HTML不具备扩展性,而XML是原标记语言,可以用于定义新的标记语言. 2侧重点: HTML侧重于如何表现信息 ...

  10. 写给Android App开发人员看的Android底层知识(5)

    (十)Service Service有两套流程,一套是启动流程,另一套是绑定流程.我们做App开发的同学都应该知道. 1)在新进程启动Service 我们先看Service启动过程,假设要启动的Ser ...