Redis Luaスクリプトによる分散ロック
6815 ワード
1.分散ロック概念:複数のプロセスが同じシステムにない場合、分散ロックで複数のプロセスのリソースへのアクセスを制御します. シーン:分散型の場合(マルチJVM)、スレッドAとスレッドBは同じJVMではない可能性が高いため、スレッドロックが機能しなくなり、分散型ロックで解決します.
2.redis Templateは分布式ロックを実現する
2.1 DistributedLockインタフェース
2.2 Spring Bootでのredis Templateの構成
参照redis template構成
2.3ロック実装の取得
ロックを取得するにはluaスクリプトを使用します.これにより、ロックと失効時間の原子性を保証できます.ロックの取得に成功した後、異常に終了し、ロックが解放されないという問題を回避します. luaスクリプトluaスクリプトはアプリケーションに配置する.propertiesではjedisのsetnxコマンドで直接失効時間を設定できますが、Spring Boot redis Templateを使用して失効時間のあるapiは見つかりません. ロック実装
2.4ロック解除実装
ロックの解放は、解放するロックが自分で取得したロックであることを保証する.他の人が手に入れた鍵を解放すると、めちゃくちゃになります.ロックluaスクリプト を解放ロック解除 を実現する.
3分散ロックの使用
プロジェクトで分散ロックを使用するとSpring AOPと組み合わせて使用できます.
3.1ロック注記
3.2 AOP切麺
ロックを取得するキーはSpel式を解析することによって行われ,SpelUtilのコードはSpelUtilを参照する.
3.3実列の適用
分散ロックとSpring明示的なトランザクション連携の問題:分散ロックとSpringトランザクションが連携して使用される場合、分散ロックでデータベーストランザクションを保護する問題があります.実行手順1.ロックを取得します.2.トランザクションを開きます.3.トランザクションのコミット、4.ロックを解除します.しかし、ステップ4でロックの解放に失敗した場合、トランザクションがロックの失効時間を超えて実行され、ロックが自動的に解放された場合、トランザクションはどのようにロールバックされますか.私が考えている解決策は、ロックの失効時間よりも小さいトランザクションの失効時間を追加することです.@Transactionのtimeoutプロパティを使用できます.ロックの失効時間よりも少ない失効時間を設定します.分散ロックが無効になる前に、トランザクションはタイムアウトされ、トランザクションがロールバックされます.
4.まとめ
現在のredis分散ロックの実装には依然として問題があり、には単一点障害の問題があり,公式に解決策としてRedLockアルゴリズムが与えられている. ロックの取得に失敗した後、異常を放出するしかなく、スレッドをブロックすることはできません.Redissonオープンソースフレームワークはこれらの問題を解決した.
2.redis Templateは分布式ロックを実現する
2.1 DistributedLockインタフェース
public interface DistributedLock {
/**
*
* @param requestId id uuid
* @param key key
* @param expiresTime ms
* @return true
*/
boolean lock(String requestId,String key,int expiresTime);
/**
*
* @param key key
* @param requestId id uuid
* @return true
*/
boolean releaseLock(String key,String requestId);
}
2.2 Spring Bootでのredis Templateの構成
参照redis template構成
2.3ロック実装の取得
ロックを取得するにはluaスクリプトを使用します.これにより、ロックと失効時間の原子性を保証できます.ロックの取得に成功した後、異常に終了し、ロックが解放されないという問題を回避します.
lua.lockScript=if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end
public boolean lock(String requestId, String key, int expiresTime) {
DefaultRedisScript longDefaultRedisScript = new DefaultRedisScript<>(luaScript.lockScript, Long.class);
Long result = stringRedisTemplate.execute(longDefaultRedisScript, Collections.singletonList(key), requestId,String.valueOf(expiresTime));
return result == 1;
}
2.4ロック解除実装
ロックの解放は、解放するロックが自分で取得したロックであることを保証する.他の人が手に入れた鍵を解放すると、めちゃくちゃになります.
lua.releaseLockScript=if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end
@Override
public boolean releaseLock(String key, String requestId) {
DefaultRedisScript longDefaultRedisScript = new DefaultRedisScript<>(luaScript.releaseLockScript, Long.class);
Long result = stringRedisTemplate.execute(longDefaultRedisScript, Collections.singletonList(key), requestId);
return result == 1;
}
3分散ロックの使用
プロジェクトで分散ロックを使用するとSpring AOPと組み合わせて使用できます.
3.1ロック注記
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface AddLock {
//spel
String spel() ;
//
int expireTime() default 10;
//log
String logInfo() default "";
}
3.2 AOP切麺
ロックを取得するキーはSpel式を解析することによって行われ,SpelUtilのコードはSpelUtilを参照する.
@Aspect
@Component
public class AddLockAspect {
private Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private DistributedLock distributedLock;
@Pointcut("@annotation(com.example.demo.annotation.AddLock)")
public void addLockAnnotationPointcut() {
}
@Around(value = "addLockAnnotationPointcut()")
public Object addKeyMethod(ProceedingJoinPoint joinPoint) throws Throwable {
//
Object proceed;
//
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
Method method = methodSignature.getMethod();
AddLock addLock = AnnotationUtils.findAnnotation(method, AddLock.class);
String logInfo = getLogInfo(joinPoint);
//
String redisKey = getRediskey(joinPoint);
logger.info("{} ={}",logInfo,redisKey);
// ID; 。
String requestId = UUID.randomUUID().toString();
boolean lockReleased = false;
try {
boolean lock = distributedLock.lock(requestId, redisKey, addLock.expireTime());
if (!lock ) {
throw new RuntimeException(logInfo+": ");
}
//
proceed = joinPoint.proceed();
boolean releaseLock = distributedLock.releaseLock(redisKey, requestId);
lockReleased = true;
if(releaseLock){
throw new RuntimeException(logInfo+": ");
}
return proceed;
} catch (Exception exception) {
logger.error("{} ,key = {},message={}, exception = {}", logInfo,redisKey, exception.getMessage(), exception);
throw exception;
} finally {
if(!lockReleased){
logger.info("{} ={}",logInfo,redisKey);
boolean releaseLock = distributedLock.releaseLock(redisKey, requestId);
logger.info("releaseLock="+releaseLock);
}
}
}
/**
* loginfo
* AddLock
* loginfo
* @param joinPoint
* @return logInfo
*/
private String getLogInfo(ProceedingJoinPoint joinPoint){
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
Method method = methodSignature.getMethod();
AddLock annotation = AnnotationUtils.findAnnotation(method, AddLock.class);
if(annotation == null){
return methodSignature.getName();
}
return annotation.logInfo();
}
/**
*
* @param joinPoint
* @return redisKey
*/
private String getRediskey(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method targetMethod = methodSignature.getMethod();
Object target = joinPoint.getTarget();
Object[] arguments = joinPoint.getArgs();
AddLock annotation = AnnotationUtils.findAnnotation(targetMethod, AddLock.class);
String spel=null;
if(annotation != null){
spel = annotation.spel();
}
return SpelUtil.parse(target,spel, targetMethod, arguments);
}
}
3.3実列の適用
分散ロックとSpring明示的なトランザクション連携の問題:分散ロックとSpringトランザクションが連携して使用される場合、分散ロックでデータベーストランザクションを保護する問題があります.実行手順1.ロックを取得します.2.トランザクションを開きます.3.トランザクションのコミット、4.ロックを解除します.しかし、ステップ4でロックの解放に失敗した場合、トランザクションがロックの失効時間を超えて実行され、ロックが自動的に解放された場合、トランザクションはどのようにロールバックされますか.私が考えている解決策は、ロックの失効時間よりも小さいトランザクションの失効時間を追加することです.@Transactionのtimeoutプロパティを使用できます.ロックの失効時間よりも少ない失効時間を設定します.分散ロックが無効になる前に、トランザクションはタイムアウトされ、トランザクションがロールバックされます.
@AddLock(spel = "'spel:'+#p0",logInfo = " ")
@Transactional(timeout = 5)
public void doWorker(String key) {}
4.まとめ
現在のredis分散ロックの実装には依然として問題があり、