切断面と注解によるRedis分布式ロックを実現しました。
35776 ワード
説明 SprigEL表現に基づいて、動的構成 切断面に基づいて、シームレスに切断する 。は、鍵の取得に失敗したときの挙動をサポートしています。異常を投げても待ち続けてもいいです。2つの方法のロックは、再試行を待っています。1つは直接 から退出します。
ソースの住所:https://github.com/shawntime/shawn-common-utils/tree/master/src/main/java/com/shawntime/common/lock
使い方
一つ目は、setnx()、get()、getsset()を使用する方法です。 setnx(lockkey、現在時間+期限切れタイムアウト時間)は、1を返したらロックに成功します。0に戻るとロックが取れず、2に移る。 get(lockkey)は、値oldExpireTimeを取得し、このvalue値を現在のシステム時間と比較し、現在のシステム時間より小さい場合、このロックはすでにタイムアウトしたと見なし、他の要求を再取得し、3に移ることができる。 は、newExpireTime=現在の時間+期限切れのタイムアウト時間を計算し、そしてgetset(lockkey、newExpireTime)は、現在のlockkeyの値current ExpireTimeに戻ります。 は、current ExpireTimeとoldExpireTimeが等しいかどうかを判断し、等しい場合、現在のgetsset設定が成功したと説明し、ロックを取得した。もし同じではないならば、このロックはまた他の要求によって取得されたと説明しています。現在の要求は直接に失敗に戻ります。または再試行を続けます。 ロックを取得した後、現在のスレッドは自分の業務処理を開始することができ、処理が完了した後、自分の処理時間とロックに対するタイムアウト時間を比較し、ロック設定のタイムアウト時間より小さい場合は、Deleteリリースロックを直接実行する。ロック設定のタイムアウト時間より大きい場合、再度ロックして処理する必要はありません。
EX second:設定キーの賞味期限はsecond秒です。SET key value EX second効果はSETEX key second valueに相当します。PX milisecond:設定キーの賞味期限はmilisecond msです。SET key value PX milisecond効果はPSETEX key milisecond valueに相当します。NX:キーが存在しない場合のみ、キーの設定操作を行います。SET key value NX効果はSETNX key valueに相当します。XX:キーが既に存在している場合のみ、キーの設定操作を行います。
ソースの住所:https://github.com/shawntime/shawn-common-utils/tree/master/src/main/java/com/shawntime/common/lock
使い方
@RedisLockable(key = {"#in.activityId", "#in.userMobile"}, expiration = 120, isWaiting = true, retryCount = 2)
@Override
public PlaceOrderOut placeOrder(OrderIn in) {
// ------
}
コードの実装@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisLockable {
String prefix() default "";
String[] key() default "";
long expiration() default 60;
boolean isWaiting() default false; // ,
int retryCount() default -1; // ,-1
int retryWaitingTime() default 10; // , 10
}
@Aspect
@Component
public class RedisLockInterceptor {
private static final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
private static final ExpressionParser PARSER = new SpelExpressionParser();
@Pointcut("@annotation(com.shawntime.common.lock.RedisLockable)")
public void pointcut() {
}
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method targetMethod = AopUtils.getMostSpecificMethod(methodSignature.getMethod(), point.getTarget().getClass());
String targetName = point.getTarget().getClass().getName();
String methodName = point.getSignature().getName();
Object[] arguments = point.getArgs();
RedisLockable redisLock = targetMethod.getAnnotation(RedisLockable.class);
long expire = redisLock.expiration();
String redisKey = getLockKey(redisLock, targetMethod, targetName, methodName, arguments);
String uuid;
if (redisLock.isWaiting()) {
uuid = waitingLock(redisKey, expire, redisLock.retryCount(), redisLock.retryWaitingTime());
} else {
uuid = noWaitingLock(redisKey, expire);
}
if (StringUtils.isNotEmpty(uuid)) {
try {
return point.proceed();
} finally {
RedisLockUtil.unLock(redisKey, uuid);
}
} else {
throw new RedisLockException(redisKey);
}
}
private String getLockKey(RedisLockable redisLock, Method targetMethod,
String targetName, String methodName, Object[] arguments) {
String[] keys = redisLock.key();
String prefix = redisLock.prefix();
StringBuilder sb = new StringBuilder("lock.");
if (StringUtils.isEmpty(prefix)) {
sb.append(targetName).append(".").append(methodName);
} else {
sb.append(prefix);
}
if (keys != null) {
String keyStr = Joiner.on("+ '.' +").skipNulls().join(keys);
EvaluationContext context = new StandardEvaluationContext(targetMethod);
String[] parameterNames = DISCOVERER.getParameterNames(targetMethod);
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], arguments[i]);
}
Object key = PARSER.parseExpression(keyStr).getValue(context);
sb.append("#").append(key);
}
return sb.toString();
}
private String noWaitingLock(String key, long expire) {
return RedisLockUtil.lock(key, expire);
}
private String waitingLock(String key, long expire, int retryCount, int retryWaitingTime)
throws InterruptedException {
int count = 0;
while (retryCount == -1 || count <= retryCount) {
String uuid = noWaitingLock(key, expire);
if (!StringUtils.isEmpty(uuid)) {
return uuid;
}
try {
TimeUnit.MILLISECONDS.sleep(retryWaitingTime);
} catch (InterruptedException e) {
throw e;
}
count++;
}
return null;
}
}
/**
*
*/
public final class RedisLockUtil {
private static final int DEFAULT_EXPIRE = 60;
private static final String SCRIPT =
"if redis.call(\"get\",KEYS[1]) == ARGV[1]
"
+ "then
"
+ " return redis.call(\"del\",KEYS[1])
"
+ "else
"
+ " return 0
"
+ "end";
private RedisLockUtil() {
super();
}
/**
*
* @param key key
* @return value null, , null
*/
public static String lock(String key) {
return lock(key, DEFAULT_EXPIRE);
}
public static boolean lock(String key, String value) {
return lock(key, value, DEFAULT_EXPIRE);
}
public static String lock(String key, long expire) {
String value = UUID.randomUUID().toString();
boolean nx = SpringRedisUtils.setNX(key, value, expire);
return nx ? value : null;
}
public static boolean lock(String key, String value, long expire) {
return SpringRedisUtils.setNX(key, value, expire);
}
public static void unLock(String key, String value) {
SpringRedisUtils.lua(SCRIPT, Collections.singletonList(key), Collections.singletonList(value));
}
}
redis分散ロックの3つの実装一つ目は、setnx()、get()、getsset()を使用する方法です。
> SETNX (SET if Not eXists)\
:SETNX key value\
: , key , key value , 1; key , SETNX , 0。\
GETSET \
:GETSET key value\
: key value , key (old value), key , , key , nil。\
GET \
:GET key\
: key , key nil 。\
DEL \
:DEL key [KEY …]\
: key , key 。
/**
*
*
* @param key redis key
* @param expire ,
* @return true: ,false,
*/
private boolean waitingLock(String key, long expire, int retryCount) {
int count = 0;
while (retryCount == -1 || count <= retryCount) {
if (noWaitingLock(key, expire)) {
return true;
}
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
return false;
}
/**
*
*
* @param key redis key
* @param expire ,
* @return true: ,false,
*/
private boolean noWaitingLock(String key, long expire) {
long value = System.currentTimeMillis() + expire * 1000;
long status = redisClient.setnx(key, value);
if (status == 1) {
return true;
}
long oldExpireTime = Long.parseLong(redisClient.get(key, "0", false));
if (oldExpireTime < System.currentTimeMillis()) {
long newExpireTime = System.currentTimeMillis() + expire * 1000;
String currentExpireTimeStr = redisClient.getSet(key, String.valueOf(newExpireTime));
if (StringUtils.isEmpty(currentExpireTimeStr)) {
return true;
}
long currentExpireTime = Long.parseLong(currentExpireTimeStr);
if (currentExpireTime == oldExpireTime) {
return true;
}
}
return false;
}
private void unLock(String key, long startTime, long expire) {
long parseTime = System.currentTimeMillis() - startTime;
if (parseTime <= expire * 1000) {
redisClient.del(key);
}
}
SET key value[EX seconds][PX miliseconds][NX|XX]により実現しました。EX second:設定キーの賞味期限はsecond秒です。SET key value EX second効果はSETEX key second valueに相当します。PX milisecond:設定キーの賞味期限はmilisecond msです。SET key value PX milisecond効果はPSETEX key milisecond valueに相当します。NX:キーが存在しない場合のみ、キーの設定操作を行います。SET key value NX効果はSETNX key valueに相当します。XX:キーが既に存在している場合のみ、キーの設定操作を行います。
private boolean noWaitingLock2(String key, String uuid, long expire) {
String value = redisClient.setnx(key, uuid, expire);
return value != null;
}
private boolean waitingLock2(String key, String uuid, long expire, int retryCount) {
int count = 0;
while (retryCount == -1 || count <= retryCount) {
if (noWaitingLock2(key, uuid, expire)) {
return true;
}
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
return false;
}
ロックを削除するメカニズムは直接Delを使ってはいけません。他の人のロックを誤魔化す可能性があります。例えば、この錠は10 sかかりましたが、10 sより処理時間が長くなりました。この錠は自動的に期限が切れました。他の人に取り去られました。また鍵をかけなおしました。この時、Redisを呼び出します。delは他の人が作ったロックを削除します。luaスクリプトを使って、まずgetして、delを行います。private static final String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]
" +
"then
" +
" return redis.call(\"del\",KEYS[1])
" +
"else
" +
" return 0
" +
"end";
private void unLock2(String key, String uuid) {
Object result = redisClient.lua(script, Collections.singletonList(key), Collections.singletonList(uuid));
System.out.println(result);
}
public Object lua(final String script, List keys, List args) {
Jedis jedis = null;
try {
jedis = pool.getResource();
return jedis.eval(script, keys, args);
} catch (Exception ex) {
LOGGER.error(ex);
return 0;
} finally {
returnResource(jedis);
}
}
Redissonsは分散ロックを実現します。RLock rLock = redisson.getLock(lockKey);
long expired = lock.expire();
boolean isLock = rLock.tryLock(expired, TimeUnit.SECONDS);
if (isLock) {
try {
//
} finally {
rLock.unlock();
}
}
rLock.tryLock(3, expired, TimeUnit.SECONDS);