Redis Client Redisson 四
- 官网地址:https://redisson.org
- 项目地址:https://github.com/redisson/redisson
简介
- Redisson是基于 Netty框架的事件驱动通讯的, 所以方法调用是异步的同时线程安全的, Redisson提供一系列的分布式服务及数据结构, 如 Lock, BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, AtomicLong, CountDownLatch, Publish/Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service
分布式锁
- 分布式系统有一个著名的理论 CAP, 指在一个分布式系统中, 最多只能同时满足一致性(Consistency), 可用性(Availability), 分区容错性(Partition tolerance)中的两项. 所以设计系统时, 需要权衡
基本特性
- 锁互斥: 高并发时, 保证同一时间只能有一个线程获得锁
- 重入锁: 高并发时, 在同一个线程不等待解锁, 可重复加锁(递归形式), 执行代码. 此时其它线程只能等到当前(被锁定)的线程的锁(包括重入锁), 执行完代码后解锁(递归形式), 方可开始抢锁.
- 释放锁: 高并发时, 某个线程加锁后, 因为系统故障或者其它原因导致无法执行解锁的命令, 造成死锁. 为了防止此种情况, 需要设置锁的有效时间, 如果指定的时间内未解锁, 系统主动释放锁
RedissonLock源码
public class RedissonLock extends RedissonExpirable implements RLock {
private static final Logger log = LoggerFactory.getLogger(RedissonLock.class);
private static final ConcurrentMap<String, RedissonLock.ExpirationEntry> EXPIRATION_RENEWAL_MAP = new ConcurrentHashMap();
protected long internalLockLeaseTime;
final UUID id;
final String entryName;
protected final LockPubSub pubSub;
final CommandAsyncExecutor commandExecutor;
private static final RedisCommand<Integer> HGET;
public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
this.commandExecutor = commandExecutor;
this.id = commandExecutor.getConnectionManager().getId();
this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
this.entryName = this.id + ":" + name;
this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();
}
protected String getEntryName() {
return this.entryName;
}
String getChannelName() {
return prefixName("redisson_lock__channel", this.getName());
}
protected String getLockName(long threadId) {
return this.id + ":" + threadId;
}
public void lock() {
try {
this.lock(-1L, (TimeUnit)null, false);
} catch (InterruptedException var2) {
throw new IllegalStateException();
}
}
public void lock(long leaseTime, TimeUnit unit) {
try {
this.lock(leaseTime, unit, false);
} catch (InterruptedException var5) {
throw new IllegalStateException();
}
}
public void lockInterruptibly() throws InterruptedException {
this.lock(-1L, (TimeUnit)null, true);
}
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
this.lock(leaseTime, unit, true);
}
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
long threadId = Thread.currentThread().getId();
Long ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl != null) {
RFuture<RedissonLockEntry> future = this.subscribe(threadId);
this.commandExecutor.syncSubscription(future);
try {
while(true) {
ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl == null) {
return;
}
if (ttl >= 0L) {
try {
this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException var13) {
if (interruptibly) {
throw var13;
}
this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
} else if (interruptibly) {
this.getEntry(threadId).getLatch().acquire();
} else {
this.getEntry(threadId).getLatch().acquireUninterruptibly();
}
}
} finally {
this.unsubscribe(future, threadId);
}
}
}
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
return (Long)this.get(this.tryAcquireAsync(leaseTime, unit, threadId));
}
private RFuture<Boolean> tryAcquireOnceAsync(long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1L) {
return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
} else {
RFuture<Boolean> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null) {
if (ttlRemaining) {
this.scheduleExpirationRenewal(threadId);
}
}
});
return ttlRemainingFuture;
}
}
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1L) {
return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null) {
if (ttlRemaining == null) {
this.scheduleExpirationRenewal(threadId);
}
}
});
return ttlRemainingFuture;
}
}
public boolean tryLock() {
return (Boolean)this.get(this.tryLockAsync());
}
private void renewExpiration() {
RedissonLock.ExpirationEntry ee = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
if (ee != null) {
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
RedissonLock.ExpirationEntry ent = (RedissonLock.ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());
if (ent != null) {
Long threadId = ent.getFirstThreadId();
if (threadId != null) {
RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);
} else {
if (res) {
RedissonLock.this.renewExpiration();
}
}
});
}
}
}
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
}
private void scheduleExpirationRenewal(long threadId) {
RedissonLock.ExpirationEntry entry = new RedissonLock.ExpirationEntry();
RedissonLock.ExpirationEntry oldEntry = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
if (oldEntry != null) {
oldEntry.addThreadId(threadId);
} else {
entry.addThreadId(threadId);
this.renewExpiration();
}
}
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end; return 0;", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
}
void cancelExpirationRenewal(Long threadId) {
RedissonLock.ExpirationEntry task = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
if (task != null) {
if (threadId != null) {
task.removeThreadId(threadId);
}
if (threadId == null || task.hasNoThreads()) {
task.getTimeout().cancel();
EXPIRATION_RENEWAL_MAP.remove(this.getEntryName());
}
}
}
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
this.internalLockLeaseTime = unit.toMillis(leaseTime);
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
}
private void acquireFailed(long threadId) {
this.get(this.acquireFailedAsync(threadId));
}
protected RFuture<Void> acquireFailedAsync(long threadId) {
return RedissonPromise.newSucceededFuture((Object)null);
}
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime);
long current = System.currentTimeMillis();
long threadId = Thread.currentThread().getId();
Long ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl == null) {
return true;
} else {
time -= System.currentTimeMillis() - current;
if (time <= 0L) {
this.acquireFailed(threadId);
return false;
} else {
current = System.currentTimeMillis();
RFuture<RedissonLockEntry> subscribeFuture = this.subscribe(threadId);
if (!this.await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
subscribeFuture.onComplete((res, e) -> {
if (e == null) {
this.unsubscribe(subscribeFuture, threadId);
}
});
}
this.acquireFailed(threadId);
return false;
} else {
try {
time -= System.currentTimeMillis() - current;
if (time <= 0L) {
this.acquireFailed(threadId);
boolean var20 = false;
return var20;
} else {
boolean var16;
do {
long currentTime = System.currentTimeMillis();
ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl == null) {
var16 = true;
return var16;
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0L) {
this.acquireFailed(threadId);
var16 = false;
return var16;
}
currentTime = System.currentTimeMillis();
if (ttl >= 0L && ttl < time) {
this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
this.getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
time -= System.currentTimeMillis() - currentTime;
} while(time > 0L);
this.acquireFailed(threadId);
var16 = false;
return var16;
}
} finally {
this.unsubscribe(subscribeFuture, threadId);
}
}
}
}
}
protected RedissonLockEntry getEntry(long threadId) {
return (RedissonLockEntry)this.pubSub.getEntry(this.getEntryName());
}
protected RFuture<RedissonLockEntry> subscribe(long threadId) {
return this.pubSub.subscribe(this.getEntryName(), this.getChannelName());
}
protected void unsubscribe(RFuture<RedissonLockEntry> future, long threadId) {
this.pubSub.unsubscribe((PubSubEntry)future.getNow(), this.getEntryName(), this.getChannelName());
}
public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
return this.tryLock(waitTime, -1L, unit);
}
public void unlock() {
try {
this.get(this.unlockAsync(Thread.currentThread().getId()));
} catch (RedisException var2) {
if (var2.getCause() instanceof IllegalMonitorStateException) {
throw (IllegalMonitorStateException)var2.getCause();
} else {
throw var2;
}
}
}
public Condition newCondition() {
throw new UnsupportedOperationException();
}
public boolean forceUnlock() {
return (Boolean)this.get(this.forceUnlockAsync());
}
public RFuture<Boolean> forceUnlockAsync() {
this.cancelExpirationRenewal((Long)null);
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('del', KEYS[1]) == 1) then redis.call('publish', KEYS[2], ARGV[1]); return 1 else return 0 end", Arrays.asList(this.getName(), this.getChannelName()), new Object[]{LockPubSub.UNLOCK_MESSAGE});
}
public boolean isLocked() {
return this.isExists();
}
public RFuture<Boolean> isLockedAsync() {
return this.isExistsAsync();
}
public RFuture<Boolean> isExistsAsync() {
return this.commandExecutor.writeAsync(this.getName(), this.codec, RedisCommands.EXISTS, new Object[]{this.getName()});
}
public boolean isHeldByCurrentThread() {
return this.isHeldByThread(Thread.currentThread().getId());
}
public boolean isHeldByThread(long threadId) {
RFuture<Boolean> future = this.commandExecutor.writeAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.HEXISTS, new Object[]{this.getName(), this.getLockName(threadId)});
return (Boolean)this.get(future);
}
public RFuture<Integer> getHoldCountAsync() {
return this.commandExecutor.writeAsync(this.getName(), LongCodec.INSTANCE, HGET, new Object[]{this.getName(), this.getLockName(Thread.currentThread().getId())});
}
public int getHoldCount() {
return (Integer)this.get(this.getHoldCountAsync());
}
public RFuture<Boolean> deleteAsync() {
return this.forceUnlockAsync();
}
public RFuture<Void> unlockAsync() {
long threadId = Thread.currentThread().getId();
return this.unlockAsync(threadId);
}
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "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;", Arrays.asList(this.getName(), this.getChannelName()), new Object[]{LockPubSub.UNLOCK_MESSAGE, this.internalLockLeaseTime, this.getLockName(threadId)});
}
public RFuture<Void> unlockAsync(long threadId) {
RPromise<Void> result = new RedissonPromise();
RFuture<Boolean> future = this.unlockInnerAsync(threadId);
future.onComplete((opStatus, e) -> {
if (e != null) {
this.cancelExpirationRenewal(threadId);
result.tryFailure(e);
} else if (opStatus == null) {
IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + this.id + " thread-id: " + threadId);
result.tryFailure(cause);
} else {
this.cancelExpirationRenewal(threadId);
result.trySuccess((Object)null);
}
});
return result;
}
public RFuture<Void> lockAsync() {
return this.lockAsync(-1L, (TimeUnit)null);
}
public RFuture<Void> lockAsync(long leaseTime, TimeUnit unit) {
long currentThreadId = Thread.currentThread().getId();
return this.lockAsync(leaseTime, unit, currentThreadId);
}
public RFuture<Void> lockAsync(long currentThreadId) {
return this.lockAsync(-1L, (TimeUnit)null, currentThreadId);
}
public RFuture<Void> lockAsync(long leaseTime, TimeUnit unit, long currentThreadId) {
RPromise<Void> result = new RedissonPromise();
RFuture<Long> ttlFuture = this.tryAcquireAsync(leaseTime, unit, currentThreadId);
ttlFuture.onComplete((ttl, e) -> {
if (e != null) {
result.tryFailure(e);
} else if (ttl == null) {
if (!result.trySuccess((Object)null)) {
this.unlockAsync(currentThreadId);
}
} else {
RFuture<RedissonLockEntry> subscribeFuture = this.subscribe(currentThreadId);
subscribeFuture.onComplete((res, ex) -> {
if (ex != null) {
result.tryFailure(ex);
} else {
this.lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId);
}
});
}
});
return result;
}
private void lockAsync(long leaseTime, TimeUnit unit, RFuture<RedissonLockEntry> subscribeFuture, RPromise<Void> result, long currentThreadId) {
RFuture<Long> ttlFuture = this.tryAcquireAsync(leaseTime, unit, currentThreadId);
ttlFuture.onComplete((ttl, e) -> {
if (e != null) {
this.unsubscribe(subscribeFuture, currentThreadId);
result.tryFailure(e);
} else if (ttl == null) {
this.unsubscribe(subscribeFuture, currentThreadId);
if (!result.trySuccess((Object)null)) {
this.unlockAsync(currentThreadId);
}
} else {
final RedissonLockEntry entry = this.getEntry(currentThreadId);
if (entry.getLatch().tryAcquire()) {
this.lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId);
} else {
AtomicReference<Timeout> futureRef = new AtomicReference();
final Runnable listener = () -> {
if (futureRef.get() != null) {
((Timeout)futureRef.get()).cancel();
}
this.lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId);
};
entry.addListener(listener);
if (ttl >= 0L) {
Timeout scheduledFuture = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
if (entry.removeListener(listener)) {
RedissonLock.this.lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId);
}
}
}, ttl, TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
}
});
}
public RFuture<Boolean> tryLockAsync() {
return this.tryLockAsync(Thread.currentThread().getId());
}
public RFuture<Boolean> tryLockAsync(long threadId) {
return this.tryAcquireOnceAsync(-1L, (TimeUnit)null, threadId);
}
public RFuture<Boolean> tryLockAsync(long waitTime, TimeUnit unit) {
return this.tryLockAsync(waitTime, -1L, unit);
}
public RFuture<Boolean> tryLockAsync(long waitTime, long leaseTime, TimeUnit unit) {
long currentThreadId = Thread.currentThread().getId();
return this.tryLockAsync(waitTime, leaseTime, unit, currentThreadId);
}
public RFuture<Boolean> tryLockAsync(long waitTime, long leaseTime, TimeUnit unit, long currentThreadId) {
RPromise<Boolean> result = new RedissonPromise();
AtomicLong time = new AtomicLong(unit.toMillis(waitTime));
long currentTime = System.currentTimeMillis();
RFuture<Long> ttlFuture = this.tryAcquireAsync(leaseTime, unit, currentThreadId);
ttlFuture.onComplete((ttl, e) -> {
if (e != null) {
result.tryFailure(e);
} else if (ttl == null) {
if (!result.trySuccess(true)) {
this.unlockAsync(currentThreadId);
}
} else {
long el = System.currentTimeMillis() - currentTime;
time.addAndGet(-el);
if (time.get() <= 0L) {
this.trySuccessFalse(currentThreadId, result);
} else {
long current = System.currentTimeMillis();
AtomicReference<Timeout> futureRef = new AtomicReference();
final RFuture<RedissonLockEntry> subscribeFuture = this.subscribe(currentThreadId);
subscribeFuture.onComplete((r, ex) -> {
if (ex != null) {
result.tryFailure(ex);
} else {
if (futureRef.get() != null) {
((Timeout)futureRef.get()).cancel();
}
long elapsed = System.currentTimeMillis() - current;
time.addAndGet(-elapsed);
this.tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId);
}
});
if (!subscribeFuture.isDone()) {
Timeout scheduledFuture = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
if (!subscribeFuture.isDone()) {
subscribeFuture.cancel(false);
RedissonLock.this.trySuccessFalse(currentThreadId, result);
}
}
}, time.get(), TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
}
});
return result;
}
private void trySuccessFalse(long currentThreadId, RPromise<Boolean> result) {
this.acquireFailedAsync(currentThreadId).onComplete((res, e) -> {
if (e == null) {
result.trySuccess(false);
} else {
result.tryFailure(e);
}
});
}
private void tryLockAsync(AtomicLong time, long leaseTime, TimeUnit unit, RFuture<RedissonLockEntry> subscribeFuture, RPromise<Boolean> result, long currentThreadId) {
if (result.isDone()) {
this.unsubscribe(subscribeFuture, currentThreadId);
} else if (time.get() <= 0L) {
this.unsubscribe(subscribeFuture, currentThreadId);
this.trySuccessFalse(currentThreadId, result);
} else {
long curr = System.currentTimeMillis();
RFuture<Long> ttlFuture = this.tryAcquireAsync(leaseTime, unit, currentThreadId);
ttlFuture.onComplete((ttl, e) -> {
if (e != null) {
this.unsubscribe(subscribeFuture, currentThreadId);
result.tryFailure(e);
} else if (ttl == null) {
this.unsubscribe(subscribeFuture, currentThreadId);
if (!result.trySuccess(true)) {
this.unlockAsync(currentThreadId);
}
} else {
long el = System.currentTimeMillis() - curr;
time.addAndGet(-el);
if (time.get() <= 0L) {
this.unsubscribe(subscribeFuture, currentThreadId);
this.trySuccessFalse(currentThreadId, result);
} else {
final long current = System.currentTimeMillis();
final RedissonLockEntry entry = this.getEntry(currentThreadId);
if (entry.getLatch().tryAcquire()) {
this.tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId);
} else {
AtomicBoolean executed = new AtomicBoolean();
AtomicReference<Timeout> futureRef = new AtomicReference();
final Runnable listener = () -> {
executed.set(true);
if (futureRef.get() != null) {
((Timeout)futureRef.get()).cancel();
}
long elapsed = System.currentTimeMillis() - current;
time.addAndGet(-elapsed);
this.tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId);
};
entry.addListener(listener);
long t = time.get();
if (ttl >= 0L && ttl < time.get()) {
t = ttl;
}
if (!executed.get()) {
Timeout scheduledFuture = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
if (entry.removeListener(listener)) {
long elapsed = System.currentTimeMillis() - current;
time.addAndGet(-elapsed);
RedissonLock.this.tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId);
}
}
}, t, TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
}
}
});
}
}
static {
HGET = new RedisCommand("HGET", ValueType.MAP_VALUE, new IntegerReplayConvertor(0));
}
public static class ExpirationEntry {
private final Map<Long, Integer> threadIds = new LinkedHashMap();
private volatile Timeout timeout;
public ExpirationEntry() {
}
public void addThreadId(long threadId) {
Integer counter = (Integer)this.threadIds.get(threadId);
if (counter == null) {
counter = 1;
} else {
counter = counter + 1;
}
this.threadIds.put(threadId, counter);
}
public boolean hasNoThreads() {
return this.threadIds.isEmpty();
}
public Long getFirstThreadId() {
return this.threadIds.isEmpty() ? null : (Long)this.threadIds.keySet().iterator().next();
}
public void removeThreadId(long threadId) {
Integer counter = (Integer)this.threadIds.get(threadId);
if (counter != null) {
counter = counter - 1;
if (counter == 0) {
this.threadIds.remove(threadId);
} else {
this.threadIds.put(threadId, counter);
}
}
}
public void setTimeout(Timeout timeout) {
this.timeout = timeout;
}
public Timeout getTimeout() {
return this.timeout;
}
}
}
Redisson加锁& 解锁实现过程
常用的两种加锁方法
- lock(long leaseTime, TimeUnit unit), 可设置过期时间, 当业务处理超过过期时间或系统出了故障未执行解锁命令时, 系统自动释放锁
- lock(), 无参方法默认过期时间为 -1, 此方法加锁同时会启动一个看门狗(Watch dog)在后台线程监控当前锁, 不断的延长锁的生命周期, 避免业务处理超出预期时间时, 保证在业务执行完后释放锁
给每次请求赋予唯一编号
protected String getLockName(long threadId) {
# id是 UUID.randomUUID()
# threadId是线程编号
# 类似返回 d2518228-aad9-4fd8-b814-f450541cab3c:1
return this.id + ":" + threadId;
}
- 请求多个命令的代码块时, 为了保证原子性使用 Lua脚本(Redis对 Lua脚本的执行具有原子性)
加锁 tryLockInnerAsync方法
- KEYS[1]是 LockKey. 获取锁时指定的键, 如 redissonClient.getLock(“LockKey”);
- ARGV[1]是锁的生存时间, 如 30000L(单位为毫秒)
- ARGV[2]是请求者的唯一编号, 通过方法 String getLockName(long threadId)来获取, 类似 d2518228-aad9-4fd8-b814-f450541cab3c:1
# 判断锁存在与否 LockKey,
if (redis.call('exists', KEYS[1]) == 0) then
## 如果不存在, 使用 hset命令加锁: hset LockKey d2518228-aad9-4fd8-b814-f450541cab3c:1 1
redis.call('hset', KEYS[1], ARGV[2], 1);
## 同时设置过期时间
redis.call('pexpire', KEYS[1], ARGV[1]);
## 返回空值, 结束此脚本
return nil;
end;
# 判断当前线程是否已持有锁
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
## 如果已持有锁, 递增重入锁次数
redis.call('hincrby', KEYS[1], ARGV[2], 1);
## 重置过期时间
redis.call('pexpire', KEYS[1], ARGV[1]);
## 返回空值, 结束此脚本
return nil;
end;
# 加锁失败, 返回当前锁的剩余时间给其它线程的请求者
return redis.call('pttl', KEYS[1]);
未抢到锁
- 获取锁消息键: 请求未获取到锁时线程进入等待, 通过此消息键订阅释放锁消息, 当锁拥有者释放锁时通过此键发送释放锁消息(一个锁只有一个消息键)
String getChannelName() {
# 类似返回 redisson_lock__channel:{LockKey}
return prefixName("redisson_lock__channel", this.getName());
}
- 通过 lock方法加锁, 当未抢到锁进入等待释放锁消息
# 获取锁, 如返回 null意思是成功抢到锁, 否者返回当前锁的剩余时间
Long ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl != null) {
# 当前请求者线程, 订阅释放锁消息
RFuture<RedissonLockEntry> future = this.subscribe(threadId);
# 阻塞获取订阅结果
this.commandExecutor.syncSubscription(future);
try {
while(true) {
# 循环获取锁, 如返回 null意味着已抢到锁
ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl == null) {
return;
}
...
...
} finally {
# 取消订阅
this.unsubscribe(future, threadId);
}
...
...
解锁 unlockInnerAsync方法
- KEYS[1]是 LockKey. 获取锁时指定的键, 如 redissonClient.getLock(“LockKey”);
- KEYS[2]是 通过方法 String getChannelName()来获取, 类似 redisson_lock__channel:{LockKey}
- ARGV[1]是解锁消息, LockPubSub.UNLOCK_MESSAGE, 默认值为 0
- ARGV[2]是锁的生存时间, 如 30000L(单位为毫秒)
- ARGV[3]是请求者的唯一编号, 通过方法 String getLockName(long threadId)来获取, 类似 d2518228-aad9-4fd8-b814-f450541cab3c:1
# 判断当前线程是否没有锁
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]);
# 返回0, 结束此脚本
return 0;
else # 否者
# 如果递减后没有额外的锁, 删除锁键, 释放锁!
redis.call('del', KEYS[1]);
# 发送释放锁消息
redis.call('publish', KEYS[2], ARGV[1]);
# 返回1,结束此脚本
return 1;
end;
## 返回空值, 结束此脚本
return nil;
Redis分布式锁的非正常失效情况
- 系统时钟漂移, 改动系统时间会影响 Redis的过期时间就是锁的有效时间
- Redis在多节点模式时, 当其中某主节点加锁后宕机, 未来得及将锁同步到从节点
以上第二种情况, 可以用 Zookeeper实现分布式锁来防止锁失效情况, 因为 Zookeeper也支持主从结构, 它的主叫 leader(领导者), 从叫做 follower(跟随者), 与 Redis不同的是, 当加锁时, 将锁加到 leader后不会马上告知客户端成功信息, 而是先同步到 *所有的 follower的(半数以上)节点之后, 告知客户端.
-
Zookeeper集群架构是更多保证了 CAP中的(CP, 一致性和分区容错性), 而 Redis集群架构是更多保证了 CAP中的(AP, 可用性和分区容错性), 所以 Zookeeper牺牲了性能更多保证了一致
-
当然 Redis也可以通过类似的机制保证一致性, 如使用 Redisson的 getRedLock方法, 将多个锁合并成一个大锁, 并且统一的做加解锁. 不过这样做了已经牺牲了性能所以不如使用 Zookeeper
锁& 数据类型使用例子
- 锁例子: https://blog.csdn.net/qcl108/article/details/101039657
- RAtomicLong(分布式长整型原子类)
RAtomicLong atomicLong = redissonClient.getAtomicLong("MyAtomicLong");
System.out.println("value 1: " + atomicLong.get()); // value 1: 0
atomicLong.incrementAndGet();
System.out.println("value 2: " + atomicLong.get()); // value 2: 1
atomicLong.addAndGet(10L);
System.out.println("value 3: " + atomicLong.get()); // value 3: 11
System.out.println("value 4: " + atomicLong.getAndAdd(20L)); // value 4: 11
System.out.println("value 5: " + atomicLong.getAndIncrement()); // value 5: 31
System.out.println("value 6: " + atomicLong.getAndSet(30L)); // value 6: 32
System.out.println("value 7: " + atomicLong.getAndDelete()); // value 7: 30
System.out.println("value 8: " + atomicLong.get()); // value 8: 0
- RBitSet
- 4294967295L为最大数, 如超出此数值将会报错
- 它占用的内存大小, 不在于存了多少, 而在于最大的数值是多少
RBitSet bitset = redissonClient.getBitSet("log_20200719");
bitset.set(0L,true);
bitset.set(1L,true);
bitset.set(4L,true);
bitset.set(999L,true);
System.out.println("value 1: " + bitset.cardinality()); // value 1: 4
System.out.println("value 2: " + bitset.get(4L)); // value 2: true
System.out.println("value 3: " + bitset.get(5L)); // value 3: false
bitset.set(4L,false);
System.out.println("value 4: " + bitset.get(4L)); // value 4: false
System.out.println("value 5: " + bitset.cardinality()); // value 5: 3
bitset.set(8L,false);
System.out.println("value 6: " + bitset.cardinality()); // value 6: 3
//bitset.set(4294967295L,true); // 大约占512M左右内存空间, 4294967295L为最大数, 如超出此数值将会报错
bitset.set(1000000000L,true); // 大约占120M左右内存空间
System.out.println("value 7: " + bitset.cardinality()); // value 7: 4
bitset.clear();
System.out.println("value 8: " + bitset.cardinality()); // value 8: 0
- RHyperLogLog(基数统计)
- 每个键只需要花费12KB内存, 就可以计算接近2的64次方(2^64)个不同元素的基数, 且所需空间是固定的也很小的, 这和元素越多耗费内存就越多的集合形成鲜明对比. 但它只会按输入的元素来计算基数, 而不会储存输入元素本身, 所以不能像集合那样, 返回输入的各个元素
- 去重计数有误差, 误差是0.81%
RHyperLogLog<Long> log = redissonClient.getHyperLogLog("log_20200719");
log.add(0L);
log.add(2L);
log.add(3L);
log.add(3L);
System.out.println("count 1: " + log.count()); // count 1: 3
log.add(4294967296L);
log.add(999999999999999999L);
System.out.println("count 2: " + log.count()); // count 2: 5
log.delete();
System.out.println("count 3: " + log.count()); // count 3: 0
- RBloomFilter(布隆过滤器)
- 优点为它是二进制组成的数组, 内存占用少, 且插入和查询速度都非常快
- 缺点为, 随着数据量的增加, 误判率会增加, 且无法判断数据一定存在, 还有就是无法删除已插入的数据
RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("bloomFilter");
bloomFilter.tryInit(294967294L,0.03);
for (long i = 0; i < 1000L; i++) {
bloomFilter.add("全大爷 " + i);
}
System.out.println("全大爷 1: " + bloomFilter.contains("全大爷 " + 1)); // 全大爷 1: true
System.out.println("全大爷 1001: " + bloomFilter.contains("全大爷 " + 1001)); // 全大爷 1001: false
System.out.println("预计可插入数量: " + bloomFilter.getExpectedInsertions()); // 预计可插入数量: 294967294
System.out.println("容错率: " + bloomFilter.getFalseProbability()); // 容错率: 0.03
System.out.println("hash函数的个数: " + bloomFilter.getHashIterations()); // hash函数的个数: 5
System.out.println("已插入的对象的个数: " + bloomFilter.count()); // 已插入的对象的个数: 1000
如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!