分布式锁常见的手段有:基于redis 实现和基于zookeeper实现,小编这里简单的介绍一下采用单节点的redis来实现分布式锁。
基于redis 单节点实现分布式锁:
这里需要声明一下,redis单节点实现的锁存在的弊端有,节点的机器不允许宕机,应该能够想的通。如果各位程序员采用的是集群模式下的redis, 那么用此分布式锁,是会发生 多个客户端都会拥有锁,如果你的业务不强制限制这点,可以继续使用。
楼主环境:
三个节点的sentinel集群 , redis一主一从,java 使用的是 jedisSentinelPool 作为redis 的数据库连接池。
基础铺垫:
因为公司业务的发展时间较短,并发量以及缓存的数据量不是很大,一核一G的虚拟机单节点redis 的qps 读写大约在1-3万,公司系统是四核四G,一主一从完全够用,考虑使用sentinel 来实现99.99%高可用。因为jedisSentinelPool就是通过哨兵来获取主节点的HostAndPost , 所以该模式下 我们能够保证 java中获取的连接一直是:master的连接,即使master宕机了,slave升级到master,我们的分布式锁还是可继续使用,我们的业务不强制要求,锁只能有一个线程持有。
准备:
lua 脚本 unlock.lua 放到你项目的资源目录下,一般为:src/main/resource . 脚本内容如下:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
本地线程 和 redis 固定key :
private static final String LOCK_NODE ="LOCK";
private static ThreadLocal<String> local = new ThreadLocal<>();
获取锁的核心代码:
/**
* agui 获取分布式锁
* 弊端:只能是redis 单节点好用 所以要保证 该节点不能挂掉
* @return
*/
public static boolean getLock() {
Jedis jedis = null;
try {
jedis = jedisSentinelPool.getResource();
String value = UUID.randomUUID().toString();
String ret = jedis.set(LOCK_NODE, value, "NX", "PX", 10000);
if(!StringUtils.isEmpty(ret) && ret.equals("OK")){
local.set(value);
return true;
}
} catch (Exception e) {
logger.error("Cache获取锁失败:" + e);
return false;
} finally {
releaseResource(jedis);
}
return false;
}
锁的释放:
用 spring 提供的org.springframework.util.FileCopyUtils 来读取 unlock.lua 中的脚本代码,用jedis.evals()去执行。
/**
* agui
*
* 释放锁
*
*/
public static void releaseLock() {
String script =null;
Jedis jedis = null ;
try {
script = FileCopyUtils.copyToString(new FileReader(ResourceUtils.getFile("classpath:unlock.lua")));
jedis = jedisSentinelPool.getResource();
List<String> keys = new ArrayList<String>();
keys.add(LOCK_NODE);
List<String> args = new ArrayList<String>();
args.add(local.get());
jedis.eval(script, keys, args);
} catch (IOException e) {
logger.error("读取lua 脚本失败 :" + e.getMessage());
} catch (Exception e) {
logger.error("Cache释放锁失败:" + e);
} finally {
releaseResource(jedis);
}
System.out.println(script);
}
/**
* 释放redis资源
*
* @param jedis
*/
private static void releaseResource(Jedis jedis) {
if (jedis != null) {
jedis.close();
// jedisSentinelPool.destroy();
// jedisSentinelPool.returnResource(jedis);
}
}
jedisSentinelPool 配置 信息 :
因为楼主用的此连接池,所以通过getResource()该方法来获取jedis对象,不一定非得跟楼主一致都用哨兵连接池,能够获取到jedis 对象就好。
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="minEvictableIdleTimeMillis" value="60000" />
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<property name="numTestsPerEvictionRun" value="-1" />
<property name="maxTotal" value="100" />
<property name="maxIdle" value="50" />
<property name="minIdle" value="10" />
<property name="maxWaitMillis" value="1500"/>
</bean>
<bean id="jedisSentinelPool" class="redis.clients.jedis.JedisSentinelPool" destroy-method="destroy">
<constructor-arg name="masterName">
<value>master1</value>
</constructor-arg>
<constructor-arg name="sentinels">
<set value-type="java.lang.String">
<value>${redis.cluster.host1}</value>
<value>${redis.cluster.host2}</value>
</set>
</constructor-arg>
<constructor-arg name="password">
<value>${redis.auth.password}</value>
</constructor-arg>
<constructor-arg name="poolConfig" ref="jedisPoolConfig" />
</bean>
<bean id="redisUtils" class="com.bigpay.common.core.cache.redis.RedisUtils">
<property name="jedisSentinelPool" ref="jedisSentinelPool" />
</bean>
说明:
细心的程序员会发现,我们getLock()返回的是 boolean 类型的值,这点说明我们的锁为乐观锁,它并不会去阻塞等待锁的释放,另外因为:采用的是 此方法jedis.set(LOCK_NODE, value, “NX”, “PX”, 10000); 所以不存在当获得锁的线程A出现延迟,A锁因为过期时间到了而被释放,此时又有线程B获取到了锁,A因为走完逻辑代码之后要进行释放锁,此时的锁是线程B的,如果不用 NX 参数去限制,那么会存在A把B的锁给释放掉了, 希望此文章对你们有所帮助。