Redisson分散ロック実装解析
13253 ワード
RedissonLockコア分析
サンプル分析
RedissonLockを解析する前に、まずredisのluaスクリプトとluaスクリプトを実行する方法を大まかに理解しなければなりません.RedissonLockのコアコードはluaスクリプトコードであるからです.
luaスクリプトの例:
実行:*redis-cli–eval ratelimiting.lua rate.limitingl:127.0.0.1,103*分析解釈:*–evalパラメータはredis-cliに後のLuaスクリプトを読み取り実行するように伝え、ratelimiting.luaはスクリプトの位置であり,後にLuaスクリプトに渡されるパラメータが続く.その中で、「前のrate.limiting:127.0.0.1は操作するキーで、スクリプトではKEYS[1]で取得できます」、「後の10と3はパラメータで、スクリプトではARGV[1]とARGV[2]で取得できます.注意:「両方のスペースは省略できません.エラーが発生します.*スクリプトの内容に合わせて、この行のコマンドの役割は、アクセス頻度を10秒ごとに最大3回に制限することです.そのため、端末でこのコマンドを繰り返し実行すると、アクセス頻度が10秒以内に3回未満の場合は1を返し、そうでない場合は0を返します.
RedissonLock分析
RLockはRedissonベースの同期ロック実装である
次の基本的な使用方法を示します.
説明:*RLockの使い方は実はReentrantLockの使い方と似ています*異なる点はRLockにkey IDがあることです*RLockの生存時間を設定することもできます(ロックを自動的に解放する時間は、ReentrantLockとは異なり、ReentrantLockは手動でロックを解放する必要があります)*同時にRLockは手動でロックを解除することもできます*以上は使い方の表面レベルから見るとReentrantLockと差が少ないだけで、本質的には差が大きいのか*同じならなぜRLockを一挙に使うのか...*ここでは、最大の違いはRLockがRedisの特性を利用して分散ロックメカニズムを実現していることです.これはReentrantLockにはない特性です
rLockを取得してからキーコードのロックを開始します.以下はRedissonがlockを実行するプロセスです.
tryLockInnerAsync:
ロック(非同期)*–1つのキー、2つのパラメータ*keys="lock"*args="30000 3 a 4 e 8 d 51-f 191-40 a 4-8220-f 80 d 39759 bc 0:1"*{keys:lock->RLock存在フラグargs:30000->RLock生存時間、単位ms 3 a 4 e 8 d 51-f 191-40 a 4-8220-f 80 d 39759 bc 0:1->RLock同一実行スレッド一意フラグ、主に同一実行スレッド}
unlock:
ロック解除*–2つのキー、3つのパラメータ*keys="lock redisson_lock__channel__{lock}"*args="0 30000 3 a 4 e 8 d 51-f 191-40 a 4-8220-f 80 d 39759 bc 0:1"*{keys:key->Rlockフラグredisson_lock__channel_{lock}->指定されたチャンネルchannel(PUBLISH channel message、その他の情報はpublishコマンドを参照)args:0->30000->RLock生存時間、単位ms 3 a 4 e 8 d 51-f 191-40 a 4-8220-f 80 d 39759 bc 0:1->同一実行スレッドRLockフラグ}
forceUnlock:
強制ロック解除*–2つのキー、1つのパラメータ*keys="lock redisson_lock__channel__{lock}"*args="0"*{keys:lock->Rlockフラグredisson_lock__channel_{lock}->指定したチャンネルchannel(PUBLISH channel message、その他の情報はpublishコマンドを参照)args:0}
unlockAsync:
ロック解除(非同期)注記unlock*–2つのkey、3つのパラメータ*keys="*args=""
サンプル分析
RedissonLockを解析する前に、まずredisのluaスクリプトとluaスクリプトを実行する方法を大まかに理解しなければなりません.RedissonLockのコアコードはluaスクリプトコードであるからです.
luaスクリプトの例:
local times = redis.call('incr',KEYS[1])
if times == 1 then
redis.call('expire',KEYS[1], ARGV[1])
end
if times > tonumber(ARGV[2]) then
return 0
end
return 1
実行:*redis-cli–eval ratelimiting.lua rate.limitingl:127.0.0.1,103*分析解釈:*–evalパラメータはredis-cliに後のLuaスクリプトを読み取り実行するように伝え、ratelimiting.luaはスクリプトの位置であり,後にLuaスクリプトに渡されるパラメータが続く.その中で、「前のrate.limiting:127.0.0.1は操作するキーで、スクリプトではKEYS[1]で取得できます」、「後の10と3はパラメータで、スクリプトではARGV[1]とARGV[2]で取得できます.注意:「両方のスペースは省略できません.エラーが発生します.*スクリプトの内容に合わせて、この行のコマンドの役割は、アクセス頻度を10秒ごとに最大3回に制限することです.そのため、端末でこのコマンドを繰り返し実行すると、アクセス頻度が10秒以内に3回未満の場合は1を返し、そうでない場合は0を返します.
RedissonLock分析
RLockはRedissonベースの同期ロック実装である
次の基本的な使用方法を示します.
RLock rLock = redisson.getLock("lock");
rLock.lock(100, TimeUnit.SECONDS);
//Todo: your code!
rLock.unlock();
説明:*RLockの使い方は実はReentrantLockの使い方と似ています*異なる点はRLockにkey IDがあることです*RLockの生存時間を設定することもできます(ロックを自動的に解放する時間は、ReentrantLockとは異なり、ReentrantLockは手動でロックを解放する必要があります)*同時にRLockは手動でロックを解除することもできます*以上は使い方の表面レベルから見るとReentrantLockと差が少ないだけで、本質的には差が大きいのか*同じならなぜRLockを一挙に使うのか...*ここでは、最大の違いはRLockがRedisの特性を利用して分散ロックメカニズムを実現していることです.これはReentrantLockにはない特性です
rLockを取得してからキーコードのロックを開始します.以下はRedissonがlockを実行するプロセスです.
@Override
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
Long ttl = tryAcquire(leaseTime, unit);//--> tryLockInnerAsync,
// lock acquired
if (ttl == null) {// lua ,
return;
}
long threadId = Thread.currentThread().getId();
Future future = subscribe(threadId);
get(future);
try {
while (true) {
ttl = tryAcquire(leaseTime, unit);
// lock acquired
if (ttl == null) {
break;
}
// waiting for message
if (ttl >= 0) {
getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
getEntry(threadId).getLatch().acquire();
}
}
} finally {
unsubscribe(future, threadId);
}
}
tryLockInnerAsync:
ロック(非同期)*–1つのキー、2つのパラメータ*keys="lock"*args="30000 3 a 4 e 8 d 51-f 191-40 a 4-8220-f 80 d 39759 bc 0:1"*{keys:lock->RLock存在フラグargs:30000->RLock生存時間、単位ms 3 a 4 e 8 d 51-f 191-40 a 4-8220-f 80 d 39759 bc 0:1->RLock同一実行スレッド一意フラグ、主に同一実行スレッド}
if (redis.call('exists', KEYS[1]) == 0) then -- RLock
redis.call('hset', KEYS[1], ARGV[2], 1); -- hset RLock
redis.call('pexpire', KEYS[1], ARGV[1]); -- RLock ,pexpire expire , pexpire ms, expire s
return nil; -- RLock
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then -- RLock , , RLock 1, RLock
redis.call('hincrby', KEYS[1], ARGV[2], 1); --RLock 1
redis.call('pexpire', KEYS[1], ARGV[1]); -- RLock
return nil; -- RLock
end;
return redis.call('pttl', KEYS[1]); -- RLock
unlock:
ロック解除*–2つのキー、3つのパラメータ*keys="lock redisson_lock__channel__{lock}"*args="0 30000 3 a 4 e 8 d 51-f 191-40 a 4-8220-f 80 d 39759 bc 0:1"*{keys:key->Rlockフラグredisson_lock__channel_{lock}->指定されたチャンネルchannel(PUBLISH channel message、その他の情報はpublishコマンドを参照)args:0->30000->RLock生存時間、単位ms 3 a 4 e 8 d 51-f 191-40 a 4-8220-f 80 d 39759 bc 0:1->同一実行スレッドRLockフラグ}
if (redis.call('exists', KEYS[1]) == 0) then --RLock ,
redis.call('publish', KEYS[2], ARGV[1]); -- 0 channel
return 1; -- 1
end;
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then --RLock ,
return nil; -- nil redisson :"attempt to unlock lock, not locked by current thread by node id: "+ id + " thread-id: " + Thread.currentThread().getId()
end;
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); --RLock , , 1
if (counter > 0) then -- 0
redis.call('pexpire', KEYS[1], ARGV[2]); --
return 0; -- 0
else --
redis.call('del', KEYS[1]); -- RLock
redis.call('publish', KEYS[2], ARGV[1]); -- 0 channel
return 1; -- 1
end;
return nil; -- nil redisson :"attempt to unlock lock, not locked by current thread by node id: "+ id + " thread-id: " + Thread.currentThread().getId()
forceUnlock:
強制ロック解除*–2つのキー、1つのパラメータ*keys="lock redisson_lock__channel__{lock}"*args="0"*{keys:lock->Rlockフラグredisson_lock__channel_{lock}->指定したチャンネルchannel(PUBLISH channel message、その他の情報はpublishコマンドを参照)args:0}
if (redis.call('del', KEYS[1]) == 1) then --
redis.call('publish', KEYS[2], ARGV[1]); -- 0 channel
return 1 -- 1
else
return 0 -- 0
end
unlockAsync:
ロック解除(非同期)注記unlock*–2つのkey、3つのパラメータ*keys="*args=""
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('publish', KEYS[2], ARGV[1]);
return 1;
end;
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
return nil;
end;
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0) then
redis.call('pexpire', KEYS[1], ARGV[2]);
return 0;
else
redis.call('del', KEYS[1]);
redis.call('publish', KEYS[2], ARGV[1]);
return 1;
end;
return nil;