1 分布式锁从0到1落地实现 redis终极版
//1:独占排他预防栈内存溢出
//2:预防死锁 集群中客户端的某个节点挂了,自己的锁没释放,导致其他客户端没法获取到锁
//3:原子性
//4:防止误删
//5:可重入
//6:自动续期
在之前的文章中已经处理了各种 情况下的 redis 的锁 现在还剩下两个问题
//5:可重入
//6:自动续期
下面就来集合 lua 实现一个完整的分布式锁
1>创建一个分布式锁工厂类 用于创建不同的分布式锁 redis zk mysql 的,现在暂时只要有 redis 的
=========================================================
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.wl.study.lock.DistributedRedisLock;
import java.util.UUID;
@Component
public class DistributedLockFactory {
//单例对象 使用uuid 每个服务生成的是一样的,后面再加上当前线程的 id 来作为分布式锁的filed
@Autowired
private StringRedisTemplate redisTemplate; //redis 模板工具类也需要时同一个
private String uuid;
public DistributedLockFactory() {
this.uuid = UUID.randomUUID().toString();
}
public DistributedRedisLock getRedisLock(String lockName) {
return new DistributedRedisLock(lockName, redisTemplate,uuid);
}
}
===========================================================
锁对象及其方法实现
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;
public class DistributedRedisLock implements Lock {
private final String lockName;
private StringRedisTemplate redisTemplate;
private String uuid;
private Long expireTime = 10L;
public DistributedRedisLock(String lockName, StringRedisTemplate redisTemplate, String uuid) {
this.lockName = lockName;
this.redisTemplate = redisTemplate;
this.uuid = uuid;
}
@Override
public void lock() {
this.tryLock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
try {
this.tryLock(-1L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return false;
}
//加锁
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
if (time != -1) {
this.expireTime = unit.toSeconds(time);
}
// KEYS[1] 代表第一个key ,ARGV[1] 第一个KEY的值
// ARGV[2] 代表过期时间
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 (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), getLockFiled(), String.valueOf(expireTime))) {
Thread.sleep(50);
}
return true;
}
//解锁
@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 " +
" return redis.call('del',KEYS[1]) " +
"else " +
"return 0 " +
"end";
Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), getLockFiled());
if (flag == null) {
throw new IllegalMonitorStateException("this lock is not belong to you");
}
}
@Override
public Condition newCondition() {
return null;
}
private String getLockFiled(){
return uuid+":"+Thread.currentThread().getId();
}
}
================================================
service 调用
//自己手动完成
@Autowired
private StringRedisTemplate redisTemplate;
//1:独占排他预防栈内存溢出
//2:预防死锁 集群中客户端的某个节点挂了,自己的锁没释放,导致其他客户端没法获取到锁
//3:原子性
//4:防止误删
//5:可重入
//6:自动续期
public void reduce() {
DistributedRedisLock redisLock = distributedLockFactory.getRedisLock("lock");
redisLock.lock();
try {
String stock = this.redisTemplate.opsForValue().get("stock");
if (stock!= null && stock.length() != 0) {
Integer st = Integer.parseInt(stock);
if (st > 0) {
this.redisTemplate.opsForValue().set("stock", String.valueOf(--st));
}
}
this.testReEnter(); //测试重入
}catch (Exception e){
e.printStackTrace();
}finally {
redisLock.unlock();
}
}
public void testReEnter(){
DistributedRedisLock redisLock = distributedLockFactory.getRedisLock("lock");
redisLock.lock();
System.out.println(".......................测试可重入..............");
redisLock.unlock();
}
执行结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/30150ae7f0fb48e584e3f0d90ce4393e.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a62eb2fc21bb4dbeadeadd3e3eaefc59.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/92447dad6ebc44debb93b36771bf8aa4.png)
2 自动续期以及带来的问题
package org.wl.study.lock;
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;
public class DistributedRedisLock implements Lock {
private final String lockName;
private StringRedisTemplate redisTemplate;
private String uuid;
private Long expireTime = 10L;
public DistributedRedisLock(String lockName, StringRedisTemplate redisTemplate, String uuid) {
this.lockName = lockName;
this.redisTemplate = redisTemplate;
this.uuid = uuid;
}
@Override
public void lock() {
this.tryLock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
try {
this.tryLock(-1L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return false;
}
//加锁
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
if (time != -1) {
this.expireTime = unit.toSeconds(time);
}
// KEYS[1] 代表第一个key ,ARGV[1] 第一个KEY的值
// ARGV[2] 代表过期时间
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 (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), getLockFiled(), String.valueOf(expireTime))) {
Thread.sleep(50);
}
this.reNewExpire();//重置过期时间
return true;
}
//解锁
@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 " +
" return redis.call('del',KEYS[1]) " +
"else " +
"return 0 " +
"end";
Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), getLockFiled());
if (flag == null) {
throw new IllegalMonitorStateException("this lock is not belong to you");
}
}
@Override
public Condition newCondition() {
return null;
}
//这个方法在自动续期的时候 会有问题
private String getLockFiled(){
return uuid+":"+Thread.currentThread().getId();//当前线程
}
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() {
//子线程 getLockFiled() 拿到的时子线程已经不能给原来的线程操作的锁做自动续期了
if(redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class),Arrays.asList(lockName),getLockFiled())){
reNewExpire();
}
}
},this.expireTime * 1000 / 2,this.expireTime * 1000 / 2);
}
}
需要修改代码
这个方法去掉
private String getLockFiled(){
return uuid+":"+Thread.currentThread().getId();//当前线程
}
锁的构造方法
public DistributedRedisLock(String lockName, StringRedisTemplate redisTemplate, String uuid) {
this.lockName = lockName;
this.redisTemplate = redisTemplate;
this.uuid = uuid +":"+Thread.currentThread().getId(); //这里来生产uuid
}
替换加锁 解锁 和续期的 使用到uuid 的 地方
package org.wl.study.lock;
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;
public class DistributedRedisLock implements Lock {
private final String lockName;
private StringRedisTemplate redisTemplate;
private String uuid;
private Long expireTime = 10L;
public DistributedRedisLock(String lockName, StringRedisTemplate redisTemplate, String uuid) {
this.lockName = lockName;
this.redisTemplate = redisTemplate;
this.uuid = uuid +":"+Thread.currentThread().getId();
}
@Override
public void lock() {
this.tryLock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
try {
this.tryLock(-1L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return false;
}
//加锁
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
if (time != -1) {
this.expireTime = unit.toSeconds(time);
}
// KEYS[1] 代表第一个key ,ARGV[1] 第一个KEY的值
// ARGV[2] 代表过期时间
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 (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expireTime))) {
Thread.sleep(50);
}
this.reNewExpire();//重置过期时间
return true;
}
//解锁
@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 " +
" return redis.call('del',KEYS[1]) " +
"else " +
"return 0 " +
"end";
Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);
if (flag == null) {
throw new IllegalMonitorStateException("this lock is not belong to you");
}
}
@Override
public Condition newCondition() {
return null;
}
//这个方法在自动续期的时候 会有问题
// private String getLockFiled(){
// return uuid+":"+Thread.currentThread().getId();//当前线程
// }
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() {
//子线程 getLockFiled() 拿到的时子线程已经不能给原来的线程操作的锁做自动续期了
if(redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class),Arrays.asList(lockName),uuid)){
reNewExpire();
}
}
},this.expireTime * 1000 / 2,this.expireTime * 1000 / 2);
}
}
到这里redis 分布式锁的开发全部完成