分布式锁—2.Redisson的可重入锁一
大纲
1.Redisson可重入锁RedissonLock概述
2.可重入锁源码之创建RedissonClient实例
3.可重入锁源码之lua脚本加锁逻辑
4.可重入锁源码之WatchDog维持加锁逻辑
5.可重入锁源码之可重入加锁逻辑
6.可重入锁源码之锁的互斥阻塞逻辑
7.可重入锁源码之释放锁逻辑
8.可重入锁源码之获取锁超时与锁超时自动释放逻辑
9.可重入锁源码总结
1.Redisson可重入锁RedissonLock概述
(1)在pom.xml里引入依赖
(2)构建RedissonClient并使用Redisson
(3)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实例
(1)初始化与Redis的连接管理器ConnectionManager
(2)初始化Redis的命令执行器CommandExecutor
使用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实例
(2)加锁时的执行流程
(3)加锁时执行的lua脚本
(4)执行加锁lua脚本的命令执行器逻辑
(5)如何根据slot值获取对应的节点
(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);}...
}
相关文章:

分布式锁—2.Redisson的可重入锁一
大纲 1.Redisson可重入锁RedissonLock概述 2.可重入锁源码之创建RedissonClient实例 3.可重入锁源码之lua脚本加锁逻辑 4.可重入锁源码之WatchDog维持加锁逻辑 5.可重入锁源码之可重入加锁逻辑 6.可重入锁源码之锁的互斥阻塞逻辑 7.可重入锁源码之释放锁逻辑 8.可重入锁…...

计算机毕业设计SpringBoot+Vue.js球队训练信息管理系统(源码+文档+PPT+讲解)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...

FFmpeg入门:最简单的音视频播放器
FFmpeg入门:最简单的音视频播放器 前两章,我们已经了解了分别如何构建一个简单和音频播放器和视频播放器。 FFmpeg入门:最简单的音频播放器 FFmpeg入门:最简单的视频播放器 本章我们将结合上述两章的知识,看看如何融…...

java 查找两个集合的交集部分数据
利用了Java 8的Stream API,代码简洁且效率高 import java.util.stream.Collectors; import java.util.List; import java.util.HashSet; import java.util.Set;public class ListIntersection {public static List<Long> findIntersection(List<Long> …...

【系统架构设计师】以数据为中心的体系结构风格
目录 1. 说明2. 仓库体系结构风格3. 黑板体系结构风格 1. 说明 1.以数据为中心的体系结构风格主要包括仓库体系结构风格和黑板体系结构风格。 2. 仓库体系结构风格 1.仓库(Repository)是存储和维护数据的中心场所。2.在仓库风格中,有两种不…...

通过HTML有序列表(ol/li)实现自动递增编号的完整解决方案
以下是通过HTML有序列表(ol/li)实现自动递增编号的完整解决方案: <!DOCTYPE html> <html> <head> <style> /* 基础样式 */ ol {margin: 1em 0;padding-left: 2em; }/* 方案1:默认数字编号 */ ol.default {list-style-type: dec…...

【Python 数据结构 4.单向链表】
目录 一、单向链表的基本概念 1.单向链表的概念 2.单向链表的元素插入 元素插入的步骤 3.单向链表的元素删除 元素删除的步骤 4.单向链表的元素查找 元素查找的步骤 5.单向链表的元素索引 元素索引的步骤 6.单向链表的元素修改 元素修改的步骤 二、Python中的单向链表 编辑 三…...

基于 vLLM 部署 LSTM 时序预测模型的“下饭”(智能告警预测与根因分析部署)指南
Alright,各位看官老爷们,准备好迎接史上最爆笑、最通俗易懂的 “基于 vLLM 部署 LSTM 时序预测模型的智能告警预测与根因分析部署指南” 吗? 保证让你笑出猪叫,看完直接变身技术大咖!🚀😂 咱们今天的主题,就像是要打造一个“智能运维小管家”! 这个小管家,不仅能提…...

Java多线程与高并发专题——ConcurrentHashMap 在 Java7 和 8 有何不同?
引入 上一篇我们提到HashMap 是线程不安全的,并推荐使用线程安全同时性能比较好的 ConcurrentHashMap。 而在 Java 8 中,对于 ConcurrentHashMap 这个常用的工具类进行了很大的升级,对比之前 Java 7 版本在诸多方面都进行了调整和变化。不过…...

NL2SQL-基于Dify+阿里通义千问大模型,实现自然语音自动生产SQL语句
本文基于Dify阿里通义千问大模型,实现自然语音自动生产SQL语句功能,话不多说直接上效果图 我们可以试着问他几个问题 查询每个部门的员工数量SELECT d.dept_name, COUNT(e.emp_no) AS employee_count FROM employees e JOIN dept_emp de ON e.emp_no d…...

LeetCode 1328.破坏回文串:贪心
【LetMeFly】1328.破坏回文串:贪心 力扣题目链接:https://leetcode.cn/problems/break-a-palindrome/ 给你一个由小写英文字母组成的回文字符串 palindrome ,请你将其中 一个 字符用任意小写英文字母替换,使得结果字符串的 字典…...

计算机视觉|ViT详解:打破视觉与语言界限
一、ViT 的诞生背景 在计算机视觉领域的发展中,卷积神经网络(CNN)一直占据重要地位。自 2012 年 AlexNet 在 ImageNet 大赛中取得优异成绩后,CNN 在图像分类任务中显示出强大能力。随后,VGG、ResNet 等深度网络架构不…...

//定义一个方法,把int数组中的数据按照指定的格式拼接成一个字符串返回,调用该方法,并在控制台输出结果
import java.util.Scanner; public class cha{ public static void main(String[] args){//定义一个方法,把int数组中的数据按照指定的格式拼接成一个字符串返回,调用该方法,并在控制台输出结果//eg: 数组为:int[] arr…...

Python快捷手册
Python快捷手册 后续会陆续更新Python对应的依赖或者工具使用方法 文章目录 Python快捷手册[toc]1-依赖1-词云小工具2-图片添加文字3-BeautifulSoup网络爬虫4-Tkinter界面绘制5-PDF转Word 2-开发1-多线程和队列 3-运维1-Requirement依赖2-波尔实验室3-Anaconda3使用教程4-CentO…...

QT5 GPU使用
一、问题1 1、现象 2、原因分析 出现上图错误,无法创建EGL表面,错误=0x300b。申请不上native window有可能是缺少libqeglfs-mali-integration.so 这个库 3、解决方法 需要将其adb push 到小机端的/usr/lib/qt5/plugins/egldeviceintegrat…...

如何在Spring Boot中读取JAR包内resources目录下文件
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 以下是如何在Spring Boot中读取JAR包内resources目录下文件的教程,分为多种方法及详细说明: 方法1:使用 ClassPathResour…...

《张一鸣,创业心路与算法思维》
张一鸣,多年如一日的阅读习惯。 爱读人物传记,称教科书式人类知识最浓缩的书,也爱看心理学,创业以及商业管理类的书。 冯仑,王石,联想,杰克韦尔奇,思科。 《乔布斯传》《埃隆马斯…...

SSE 和 WebSocket 的对比
SSE 和 WebSocket 的对比 在现代Web开发中,实时通信是提升用户体验的重要手段。Server-Sent Events(SSE)和WebSocket是两种实现服务器与客户端之间实时数据传输的技术,但它们在功能、适用场景以及实现方式上有所不同。 1. 基本概…...

es如何进行refresh?
在 Elasticsearch 中,refresh 操作的作用是让最近写入的数据可以被搜索到。以下为你介绍几种常见的执行 refresh 操作的方式: 1. 使用 RESTful API 手动刷新 你可以通过向 Elasticsearch 发送 HTTP 请求来手动触发 refresh 操作。可以针对单个索引、多个索引或者所有索引进…...

Kubespray部署企业级高可用K8S指南
目录 前言1 K8S集群节点准备1.1 主机列表1.2 kubespray节点python3及pip3准备1.2.1. 更新系统1.2.2. 安装依赖1.2.3. 下载Python 3.12源码1.2.4. 解压源码包1.2.5. 编译和安装Python1.2.6. 验证安装1.2.7. 设置Python 3.12为默认版本(可选)1.2.8. 安装pi…...

【实战篇】【深度解析DeepSeek:从机器学习到深度学习的全场景落地指南】
一、机器学习模型:DeepSeek的降维打击 1.1 监督学习与无监督学习的"左右互搏" 监督学习就像学霸刷题——给标注数据(参考答案)训练模型。DeepSeek在信贷风控场景中,用逻辑回归模型分析百万级用户数据,通过特征工程挖掘出"凌晨3点频繁申请贷款"这类魔…...

优选算法的智慧之光:滑动窗口专题(二)
专栏:算法的魔法世界 个人主页:手握风云 目录 一、例题讲解 1.1. 最大连续1的个数 III 1.2. 找到字符串中所有字母异位词 1.3. 串联所有单词的子串 1.4. 最小覆盖子串 一、例题讲解 1.1. 最大连续1的个数 III 题目要求是二进制数组&am…...

Kylin麒麟操作系统服务部署 | NFS服务部署
以下所使用的环境为: 虚拟化软件:VMware Workstation 17 Pro 麒麟系统版本:Kylin-Server-V10-SP3-2403-Release-20240426-x86_64 一、 NFS服务概述 NFS(Network File System),即网络文件系统。是一种使用于…...

7.1.2 计算机网络的分类
文章目录 分布范围交换方式 分布范围 计算机网络按照分布范围可分为局域网、广域网、城域网。局域网的范围在10m~1km,例如校园网,网速高,主要用于共享网络资源,拓扑结构简单,约束少。广域网的范围在100km,例…...

Spring Cloud Alibaba 实战:轻松实现 Nacos 服务发现与动态配置管理
1. Nacos 介绍 1.1 什么是 Nacos? Nacos(Naming and Configuration Service)是阿里巴巴开源的一个服务注册中心和配置管理中心。它支持动态服务发现、配置管理和服务治理,适用于微服务架构,尤其是基于 Spring Cloud …...

【数据结构】LRUCache|并查集
目录 一、LRUCache 1.概念 2.实现:哈希表双向链表 3.JDK中类似LRUCahe的数据结构LinkedHashMap 🔥4.OJ练习 二、并查集 1. 并查集原理 2.并查集代码实现 3.并查集OJ 一、LRUCache 1.概念 最近最少使用的,一直Cache替换算法 LRU是Least Recent…...

智能合约中权限管理不当
权限管理不当 : 权限管理不当是智能合约中常见的安全问题之一,尤其是在管理员或特定账户被过度赋予权限的情况下。如果合约中的关键功能,如转移资产、修改合约状态或升级合约逻辑,可以被未经授权的实体随意操作,这将构…...

MariaDB Galera 原理及用例说明
一、底层原理 MariaDB Galera 集群是一种基于同步多主架构的高可用数据库解决方案,适合需要高并发、低延迟和数据强一致性的场景。以下是部署和配置 MariaDB Galera 集群的简明步骤: 1. 环境准备 节点要求:至少 3 个节点(奇数节点…...

【RAG 篇】万字长文:向量数据库选型指南 —— Milvus 与 FAISS/Pinecone/Weaviate 等工具深度对比
大家好,我是大 F,深耕AI算法十余年,互联网大厂技术岗。分享AI算法干货、技术心得。 欢迎关注《大模型理论和实战》、《DeepSeek技术解析和实战》,一起探索技术的无限可能! 文章目录 向量数据库的核心价值主流工具横向对比 FAISS:Meta 的高效检索引擎Pinecone:全托管商业…...

关于服务器cpu过高的问题排查
1.定位是哪个程序造成的cpu过高 如果有云服务器,就用云服务器自带的监控功能,查时间段 如果没有,则使用: ps -eo pid,comm,pcpu,pmem,cputime --sort-cputime | head -n 100 2.定位到问题 发现是uwsgi的cpu消耗过高࿰…...