redisに基づいて分散錠を実現する(二)
前に提供した二つの方法は実は問題があります.デッドロックか、サーバー時間同期に依存します.Redis 2.6.12バージョンから、SETコマンドはパラメータによって、SETNX、SETEX、PSETEXの3つのコマンドの効果を実現することができます.このように、私たちのロック操作は一つのセットコマンドで実現できます.直接に原子的な操作です.デッドロックのリスクもないし、サーバーの時間同期にも依存しないので、この二つの問題を完璧に解決できます.redis文書に詳細があります.http://doc.redisfans.com/string/set.html
redisのSET reource-name anystring NX EX max-lock-time方式を使用して、分散式ロックに使用します.
原理
命令SET reource-name anystring NX EX max-lock-timeはRedisでロックを実現する簡単な方法です.
クライアントは以上のコマンドを実行します.
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
あり得る問題redisがevalコマンドをサポートすることを保証します.
具体的に実現する
ロックは具体的にRedis Lockを実現します.
package com.xiaolyuh.redis.lock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisCommands;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
/**
* Redis
* SET resource-name anystring NX EX max-lock-time
*
* Redis SET 。
* http://doc.redisfans.com/string/set.html
*
* , Redis 2.6.12 SET ,
* SET key value [EX seconds] [PX milliseconds] [NX|XX], :
*
* EX seconds — key ;
* PX milliseconds — key ;
* NX — key value , key , SETNX。
* XX — key value , key , SETEX。
*
* SET resource-name anystring NX EX max-lock-time Redis 。
*
* :
*
* OK , 。
* NIL , , 。
*
* @author yuhao.wangwang
* @version 1.0
* @date 2017 11 3 10:21:27
*/
public class RedisLock3 {
private static Logger logger = LoggerFactory.getLogger(RedisLock3.class);
private StringRedisTemplate redisTemplate;
/**
* key value , key , SETNX。
*/
public static final String NX = "NX";
/**
* seconds — key , EXPIRE key seconds
*/
public static final String EX = "EX";
/**
* set
*/
public static final String OK = "OK";
/**
* (ms )
*/
private static final long TIME_OUT = 100;
/**
* (s)
*/
public static final int EXPIRE = 60;
/**
* lua
*/
public static final String UNLOCK_LUA;
static {
StringBuilder sb = new StringBuilder();
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
sb.append("then ");
sb.append(" return redis.call(\"del\",KEYS[1]) ");
sb.append("else ");
sb.append(" return 0 ");
sb.append("end ");
UNLOCK_LUA = sb.toString();
}
/**
* key
*/
private String lockKey;
/**
* key
*/
private String lockKeyLog = "";
/**
*
*/
private String lockValue;
/**
* (s)
*/
private int expireTime = EXPIRE;
/**
* (ms)
*/
private long timeOut = TIME_OUT;
/**
*
*/
private volatile boolean locked = false;
final Random random = new Random();
/**
*
*
* @param redisTemplate
* @param lockKey key(Redis Key)
*/
public RedisLock3(StringRedisTemplate redisTemplate, String lockKey) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey + "_lock";
}
/**
* ,
*
* @param redisTemplate
* @param lockKey key(Redis Key)
* @param expireTime ( : )
*/
public RedisLock3(StringRedisTemplate redisTemplate, String lockKey, int expireTime) {
this(redisTemplate, lockKey);
this.expireTime = expireTime;
}
/**
* ,
*
* @param redisTemplate
* @param lockKey key(Redis Key)
* @param timeOut ( : )
*/
public RedisLock3(StringRedisTemplate redisTemplate, String lockKey, long timeOut) {
this(redisTemplate, lockKey);
this.timeOut = timeOut;
}
/**
*
*
* @param redisTemplate
* @param lockKey key(Redis Key)
* @param expireTime ( : )
* @param timeOut ( : )
*/
public RedisLock3(StringRedisTemplate redisTemplate, String lockKey, int expireTime, long timeOut) {
this(redisTemplate, lockKey, expireTime);
this.timeOut = timeOut;
}
/**
*
*
* @return
*/
public boolean tryLock() {
// key
lockValue = UUID.randomUUID().toString();
// ,
long timeout = timeOut * 1000000;
// ,
long nowTime = System.nanoTime();
while ((System.nanoTime() - nowTime) < timeout) {
if (OK.equalsIgnoreCase(this.set(lockKey, lockValue, expireTime))) {
locked = true;
//
return true;
}
//
seleep(10, 50000);
}
return locked;
}
/**
*
*
* @return
*/
public boolean lock() {
lockValue = UUID.randomUUID().toString();
// ( ms)
String result = set(lockKey, lockValue, expireTime);
locked = OK.equalsIgnoreCase(result);
return locked;
}
/**
*
*
* @return
*/
public boolean lockBlock() {
lockValue = UUID.randomUUID().toString();
while (true) {
// ( ms)
String result = set(lockKey, lockValue, expireTime);
if (OK.equalsIgnoreCase(result)) {
locked = true;
return locked;
}
//
seleep(10, 50000);
}
}
/**
*
*
* , :
*
* , (non-guessable) , (token)。
* DEL , Lua , , 。
* 。
*/
public Boolean unlock() {
//
//
if (locked) {
try {
return redisTemplate.execute((RedisConnection connection) -> {
Object nativeConnection = connection.getNativeConnection();
Long result = 0L;
List keys = new ArrayList<>();
keys.add(lockKey);
List values = new ArrayList<>();
values.add(lockValue);
//
if (nativeConnection instanceof JedisCluster) {
result = (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, values);
}
//
if (nativeConnection instanceof Jedis) {
result = (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, values);
}
if (result == 0 && !StringUtils.isEmpty(lockKeyLog)) {
logger.debug("Redis , {} ! :{}", lockKeyLog, System.currentTimeMillis());
}
locked = result == 0;
return result == 1;
});
} catch (Throwable e) {
logger.warn("Redis EVAL , :{}", e.getMessage());
String value = this.get(lockKey, String.class);
if (lockValue.equals(value)) {
redisTemplate.delete(lockKey);
return true;
}
return false;
}
}
return true;
}
/**
*
*
* @return
* @Title: isLock
* @author yuhao.wang
*/
public boolean isLock() {
return locked;
}
/**
* redisTemplate set
*
* SET resource-name anystring NX EX max-lock-time Redis 。
*
* :
*
* OK , 。
* NIL , , 。
*
* @param key Key
* @param value
* @param seconds ( )
* @return
*/
private String set(final String key, final String value, final long seconds) {
Assert.isTrue(!StringUtils.isEmpty(key), "key ");
return redisTemplate.execute(new RedisCallback() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
String result = null;
if (nativeConnection instanceof JedisCommands) {
result = ((JedisCommands) nativeConnection).set(key, value, NX, EX, seconds);
}
if (!StringUtils.isEmpty(lockKeyLog) && !StringUtils.isEmpty(result)) {
logger.info(" {} :{}", lockKeyLog, System.currentTimeMillis());
}
return result;
}
});
}
/**
* redis
*
* @param key key
* @param aClass class
* @return T
*/
private T get(final String key, Class aClass) {
Assert.isTrue(!StringUtils.isEmpty(key), "key ");
return redisTemplate.execute((RedisConnection connection) -> {
Object nativeConnection = connection.getNativeConnection();
Object result = null;
if (nativeConnection instanceof JedisCommands) {
result = ((JedisCommands) nativeConnection).get(key);
}
return (T) result;
});
}
/**
* @param millis
* @param nanos
* @Title: seleep
* @Description:
* @author yuhao.wang
*/
private void seleep(long millis, int nanos) {
try {
Thread.sleep(millis, random.nextInt(nanos));
} catch (InterruptedException e) {
logger.info(" :", e);
}
}
public String getLockKeyLog() {
return lockKeyLog;
}
public void setLockKeyLog(String lockKeyLog) {
this.lockKeyLog = lockKeyLog;
}
public int getExpireTime() {
return expireTime;
}
public void setExpireTime(int expireTime) {
this.expireTime = expireTime;
}
public long getTimeOut() {
return timeOut;
}
public void setTimeOut(long timeOut) {
this.timeOut = timeOut;
}
}
呼び出し方法:public void redisLock3(int i) {
RedisLock3 redisLock3 = new RedisLock3(redisTemplate, "redisLock:" + i % 10, 5 * 60, 500);
try {
long now = System.currentTimeMillis();
if (redisLock3.tryLock()) {
logger.info("=" + (System.currentTimeMillis() - now));
// TODO
logger.info("j:" + j++);
} else {
logger.info("k:" + k++);
}
} catch (Exception e) {
logger.info(e.getMessage(), e);
} finally {
redisLock2.unlock();
}
}
この種類のredisに対して分散式のロックを実現する方案はやはり問題があります.つまりロックを取ってから業務ロジックを実行するコードはredisロックの有効時間内しかないです.このロックの有効時間は必ず業務に合わせて評価します.ソース: https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases
spring-book-student-data-redis-distributed-lock工程
参考: