По сравнению с большим количеством блокировок потоков, распределенные блокировки являются более продвинутыми. Область его действия также преобразуется из автономной в распределенную, и это широко используемый метод координации ресурсов. Обычно используются распределенная блокировка redis и распределенная блокировка zk. Но какая между ними разница? Как мы должны выбирать при обычном использовании?
1. Анализ
Эта задача предъявляет высокие требования, нужно не только понимать метод реализации, но и понимать принцип. Таким образом, ответ на вопрос делится на множество уровней.
Как мы все знаем, Redis рекламируется как легковесный, а интуитивно понятные распределенные блокировки легче реализовать, например, с помощью setnx, но как только будет добавлен атрибут высокой доступности, сложность реализации блокировок Redis резко возрастет.
Вкупе с некоторыми другими свойствами блокировок: оптимистичными и пессимистичными, блокировками чтения-записи и т. д., все будет сложнее.
Если ты знаешь все это, ты не сможешь закончить разговор за день.
2. Попробуйте проанализировать следующее
Начнем с относительно простого и вводного анализа:
- Распределенная блокировка redis может быть реализована на основе инструкции setnx (но на самом деле более рекомендуется использовать инструкцию set с параметром nx)
- Распределенная блокировка zk основана на упорядочении временных узлов и механизме мониторинга узлов.
В этом способе ответа я прямо иду в обход самого себя, потому что он включает в себя множество деталей. Другие просто спрашивают разницу, почему вы зацикливаетесь на уровне исходного кода?
Рекомендуется анализировать следующим образом:
- Redis, RedLock, завернутый в redisson
- зк, InterProcessMutex завернутый куратором
В сравнении:
- Сложность реализации: Zookeeper >= redis
- Производительность сервера: Redis > Zookeeper
- Производительность клиента: Zookeeper > Redis
- Надежность: Zookeeper > Redis
Расскажите подробно:
2.1 Сложность реализации
Для непосредственного управления базовым API сложность реализации аналогична, и необходимо учитывать множество граничных сценариев. Но поскольку ZNode от Zk естественным образом обладает свойством блокировки, начать работу напрямую очень просто.
Redis необходимо учитывать слишком много аномальных сценариев, таких как тайм-аут блокировки, высокая доступность блокировок и т. д., которые сложно реализовать.
2.2 Производительность сервера
Zk основан на протоколе Zab, который требует, чтобы половина узлов была подтверждена до того, как запись будет успешной, а пропускная способность низкая. Если вы часто блокируете и снимаете блокировки, кластер серверов будет находиться под большим давлением.
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 или установить инструкции для выполнения этих действий, xjjdog может только сказать, что он хочет, чтобы его оскорбили. Мы можем понять основные принципы, но эти детали не могут быть поняты без небольшого усилия.
После долгого разговора, что нам делать, когда мы выбираем модель? Это зависит от вашей инфраструктуры. Если ваше приложение использует zk, а производительность кластера очень высока, предпочтительнее использовать zk. Если у вас есть только redis и вы не хотите вводить раздутый zk для распределенной блокировки, используйте redis.
Об авторе:Мисс сестра вкус(xjjdog), публичная учетная запись, которая не позволяет программистам идти в обход. Сосредоточьтесь на инфраструктуре и Linux. Десять лет архитектуры, десятки миллиардов ежедневного трафика, обсуждение с вами мира высокой параллелизма, дающие вам другой вкус. Мой личный WeChat xjjdog0, добро пожаловать в друзья для дальнейшего общения.
xjjdog.cnБолее 200 оригинальных статей были подробно классифицированы, что делает чтение более беглым, добро пожаловать в коллекцию.