├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── plen │ │ └── opensource │ │ ├── RedislockApplication.java │ │ ├── config │ │ └── JedisConfig.java │ │ ├── controller │ │ └── TestController.java │ │ └── implement │ │ ├── RedisLocker.java │ │ └── RedisLockerImpl.java └── resources │ └── application.properties └── test └── java └── com └── plen └── opensource └── RedislockApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Plen-wang/redis-lock/efc15d315500068ee5272bf2a4f9ae8eabf4fa4a/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redis-lock 2 | 3 | * redis setnx command 4 | * java object condition queue 条件队列 5 | * retrycount 带有重试次数限制 6 | * object wait time 带有超时时间的wait 7 | * delete lock 删除远程锁 8 | * acquire lock 申请lock 9 | * release lock 释放lock 10 | * demo 演示 11 | * 锁的粒度问题,锁分解、锁分段 12 | * github https://github.com/Plen-wang/redis-lock 13 | 14 | ## redis setnx 命令 15 | redis setnx 命令特性 16 | >当指定key不存在时才设置。也就是说,如果返回1说明你的命令被执行成功了,redis服务器中的key是你之前设置的值。如果返回0,说明你设置的key在redis服务器里已经存在。 17 | ``` 18 | status = jedis.setnx(lockKey, redisIdentityKey);/**设置 lock key.*/ 19 | if (status > 0) { 20 | expire = jedis.expire(lockKey, lockKeyExpireSecond);/**set redis key expire time.*/ 21 | } 22 | ``` 23 | 如果设置成功了,才进行过期时间设置,防止你的retry lock重复设置这个过期时间,导致永远不过期。 24 | ## java object condition queue 条件队列 25 | 这里有一个小窍门,可以尽可能的最大化cpu利用率又可以解决公平性问题。 26 | 27 | 当你频繁retry的时候,要么while(true)死循环,然后加个Thread.sleep,或者CAS。前者存在一定线程上下文切换开销(Thread.sleep是不会释放出当前内置锁),而CAS在不清楚远程锁被占用多久的情况会浪费很多CPU计算周期,有可能一个任务计算个十几分钟,CPU不可能空转这么久。 28 | 29 | 这里我尝试使用condition queue条件队列特性来实现(当然肯定还有其他更优的方法)。 30 | ``` 31 | if (isWait && retryCounts < RetryCount) { 32 | retryCounts++; 33 | synchronized (this) {//借助object condition queue 来提高CPU利用率 34 | logger.info(String. 35 | format("t:%s,当前节点:%s,尝试等待获取锁:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); 36 | this.wait(WaitLockTimeSecond); //未能获取到lock,进行指定时间的wait再重试. 37 | } 38 | } else if (retryCounts == RetryCount) { 39 | logger.info(String. 40 | format("t:%s,当前节点:%s,指定时间内获取锁失败:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); 41 | return false; 42 | } else { 43 | return false;//不需要等待,直接退出。 44 | } 45 | ``` 46 | 47 | 使用条件队列的好处就是,它虽然释放出了CPU但是也不会持有当前synchronized,这样就可以让其他并发进来的线程也可以获取到当前内置锁,然后形成队列。当wait时间到了被调度唤醒之后才会重新来申请synchronized锁。 48 | 简单讲就是不会再锁上等待而是在队列里等待。java object每一个对象都持有一个条件队列,与当前内置锁配合使用。 49 | ## retrycount 带有重试次数限制 50 | 等待远程redis lock肯定是需要一定重试机制,但是这种重试是需要一定的限制。 51 | ``` 52 | /** 53 | * 重试获取锁的次数,可以根据当前任务的执行时间来设置。 54 | * 需要时间=RetryCount*(WaitLockTimeSecond/1000) 55 | */ 56 | private static final int RetryCount = 10; 57 | ``` 58 | 这种等待是需要用户指定的, if (isWait && retryCounts < RetryCount) ,当isWait为true才会进行重试。 59 | ## object wait time 带有超时时间的wait 60 | object.wait(timeout),条件队列中的方法wait是需要一个waittime。 61 | ``` 62 | /** 63 | * 等待获取锁的时间,可以根据当前任务的执行时间来设置。 64 | * 设置的太短,浪费CPU,设置的太长锁就不太公平。 65 | */ 66 | private static final long WaitLockTimeSecond = 2000; 67 | ``` 68 | 默认2000毫秒。 69 | ``` 70 | this.wait(WaitLockTimeSecond); //未能获取到lock,进行指定时间的wait再重试. 71 | ``` 72 | > 注意:this.wait虽然会blocking住,但是这里的内置锁是会立即释放出来的。所以,有时候我们可以借助这种特性来优化特殊场景。 73 | 74 | ## delete lock 删除远程锁 75 | 释放redis lock比较简单,直接del key就好了 76 | ``` 77 | long status = jedis.del(lockKey); 78 | if (status > 0) { 79 | logger.info(String. 80 | format("t:%s,当前节点:%s,释放锁:%s 成功。", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); 81 | return true; 82 | } 83 | ``` 84 | 一旦delete 之后,首先wait唤醒的线程将会获得锁。 85 | ## acquire lock 申请lock 86 | ``` 87 | /** 88 | * 带超时时间的redis lock. 89 | * 90 | * @param lockKeyExpireSecond 锁key在redis中的过去时间 91 | * @param lockKey lock key 92 | * @param isWait 当获取不到锁时是否需要等待 93 | * @throws Exception lockKey is empty throw exception. 94 | */ 95 | public Boolean acquireLockWithTimeout(int lockKeyExpireSecond, String lockKey, Boolean isWait) throws Exception { 96 | if (StringUtils.isEmpty(lockKey)) throw new Exception("lockKey is empty."); 97 | 98 | int retryCounts = 0; 99 | while (true) { 100 | Long status, expire = 0L; 101 | status = jedis.setnx(lockKey, redisIdentityKey);/**设置 lock key.*/ 102 | if (status > 0) { 103 | expire = jedis.expire(lockKey, lockKeyExpireSecond);/**set redis key expire time.*/ 104 | } 105 | if (status > 0 && expire > 0) { 106 | logger.info(String. 107 | format("t:%s,当前节点:%s,获取到锁:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); 108 | return true;/**获取到lock*/ 109 | } 110 | 111 | try { 112 | if (isWait && retryCounts < RetryCount) { 113 | retryCounts++; 114 | synchronized (this) {//借助object condition queue 来提高CPU利用率 115 | logger.info(String. 116 | format("t:%s,当前节点:%s,尝试等待获取锁:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); 117 | this.wait(WaitLockTimeSecond); //未能获取到lock,进行指定时间的wait再重试. 118 | } 119 | } else if (retryCounts == RetryCount) { 120 | logger.info(String. 121 | format("t:%s,当前节点:%s,指定时间内获取锁失败:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); 122 | return false; 123 | } else { 124 | return false;//不需要等待,直接退出。 125 | } 126 | } catch (InterruptedException e) { 127 | e.printStackTrace(); 128 | } 129 | } 130 | } 131 | ``` 132 | 133 | ## release lock 释放lock 134 | /** 135 | * 释放redis lock。 136 | * 137 | * @param lockKey lock key 138 | * @throws Exception lockKey is empty throw exception. 139 | */ 140 | public Boolean releaseLockWithTimeout(String lockKey) throws Exception { 141 | if (StringUtils.isEmpty(lockKey)) throw new Exception("lockKey is empty."); 142 | 143 | long status = jedis.del(lockKey); 144 | if (status > 0) { 145 | logger.info(String.format("当前节点:%s,释放锁:%s 成功。", getRedisIdentityKey(), lockKey)); 146 | return true; 147 | } 148 | logger.info(String.format("当前节点:%s,释放锁:%s 失败。", getRedisIdentityKey(), lockKey)); 149 | return false; 150 | } 151 | 152 | ## demo 演示 153 | >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 154 | 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 155 | 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 156 | 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 157 | 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 158 | 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 159 | 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 160 | 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 161 | 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 162 | 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 163 | 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 164 | 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 165 | 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 166 | 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 167 | 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 168 | 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 169 | 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 170 | 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 171 | 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 172 | 173 | thread 23 优先获取到对商品ID 10100101 进行修改,所以先锁住当前商品。 174 | >t:23,当前节点:843d3ec0-9c22-4d8a-bcaa-745dba35b8a4,获取到锁:product:10100101:shopping 175 | 176 | 紧接着,thread 25也来对当前商品 10100101进行修改,所以在尝试获取锁。 177 | >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 178 | 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 179 | 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 180 | 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 181 | 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 182 | 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 183 | 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 184 | 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 185 | 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 186 | 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 187 | 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 188 | 189 | 在进行了retry10次(2000毫秒,2秒)之后,获取失败,直接返回,等待下次任务调度开始。 190 | >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 191 | 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 192 | 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 193 | 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 194 | 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 195 | 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 196 | 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 197 | 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 198 | 199 | thread 28 发起对商品 10100101 进行修改,retry6次之后获取到lock。 200 | 201 | ## 锁的粒度问题,锁分解、锁分段 202 | 这里的例子比较简单。如果在并发比较大的情况下是需要结合锁分解、锁分段来进行优化的。 203 | 修改商品,没有必要锁住整个商品库,只需要锁住你需要修改的指定ID的商品。也可以借鉴锁分段思路,将数据按照一定维度进行划分,然后加上不同维度的锁,可以提升CPU性能。可以根据商品catagory来设计段锁或者batch来设计段锁。 204 | 205 | ## github 206 | 源码已提交gihub,代码如有不对请多指教。 207 | github地址:https://github.com/Plen-wang/redis-lock -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.plen.opensource 7 | redislock 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | redislock 12 | redis lock 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.4.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-redis 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-web 41 | 42 | 43 | 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-maven-plugin 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/main/java/com/plen/opensource/RedislockApplication.java: -------------------------------------------------------------------------------- 1 | package com.plen.opensource; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class RedislockApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(RedislockApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/plen/opensource/config/JedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.plen.opensource.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import redis.clients.jedis.Jedis; 6 | import redis.clients.jedis.JedisPool; 7 | 8 | 9 | /** 10 | * Created by plen on 2017/6/17. 11 | */ 12 | @Configuration 13 | public class JedisConfig { 14 | 15 | private static JedisPool jedisPool; 16 | 17 | @Bean 18 | public Jedis getBuild() { 19 | jedisPool = new JedisPool("192.168.1.100", 6379); 20 | Jedis jedis = jedisPool.getResource(); 21 | return jedis; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/plen/opensource/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package com.plen.opensource.controller; 2 | 3 | import com.plen.opensource.implement.RedisLocker; 4 | import com.plen.opensource.implement.RedisLockerImpl; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | /** 11 | * Created by plen on 2017/6/17. 12 | */ 13 | @RestController 14 | public class TestController { 15 | 16 | @Autowired 17 | private RedisLocker redisLocker; 18 | 19 | 20 | @RequestMapping(value = "/lock", produces = "application/json;charset=utf-8", method = RequestMethod.GET) 21 | public boolean acrequireLock() { 22 | try { 23 | return redisLocker.acquireLockWithTimeout(50, "product:10100101:shopping", true); 24 | } catch (Exception e) { 25 | e.printStackTrace(); 26 | } 27 | 28 | return false; 29 | } 30 | 31 | @RequestMapping(value = "/releaseLock", produces = "application/json;charset=utf-8", method = RequestMethod.GET) 32 | public boolean releaseLock() { 33 | try { 34 | return redisLocker.releaseLockWithTimeout("product:10100101:shopping"); 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | } 38 | return false; 39 | } 40 | 41 | 42 | @RequestMapping(value = "/getLockIdentity", produces = "application/json;charset=utf-8", method = RequestMethod.GET) 43 | public String getCurrentNodeLockIdentity() { 44 | return RedisLockerImpl.getRedisIdentityKey(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/plen/opensource/implement/RedisLocker.java: -------------------------------------------------------------------------------- 1 | package com.plen.opensource.implement; 2 | 3 | /** 4 | * Created by plen on 2017/6/17. 5 | */ 6 | public interface RedisLocker { 7 | Boolean acquireLockWithTimeout(int lockKeyExpireSecond, String lockKey, Boolean isWait) throws Exception; 8 | Boolean releaseLockWithTimeout(String lockKey) throws Exception; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/plen/opensource/implement/RedisLockerImpl.java: -------------------------------------------------------------------------------- 1 | package com.plen.opensource.implement; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.util.StringUtils; 6 | import redis.clients.jedis.Jedis; 7 | 8 | import java.util.UUID; 9 | 10 | /** 11 | * Created by plen on 2017/6/17. 12 | */ 13 | @Service 14 | public class RedisLockerImpl implements RedisLocker { 15 | 16 | private org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(RedisLocker.class); 17 | 18 | /** 19 | * 当前机器节点锁标识。 20 | */ 21 | private static String redisIdentityKey = UUID.randomUUID().toString(); 22 | 23 | /** 24 | * 获取当前机器节点在锁中的标示符。 25 | */ 26 | public static String getRedisIdentityKey() { 27 | return redisIdentityKey; 28 | } 29 | 30 | /** 31 | * 等待获取锁的时间,可以根据当前任务的执行时间来设置。 32 | * 设置的太短,浪费CPU,设置的太长锁就不太公平。 33 | */ 34 | private static final long WaitLockTimeSecond = 2000; 35 | /** 36 | * 重试获取锁的次数,可以根据当前任务的执行时间来设置。 37 | * 需要时间=RetryCount*(WaitLockTimeSecond/1000) 38 | */ 39 | private static final int RetryCount = 10; 40 | 41 | @Autowired 42 | private Jedis jedis; 43 | 44 | /** 45 | * 带超时时间的redis lock. 46 | * 47 | * @param lockKeyExpireSecond 锁key在redis中的过去时间 48 | * @param lockKey lock key 49 | * @param isWait 当获取不到锁时是否需要等待 50 | * @throws Exception lockKey is empty throw exception. 51 | */ 52 | public Boolean acquireLockWithTimeout(int lockKeyExpireSecond, String lockKey, Boolean isWait) throws Exception { 53 | if (StringUtils.isEmpty(lockKey)) throw new Exception("lockKey is empty."); 54 | 55 | int retryCounts = 0; 56 | while (true) { 57 | Long status, expire = 0L; 58 | status = jedis.setnx(lockKey, redisIdentityKey);/**设置 lock key.*/ 59 | if (status > 0) { 60 | expire = jedis.expire(lockKey, lockKeyExpireSecond);/**set redis key expire time.*/ 61 | } 62 | if (status > 0 && expire > 0) { 63 | logger.info(String. 64 | format("t:%s,当前节点:%s,获取到锁:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); 65 | return true;/**获取到lock*/ 66 | } 67 | 68 | try { 69 | if (isWait && retryCounts < RetryCount) { 70 | retryCounts++; 71 | synchronized (this) {//借助object condition queue 来提高CPU利用率 72 | logger.info(String. 73 | format("t:%s,当前节点:%s,尝试等待获取锁:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); 74 | this.wait(WaitLockTimeSecond); //未能获取到lock,进行指定时间的wait再重试. 75 | } 76 | } else if (retryCounts == RetryCount) { 77 | logger.info(String. 78 | format("t:%s,当前节点:%s,指定时间内获取锁失败:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); 79 | return false; 80 | } else { 81 | return false;//不需要等待,直接退出。 82 | } 83 | } catch (InterruptedException e) { 84 | e.printStackTrace(); 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * 释放redis lock。 91 | * 92 | * @param lockKey lock key 93 | * @throws Exception lockKey is empty throw exception. 94 | */ 95 | public Boolean releaseLockWithTimeout(String lockKey) throws Exception { 96 | if (StringUtils.isEmpty(lockKey)) throw new Exception("lockKey is empty."); 97 | 98 | long status = jedis.del(lockKey); 99 | if (status > 0) { 100 | logger.info(String. 101 | format("t:%s,当前节点:%s,释放锁:%s 成功。", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); 102 | return true; 103 | } 104 | logger.info(String. 105 | format("t:%s,当前节点:%s,释放锁:%s 失败。", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); 106 | return false; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Plen-wang/redis-lock/efc15d315500068ee5272bf2a4f9ae8eabf4fa4a/src/main/resources/application.properties -------------------------------------------------------------------------------- /src/test/java/com/plen/opensource/RedislockApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.plen.opensource; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class RedislockApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------