1.什么是分布式锁?
1.1 情景
假设,对一个商品表的简单操作,一个线程去修改这个商品的信息,首先得从数据库中查出这条数据,然后加载在内存中,在内存修改完成之后,再存到数据库里面,对于单线程而言这一个完整的操作是没问题的,但是在多线程中,由于读取,修改,保存到数据库不是原子操作(原子特性:不可分割,要么全都成功,要么全都失败),所以这种操作出现在多线程中就会有很大的问题。
1.2 用redis实现分布式锁的思路?
分布式锁的思路不难,其实就是先进来的线程先占位置,当其他线程进来时,发现位置被人占了,就会放弃争夺或者稍后再来争夺。
在redis中,用setnx命令和del命令,可以简单的实现分布式锁,setnx命令返回1就代表线程抢到锁了,setnx命令返回0代表线程没抢到锁,del命令可以释放锁
2. 简单实现
2.1 jedisutils
package com.yl;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class JedisUtils {
private static JedisPool jedisPool = null;
public static Jedis getJedisObject() {
if (jedisPool == null) {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxIdle(400);
config.setMaxTotal(2000);
config.setMaxWaitMillis(300000);
jedisPool = new JedisPool(config,"192.168.244.129",6379,30000,"root123");
}
try {
Jedis jedis = jedisPool.getResource();
jedis.auth("root123");
return jedis;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
2.1 测试
package com.yl;
import redis.clients.jedis.Jedis;
public class LockTest {
public static void main(String[] args) {
Jedis jedis = JedisUtils.getJedisObject();
Long setnx = jedis.setnx("k1", "v1");
if (setnx == 1) {
jedis.expire("k1",5);
jedis.set("name", "yl");
String name = jedis.get("name");
System.out.println(name);
jedis.del("k1");
} else {
System.out.println("没有获取到锁!");
}
}
}
3. (2.0简单实现分布锁)存在的问题以及解决方案
3.1 存在的问题
从2.0可以看到,获取到锁后,要设置过期时间(设置过期时间是预防业务代码出错,锁一直没释放掉!),那么这里会出现一种情况,假设这个过期时间设得比较小,然后,线程一进来了,执行它自己的业务代码,很复杂,执行得很慢,花费的时间远大于锁的超时时间,这个时候,线程一还在执行自己的业务代码,线程二抢到了锁进来了,然后也执行自己的业务代码,执行到一半,线程一的业务代码执行完了,线程二的还没执行完,线程一按道理还是会去释放锁的,这个时候线程二是锁的拥有者,线程一释放掉线程二的锁,这种情况合理?显然不合理,2.0中锁的value都固定为v1,所以它们公用同一个锁,且它们的value都是一样的!
3.2 解决方案:锁的value用随机字符串来替代再结合lua脚本判断传入的key去redis中查到的value和传入的value是否一致,如果一致就del掉key,否则直接返回
3.2.1 进入到redis文件,创建脚本文件
mkdir lua
cd lua/
vi release.lua
3.2.2 release.lua脚本文件内容
if redis.call("get",KEYS[1])==ARGV[1]then
return redis.call("del",KEYS[1])
else
return 0
end
3.2.3 求出SHA1和,作用:将lua脚本文件加载到redis缓存中,并且返回一个参数,在调用lua脚本时要传这个参数
cat lua/release.lua | redis-cli -a root123 script load --pipe
3.2.4 测试
package com.yl;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
import java.util.Arrays;
import java.util.UUID;
public class LockTest3 {
public static void main(String[] args) {
Jedis jedis = JedisUtils.getJedisObject();
String str= UUID.randomUUID().toString();
String lock = jedis.set("k1", str, new SetParams().nx().ex(5));
if (lock != null && "OK".equals(lock)) {
jedis.set("name","yl");
jedis.set("age","23");
System.out.println(jedis.get("name"));
System.out.println(jedis.get("age"));
jedis.evalsha("b8059ba43af6ffe8bed3db65bac35d452f8115d8", Arrays.asList("k1"),Arrays.asList(str));
} else {
System.out.println("没有获取到锁!");
}
}
}
|