您的位置:首页 > 数据库 > Redis

Redis分布式锁使用lua脚本,实现分布式锁,及封装(Spring_boot)

2020-07-13 05:13 330 查看

一、分布式锁

分布式锁,是一种思想,它的实现方式有很多。比如,我们将沙滩当做分布式锁的组件,那么它看起来应该是这样的:

加锁

在沙滩上踩一脚,留下自己的脚印,就对应了加锁操作。其他进程或者线程,看到沙滩上已经有脚印,证明锁已被别人持有,则等待。

解锁

把脚印从沙滩上抹去,就是解锁的过程。

锁超时

为了避免死锁,我们可以设置一阵风,在单位时间后刮起,将脚印自动抹去。

分布式锁的实现有很多,比如基于数据库、memcached、Redis、系统文件、zookeeper等。它们的核心的理念跟上面的过程大致相同。

二、redis

我们先来看如何通过单节点Redis实现一个简单的分布式锁。

1、加锁
加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间。

值得注意的是:
lockValue 是客户端生成的唯一的字符串。
flag 代表只在键不存在时,才对键进行设置操作。
timeout 设置键的过期时间。

这样,如果上面的命令执行成功,则证明客户端获取到了锁。
LUA代码如下

----
--锁的名称
local lockName=KEYS[1]
--锁的value
local lockValue=ARGV[1]
--过期时间
local timeout=tonumber(ARGV[2])
--尝试进行加锁
local flag=redis.call('setnx',lockName,lockValue)
--判断是否获得锁
if flag==1 then
redis.call('expire',lockName,timeout)
end

--返回标识
return flag

2、解锁
解锁的过程就是将Key键删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉。这时候random_value的作用就体现出来。

为了保证解锁操作的原子性,我们用LUA脚本完成这一操作。先判断当前锁的字符串是否与传入的值相等,是的话就删除Key,解锁成功。
LUA代码如下

--锁的名称
local lockName=KEYS[1]
--锁的value
local lockValue=ARGV[1]
--判断锁是否存在,以及锁的内容是否为自己加的
local value=redis.call('get', lockName)
--判断是否相同
if value == lockValue then
redis.call('del', lockName)
return 1
end
return 0

三、Java使用Redis的lua脚本,实现分布式锁,及封装

pom.xml

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.yml

spring:
redis:
host: xxx.xxx.xxx.xx
port: 6379

LockUtil.java

@Component
public class LockUtil {

@Autowired
private StringRedisTemplate redisTemplate;

//加锁的lua脚本
private String lockLua = "--锁的名称\n" +
"local lockName=KEYS[1]\n" +
"--锁的value\n" +
"local lockValue=ARGV[1]\n" +
"--过期时间 秒\n" +
"local timeout=tonumber(ARGV[2])\n" +
"--尝试进行加锁\n" +
"local flag=redis.call('setnx', lockName, lockValue)\n" +
"--判断是否获得锁\n" +
"if flag==1 then\n" +
"--获得分布式锁,设置过期时间\n" +
"redis.call('expire', lockName, timeout)\n" +
"end\n" +
"--返回标识\n" +
"return flag ";

//解锁的lua脚本
private String unLockLua = "--锁的名称\n" +
"local lockName=KEYS[1]\n" +
"--锁的value\n" +
"local lockValue=ARGV[1]\n" +
"--判断锁是否存在,以及锁的内容是否为自己加的\n" +
"local value=redis.call('get', lockName)\n" +
"--判断是否相同\n" +
"if value == lockValue then\n" +
"     redis.call('del', lockName)\n" +
"     return 1\n" +
"end\n" +
"return 0";

private ThreadLocal<String> tokens = new ThreadLocal<>();

/**
* 加锁(默认超时时间30s)
* @return
*/
public void lock(String lockName){
lock(lockName, 30);
}
/**
* 加锁可自定义超时时间timeout
* @return
*/
public void lock(String lockName, Integer timeout){

String token = UUID.randomUUID().toString();
//设置给threadLocal
tokens.set(token);

//分布式锁 - 加锁
Long flag = (Long) redisTemplate.execute(new DefaultRedisScript(lockLua, Long.class),
Collections.singletonList(lockName),
token, timeout + ""
);

System.out.println("获得锁的结果:" + flag);

//设置锁的自旋
if (flag == 0) {
//未获得锁
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}

lock(lockName, timeout);
}
}

/**
* 解锁
* @return
*/
public boolean unlock(String lockName){

//获得ThreadLocal
String token = tokens.get();

//解锁
Long result = (Long) redisTemplate.execute(new DefaultRedisScript(unLockLua, Long.class),
Collections.singletonList(lockName),
token);

System.out.println("删除锁的结果:" + result);

return result == 1;
}
}

觉得楼主写的不错的点个关注和赞哟~

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: