分散ロックは、より高度なスレッドロックよりも高度です。そのアクションの範囲も、単一のマシンから分散されたマシンに変換されました。これは、リソース調整の一般的に使用される手段です。一般的に使用されるのは、redis分散ロックとzk分散ロックです。しかし、それらの違いは何ですか?私たちの日常の使用では、どのように選択する必要があります。
1.分析
この問題は厳しいものであり、実施方法を理解するだけでなく、原理を把握する必要があります。したがって、質問は多くのレベルで答えられます。
ご存知のとおり、Redisは、setnxを使用するなど、軽量で直感的に分散されたロックがより適切に実装されることを宣伝していますが、高可用性の属性が追加されると、Redisロックの実装の難しさが爆発します。
ロックの他のいくつかのプロパティ(楽観的および悲観的、読み取り/書き込みロックなど)と組み合わせると、事態はさらに複雑になります。
それらすべてを知っていると、一日中話すことができなくなります。
2.以下を分析してみてください
より単純で入門的な分析から始めましょう。
redisの分散ロックは、setnx命令に基づいて実装できます(ただし、実際には、nxパラメーターを指定してset命令を使用することをお勧めします)。zk
の分散ロックは、一時ノードの順序と監視メカニズムに基づいています。ノードの。
この答え方は、多くの詳細を含んでいたので、私自身が直接関わっていました。他の人はただ違いを尋ねます、なぜあなたはソースコードレベルを回るのですか?
次のように分析することをお勧めします。
Redis、Redissonにパッケージ化されたRedLock
Zk、キュレーターにパッケージ化されたInterProcessMutex
比較:
実装の難しさ:Zookeeper> = redis
サーバーのパフォーマンス:redis> Zookeeper
クライアントのパフォーマンス:Zookeeper> redisの
信頼性:Zookeeper> redis
詳細に話す:
2.1実装の難しさ
基盤となるAPIを直接操作する場合、実装の難しさは同様であり、多くの境界シナリオを考慮する必要があります。ただし、ZkのZNodeには当然ロックの特性があるため、直接開始するのは非常に簡単です。
Redisは、ロックタイムアウト、ロック高可用性など、実装が難しい異常なシナリオをあまりにも多く考慮する必要があります。
2.2サーバーのパフォーマンス
ZkはZabプロトコルに基づいており、ノードACKの半分が成功したと見なされる必要があり、スループットが低くなっています。ロックを頻繁に追加および解放すると、サーバークラスターに大きなプレッシャーがかかります。
Redisはメモリに基づいており、マスターへの書き込みは成功し、Redisサーバーへのスループットと負荷は低くなります。
2.3クライアントのパフォーマンス
Zkには通知メカニズムがあるため、ロックを取得するプロセスでリスナーを追加するだけです。ポーリングが回避され、パフォーマンスの消費が少なくなります。
Redisには通知メカニズムがなく、CASと同様のポーリング方法を使用してロックを競合することしかできません。アイドリングが増えると、クライアントにプレッシャーがかかります。
2.4信頼性
これは明らかです。Zookeeperは調整のために生まれました。データの整合性を制御するための厳密なZabプロトコルがあり、ロックモデルは堅牢です。
Redisはスループットを追求しており、信頼性がわずかに劣っています。Redlockを使用しても、100%の堅牢性は保証できませんが、一般的なアプリケーションでは極端なシナリオが発生しないため、一般的に使用されています。
3.拡張
Zkの分散ロックのようなコードサンプル:
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import java.util.concurrent.TimeUnit;
public class ExampleClientThatLocks
{
private final InterProcessMutex lock;
private final FakeLimitedResource resource;
private final String clientName;
public ExampleClientThatLocks(CuratorFramework client, String lockPath, FakeLimitedResource resource, String clientName)
{
this.resource = resource;
this.clientName = clientName;
lock = new InterProcessMutex(client, lockPath);
}
public void doWork(long time, TimeUnit unit) throws Exception
{
if ( !lock.acquire(time, unit) )
{
throw new IllegalStateException(clientName + " could not acquire the lock");
}
try
{
System.out.println(clientName + " has the lock");
resource.use();
}
finally
{
System.out.println(clientName + " releasing the lock");
lock.release(); // always release the lock in a finally block
}
}
}
RedLockの分散ロックの使用例:
String resourceKey = "goodgirl";
RLock lock = redisson.getLock(resourceKey);
try {
lock.lock(5, TimeUnit.SECONDS);
//真正的业务
Thread.sleep(100);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (lock.isLocked()) {
lock.unlock();
}
}
RedLockの内部ロックおよびロック解除コードの実装のセクションが添付されているため、その複雑さをある程度理解できます。
@Override
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('hset', KEYS[1], 'mode', 'read'); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('set', KEYS[2] .. ':1', 1); " +
"redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " +
"local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local key = KEYS[2] .. ':' .. ind;" +
"redis.call('set', key, 1); " +
"redis.call('pexpire', key, ARGV[1]); " +
"local remainTime = redis.call('pttl', KEYS[1]); " +
"redis.call('pexpire', KEYS[1], math.max(remainTime, ARGV[1])); " +
"return nil; " +
"end;" +
"return redis.call('pttl', KEYS[1]);",
Arrays.<Object>asList(getName(), getReadWriteTimeoutNamePrefix(threadId)),
internalLockLeaseTime, getLockName(threadId), getWriteLockName(threadId));
}
@Override
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
String timeoutPrefix = getReadWriteTimeoutNamePrefix(threadId);
String keyPrefix = getKeyPrefix(threadId, timeoutPrefix);
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end; " +
"local lockExists = redis.call('hexists', KEYS[1], ARGV[2]); " +
"if (lockExists == 0) then " +
"return nil;" +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); " +
"if (counter == 0) then " +
"redis.call('hdel', KEYS[1], ARGV[2]); " +
"end;" +
"redis.call('del', KEYS[3] .. ':' .. (counter+1)); " +
"if (redis.call('hlen', KEYS[1]) > 1) then " +
"local maxRemainTime = -3; " +
"local keys = redis.call('hkeys', KEYS[1]); " +
"for n, key in ipairs(keys) do " +
"counter = tonumber(redis.call('hget', KEYS[1], key)); " +
"if type(counter) == 'number' then " +
"for i=counter, 1, -1 do " +
"local remainTime = redis.call('pttl', KEYS[4] .. ':' .. key .. ':rwlock_timeout:' .. i); " +
"maxRemainTime = math.max(remainTime, maxRemainTime);" +
"end; " +
"end; " +
"end; " +
"if maxRemainTime > 0 then " +
"redis.call('pexpire', KEYS[1], maxRemainTime); " +
"return 0; " +
"end;" +
"if mode == 'write' then " +
"return 0;" +
"end; " +
"end; " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; ",
Arrays.<Object>asList(getName(), getChannelName(), timeoutPrefix, keyPrefix),
LockPubSub.UNLOCK_MESSAGE, getLockName(threadId));
}
したがって、すでにパッケージ化されているコンポーネントを使用することをお勧めします。これらのことを行うためにsetnxまたはset命令を使用する必要がある場合、xjjdogはあなたが悪用されたいとだけ言うことができます。基本的な原則は理解できますが、これらの詳細は少しの努力なしでは不明確です。
長い間そう言ってきましたが、モデルを選ぶときはどうしたらいいのでしょうか?それはあなたのインフラストラクチャに依存します。アプリケーションでzkを使用していて、クラスターのパフォーマンスが非常に高い場合は、zkをお勧めします。redisしかなく、分散ロックに肥大化したzkを導入したくない場合は、redisを使用してください。