一、解决思路问题场景:一个简单的修改功能,先去数据库查出要修改的数据,然后再内存中进行修改,修改完再存回去。在单线程中这样的操作没问题,但是在多线程中,由于查询、修改、保存是三个操作,那就会出问题
第一个进来的线程先进行占位操作,然后去执行业务代码。当第二个线程进来时发现已经有别的线程占位了,就需要等待或稍后再试。在redis中,占位一般有setnx命令,先进来的线程先占位,使用完后再用del指令释放资源
二、使用步骤 1.引入库代码如下(示例):
2.建个测试类,代码如下redis.clients jedis 3.3.0 jar compile
package com.example.distuibuted;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;
import java.util.Arrays;
import java.util.UUID;
public class LuaTest {
public static void main(String[] args) {
//1.构建一个jedis连接池
JedisPool jedisPool = new JedisPool("填自己的redis安装地址ip,windos就是127.0.0.1", 6379);
//2.从连接池获取一个jedis连接
try (Jedis jedis = jedisPool.getResource()) {
jedis.auth("123456");
//3.jedis操作
String ping = jedis.ping();
System.out.println(ping);
//4.获取一个随机字符串
String value = UUID.randomUUID().toString();
//获取锁
String k1 = jedis.set("k1", value, new SetParams().nx().ex(5));
//5.判断是否成功拿到锁
if (k1 != null && "OK".equals(k1)) {
//6.业务操作
jedis.set("set", "业务操作");
String set = jedis.get("set");
System.out.println(set);
//7.释放锁(里面的字符串是lua脚本的id,需要换成自己的,见第三步)
jedis.evalsha("b8059ba43af6ffe8bed3db65bac35d452f8115d8", Arrays.asList("k1"), Arrays.asList(value));
} else {
System.out.println("没拿到锁");
}
}
}
}
3.lua脚本代码
if redis.call("get",KEYS[1])==ARGV[1] then
return redis.call("del".KEYS[1])
else
return 0
end
这里的keys对应着代码中的evalsha方法中的参数(Arrays.asList(“k1”)),argv代表 Arrays.asList(value)。
至于为什么要用lua脚本来实现则是为了解决超时死锁问题。
比如说锁在释放之前出现了异常,那么就会造成死锁的问题,所以就给锁添加了一个过期时间,这样的话即使就防止应用抛出异常导致锁无法及时释放。
但是这时又会引出另一个问题,比如我设置的超时时间是5秒,那么在第二秒时程序发生了异常,而此时锁还未释放,又有第二个线程来访问应用。那么第二个线程执行到第三秒时,第一个线程的锁刚好达到五秒,便会释放。但是释放的却是第二个线程的锁,这就造成了一种紊乱现象。所以为了解决这个问题。我使用的就是SetParams中的方法,使过期时间和获取锁同时执行。另外,为了防止释放锁的过程中出现问题。因此使用了lua脚本。思路就是首先获取一个随机字符串,然后把这个字符串存进jedis中。当释放锁的时候需要传进去一个value,只有这个value相同时才说明第一个线程释放的是自己的锁,而不是另外一个线程的锁。



