1. 开局
在多线程环境中,经常会碰到需要加锁的情况,由于现在的系统基本都是集群分布式部署,JVM的lock已经不能满足分布式要求,分布式锁就这样产生了。。。
百度一下,网上有很多分布式锁的方案或者例子,琳琅满目,看了之后不知所措,总体来说有以下几种:
基于数据库
基于zookeeper
基于redis
基于memcached
各有优缺点和实现难度,这里就不一一分析。本文主要是基于redis的setnx实现分布式锁,比较简单有一定的局限性,欢迎大家提出意见建议!
2. 加锁过程
执行redis的setnx,只有key不存在才能set成功(实际使用的是set(key, value, "NX", "EX", seconds),redis较新版本支持)
如果set成功(同时也设置了key的过期时间),则表示加锁成功
如果set失败,则每次sleep(x)毫秒后不断尝试,直到成功或者超时
3. 释放过程
判断加锁是否成功
如果成功,则执行redis的del删除
4. 问题思考
加锁时,锁的redis key过期时间多长合适?
需要根据业务执行的时间长度来评估,默认30秒满足绝大部分需求,支持动态修改
加锁时,重试超时时间多长合适?本文设置的是过期时间的1.2倍,目的是在最坏的情况下等待锁过期后,尽量保证获取到锁,否则抛出超时异常。这个设置不完全合理
加锁时,重试的sleep时间多长合适?本文采用的是随机[50-300)毫秒,避免出现大量线程同时竞争,目的是错峰吧
释放时,如何避免释放了其他线程的锁(A获取锁后由于挂起导致锁到期自动释放;此时B获取到锁,而A又恢复运行释放了B的锁)?在初始化锁时生个一个唯一字符串,作为redis锁的value;value一致时表明是自己的锁,可以释放
5. 上代码!
用法
RedisLock lock = new RedisLock(redisHelper, lockKey);
try {
// 执行加锁,防止并发问题
lock.tryLock();
// do somethings
doSomethings()
}
finally {
// 释放锁
lock.release();
}
RedisLock实现(注:依赖RedisHepler类,仅仅是对jedis的一层封装,可自行实现)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* RedisLock
*
* @version 2017-9-21上午11:56:27
* @author xiaoyun.zeng
*/
public class RedisLock {
private Logger logger = LoggerFactory.getLogger(getClass()