- 基本思路
1 sennx(记录主键,当前时间+过期时间)返回1表示获取到锁,0则到2; 主键可以是锁名称
2 查询锁对应的value值,防止缓存清理失败导致服务不可用;
2.1get(记录主键)获取value,即时间,小于当前时间表示该key过期,没有其他实例使用;
2.1.1使用getset去竞争锁,getSet结果等于2.1的value,本次获取锁成功,返回;
2.1.2使用getset去竞争锁,getSet结果与2.1的value不相等,本次获取锁失败,等待1S,继续1;
2.2get(记录主键)获取value,即时间,大于当前时间表示该key有效,有实例在使用锁;
3 获得锁,处理业务逻辑,无论是否成功都需要调用4;
4 delete(获取锁的主键),结束;
- 具体实现
Redis操作基础操作,支持分布式锁
public class JedisCluster {
private List<JedisPool> pools;
private Integer retryTime = 3;
public String getSet(String key, String obj, Integer exprTime) {
Jedis j = null;
Integer index = 0;
JedisPool jedisPool = null;
while (index++ < retryTime) {
try {
jedisPool = randomJedis();
j = jedisPool.getResource();
String value = j.getSet(key, obj);
j.expire(key, exprTime);
return value;
} catch (Throwable e) {
logger.error("JedisCluster getSet error,retry time is " + index, e);
continue;
} finally {
if (j != null && jedisPool != null) {
jedisPool.returnResource(j);
}
}
}
return null;
}
public Long setNx(String key, String value, Integer expreTime) {
Jedis j = null;
Integer index = 0;
Long result = 0L;
JedisPool jedisPool = null;
while (index++ < retryTime) {
try {
jedisPool = randomJedis();
j = jedisPool.getResource();
result = j.setnx(key, value);
if (result == 1) {
j.expire(key, expreTime);
}
return result;
} catch (Throwable e) {
logger.error("JedisCluster setNx error,retry time is " + index, e);
continue;
} finally {
if (j != null && jedisPool != null) {
jedisPool.returnResource(j);
}
}
}
return result;
}
public void set(String key, String value, Integer expreTime) {
Jedis j = null;
Integer index = 0;
JedisPool jedisPool = null;
while (index++ < retryTime) {
try {
jedisPool = randomJedis();
j = jedisPool.getResource();
j.setex(key, expreTime, value);
} catch (Throwable e) {
logger.error("JedisCluster set error,retry time is " + index, e);
continue;
} finally {
if (j != null && jedisPool != null) {
jedisPool.returnResource(j);
}
}
}
}
public void remove(String key) {
Jedis j = null;
JedisPool jedisPool = null;
Integer index = 0;
while (index++ < retryTime) {
try {
jedisPool = randomJedis();
j = jedisPool.getResource();
j.del(key);
} catch (Throwable e) {
logger.error("JedisCluster set error,retry time is " + index, e);
continue;
} finally {
if (j != null && jedisPool != null) {
jedisPool.returnResource(j);
}
}
}
}
public String get(String key) {
Jedis j = null;
JedisPool jedisPool = null;
String result = null;
Integer index = 0;
while (index++ < retryTime) {
try {
jedisPool = randomJedis();
j = jedisPool.getResource();
result = j.get(key);
return result;
} catch (Throwable e) {
logger.error("JedisCluster get error,retry time is " + index, e);
continue;
} finally {
if (j != null && jedisPool != null) {
jedisPool.returnResource(j);
}
}
}
return result;
}
public String setNxExAtomic(String key, String value, Integer expireTime) {
String script = "if redis.call(\"SETNX\",KEYS[1],ARGV[1]) == 1\n"
+ "then\n"
+ " return redis.call(\"EXPIRE\",KEYS[1],ARGV[2])\n"
+ "else\n"
+ " return 0\n"
+ "end";
List<String> keys = Lists.newArrayList();
keys.add(key);
List<String> tokens = Lists.newArrayList();
tokens.add(value);
tokens.add(expireTime.toString());
String result = eval(script, keys, tokens);
return result;
}
public String evalUnLock(Lock lock) {
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n"
+ "then\n"
+ " return redis.call(\"del\",KEYS[1])\n"
+ "else\n"
+ " return 0\n"
+ "end";
List<String> keys = Lists.newArrayList();
keys.add(lock.getKey());
List<String> tokens = Lists.newArrayList();
tokens.add(lock.getValue());
String result = eval(script, keys, tokens);
return result;
}
public String eval(String script, List<String> keys, List<String> params) {
String result = null;
Jedis j = null;
JedisPool jedisPool = null;
Integer index = 0;
Boolean isOKResource = true;
while (index++ < retryTime) {
try {
jedisPool = randomJedis();
j = jedisPool.getResource();
result = ((Long) j.eval(script, keys, params)).toString();
return result;
} catch (Throwable e) {
logger.error("JedisCluster get error,retry time is " + index, e);
isOKResource = false;
continue;
} finally {
if (j != null && jedisPool != null) {
if (isOKResource) {
jedisPool.returnResource(j);
} else {
jedisPool.returnBrokenResource(j);
}
isOKResource = true;
}
}
}
return result;
}
private JedisPool randomJedis() {
Random r = new Random();
int jIndex = r.nextInt(pools.size());
return pools.get(jIndex);
}
public List<JedisPool> getPools() {
return pools;
}
public void setPools(List<JedisPool> pools) {
this.pools = pools;
}
}
分布式锁
public class DistributedLock {
private JedisCluster jedisCluster;
private static final Long MAX_CACHE_TIME = 60 * 1000L;
private static final Long ONE_SLEEP_TIME = 1 * 1000L;
private static final Integer MAX_RETRY_TIMES = 10;
public Boolean getLock(String lock) {
Long currentTime = System.currentTimeMillis();
Long value = currentTime + MAX_CACHE_TIME;
Integer retryTime = 0;
try {
while (retryTime < MAX_RETRY_TIMES) {
retryTime++;
// 1 setnx
Long setNxResult = jedisCluster.setNx(lock, value.toString(), MAX_CACHE_TIME.intValue());
if (setNxResult == 1) {
logger.info("setNx OK,getLock");
return true;
} else {
// 2 锁value检查,防止缓存清理失败导致服务不可用
String cacheValue = jedisCluster.get(lock);
Long oldLockValue = null == cacheValue ? null : Long.parseLong(cacheValue);
// 2.1get(记录主键)获取value,即时间,小于当前时间表示该key过期,没有其他实例使用,使用getset去竞争锁
if (oldLockValue != null && currentTime > oldLockValue) {
logger.info("oldLockTime less than curentTime ,maybe getLock!");
String getResultStr = jedisCluster.getSet(lock, value.toString(), MAX_CACHE_TIME.intValue());
// 2.1.1使用getset去竞争锁,getSet结果等于当前时间本次获取锁成功,返回;
if (getResultStr == null || (getResultStr != null && getResultStr
.equals(oldLockValue.toString()))) {
logger.info("getset old time equals oldLockTime ,getLock!");
return true;
} else {
// 2.1.2使用getset去竞争锁,getSet结果与2.1的value不相等,本次获取锁失败,等待1S,继续1;
logger.info("getset old time not equals oldLockTime ,not getLock,sleep 1S!");
Thread.sleep(ONE_SLEEP_TIME);
continue;
}
} else { // 2.2锁不可用,等待重试
logger.info("oldLockTime great than curentTime ,not getLock,sleep 1S!");
Thread.sleep(ONE_SLEEP_TIME);
continue;
}
}
}
} catch (Throwable t) {
logger.error("get distributed lock error ,key is " + lock, t);
}
// 如果重试10次都没有成功,打日志继续业务操作.
logger.error("getLock failure,continue process,but it is error!");
return false;
}
public void deleteLock(String lock) {
try {
jedisCluster.remove(lock);
} catch (Throwable t) {
logger.error("delete distributed lock error ,key is " + lock, t);
}
}
public JedisCluster getJedisCluster() {
return jedisCluster;
}
public void setJedisCluster(JedisCluster jedisCluster) {
this.jedisCluster = jedisCluster;
}
}