1.Redisson可重入锁RedissonLock概述
(1)在pom.xml里引入依赖
<dependencies><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.8</version></dependency>
</dependencies>
(2)构建RedissonClient并使用Redisson
参考官网中文文档,连接上3主3从的Redis Cluster。
//https://github.com/redisson/redisson/wiki/目录
public class Application {public static void main(String[] args) throws Exception {//连接3主3从的Redis CLusterConfig config = new Config();config.useClusterServers().addNodeAddress("redis://192.168.1.110:7001").addNodeAddress("redis://192.168.1.110:7002").addNodeAddress("redis://192.168.1.110:7003").addNodeAddress("redis://192.168.1.111:7001").addNodeAddress("redis://192.168.1.111:7002").addNodeAddress("redis://192.168.1.111:7003");//创建RedissonClient实例RedissonClient redisson = Redisson.create(config);//获取可重入锁RLock lock = redisson.getLock("myLock");lock.lock();lock.unlock();RMap<String, Object> map = redisson.getMap("myMap");map.put("foo", "bar"); map = redisson.getMap("myMap");System.out.println(map.get("foo")); }
}
(3)Redisson可重入锁RedissonLock简单使用
Redisson可重入锁RLock实现了java.util.concurrent.locks.Lock接口,同时还提供了异步(Async)、响应式(Reactive)和RxJava2标准的接口。
RLock lock = redisson.getLock("myLock");
//最常见的使用方法
lock.lock();
如果设置锁的超时时间不合理,导致超时时间已到时锁还没能主动释放,但实际上锁却被Redis节点通过过期时间释放了,这会有问题。
为了避免这种情况,Redisson内部提供了一个用来监控锁的WatchDog。WatchDog的作用是在Redisson实例被关闭前,不断地延长锁的有效期。
WatchDog检查锁的默认超时时间是30秒,可通过Config.lockWatchdogTimeout来指定。
RLock的tryLock方法提供了leaseTime参数来指定加锁的超时时间,超过这个时间后锁便自动被释放。
//如果没有主动释放锁的话,10秒后将会自动释放锁
lock.lock(10, TimeUnit.SECONDS);//加锁等待最多是100秒;加锁成功后如果没有主动释放锁的话,锁会在10秒后自动释放
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {try {...} finally {lock.unlock();}
}
RLock完全符合Java的Lock规范,即只有拥有锁的进程才能解锁,其他进程解锁则会抛出IllegalMonitorStateException错误。如果需要其他进程也能解锁,那么可以使用分布式信号量Semaphore。
2.可重入锁源码之创建RedissonClient实例
使用Redisson.create()方法可以根据配置创建一个RedissonClient实例,因为Redisson类会实现RedissonClient接口,而创建RedissonClient实例的主要工作其实就是:
一.初始化与Redis的连接管理器ConnectionManager
二.初始化Redis的命令执行器CommandExecutor
(1)初始化与Redis的连接管理器ConnectionManager
Redis的配置类Config会被封装在连接管理器ConnectionManager中,后续可以通过连接管理器ConnectionManager获取Redis的配置类Config。
public class Application {public static void main(String[] args) throws Exception {Config config = new Config();config.useClusterServers().addNodeAddress("redis://192.168.1.110:7001");//创建RedissonClient实例RedissonClient redisson = Redisson.create(config);...}
}//创建RedissonClient实例的源码
public class Redisson implements RedissonClient {protected final Config config;//Redis配置类protected final ConnectionManager connectionManager;//Redis的连接管理器protected final CommandAsyncExecutor commandExecutor;//Redis的命令执行器...public static RedissonClient create(Config config) {return new Redisson(config);}protected Redisson(Config config) {this.config = config;Config configCopy = new Config(config);//根据Redis配置类Config实例创建和Redis的连接管理器connectionManager = ConfigSupport.createConnectionManager(configCopy);RedissonObjectBuilder objectBuilder = null;if (config.isReferenceEnabled()) {objectBuilder = new RedissonObjectBuilder(this);}//创建Redis的命令执行器commandExecutor = new CommandSyncService(connectionManager, objectBuilder);evictionScheduler = new EvictionScheduler(commandExecutor);writeBehindService = new WriteBehindService(commandExecutor);}...
}public class ConfigSupport {...//创建Redis的连接管理器public static ConnectionManager createConnectionManager(Config configCopy) {//生成UUIDUUID id = UUID.randomUUID();...if (configCopy.getClusterServersConfig() != null) {validate(configCopy.getClusterServersConfig());//返回ClusterConnectionManager实例return new ClusterConnectionManager(configCopy.getClusterServersConfig(), configCopy, id);}...}...
}public class ClusterConnectionManager extends MasterSlaveConnectionManager {public ClusterConnectionManager(ClusterServersConfig cfg, Config config, UUID id) {super(config, id);...this.natMapper = cfg.getNatMapper();//将Redis的配置类Config封装在ConnectionManager中this.config = create(cfg);initTimer(this.config);Throwable lastException = null;List<String> failedMasters = new ArrayList<String>();for (String address : cfg.getNodeAddresses()) {RedisURI addr = new RedisURI(address);//异步连接Redis节点CompletionStage<RedisConnection> connectionFuture = connectToNode(cfg, addr, addr.getHost());...//通过connectionFuture阻塞获取建立好的连接RedisConnection connection = connectionFuture.toCompletableFuture().join();...List<ClusterNodeInfo> nodes = connection.sync(clusterNodesCommand);...CompletableFuture<Collection<ClusterPartition>> partitionsFuture = parsePartitions(nodes);Collection<ClusterPartition> partitions = partitionsFuture.join();List<CompletableFuture<Void>> masterFutures = new ArrayList<>();for (ClusterPartition partition : partitions) {if (partition.isMasterFail()) {failedMasters.add(partition.getMasterAddress().toString());continue;}if (partition.getMasterAddress() == null) {throw new IllegalStateException("Master node: " + partition.getNodeId() + " doesn't have address.");}CompletableFuture<Void> masterFuture = addMasterEntry(partition, cfg);masterFutures.add(masterFuture);}CompletableFuture<Void> masterFuture = CompletableFuture.allOf(masterFutures.toArray(new CompletableFuture[0]));masterFuture.join();...}...}...
}public class MasterSlaveConnectionManager implements ConnectionManager {protected final String id;//初始化时为UUIDprivate final Map<RedisURI, RedisConnection> nodeConnections = new ConcurrentHashMap<>();...protected MasterSlaveConnectionManager(Config cfg, UUID id) {this.id = id.toString();//传入的是UUIDthis.cfg = cfg;...}protected final CompletionStage<RedisConnection> connectToNode(NodeType type, BaseConfig<?> cfg, RedisURI addr, String sslHostname) {RedisConnection conn = nodeConnections.get(addr);if (conn != null) {if (!conn.isActive()) {closeNodeConnection(conn);} else {return CompletableFuture.completedFuture(conn);}}//创建Redis客户端连接实例RedisClient client = createClient(type, addr, cfg.getConnectTimeout(), cfg.getTimeout(), sslHostname);//向Redis服务端发起异步连接请求,这个future会层层往外返回CompletionStage<RedisConnection> future = client.connectAsync();return future.thenCompose(connection -> {if (connection.isActive()) {if (!addr.isIP()) {RedisURI address = new RedisURI(addr.getScheme() + "://" + connection.getRedisClient().getAddr().getAddress().getHostAddress() + ":" + connection.getRedisClient().getAddr().getPort());nodeConnections.put(address, connection);}nodeConnections.put(addr, connection);return CompletableFuture.completedFuture(connection);} else {connection.closeAsync();CompletableFuture<RedisConnection> f = new CompletableFuture<>();f.completeExceptionally(new RedisException("Connection to " + connection.getRedisClient().getAddr() + " is not active!"));return f;}});}//创建Redis客户端连接实例@Overridepublic RedisClient createClient(NodeType type, RedisURI address, int timeout, int commandTimeout, String sslHostname) {RedisClientConfig redisConfig = createRedisConfig(type, address, timeout, commandTimeout, sslHostname);return RedisClient.create(redisConfig);}...
}//Redisson主要使用Netty去和Redis服务端建立连接
public final class RedisClient {private final Bootstrap bootstrap;private final Bootstrap pubSubBootstrap;...public static RedisClient create(RedisClientConfig config) {return new RedisClient(config);}private RedisClient(RedisClientConfig config) {...bootstrap = createBootstrap(copy, Type.PLAIN);pubSubBootstrap = createBootstrap(copy, Type.PUBSUB);this.commandTimeout = copy.getCommandTimeout();}private Bootstrap createBootstrap(RedisClientConfig config, Type type) {Bootstrap bootstrap = new Bootstrap().resolver(config.getResolverGroup()).channel(config.getSocketChannelClass()).group(config.getGroup());bootstrap.handler(new RedisChannelInitializer(bootstrap, config, this, channels, type));bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectTimeout());bootstrap.option(ChannelOption.SO_KEEPALIVE, config.isKeepAlive());bootstrap.option(ChannelOption.TCP_NODELAY, config.isTcpNoDelay());config.getNettyHook().afterBoostrapInitialization(bootstrap);return bootstrap;}//向Redis服务端发起异步连接请求public RFuture<RedisConnection> connectAsync() {CompletableFuture<InetSocketAddress> addrFuture = resolveAddr();CompletableFuture<RedisConnection> f = addrFuture.thenCompose(res -> {CompletableFuture<RedisConnection> r = new CompletableFuture<>();//Netty的Bootstrap发起连接ChannelFuture channelFuture = bootstrap.connect(res);channelFuture.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(final ChannelFuture future) throws Exception {if (bootstrap.config().group().isShuttingDown()) {IllegalStateException cause = new IllegalStateException("RedisClient is shutdown");r.completeExceptionally(cause);return;}if (future.isSuccess()) {RedisConnection c = RedisConnection.getFrom(future.channel());c.getConnectionPromise().whenComplete((res, e) -> {bootstrap.config().group().execute(new Runnable() {@Overridepublic void run() {if (e == null) {if (!r.complete(c)) {c.closeAsync();}} else {r.completeExceptionally(e);c.closeAsync();}}});});} else {bootstrap.config().group().execute(new Runnable() {public void run() {r.completeExceptionally(future.cause());}});}}});return r;});return new CompletableFutureWrapper<>(f);}...
}
(2)初始化Redis的命令执行器CommandExecutor
首先,CommandSyncService继承自CommandAsyncService类。
而CommandAsyncService类实现了CommandExecutor接口。
然后,ConnectionManager连接管理器会封装在命令执行器CommandExecutor中。
所以,通过CommandExecutor命令执行器可以获取连接管理器ConnectionManager。
//Redis命令的同步执行器CommandSyncService
public class CommandSyncService extends CommandAsyncService implements CommandExecutor {//初始化CommandExecutorpublic CommandSyncService(ConnectionManager connectionManager, RedissonObjectBuilder objectBuilder) {super(connectionManager, objectBuilder, RedissonObjectBuilder.ReferenceType.DEFAULT);}public <T, R> R read(String key, RedisCommand<T> command, Object... params) {return read(key, connectionManager.getCodec(), command, params);}public <T, R> R read(String key, Codec codec, RedisCommand<T> command, Object... params) {RFuture<R> res = readAsync(key, codec, command, params);return get(res);}public <T, R> R evalRead(String key, RedisCommand<T> evalCommandType, String script, List<Object> keys, Object... params) {return evalRead(key, connectionManager.getCodec(), evalCommandType, script, keys, params);}public <T, R> R evalRead(String key, Codec codec, RedisCommand<T> evalCommandType, String script, List<Object> keys, Object... params) {RFuture<R> res = evalReadAsync(key, codec, evalCommandType, script, keys, params);return get(res);}public <T, R> R evalWrite(String key, RedisCommand<T> evalCommandType, String script, List<Object> keys, Object... params) {return evalWrite(key, connectionManager.getCodec(), evalCommandType, script, keys, params);}public <T, R> R evalWrite(String key, Codec codec, RedisCommand<T> evalCommandType, String script, List<Object> keys, Object... params) {RFuture<R> res = evalWriteAsync(key, codec, evalCommandType, script, keys, params);return get(res);}
}//Redis命令的异步执行器CommandAsyncService
public class CommandAsyncService implements CommandAsyncExecutor {//Redis连接管理器final ConnectionManager connectionManager;final RedissonObjectBuilder objectBuilder;final RedissonObjectBuilder.ReferenceType referenceType;public CommandAsyncService(ConnectionManager connectionManager, RedissonObjectBuilder objectBuilder, RedissonObjectBuilder.ReferenceType referenceType) {this.connectionManager = connectionManager;this.objectBuilder = objectBuilder;this.referenceType = referenceType;}@Overridepublic <V> V getNow(CompletableFuture<V> future) {try {return future.getNow(null);} catch (Exception e) {return null;}}@Overridepublic <T, R> R read(String key, Codec codec, RedisCommand<T> command, Object... params) {RFuture<R> res = readAsync(key, codec, command, params);return get(res);}@Overridepublic <T, R> RFuture<R> readAsync(String key, Codec codec, RedisCommand<T> command, Object... params) {NodeSource source = getNodeSource(key);return async(true, source, codec, command, params, false, false);}private NodeSource getNodeSource(String key) {int slot = connectionManager.calcSlot(key);return new NodeSource(slot);}public <V, R> RFuture<R> async(boolean readOnlyMode, NodeSource source, Codec codec, RedisCommand<V> command, Object[] params, boolean ignoreRedirect, boolean noRetry) {CompletableFuture<R> mainPromise = createPromise();RedisExecutor<V, R> executor = new RedisExecutor<>(readOnlyMode, source, codec, command, params, mainPromise, ignoreRedirect, connectionManager, objectBuilder, referenceType, noRetry);executor.execute();return new CompletableFutureWrapper<>(mainPromise);}@Overridepublic <V> V get(RFuture<V> future) {if (Thread.currentThread().getName().startsWith("redisson-netty")) {throw new IllegalStateException("Sync methods can't be invoked from async/rx/reactive listeners");}try {return future.toCompletableFuture().get();} catch (InterruptedException e) {future.cancel(true);Thread.currentThread().interrupt();throw new RedisException(e);} catch (ExecutionException e) {throw convertException(e);}}...
}
3.可重入锁源码之lua脚本加锁逻辑
(1)通过Redisson.getLock()方法获取一个RedissonLock实例
在Redisson.getLock()方法中,会传入命令执行器CommandExecutor来创建一个RedissonLock实例,而命令执行器CommandExecutor是在执行Redisson.create()方法时初始化好的,所以命令执行器CommandExecutor会被封装在RedissonLock实例中。
因此,通过RedissonLock实例可以获取一个命令执行器CommandExecutor,通过命令执行器CommandExecutor可获取连接管理器ConnectionManager,通过连接管理器ConnectionManager可获取Redis的配置信息类Config,通过Redis的配置信息类Config可以获取各种配置信息。
RedissonLock类继承自实现了RLock接口的RedissonBaseLock类。在RedissonLock的构造方法里面,有个internalLockLeaseTime变量,这个internalLockLeaseTime变量与WatchDog看门狗有关系。interlnalLockLeaseTime的默认值是30000毫秒,即30秒;
public class Application {public static void main(String[] args) throws Exception {Config config = new Config();config.useClusterServers().addNodeAddress("redis://192.168.1.110:7001");//创建RedissonClient实例RedissonClient redisson = Redisson.create(config);//获取可重入锁RLock lock = redisson.getLock("myLock");lock.lock();...}
}//创建Redisson实例
public class Redisson implements RedissonClient {protected final Config config;//Redis配置类protected final ConnectionManager connectionManager;//Redis的连接管理器protected final CommandAsyncExecutor commandExecutor;//Redis的命令执行器...public static RedissonClient create(Config config) {return new Redisson(config);}protected Redisson(Config config) {...//根据Redis配置类Config实例创建和Redis的连接管理器connectionManager = ConfigSupport.createConnectionManager(configCopy);//创建Redis的命令执行器commandExecutor = new CommandSyncService(connectionManager, objectBuilder);...}...@Overridepublic RLock getLock(String name) {return new RedissonLock(commandExecutor, name);}...
}//创建RedissonLock实例
//通过RedissonLock实例可以获取一个命令执行器CommandExecutor;
public class RedissonLock extends RedissonBaseLock {protected long internalLockLeaseTime;protected final LockPubSub pubSub;final CommandAsyncExecutor commandExecutor;public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {super(commandExecutor, name);this.commandExecutor = commandExecutor;//与WatchDog有关的internalLockLeaseTime//通过命令执行器CommandExecutor可以获取连接管理器ConnectionManager//通过连接管理器ConnectionManager可以获取Redis的配置信息类Config//通过Redis的配置信息类Config可以获取lockWatchdogTimeout超时时间this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();}...
}//创建Redis的命令执行器
//通过命令执行器CommandExecutor可以获取连接管理器ConnectionManager
public class CommandAsyncService implements CommandAsyncExecutor {final ConnectionManager connectionManager;...public CommandAsyncService(ConnectionManager connectionManager, RedissonObjectBuilder objectBuilder, RedissonObjectBuilder.ReferenceType referenceType) {this.connectionManager = connectionManager;this.objectBuilder = objectBuilder;this.referenceType = referenceType;}@Overridepublic ConnectionManager getConnectionManager() {return connectionManager;}...
}//创建Redis的连接管理器
//通过连接管理器ConnectionManager可以获取Redis的配置信息类Config
public class ClusterConnectionManager extends MasterSlaveConnectionManager {...public ClusterConnectionManager(ClusterServersConfig cfg, Config config, UUID id) {super(config, id);...}...
}//创建Redis的连接管理器
//通过连接管理器ConnectionManager可以获取Redis的配置信息类Config
public class MasterSlaveConnectionManager implements ConnectionManager {private final Config cfg;protected final String id;//初始化时为UUID...protected MasterSlaveConnectionManager(Config cfg, UUID id) {this.id = id.toString();//传入的是UUIDthis.cfg = cfg;...}@Overridepublic Config getCfg() {return cfg;}...
}//配置信息类Config中的lockWatchdogTimeout变量初始化为30秒,该变量与WatchDog有关
public class Config {private long lockWatchdogTimeout = 30 * 1000;...//This parameter is only used if lock has been acquired without leaseTimeout parameter definition. //Lock expires after "lockWatchdogTimeout" if watchdog didn't extend it to next "lockWatchdogTimeout" time interval.//This prevents against infinity locked locks due to Redisson client crush or any other reason when lock can't be released in proper way.//Default is 30000 millisecondspublic Config setLockWatchdogTimeout(long lockWatchdogTimeout) {this.lockWatchdogTimeout = lockWatchdogTimeout;return this;}public long getLockWatchdogTimeout() {return lockWatchdogTimeout;}
}
默认情况下,调用RedissonLock.lock()方法加锁时,传入的leaseTime为-1。此时锁的超时时间会设为lockWatchdogTimeout默认的30秒,从而避免出现死锁的情况。
public class RedissonLock extends RedissonBaseLock {...//加锁@Overridepublic void lock() {try {lock(-1, null, false);} catch (InterruptedException e) {throw new IllegalStateException();}}private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {long threadId = Thread.currentThread().getId();Long ttl = tryAcquire(-1, leaseTime, unit, threadId);...}//解锁@Overridepublic void unlock() {try {get(unlockAsync(Thread.currentThread().getId()));} catch (RedisException e) {if (e.getCause() instanceof IllegalMonitorStateException) {throw (IllegalMonitorStateException) e.getCause();} else {throw e;}}}...
}
(2)加锁时的执行流程
首先会调用RedissonLock的tryAcquire()方法处理异步RFuture相关,然后调用RedissonLock的tryAcquireAsync()方法对执行脚本的结果进行处理,接着调用RedissonLock.tryLockInnerAsync方法执行加锁的lua脚本。
public class RedissonLock extends RedissonBaseLock {protected long internalLockLeaseTime;protected final LockPubSub pubSub;final CommandAsyncExecutor commandExecutor;public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {super(commandExecutor, name);this.commandExecutor = commandExecutor;//与WatchDog有关的internalLockLeaseTime//通过命令执行器CommandExecutor可以获取连接管理器ConnectionManager//通过连接管理器ConnectionManager可以获取Redis的配置信息类Config//通过Redis的配置信息类Config可以获取lockWatchdogTimeout超时时间this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();}...//加锁@Overridepublic void lock() {try {lock(-1, null, false);} catch (InterruptedException e) {throw new IllegalStateException();}}private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {//线程ID,用来生成设置Hash的值long threadId = Thread.currentThread().getId();//尝试加锁,此时执行RedissonLock.lock()方法默认传入的leaseTime=-1Long ttl = tryAcquire(-1, leaseTime, unit, threadId);//ttl为null说明加锁成功if (ttl == null) {return;}//加锁失败时的处理CompletableFuture<RedissonLockEntry> future = subscribe(threadId);if (interruptibly) {commandExecutor.syncSubscriptionInterrupted(future);} else {commandExecutor.syncSubscription(future);}try {while (true) {ttl = tryAcquire(-1, leaseTime, unit, threadId);// lock acquiredif (ttl == null) {break;}// waiting for messageif (ttl >= 0) {try {commandExecutor.getNow(future).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} catch (InterruptedException e) {if (interruptibly) {throw e;}commandExecutor.getNow(future).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);}} else {if (interruptibly) {commandExecutor.getNow(future).getLatch().acquire();} else {commandExecutor.getNow(future).getLatch().acquireUninterruptibly();}}}} finally {unsubscribe(commandExecutor.getNow(future), threadId);}}...private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {//默认下waitTime和leaseTime都是-1,下面调用的get方法是来自于RedissonObject的get()方法//可以理解为异步转同步:将异步的tryAcquireAsync通过get转同步return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));}private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFuture<Long> ttlRemainingFuture;if (leaseTime != -1) {ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {//默认情况下,由于leaseTime=-1,所以会使用初始化RedissonLock实例时的internalLockLeaseTime//internalLockLeaseTime的默认值就是lockWatchdogTimeout的默认值,30秒ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {//加锁返回的ttlRemaining为null表示加锁成功if (ttlRemaining == null) {if (leaseTime != -1) {internalLockLeaseTime = unit.toMillis(leaseTime);} else {scheduleExpirationRenewal(threadId);}}return ttlRemaining;});return new CompletableFutureWrapper<>(f);}//默认情况下,外部传入的leaseTime=-1时,会取lockWatchdogTimeout的默认值=30秒<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('hincrby', 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(getRawName()),//锁的名字:KEYS[1]unit.toMillis(leaseTime),//过期时间:ARGV[1],默认时为30秒getLockName(threadId)//ARGV[2],值为UUID + 线程ID);}...
}public abstract class RedissonBaseLock extends RedissonExpirable implements RLock {final String id;final String entryName;final CommandAsyncExecutor commandExecutor;public RedissonBaseLock(CommandAsyncExecutor commandExecutor, String name) {super(commandExecutor, name);this.commandExecutor = commandExecutor;this.id = commandExecutor.getConnectionManager().getId();this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();this.entryName = id + ":" + name;}protected String getLockName(long threadId) {return id + ":" + threadId;}...
}abstract class RedissonExpirable extends RedissonObject implements RExpirable {...RedissonExpirable(CommandAsyncExecutor connectionManager, String name) {super(connectionManager, name);}...
}public abstract class RedissonObject implements RObject {protected final CommandAsyncExecutor commandExecutor;protected String name;protected final Codec codec;public RedissonObject(Codec codec, CommandAsyncExecutor commandExecutor, String name) {this.codec = codec;this.commandExecutor = commandExecutor;if (name == null) {throw new NullPointerException("name can't be null");}setName(name);}...protected final <V> V get(RFuture<V> future) {//下面会调用CommandAsyncService.get()方法return commandExecutor.get(future);}...
}public class CommandAsyncService implements CommandAsyncExecutor {...@Overridepublic <V> V get(RFuture<V> future) {if (Thread.currentThread().getName().startsWith("redisson-netty")) {throw new IllegalStateException("Sync methods can't be invoked from async/rx/reactive listeners");}try {return future.toCompletableFuture().get();} catch (InterruptedException e) {future.cancel(true);Thread.currentThread().interrupt();throw new RedisException(e);} catch (ExecutionException e) {throw convertException(e);}}...
}
(3)加锁时执行的lua脚本
public class RedissonLock extends RedissonBaseLock {//默认情况下,外部传入的leaseTime=-1时,会取lockWatchdogTimeout的默认值=30秒<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('hincrby', 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(getRawName()),//锁的名字:KEYS[1],比如"myLock"unit.toMillis(leaseTime),//过期时间:ARGV[1],默认时为30秒getLockName(threadId)//ARGV[2],值为UUID + 线程ID);}...
}
首先执行Redis的exists命令,判断key为锁名的Hash值是否不存在,也就是判断key为锁名myLock的Hash值是否存在。
一.如果key为锁名的Hash值不存在,那么就进行如下加锁处理
首先通过Redis的hset命令设置一个key为锁名的Hash值。该Hash值的key为锁名,value是一个映射。也就是在value值中会有一个field为UUID + 线程ID,value为1的映射。比如:hset myLock UUID:ThreadID 1,lua脚本中的ARGV[2]就是由UUID + 线程ID组成的唯一值。
然后通过Redis的pexpire命令设置key为锁名的Hash值的过期时间,也就是设置key为锁名的Hash值的过期时间为30秒。比如:pexpire myLock 30000。所以默认情况下,myLock这个锁在30秒后就会自动过期。
二.如果key为锁名的Hash值存在,那么就执行如下判断处理
首先通过Redis的hexists命令判断在key为锁名的Hash值里,field为UUID + 线程ID的映射是否已经存在。
如果在key为锁名的Hash值里,field为UUID + 线程ID的映射存在,那么就通过Redis的hincrby命令,对field为UUID + 线程ID的value值进行递增1。比如:hincrby myLock UUID:ThreadID 1。也就是在key为myLock的Hash值里,把field为UUID:ThreadID的value值从1累加到2,发生这种情况的时候往往就是当前线程对锁进行了重入。接着执行:pexpire myLock 30000,再次将myLock的有效期设置为30秒。
如果在key为锁名的Hash值里,field为UUID + 线程ID的映射不存在,发生这种情况的时候往往就是其他线程获取不到这把锁而产生互斥。那么就通过Redis的pttl命令,返回key为锁名的Hash值的剩余存活时间,因为不同线程的ARGV[2]是不一样的,ARGV[2] = UUID + 线程ID。
(4)执行加锁lua脚本的命令执行器逻辑
在RedissonLock的tryLockInnerAsync()方法中,会通过RedissonBaseLock的evalWriteAsync()方法执行lua脚本,即通过CommandAsyncService的evalWriteAsync()方法执行lua脚本。
在CommandAsyncService的evalWriteAsync()方法中,首先会执行CommandAsyncService的getNodeSource()方法获取对应的节点。然后执行CommandAsyncService的evalAsync()方法来执行lua脚本。
在CommandAsyncService的getNodeSource()方法中,会根据key进行CRC16运算,然后再对16384取模,计算出key的slot值。然后根据这个slot值创建一个NodeSource实例进行返回。
在CommandAsyncService的evalAsync()方法中,会将获得的NodeSource实例封装到Redis执行器RedisExecutor里。然后执行RedisExecutor,实现将脚本请求发送给对应的Redis节点处理。
public abstract class RedissonBaseLock extends RedissonExpirable implements RLock {//从外部传入的:在创建实现了RedissonClient的Redisson实例时,初始化的命令执行器CommandExecutorfinal CommandAsyncExecutor commandExecutor;public RedissonBaseLock(CommandAsyncExecutor commandExecutor, String name) {super(commandExecutor, name);this.commandExecutor = commandExecutor;this.id = commandExecutor.getConnectionManager().getId();this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();this.entryName = id + ":" + name;}...protected <T> RFuture<T> evalWriteAsync(String key, Codec codec, RedisCommand<T> evalCommandType, String script, List<Object> keys, Object... params) {//获取可用的节点,并继续封装一个命令执行器CommandBatchServiceMasterSlaveEntry entry = commandExecutor.getConnectionManager().getEntry(getRawName());int availableSlaves = entry.getAvailableSlaves();CommandBatchService executorService = createCommandBatchService(availableSlaves);//通过CommandAsyncService.evalWriteAsync方法执行lua脚本RFuture<T> result = executorService.evalWriteAsync(key, codec, evalCommandType, script, keys, params);if (commandExecutor instanceof CommandBatchService) {return result;}//异步执行然后获取结果RFuture<BatchResult<?>> future = executorService.executeAsync();CompletionStage<T> f = future.handle((res, ex) -> {if (ex == null && res.getSyncedSlaves() != availableSlaves) {throw new CompletionException(new IllegalStateException("Only " + res.getSyncedSlaves() + " of " + availableSlaves + " slaves were synced"));}return result.getNow();});return new CompletableFutureWrapper<>(f);}private CommandBatchService createCommandBatchService(int availableSlaves) {if (commandExecutor instanceof CommandBatchService) {return (CommandBatchService) commandExecutor;}BatchOptions options = BatchOptions.defaults().syncSlaves(availableSlaves, 1, TimeUnit.SECONDS);return new CommandBatchService(commandExecutor, options);}...
}public class CommandBatchService extends CommandAsyncService {...public CommandBatchService(CommandAsyncExecutor executor, BatchOptions options) {this(executor.getConnectionManager(), options, executor.getObjectBuilder(), RedissonObjectBuilder.ReferenceType.DEFAULT);}private CommandBatchService(ConnectionManager connectionManager, BatchOptions options, RedissonObjectBuilder objectBuilder, RedissonObjectBuilder.ReferenceType referenceType) {super(connectionManager, objectBuilder, referenceType);this.options = options;}...
}public class CommandAsyncService implements CommandAsyncExecutor {final ConnectionManager connectionManager;final RedissonObjectBuilder objectBuilder;final RedissonObjectBuilder.ReferenceType referenceType;public CommandAsyncService(ConnectionManager connectionManager, RedissonObjectBuilder objectBuilder, RedissonObjectBuilder.ReferenceType referenceType) {this.connectionManager = connectionManager;this.objectBuilder = objectBuilder;this.referenceType = referenceType;}...@Overridepublic <T, R> RFuture<R> evalWriteAsync(String key, Codec codec, RedisCommand<T> evalCommandType, String script, List<Object> keys, Object... params) {//获取key对应的节点NodeSource source = getNodeSource(key);//让对应的节点执行lua脚本请求return evalAsync(source, false, codec, evalCommandType, script, keys, false, params);}//获取key对应的Redis Cluster节点private NodeSource getNodeSource(String key) {//先计算key对应的slot值int slot = connectionManager.calcSlot(key);//返回节点实例return new NodeSource(slot);}//执行lua脚本private <T, R> RFuture<R> evalAsync(NodeSource nodeSource, boolean readOnlyMode, Codec codec, RedisCommand<T> evalCommandType, String script, List<Object> keys, boolean noRetry, Object... params) {if (isEvalCacheActive() && evalCommandType.getName().equals("EVAL")) {CompletableFuture<R> mainPromise = new CompletableFuture<>();Object[] pps = copy(params);CompletableFuture<R> promise = new CompletableFuture<>();String sha1 = calcSHA(script);RedisCommand cmd = new RedisCommand(evalCommandType, "EVALSHA");List<Object> args = new ArrayList<Object>(2 + keys.size() + params.length);args.add(sha1);args.add(keys.size());args.addAll(keys);args.addAll(Arrays.asList(params));//将根据key进行CRC16运算然后对16384取模获取到的NodeSource实例,封装到Redis执行器RedisExecutor中RedisExecutor<T, R> executor = new RedisExecutor<>(readOnlyMode, nodeSource, codec, cmd, args.toArray(), promise, false, connectionManager, objectBuilder, referenceType, noRetry);//通过执行Redis执行器RedisExecutor,来实现将lua脚本请求发送给对应的Redis节点进行处理executor.execute();...}...}...
}public class ClusterConnectionManager extends MasterSlaveConnectionManager {public static final int MAX_SLOT = 16384;//Redis Cluster默认有16384个slot...//对key进行CRC16运算,然后再对16384取模@Overridepublic int calcSlot(String key) {if (key == null) {return 0;}int start = key.indexOf('{');if (start != -1) {int end = key.indexOf('}');if (end != -1 && start + 1 < end) {key = key.substring(start + 1, end);}}int result = CRC16.crc16(key.getBytes()) % MAX_SLOT;return result;}...
}
(5)如何根据slot值获取对应的节点
因为最后会执行封装了NodeSource实例的RedisExecutor的excute()方法,而NodeSource实例中又会封装了锁名key对应的slot值,所以RedisExecutor的excute()方法可以通过getConnection()方法获取对应节点的连接。
其中RedisExecutor的getConnection()方法会调用到MasterSlaveConnectionManager的connectionWriteOp()方法,该方法又会通过调用ConnectionManager的getEntry()方法根据slot值获取节点,也就是由ClusterConnectionManager的getEntry()方法去获取Redis的主节点。
其实在初始化连接管理器ClusterConnectionManager时,就已经根据配置初始化好哪些slot映射到那个Redis主节点了。
public class RedisExecutor<V, R> {NodeSource source;...public void execute() {...//异步获取建立好的Redis连接CompletableFuture<RedisConnection> connectionFuture = getConnection().toCompletableFuture();...}protected CompletableFuture<RedisConnection> getConnection() {...connectionFuture = connectionManager.connectionWriteOp(source, command);return connectionFuture;}...
}public class MasterSlaveConnectionManager implements ConnectionManager {...@Overridepublic CompletableFuture<RedisConnection> connectionWriteOp(NodeSource source, RedisCommand<?> command) {MasterSlaveEntry entry = getEntry(source);...}private MasterSlaveEntry getEntry(NodeSource source) {if (source.getRedirect() != null) {return getEntry(source.getAddr());}MasterSlaveEntry entry = source.getEntry();if (source.getRedisClient() != null) {entry = getEntry(source.getRedisClient());}if (entry == null && source.getSlot() != null) {//根据slot获取Redis的主节点entry = getEntry(source.getSlot());}return entry;}...
}public class ClusterConnectionManager extends MasterSlaveConnectionManager {//slot和Redis主节点的原子映射数组private final AtomicReferenceArray<MasterSlaveEntry> slot2entry = new AtomicReferenceArray<>(MAX_SLOT);//Redis客户端连接和Redis主节点的映射关系private final Map<RedisClient, MasterSlaveEntry> client2entry = new ConcurrentHashMap<>();...@Overridepublic MasterSlaveEntry getEntry(int slot) {//根据slot获取Redis的主节点return slot2entry.get(slot);}...//初始化连接管理器ClusterConnectionManager时//就已经根据配置初始化好那些slot映射到那个Redis主节点了public ClusterConnectionManager(ClusterServersConfig cfg, Config config, UUID id) {...for (String address : cfg.getNodeAddresses()) {...CompletableFuture<Collection<ClusterPartition>> partitionsFuture = parsePartitions(nodes);Collection<ClusterPartition> partitions = partitionsFuture.join();List<CompletableFuture<Void>> masterFutures = new ArrayList<>();for (ClusterPartition partition : partitions) {...CompletableFuture<Void> masterFuture = addMasterEntry(partition, cfg);masterFutures.add(masterFuture);}...}...}private CompletableFuture<Void> addMasterEntry(ClusterPartition partition, ClusterServersConfig cfg) {...CompletionStage<RedisConnection> connectionFuture = connectToNode(cfg, partition.getMasterAddress(), configEndpointHostName);connectionFuture.whenComplete((connection, ex1) -> {//成功连接时的处理if (ex1 != null) {log.error("Can't connect to master: {} with slot ranges: {}", partition.getMasterAddress(), partition.getSlotRanges());result.completeExceptionally(ex1);return;}MasterSlaveServersConfig config = create(cfg);config.setMasterAddress(partition.getMasterAddress().toString());//创建Redis的主节点MasterSlaveEntry entry;if (config.checkSkipSlavesInit()) {entry = new SingleEntry(ClusterConnectionManager.this, config);} else {Set<String> slaveAddresses = partition.getSlaveAddresses().stream().map(r -> r.toString()).collect(Collectors.toSet());config.setSlaveAddresses(slaveAddresses);entry = new MasterSlaveEntry(ClusterConnectionManager.this, config);}CompletableFuture<RedisClient> f = entry.setupMasterEntry(new RedisURI(config.getMasterAddress()), configEndpointHostName);f.whenComplete((masterClient, ex3) -> {if (ex3 != null) {log.error("Can't add master: " + partition.getMasterAddress() + " for slot ranges: " + partition.getSlotRanges(), ex3);result.completeExceptionally(ex3);return;}//为创建的Redis的主节点添加slot值for (Integer slot : partition.getSlots()) {addEntry(slot, entry);lastPartitions.put(slot, partition);}...});});...}//添加slot到对应节点的映射关系private void addEntry(Integer slot, MasterSlaveEntry entry) {MasterSlaveEntry oldEntry = slot2entry.getAndSet(slot, entry);if (oldEntry != entry) {entry.incReference();shutdownEntry(oldEntry);}client2entry.put(entry.getClient(), entry);}...
}
4.可重入锁源码之WatchDog维持加锁逻辑
如果某个客户端上锁后,过了几分钟都没释放掉这个锁,而锁对应的key一开始的过期时间其实只设置了30秒而已,那么在这种场景下这个锁就不能在上锁的30秒后被自动释放。
RedissonLock中有一个WatchDog机制:
当客户端对myLock这个key成功加锁后,就会创建一个定时任务,这个定时任务会默认10秒后更新myLock这个key的过期时间为30秒。当然,前提是客户端一直持有myLock这个锁,还没有对锁进行释放。只要客户端一直持有myLock这个锁没有对其进行释放,那么就会不断创建一个定时任务,10秒后更新myLock这个key的过期时间。
下面详细介绍加锁成功后的定时任务,是如何每隔10秒去更新锁的过期时间的。
(1)异步执行完加锁的lua脚本会触发执行thenApply()里的回调
异步执行完RedissonLock.tryLockInnerAsync()方法里的加锁lua脚本内容后,会触发执行ttlRemainingFuture的thenApply()方法里的回调。其中加锁lua脚本会返回Long型的ttlRemaining变量,ttlRemainingFuture的thenApply()方法里的回调入参便是这个变量。
具体会先在RedissonLock的tryAcquireAsync()方法中,定义一个RFuture类型的名为ttlRemainingFuture的变量。这个变量封装了当前这个锁对应的key的剩余存活时间,单位毫秒,这个剩余存活时间是在执行完加锁lua脚本时返回的。
然后通过RFuture的thenApply()方法给ttlRemainingFuture添加一个回调。这样当lua脚本执行完成后,就会触发ttlRemainingFuture的thenApply()方法里的回调,而回调里的入参ttlRemaining就是执行加锁lua脚本的返回值。
public class RedissonLock extends RedissonBaseLock {protected long internalLockLeaseTime;protected final LockPubSub pubSub;final CommandAsyncExecutor commandExecutor;public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {super(commandExecutor, name);this.commandExecutor = commandExecutor;//与WatchDog有关的internalLockLeaseTime//通过命令执行器CommandExecutor可以获取连接管理器ConnectionManager//通过连接管理器ConnectionManager可以获取Redis的配置信息类Config//通过Redis的配置信息类Config可以获取lockWatchdogTimeout超时时间this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();}//加锁@Overridepublic void lock() {try {lock(-1, null, false);} catch (InterruptedException e) {throw new IllegalStateException();}}...private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {//线程ID,用来生成设置Hash的值long threadId = Thread.currentThread().getId();//尝试加锁,此时执行RedissonLock.lock()方法默认传入的leaseTime=-1Long ttl = tryAcquire(-1, leaseTime, unit, threadId);//ttl为null说明加锁成功if (ttl == null) {return;} //加锁失败时的处理...}...private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {//默认下waitTime和leaseTime都是-1,下面调用的get方法是来自于RedissonObject的get()方法//可以理解为异步转同步:将异步的tryAcquireAsync通过get转同步return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));}private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFuture<Long> ttlRemainingFuture;if (leaseTime != -1) {ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {//默认情况下,由于leaseTime=-1,所以会使用初始化RedissonLock实例时的internalLockLeaseTime//internalLockLeaseTime的默认值就是lockWatchdogTimeout的默认值,30秒ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {//异步执行完tryLockInnerAsync()里加锁lua脚本内容后//就会触发执行ttlRemainingFuture.thenApply()里的回调//加锁返回的ttlRemaining为null表示加锁成功if (ttlRemaining == null) {if (leaseTime != -1) {internalLockLeaseTime = unit.toMillis(leaseTime);} else {//传入加锁成功的线程ID,启动一个定时任务scheduleExpirationRenewal(threadId);}}return ttlRemaining;});return new CompletableFutureWrapper<>(f);}//默认情况下,外部传入的leaseTime=-1时,会取lockWatchdogTimeout的默认值=30秒<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('hincrby', 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(getRawName()),//锁的名字:KEYS[1]unit.toMillis(leaseTime),//过期时间:ARGV[1],默认时为30秒getLockName(threadId)//ARGV[2],值为UUID + 线程ID);}...
}
(2)根据执行加锁lua脚本的返回值来决定是否创建定时调度任务
当执行加锁lua脚本的返回值ttlRemaining为null时,则表明锁获取成功。
如果锁获取成功,且指定锁的过期时间,即leaseTime不是默认的-1,那么此时并不会创建定时任务。如果锁获取成功,且没指定锁的过期时间,即leaseTime是默认的-1,那么此时才会创建定时调度任务,并且是根据Netty的时间轮来实现创建。
所以调用scheduleExpirationRenewal()方法会创建一个定时调度任务,只要锁还被客户端持有,定时任务就会不断更新锁对应的key的过期时间。
public class RedissonLock extends RedissonBaseLock {...private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFuture<Long> ttlRemainingFuture;if (leaseTime != -1) {ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {//异步执行完tryLockInnerAsync()里的内容后,就会执行ttlRemainingFuture.thenApply()里的逻辑//加锁返回的ttlRemaining为null表示加锁成功if (ttlRemaining == null) {if (leaseTime != -1) {//如果指定了锁的过期时间,并不会启动定时任务internalLockLeaseTime = unit.toMillis(leaseTime);} else {//传入加锁成功的线程ID,启动一个定时任务scheduleExpirationRenewal(threadId);}}return ttlRemaining;});return new CompletableFutureWrapper<>(f);}...
}
(3)定时调度任务由Netty的时间轮机制来实现
Redisson的WatchDog机制底层并不是调度线程池,而是Netty时间轮。
scheduleExpirationRenewal()方法会创建一个定时调度任务TimerTask交给HashedWheelTimer,10秒后执行。
public abstract class RedissonBaseLock extends RedissonExpirable implements RLock {...protected void scheduleExpirationRenewal(long threadId) {ExpirationEntry entry = new ExpirationEntry();ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);if (oldEntry != null) {oldEntry.addThreadId(threadId);} else {entry.addThreadId(threadId);try {//创建一个更新过期时间的定时调度任务renewExpiration();} finally {if (Thread.currentThread().isInterrupted()) {cancelExpirationRenewal(threadId);}}}}//更新过期时间private void renewExpiration() {ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee == null) {return;}//使用了Netty的定时任务机制:HashedWheelTimer + TimerTask + Timeout//创建一个更新过期时间的定时调度任务,下面会调用MasterSlaveConnectionManager.newTimeout()方法//即创建一个定时调度任务TimerTask交给HashedWheelTimer,10秒后执行Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {...}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);}...
}public class MasterSlaveConnectionManager implements ConnectionManager {private HashedWheelTimer timer;//Netty的时间轮...@Overridepublic Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {try {//给Netty的时间轮添加任务taskreturn timer.newTimeout(task, delay, unit);} catch (IllegalStateException e) {if (isShuttingDown()) {return DUMMY_TIMEOUT;}throw e;}}...
}//Netty的时间轮HashedWheelTimer
public class HashedWheelTimer implements Timer {private final HashedWheelBucket[] wheel;private final Queue<HashedWheelTimeout> timeouts = PlatformDependent.newMpscQueue();private final Executor taskExecutor;...@Overridepublic Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {...//启动Worker线程start();...//封装定时调度任务TimerTask实例到HashedWheelTimeout实例HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);timeouts.add(timeout);return timeout;}...private final class Worker implements Runnable {private long tick;//轮次...@Overridepublic void run() {...do {final long deadline = waitForNextTick();if (deadline > 0) {int idx = (int) (tick & mask);processCancelledTasks();HashedWheelBucket bucket = wheel[idx];//将定时调度任务HashedWheelTimeout转换到时间轮分桶里transferTimeoutsToBuckets();//处理时间轮分桶HashedWheelBucket里的到期任务bucket.expireTimeouts(deadline);tick++;}} while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);...}private void transferTimeoutsToBuckets() {for (int i = 0; i < 100000; i++) {HashedWheelTimeout timeout = timeouts.poll();...long calculated = timeout.deadline / tickDuration;timeout.remainingRounds = (calculated - tick) / wheel.length;final long ticks = Math.max(calculated, tick); // Ensure we don't schedule for past.int stopIndex = (int) (ticks & mask);//对mask取模HashedWheelBucket bucket = wheel[stopIndex];bucket.addTimeout(timeout);}}...}private static final class HashedWheelBucket {...public void expireTimeouts(long deadline) {HashedWheelTimeout timeout = head;//process all timeoutswhile (timeout != null) {...//对定时调度任务timeout进行过期处理timeout.expire();...}}...}private static final class HashedWheelTimeout implements Timeout, Runnable {private final HashedWheelTimer timer;...public void expire() {...//执行定时调度任务,让Executor执行HashedWheelTimeout的run方法timer.taskExecutor.execute(this);...}@Overridepublic void run() {...//执行定时调度任务task.run(this);...}...}...
}
(4)10秒后会执行定时任务并判断是否要再创建一个定时任务在10秒后执行
该定时任务TimerTask实例会调用RedissonBaseLock的renewExpirationAsync()方法去执行lua脚本,renewExpirationAsync()方法会传入获取到锁的线程ID。
在lua脚本中,会通过Redis的hexists命令判断在key为锁名的Hash值里,field为UUID + 线程ID的映射是否存在。其中KEYS[1]就是锁的名字如myLock,ARGV[2]为UUID + 线程ID。
如果存在,说明获取锁的线程还在持有锁,并没有对锁进行释放。那么就通过Redis的pexpire命令,设置key的过期时间为30秒。
异步执行lua脚本完后,会传入布尔值触发future.whenComplete()方法的回调,回调中会根据布尔值来决定是否继续递归调用renewExpiration()方法。
也就是说,如果获取锁的线程还在持有锁,那么就重置锁的过期时间为30秒,并且lua脚本会返回1,接着在future.whenComplete()方法的回调中,继续调用RedissonBaseLock的renewExpiration()方法重新创建定时调度任务。
如果获取锁的线程已经释放了锁,那么lua脚本就会返回0,接着在future.whenComplete()方法的回调中,会调用RedissonBaseLock的cancelExpirationRenewal()方法执行清理工作。
public abstract class RedissonBaseLock extends RedissonExpirable implements RLock {...protected void scheduleExpirationRenewal(long threadId) {ExpirationEntry entry = new ExpirationEntry();ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);if (oldEntry != null) {oldEntry.addThreadId(threadId);} else {entry.addThreadId(threadId);try {//创建一个更新过期时间的定时调度任务renewExpiration();} finally {if (Thread.currentThread().isInterrupted()) {cancelExpirationRenewal(threadId);}}}}//更新过期时间private void renewExpiration() {ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee == null) {return;}//使用了Netty的定时任务机制:HashedWheelTimer + TimerTask + Timeout//创建一个更新过期时间的定时调度任务,下面会调用MasterSlaveConnectionManager.newTimeout()方法//即创建一个定时调度任务TimerTask交给HashedWheelTimer,10秒后执行Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ent == null) {return;}Long threadId = ent.getFirstThreadId();if (threadId == null) {return;}//异步执行lua脚本去更新锁的过期时间RFuture<Boolean> future = renewExpirationAsync(threadId);future.whenComplete((res, e) -> {if (e != null) {log.error("Can't update lock " + getRawName() + " expiration", e);EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}//res就是执行renewExpirationAsync()里的lua脚本的返回值if (res) {//重新调度自己renewExpiration();} else {//执行清理工作cancelExpirationRenewal(null);}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);}protected RFuture<Boolean> renewExpirationAsync(long threadId) {//其中KEYS[1]就是锁的名字如myLock,ARGV[2]为UUID+线程ID;return evalWriteAsync(getRawName(), 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(getRawName()),internalLockLeaseTime, getLockName(threadId));}}protected void cancelExpirationRenewal(Long threadId) {ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (task == null) {return;}if (threadId != null) {task.removeThreadId(threadId);}if (threadId == null || task.hasNoThreads()) {Timeout timeout = task.getTimeout();if (timeout != null) {timeout.cancel();}EXPIRATION_RENEWAL_MAP.remove(getEntryName());}}...
}
注意:如果持有锁的机器宕机了,就会导致该机器上的锁的WatchDog不执行了。从而锁的key会在30秒内自动过期,释放掉锁。此时其他客户端最多再等30秒就可获取到这把锁了。
5.可重入锁源码之可重入加锁逻辑
public class Application {public static void main(String[] args) throws Exception {Config config = new Config();config.useClusterServers().addNodeAddress("redis://192.168.1.110:7001");//创建RedissonClient实例RedissonClient redisson = Redisson.create(config);//获取可重入锁RLock lock = redisson.getLock("myLock");//首次加锁lock.lock();//重入加锁lock.lock();//重入加锁lock.lock();lock.unlock();lock.unlock();lock.unlock();...}
}public class RedissonLock extends RedissonBaseLock {...<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('hincrby', 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(getRawName()),//锁的名字:KEYS[1]unit.toMillis(leaseTime),//过期时间:ARGV[1],默认时为30秒getLockName(threadId)//ARGV[2],值为UUID + 线程ID);}...
}
实现可重入加锁的核心就是加锁的lua脚本。
(1)核心一是key的命名:第一层key是锁名,第二层field是UUID+线程ID
KEYS[1]的值就是锁的名字,ARGV[2]的值就是客户端UUID + 线程ID。由于第二层field包含了线程ID,所以可以区分申请加锁的线程是否在重入。对在key为锁名的Hash值中,field为UUID + 线程ID的value值进行递增1操作。
(2)核心二是Redis的exists命令、hexists命令、hincrby命令
判断锁是否存在,使用的是Redis的exists命令。判断锁是否正在被线程重入,则使用的是Redis的hexists命令。对field映射的value值进行递增,使用的是Redis的hincrby命令。
所以,持有锁的线程每重入锁一次,就会对field映射的value值递增1。比如:hincrby key UUID:ThreadID 1。
6.可重入锁源码之锁的互斥阻塞逻辑
(1)获取锁失败时会执行Redis的pttl命令返回锁的剩余存活时间
在执行加锁的lua脚本中:首先通过"exists myLock",判断是否存在myLock这个锁key,发现已经存在。接着判断"hexists myLock 另一个UUID:另一个Thread的ID",发现不存在。于是执行"pttl myLock"并返回,也就是返回myLock这个锁的剩余存活时间。
(2)ttlRemainingFuture的回调方法发现ttlRemaining不是null
当执行加锁的lua脚本发现加锁不成功,返回锁的剩余存活时间时,ttlRemainingFuture的回调方法发现ttlRemaining不是null,于是便不会创建定时调度任务在10秒之后去检查锁是否还被申请的线程持有,而是会将返回的剩余存活时间封装到RFuture中并向上返回。
(3)RedissonLock的tryAcquire()方法会返回这个剩余存活时间ttl
主要会通过RedisObject的get()方法去获取RFuture中封装的ttl值。其中异步转同步就是将异步的tryAcquireAsync()方法通过get()方法转同步。
(4)RedissonLock的lock()方法会对tryAcquire()方法返回的ttl进行判断
如果当前线程是第一次加锁,那么ttl一定是null。如果当前线程是多次加锁(可重入锁),那么ttl也一定是null。如果当前线程加锁没成功,锁被其他机器或线程占用,那么ttl就是执行加锁lua脚本时获取到的锁对应的key的剩余存活时间。
如果ttl是null,说明当前线程加锁成功,于是直接返回。如果ttl不是null,说明当前线程加锁不成功,于是会执行阻塞逻辑。
(5)RedissonLock的lock()方法在ttl不为null时利用Semaphore进行阻塞
如果ttl不为null,即加锁不成功,那么会进入while(true)死循环。在死循环内,再次执行RedissonLock的tryAcquire()方法尝试获取锁。如果再次获取锁时返回的ttl为null,即获取到锁,就退出while循环。如果再次获取锁时返回的ttl不为null,则说明其他客户端或线程还持有锁。
于是就利用同步组件Semaphore进行阻塞等待一段ttl的时间。阻塞等待一段时间后,会继续执行while循环里的逻辑,再次尝试获取锁。以此循环往复,直到获得锁。
public class RedissonLock extends RedissonBaseLock {protected long internalLockLeaseTime;protected final LockPubSub pubSub;final CommandAsyncExecutor commandExecutor;public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {super(commandExecutor, name);this.commandExecutor = commandExecutor;//与WatchDog有关的internalLockLeaseTime//通过命令执行器CommandExecutor可以获取连接管理器ConnectionManager//通过连接管理器ConnectionManager可以获取Redis的配置信息类Config//通过Redis的配置信息类Config可以获取lockWatchdogTimeout超时时间this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();}...//加锁@Overridepublic void lock() {try {lock(-1, null, false);} catch (InterruptedException e) {throw new IllegalStateException();}}private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {//线程ID,用来生成设置Hash的值long threadId = Thread.currentThread().getId();//尝试加锁,此时执行RedissonLock.lock()方法默认传入的leaseTime=-1Long ttl = tryAcquire(-1, leaseTime, unit, threadId);//ttl为null说明加锁成功if (ttl == null) {return;}//加锁失败时的处理CompletableFuture<RedissonLockEntry> future = subscribe(threadId);if (interruptibly) {commandExecutor.syncSubscriptionInterrupted(future);} else {commandExecutor.syncSubscription(future);}try {while (true) {//再次尝试获取锁ttl = tryAcquire(-1, leaseTime, unit, threadId);//返回的ttl为null,获取到锁,就退出while循环if (ttl == null) {break;}//返回的ttl不为null,则说明其他客户端或线程还持有锁//那么就利用同步组件Semaphore进行阻塞等待一段ttl的时间if (ttl >= 0) {try {commandExecutor.getNow(future).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} catch (InterruptedException e) {if (interruptibly) {throw e;}commandExecutor.getNow(future).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);}} else {if (interruptibly) {commandExecutor.getNow(future).getLatch().acquire();} else {commandExecutor.getNow(future).getLatch().acquireUninterruptibly();}}}} finally {unsubscribe(commandExecutor.getNow(future), threadId);}}...private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {//默认下waitTime和leaseTime都是-1,下面调用的get方法是来自于RedissonObject的get()方法//可以理解为异步转同步:将异步的tryAcquireAsync通过get转同步return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));}private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFuture<Long> ttlRemainingFuture;if (leaseTime != -1) {ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {//默认情况下,由于leaseTime=-1,所以会使用初始化RedissonLock实例时的internalLockLeaseTime//internalLockLeaseTime的默认值就是lockWatchdogTimeout的默认值,30秒ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {//加锁返回的ttlRemaining为null表示加锁成功if (ttlRemaining == null) {if (leaseTime != -1) {internalLockLeaseTime = unit.toMillis(leaseTime);} else {scheduleExpirationRenewal(threadId);}}return ttlRemaining;});return new CompletableFutureWrapper<>(f);}//默认情况下,外部传入的leaseTime=-1时,会取lockWatchdogTimeout的默认值=30秒<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('hincrby', 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(getRawName()),//锁的名字:KEYS[1]unit.toMillis(leaseTime),//过期时间:ARGV[1],默认时为30秒getLockName(threadId)//ARGV[2],值为UUID + 线程ID);}...
}public class RedissonLockEntry implements PubSubEntry<RedissonLockEntry> {private final Semaphore latch;...public Semaphore getLatch() {return latch;}...
}
7.可重入锁源码之释放锁逻辑
(1)宕机自动释放锁
如果获取锁的所在机器宕机,那么10秒后检查锁是否还在被线程持有的定时调度任务就没了。于是锁对应Redis里的key,就会在最多30秒内进行过期删除。之后,其他的客户端就能成功获取锁了。
当然,前提是创建锁的时候没有设置锁的存活时间leaseTime。如果指定了leaseTime,那么就不会存在10秒后检查锁的定时调度任务。此时获取锁的所在机器宕机,那么锁对应的key会在最多leaseTime后过期。
(2)线程主动释放锁的流程
主动释放锁调用的是RedissonLock的unlock()方法。在RedissonLock的unlock()方法中,会调用get(unlockAsync())方法。也就是首先调用RedissonBaseLock的unlockAsync()方法,然后再调用RedissonObject的get()方法。
其中unlockAsync()方法是异步化执行的方法,释放锁的操作就是异步执行的。而RedisObject的get()方法会通过RFuture同步等待获取异步执行的结果,可以将get(unlockAsync())理解为异步转同步。
在RedissonBaseLock的unlockAsync()方法中:首先会调用RedissonLock的unlockInnerAsync()方法进行异步释放锁,然后当完成释放锁的处理后,再通过异步去取消定时调度任务。
public class Application {public static void main(String[] args) throws Exception {Config config = new Config();config.useClusterServers().addNodeAddress("redis://192.168.1.110:7001");//创建RedissonClient实例RedissonClient redisson = Redisson.create(config);//获取可重入锁RLock lock = redisson.getLock("myLock");lock.lock();//获取锁lock.unlock();//释放锁...}
}public class RedissonLock extends RedissonBaseLock {...@Overridepublic void unlock() {...//异步转同步//首先调用的是RedissonBaseLock的unlockAsync()方法//然后调用的是RedissonObject的get()方法get(unlockAsync(Thread.currentThread().getId()));...}//异步执行释放锁的lua脚本protected RFuture<Boolean> unlockInnerAsync(long threadId) {return evalWriteAsync(getRawName(), 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(getRawName(), getChannelName()),//KEYS[1] + KEYS[2]LockPubSub.UNLOCK_MESSAGE,//ARGV[1]internalLockLeaseTime,//ARGV[2]getLockName(threadId)//ARGV[3]);}...
}public abstract class RedissonBaseLock extends RedissonExpirable implements RLock {...@Overridepublic RFuture<Void> unlockAsync(long threadId) {//异步执行释放锁的lua脚本RFuture<Boolean> future = unlockInnerAsync(threadId);CompletionStage<Void> f = future.handle((opStatus, e) -> {//取消定时调度任务cancelExpirationRenewal(threadId);if (e != null) {throw new CompletionException(e);}if (opStatus == null) {IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + threadId);throw new CompletionException(cause);}return null;});return new CompletableFutureWrapper<>(f);}...
}abstract class RedissonExpirable extends RedissonObject implements RExpirable {...RedissonExpirable(CommandAsyncExecutor connectionManager, String name) {super(connectionManager, name);}...
}public abstract class RedissonObject implements RObject {protected final CommandAsyncExecutor commandExecutor;protected String name;protected final Codec codec;public RedissonObject(Codec codec, CommandAsyncExecutor commandExecutor, String name) {this.codec = codec;this.commandExecutor = commandExecutor;if (name == null) {throw new NullPointerException("name can't be null");}setName(name);}...protected final <V> V get(RFuture<V> future) {//下面会调用CommandAsyncService.get()方法return commandExecutor.get(future);}...
}public class CommandAsyncService implements CommandAsyncExecutor {...@Overridepublic <V> V get(RFuture<V> future) {if (Thread.currentThread().getName().startsWith("redisson-netty")) {throw new IllegalStateException("Sync methods can't be invoked from async/rx/reactive listeners");}try {return future.toCompletableFuture().get();} catch (InterruptedException e) {future.cancel(true);Thread.currentThread().interrupt();throw new RedisException(e);} catch (ExecutionException e) {throw convertException(e);}}...
}
(3)主动释放锁的lua脚本分析
首先判断在key为锁名的Hash值中,field为UUID + 线程ID的映射是否存在。如果不存在,则表示锁已经被释放了,直接返回。如果存在,则在key为锁名的Hash值中对field为UUID + 线程ID的value值递减1。即调用Redis的hincrby命令,进行递减1处理。
接着对递减1后的结果进行如下判断:如果递减1后的结果大于0,则表示线程还在持有锁。这对应于持有锁的线程多次重入锁,此时需要重置锁的过期时间为30秒。如果递减1后的结果小于0,则表示线程不再持有锁,于是删除锁对应的key,并且通过Redis的publish命令,发布一个事件。
public class RedissonLock extends RedissonBaseLock {...//异步执行释放锁的lua脚本protected RFuture<Boolean> unlockInnerAsync(long threadId) {return evalWriteAsync(getRawName(), 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(getRawName(), getChannelName()),//KEYS[1] + KEYS[2]LockPubSub.UNLOCK_MESSAGE,//ARGV[1]internalLockLeaseTime,//ARGV[2]getLockName(threadId)//ARGV[3]);}...
}
8.可重入锁源码之获取锁超时与锁超时自动释放逻辑
针对如下代码方式去获取锁,如果超过60秒获取不到锁,就自动放弃获取锁,不会进行永久性阻塞。如果获取到锁之后,锁在10秒内没有被主动释放,那么就会自动释放锁。
public class Application {public static void main(String[] args) throws Exception {Config config = new Config();config.useClusterServers().addNodeAddress("redis://192.168.1.110:7001");//创建RedissonClient实例RedissonClient redisson = Redisson.create(config);//获取可重入锁RLock lock = redisson.getLock("myLock");boolean res = lock.tryLock(60, 10, TimeUnit.SECONDS);//尝试获取锁if (res) lock.unlock();//尝试获取锁成功,则释放锁...}
}
(1)尝试获取锁超时
RedissonLock的tryLock()方法,会传入waitTime和leaseTime尝试获取锁。其中waitTime就是最多可以等待多少时间去获取锁,比如60秒。leaseTime就是获取锁后锁的自动过期时间,比如10秒。
如果第一次获取锁的时间超过了等待获取锁的最大时间waitTime,那么就会返回false。
如果第一次获取锁的时间还没超过等待获取锁的最大时间waitTime,那么就进入while循环,不断地再次尝试获取锁。
如果再次尝试获取锁成功,那么就返回true。如果再次尝试获取锁失败,那么计算还剩下多少时间可以继续等待获取锁。也就是使用time去自减每次尝试获取锁的耗时以及自减每次等待的耗时。
如果发现time小于0,表示等待了waitTime都无法获取锁,则返回false。如果发现time大于0,继续下一轮while循环,尝试获取锁 + time自减耗时。
public class RedissonLock extends RedissonBaseLock {...@Overridepublic boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {long time = unit.toMillis(waitTime);//最多可以等待多少时间去获取锁long current = System.currentTimeMillis();//current是第一次尝试获取锁之前的时间戳long threadId = Thread.currentThread().getId();Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);//尝试获取锁成功if (ttl == null) {return true;}//第一次尝试获取锁失败//当前时间减去第一次获取锁之前的时间戳,就是这次尝试获取锁的耗费时间,使用time进行自减time -= System.currentTimeMillis() - current;//如果第一次获取锁的时间超过了waitTime等待获取锁的最大时间,那么就会直接返回获取锁失败if (time <= 0) {acquireFailed(waitTime, unit, threadId);return false;}//如果第一次获取锁的时间还没达到waitTime等待获取锁的最大时间current = System.currentTimeMillis();CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);try {subscribeFuture.toCompletableFuture().get(time, TimeUnit.MILLISECONDS);} catch (ExecutionException | TimeoutException e) {if (!subscribeFuture.cancel(false)) {subscribeFuture.whenComplete((res, ex) -> {if (ex == null) {unsubscribe(res, threadId);}});}acquireFailed(waitTime, unit, threadId);return false;}try {time -= System.currentTimeMillis() - current;if (time <= 0) {acquireFailed(waitTime, unit, threadId);return false;}while (true) {long currentTime = System.currentTimeMillis();ttl = tryAcquire(waitTime, leaseTime, unit, threadId);//再次尝试获取锁成功if (ttl == null) {return true;}//剩余可用等待的时间,使用time去自减每次尝试获取锁的耗时time -= System.currentTimeMillis() - currentTime;if (time <= 0) {acquireFailed(waitTime, unit, threadId);return false;}//返回的ttl不为null,则说明其他客户端或线程还持有锁//那么就利用同步组件Semaphore进行阻塞等待一段ttl的时间currentTime = System.currentTimeMillis();if (ttl >= 0 && ttl < time) {commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} else {commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);}//使用time去自减每次等待的耗时time -= System.currentTimeMillis() - currentTime;if (time <= 0) {acquireFailed(waitTime, unit, threadId);return false;}}} finally {unsubscribe(commandExecutor.getNow(subscribeFuture), threadId);}}...
}
(2)锁超时自动释放
当使用RedissonLock.tryAcquire方法尝试获取锁时,传入的leaseTime不是-1,而是一个指定的锁存活时间,那么锁超时就会自动被释放。
当leaseTime不是-1时:
一.锁的过期时间就不是lockWatchdogTimeout=30秒,而是leaseTime
二.执行加锁lua脚本成功后,不会创建一个定时调度任务在10秒后检查锁
总结:当指定了leaseTime后,那么锁最多在leaseTime后,就必须被删除。因为此时没有定时调度任务去检查锁释放还在被线程持有并更新过期时间。所以,锁要么被主动删除,要么在存活leaseTime后自动过期。
public class RedissonLock extends RedissonBaseLock {...private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {//默认下waitTime和leaseTime都是-1,下面调用的get方法是来自于RedissonObject的get()方法//可以理解为异步转同步:将异步的tryAcquireAsync通过get转同步return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));}private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFuture<Long> ttlRemainingFuture;if (leaseTime != -1) {ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {//默认情况下,由于leaseTime=-1,所以会使用初始化RedissonLock实例时的internalLockLeaseTime//internalLockLeaseTime的默认值就是lockWatchdogTimeout的默认值,30秒ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {//加锁返回的ttlRemaining为null表示加锁成功if (ttlRemaining == null) {if (leaseTime != -1) {internalLockLeaseTime = unit.toMillis(leaseTime);} else {scheduleExpirationRenewal(threadId);}}return ttlRemaining;});return new CompletableFutureWrapper<>(f);}...
}
9.可重入锁源码总结
(1)加锁
在Redis里设置两层Hash数据结构,默认的过期时间是30秒。第一层Hash值的key是锁名,第二层Hash值的key是UUID + 线程ID。涉及Redis的exists命令、hexists命令、hincrby命令。
(2)WatchDog维持加锁
如果获取锁的线程一直持有锁,那么Redis里的key就会一直保持存活。获取锁成功时会创建一个定时任务10秒后检查锁是否还被线程持有。如果线程还在持有锁,就会重置key的过期时间为30秒,并且创建一个新的定时任务在10秒后继续检查锁是否还被线程持有。
(3)可重入锁
同一个线程可以多次加锁,对第二层的key为UUID + 线程ID的Hash值,每次获取锁就进行累加1。
(4)锁互斥
不同线程尝试加锁时,会由于第二层Hash的key不同,而导致加锁不成功。也就是执行加锁lua脚本时会返回锁的剩余过期时间ttl。然后利用同步组件Semaphore进行阻塞等待一段ttl时间。阻塞等待一段时间后,继续在while循环里再次尝试获取锁。以此循环等待 + 尝试,直到获得锁。
(5)手动释放锁
使用hincrby命令对第二层key(UUID + 线程ID)的Hash值递减1。递减1后还大于0表示锁被重入,需要重置锁的过期时间。递减1后小于等于0表示锁释放完毕,需要删除锁key及取消定时调度任务。
(6)宕机自动释放锁
如果持有锁的客户端宕机,那么锁的WatchDog定时调度任务也没了。此时不会重置锁key的过期时间,于是锁key会自动释放。
(7)尝试加锁超时
在while循环中不断地尝试获取锁。使用time表示还可以等待获取锁的时间。每次循环都对time:自减尝试获取锁的耗时 + 自减每次等待的耗时。在指定时间内没有成功加锁(即time小于0),就退出循环,表示加锁失败。
(8)超时锁自动释放
当指定了leaseTime时,如果获取锁没有在leaseTime内手动释放锁,那么Redis里的锁key会自动过期,自动释放锁。
文章转载自:东阳马生架构
原文链接:分布式锁—2.Redisson的可重入锁 - 东阳马生架构 - 博客园
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构