- 按照JUC中Lock规范接口编写
- Lock加锁
- 加锁
- 自旋
- 续期
- unlock解锁
- 考虑可重入性的递减,加锁几次就要减锁几次
- 到零后,del删除
版本一
递归重试,容易导致stackoverflowerror,所以不太推荐。
另外,高并发唤醒后推荐用while 而不是if
package com.njg.redislock.v1;
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class RedisLockV1 {
@Autowired
StringRedisTemplate stringRedisTemplate;
public String sale() {
String key = "njgLock";
String uuidValue = IdUtil.randomUUID() + ":" + Thread.currentThread().getId();
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);
if (!flag){
// 暂停20s,进行递归重试
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
sale();
}else {
// 抢锁成功的请求线程,进行正常的业务逻辑操作,扣件库存
}
return "";
}
}
版本二
不用递归了,高并发下容易出错,用自旋替代递归方法重试调用
用while替代if
问题:没有过期时间
package com.njg.redislock.v2;
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class RedisLockV2 {
@Autowired
StringRedisTemplate stringRedisTemplate;
public String sale() {
String key = "njgLock";
String uuidValue = IdUtil.randomUUID() + ":" + Thread.currentThread().getId();
while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue)){
// 暂停20s,进行递归重试
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 抢锁成功的请求线程,进行正常的业务逻辑操作,扣件库存
return "";
}
}
版本三
宕机与过期,防止死锁
加锁和过期时间必须同一行,保证原子性
package com.njg.redislock.v3;
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class RedisLockV3 {
@Autowired
StringRedisTemplate stringRedisTemplate;
public String sale() {
String key = "njgLock";
String uuidValue = IdUtil.randomUUID() + ":" + Thread.currentThread().getId();
while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
// 暂停20s,进行递归重试
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// stringRedisTemplate.expire(key, 30L, TimeUnit.SECONDS);
// 抢锁成功的请求线程,进行正常的业务逻辑操作,扣件库存
return "";
}
}
版本四
防止误删key问题
问题: 1. 判断+del不是一行命令原子操作,需要用lua脚本修改
2. setnx不满足可冲入性
package com.njg.redislock.v4;
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@RestController
public class RedisLockV4 {
@Autowired
StringRedisTemplate stringRedisTemplate;
public String sale() {
String key = "njgLock";
String uuidValue = IdUtil.randomUUID() + ":" + Thread.currentThread().getId();
while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
// 暂停20s,进行递归重试
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 抢锁成功的请求线程,进行正常的业务逻辑操作,扣件库存
try {
} finally {
String luaScript =
"if redis.call('get',KEYS[1]) == ARGV[1] then " +
"return redis.call('del',KEYS[1]) " +
"else " +
"return 0 " +
"end";
stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript,Long.class), Arrays.asList(key),uuidValue);
}
return "";
}
}
版本五
lock/unlock+lua
package com.njg.redislock.v5;
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 自研分布式锁,实现Lock接口
*/
public class RedisDistributedLock implements Lock {
private StringRedisTemplate stringRedisTemplate;
private String lockName; //KEYS[1]
private String uuidValue;// ARGV[1]
private long expireTime;//ARGV[2]
public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName) {
this.stringRedisTemplate = stringRedisTemplate;
this.lockName = lockName;
this.uuidValue = IdUtil.randomUUID() + ":" + Thread.currentThread().getId();
this.expireTime = 50L;
}
@Override
public void lock() {
tryLock();
}
@Override
public boolean tryLock() {
try {
tryLock(-1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
if (time == -1) {
String script =
"if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then" +
" redis.call('hincrby',KEYS[1],ARGV[1],1)" +
" redis.call('expire',KEYS[1],ARGV[2])" +
" return 1 " +
"else" +
" return 0 " +
"end";
while (!stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
// 暂停60毫秒重试
TimeUnit.MILLISECONDS.sleep(60);
}
return true;
}
return false;
}
@Override
public void unlock() {
String script =
"if redis.call('hexists',KEYS[1],ARGV[1]) == 0 then " +
" return nil " +
"elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 then " +
" redis.call('del',KEYS[1]) " +
"else " +
" return 0 " +
"end";
Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue);
if (null == flag){
throw new RuntimeException("this lock doesn't exists");
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public Condition newCondition() {
return null;
}
}
使用
package com.njg.redislock.v5;
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
@RestController
public class RedisLockV5 {
@Autowired
StringRedisTemplate stringRedisTemplate;
Lock lock = new RedisDistributedLock(stringRedisTemplate, "njgLock");
public String sale() {
lock.lock();
// 抢锁成功的请求线程,进行正常的业务逻辑操作,扣件库存
try {
} finally {
lock.unlock();
}
return "";
}
}
版本六
利用工厂模式
问题:测试可重入失败,uuid变
package com.njg.redislock.v6;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.locks.Lock;
@Component
public class DistributedLockFactory {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private String lockName;
public Lock getDistributedLock(String lockType){
if (lockType.equalsIgnoreCase("REDIS")){
this.lockName = "njgRedisLock";
return new RedisDistributedLock(stringRedisTemplate,lockName);
} else if(lockType.equalsIgnoreCase("ZOOKEEPER")){
this.lockName = "njgZookeeperLockNode";
// TODO zookeeper版本的分布式锁
}
return null;
}
}
package com.njg.redislock.v6;
import cn.hutool.core.util.IdUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 自研分布式锁,实现Lock接口
*/
//@Component 引入DistributedLockFactory工厂模式,从工厂获得而不再从spring拿到
public class RedisDistributedLock implements Lock {
private StringRedisTemplate stringRedisTemplate;
private String lockName; //KEYS[1]
private String uuidValue;// ARGV[1]
private long expireTime;//ARGV[2]
public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName) {
this.stringRedisTemplate = stringRedisTemplate;
this.lockName = lockName;
this.uuidValue = IdUtil.randomUUID() + ":" + Thread.currentThread().getId();
this.expireTime = 50L;
}
@Override
public void lock() {
tryLock();
}
@Override
public boolean tryLock() {
try {
tryLock(-1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
if (time == -1) {
String script =
"if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then" +
" redis.call('hincrby',KEYS[1],ARGV[1],1)" +
" redis.call('expire',KEYS[1],ARGV[2])" +
" return 1 " +
"else" +
" return 0 " +
"end";
while (!stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
// 暂停60毫秒重试
TimeUnit.MILLISECONDS.sleep(60);
}
return true;
}
return false;
}
@Override
public void unlock() {
String script =
"if redis.call('hexists',KEYS[1],ARGV[1]) == 0 then " +
" return nil " +
"elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 then " +
" redis.call('del',KEYS[1]) " +
"else " +
" return 0 " +
"end";
Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue);
if (null == flag) {
throw new RuntimeException("this lock doesn't exists");
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public Condition newCondition() {
return null;
}
}
package com.njg.redislock.v6;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.locks.Lock;
@RestController
public class RedisLockV6 {
@Autowired
private DistributedLockFactory distributedLockFactory;
public String sale() {
Lock lock = distributedLockFactory.getDistributedLock("REDIS");
lock.lock();
// 抢锁成功的请求线程,进行正常的业务逻辑操作,扣件库存
try {
} finally {
lock.unlock();
}
return "";
}
}
版本七
可重入锁+设计模式
单机+并发+可重入性 测试通过
package com.njg.redislock.v6;
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.locks.Lock;
@Component
public class DistributedLockFactory {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private String lockName;
private String uuid;
public DistributedLockFactory() {
this.uuid = IdUtil.randomUUID();
}
public Lock getDistributedLock(String lockType){
if (lockType.equalsIgnoreCase("REDIS")){
this.lockName = "njgRedisLock";
return new RedisDistributedLock(stringRedisTemplate,lockName,uuid);
} else if(lockType.equalsIgnoreCase("ZOOKEEPER")){
this.lockName = "njgZookeeperLockNode";
// TODO zookeeper版本的分布式锁
}
return null;
}
}
package com.njg.redislock.v6;
import cn.hutool.core.util.IdUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 自研分布式锁,实现Lock接口
*/
//@Component 引入DistributedLockFactory工厂模式,从工厂获得而不再从spring拿到
public class RedisDistributedLock implements Lock {
private StringRedisTemplate stringRedisTemplate;
private String lockName; //KEYS[1]
private String uuidValue;// ARGV[1]
private long expireTime;//ARGV[2]
public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuidValue) {
this.stringRedisTemplate = stringRedisTemplate;
this.lockName = lockName;
this.uuidValue = uuidValue + ":" + Thread.currentThread().getId();
this.expireTime = 50L;
}
@Override
public void lock() {
tryLock();
}
@Override
public boolean tryLock() {
try {
tryLock(-1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
if (time == -1) {
String script =
"if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then" +
" redis.call('hincrby',KEYS[1],ARGV[1],1)" +
" redis.call('expire',KEYS[1],ARGV[2])" +
" return 1 " +
"else" +
" return 0 " +
"end";
while (!stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
// 暂停60毫秒重试
TimeUnit.MILLISECONDS.sleep(60);
}
return true;
}
return false;
}
@Override
public void unlock() {
String script =
"if redis.call('hexists',KEYS[1],ARGV[1]) == 0 then " +
" return nil " +
"elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 then " +
" redis.call('del',KEYS[1]) " +
"else " +
" return 0 " +
"end";
Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue);
if (null == flag) {
throw new RuntimeException("this lock doesn't exists");
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public Condition newCondition() {
return null;
}
}
package com.njg.redislock.v6;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.locks.Lock;
@RestController
public class RedisLockV6 {
@Autowired
private DistributedLockFactory distributedLockFactory;
public String sale() {
Lock lock = distributedLockFactory.getDistributedLock("REDIS");
lock.lock();
// 抢锁成功的请求线程,进行正常的业务逻辑操作,扣件库存
try {
} finally {
lock.unlock();
}
return "";
}
}
版本八
自动续期lua
package com.njg.redislock.v7;
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.locks.Lock;
@Component
public class DistributedLockFactory {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private String lockName;
private String uuid;
public DistributedLockFactory() {
this.uuid = IdUtil.randomUUID();
}
public Lock getDistributedLock(String lockType){
if (lockType.equalsIgnoreCase("REDIS")){
this.lockName = "njgRedisLock";
return new RedisDistributedLock(stringRedisTemplate,lockName,uuid);
} else if(lockType.equalsIgnoreCase("ZOOKEEPER")){
this.lockName = "njgZookeeperLockNode";
// TODO zookeeper版本的分布式锁
}
return null;
}
}
package com.njg.redislock.v7;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 自研分布式锁,实现Lock接口
*/
//@Component 引入DistributedLockFactory工厂模式,从工厂获得而不再从spring拿到
public class RedisDistributedLock implements Lock {
private StringRedisTemplate stringRedisTemplate;
private String lockName; //KEYS[1]
private String uuidValue;// ARGV[1]
private long expireTime;//ARGV[2]
public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuidValue) {
this.stringRedisTemplate = stringRedisTemplate;
this.lockName = lockName;
this.uuidValue = uuidValue + ":" + Thread.currentThread().getId();
this.expireTime = 50L;
}
@Override
public void lock() {
tryLock();
}
@Override
public boolean tryLock() {
try {
tryLock(-1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
if (time == -1) {
String script =
"if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then" +
" redis.call('hincrby',KEYS[1],ARGV[1],1)" +
" redis.call('expire',KEYS[1],ARGV[2])" +
" return 1 " +
"else" +
" return 0 " +
"end";
while (!stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
// 暂停60毫秒重试
TimeUnit.MILLISECONDS.sleep(60);
}
// 新建一个后台扫描程序,监测key目前的ttl,是否到我们规定的1/2 1/3实现续期
renewExpire();
return true;
}
return false;
}
private void renewExpire() {
String script = "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then " +
" return redis.call('expire',KEYS[1],ARGV[2]) " +
"else " +
" return 0 " +
"end";
new Timer().schedule(new TimerTask() {
@Override
public void run() {
if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
renewExpire();
}
}
}, (this.expireTime * 1000) / 3);
}
@Override
public void unlock() {
String script =
"if redis.call('hexists',KEYS[1],ARGV[1]) == 0 then " +
" return nil " +
"elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 then " +
" redis.call('del',KEYS[1]) " +
"else " +
" return 0 " +
"end";
Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue);
if (null == flag) {
throw new RuntimeException("this lock doesn't exists");
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public Condition newCondition() {
return null;
}
}
package com.njg.redislock.v7;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.locks.Lock;
@RestController
public class RedisLockV7 {
@Autowired
private DistributedLockFactory distributedLockFactory;
/**
* 实现自动续期功能的完善,后台自定义扫描程序,如果规定时间内没有完成业务逻辑,会调用加钟自动续期的脚本
*
* @return
*/
public String sale() {
Lock lock = distributedLockFactory.getDistributedLock("REDIS");
lock.lock();
// 抢锁成功的请求线程,进行正常的业务逻辑操作,扣件库存
try {
} finally {
lock.unlock();
}
return "";
}
}