分布式锁—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 CLuster
        Config 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) {
        //生成UUID
        UUID 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;//初始化时为UUID
    private final Map<RedisURI, RedisConnection> nodeConnections = new ConcurrentHashMap<>();
    ...
    protected MasterSlaveConnectionManager(Config cfg, UUID id) {
        this.id = id.toString();//传入的是UUID
        this.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客户端连接实例
    @Override
    public 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() {
                @Override
                public 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() {
                                @Override
                                public 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 {
    //初始化CommandExecutor
    public 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;
    }
    
    @Override
    public <V> V getNow(CompletableFuture<V> future) {
        try {
            return future.getNow(null);
        } catch (Exception e) {
            return null;
        }
    }
    
    @Override
    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);
    }
    
    @Override
    public <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);
    }
    
    @Override
    public <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);
        ...
    }
    ...
    @Override
    public 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;
    }
    
    @Override
    public 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();//传入的是UUID
        this.cfg = cfg;
        ...
    }
    
    @Override
    public 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 milliseconds
    public 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 {
    ...
    //加锁
    @Override
    public 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);
        ...
    }
    
    //解锁
    @Override
    public 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();
    }
    ...
    //加锁
    @Override
    public 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=-1
        Long 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 acquired
                if (ttl == null) {
                    break;
                }
                // waiting for message
                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 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 {
    ...
    @Override
    public <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实例时,初始化的命令执行器CommandExecutor
    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 <T> RFuture<T> evalWriteAsync(String key, Codec codec, RedisCommand<T> evalCommandType, String script, List<Object> keys, Object... params) {
        //获取可用的节点,并继续封装一个命令执行器CommandBatchService
        MasterSlaveEntry 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;
    }
    ...
    @Override
    public <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取模
    @Override
    public 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 {
    ...
    @Override
    public 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<>();
    ...
    @Override
    public 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);
    }
    ...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/981631.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)示例1:基础表格

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

【STM32】玩转IIC之驱动MPU6050及姿态解算

目录 前言 一.MPU6050模块介绍 1.1MPU6050简介 1.2 MPU6050的引脚定义 1.3MPU6050寄存器解析 二.MPU6050驱动开发 2.1 配置寄存器 2.2对MPU6050寄存器进行读写 2.2.1 写入寄存器 2.2.2读取寄存器 2.3 初始化MPU6050 2.3.1 设置工作模式 2.3.2 配置采样率 2.3.3 启…

三维重建(十五)——多尺度(coarse-to-fine)

文章目录 一、多尺度与图像金字塔:从全局结构到局部细节二、特征提取与匹配2.1 从数据采集的角度2.2 从数据增强的角度2.3 从特征提取的方式三、以多尺度的方式使用特征3.1 特征提取与匹配3.1.1 多尺度特征检测3.1.2 金字塔匹配3.2 深度估计与立体匹配3.2.1 多尺度立体匹配3.2…

TMS320F28P550SJ9学习笔记2:Sysconfig 配置与点亮LED

今日学习使用Sysconfig 对引脚进行配置&#xff0c;并点亮开发板上的LED4 与LED5 我的单片机开发板平台是 LAUNCHXL_F28P55x 我是在上文描述的驱动库C2000ware官方例程example的工程基础之上进行添加功能的 该例程路径如下&#xff1a;D:\C2000Ware_5_04_00_00\driverlib\f28p…

[Windows] 批量为视频或者音频生成字幕 video subtitle master 1.5.2

Video Subtitle Master 1.5.2 介绍 Video Subtitle Master 1.5.2 是一款功能强大的客户端工具&#xff0c;能够批量为视频或音频生成字幕&#xff0c;还支持批量将字幕翻译成其他语言。该工具具有跨平台性&#xff0c;无论是 mac 系统还是 windows 系统都能使用。 参考原文&a…

Vue3的核心语法【未完】

Vue3的核心语法 OptionsAPI与CompositionAPI Options API&#xff08;选项式&#xff09; 和 Composition API &#xff08;组合式&#xff09;是 Vue.js 中用于构建组件的两种不同方式。Options API Options API Options API 是 Vue 2 中的传统模式&#xff0c;并在 Vue 3…

计算机视觉|ViT详解:打破视觉与语言界限

一、ViT 的诞生背景 在计算机视觉领域的发展中&#xff0c;卷积神经网络&#xff08;CNN&#xff09;一直占据重要地位。自 2012 年 AlexNet 在 ImageNet 大赛中取得优异成绩后&#xff0c;CNN 在图像分类任务中显示出强大能力。随后&#xff0c;VGG、ResNet 等深度网络架构不…

BUU44 [BJDCTF2020]ZJCTF,不过如此1 [php://filter][正则表达式get输入数据][捕获组反向引用][php中单双引号]

题目&#xff1a; 我仿佛见到了一位故人。。。也难怪&#xff0c;题目就是ZJCTF 按要求提交/?textdata://,I have a dream&filenext.php后&#xff1a; ......不太行&#xff0c;好像得用filephp://filter/convert.base64-encode/resourcenext.php 耶&#xff1f;那 f…

区块链-未来世界的网络形态?

前言 各位读者们&#xff0c;时隔半年作者终于想起了自己的账号密码&#xff0c;今天终于又双叒叕更新啦。今天带给大家的内容仍旧是区块链相关&#xff0c;主要谈谈作者对区块链行业的看法&#xff0c;废话不多说让我们开始发车(扶稳坐好)。 引言 过去的几个月中&#xff0c;比…

Linux总结

1 用户与用户组管理 1.1 用户与用户组 //linux用户和用户组 Linux系统是一个多用户多任务的分时操作系统 使用系统资源的用户需要账号进入系统 账号是用户在系统上的标识&#xff0c;系统根据该标识分配不同的权限和资源 一个账号包含用户和用户组 //用户分类 超级管理员 UID…

掌握 findIndex、push 和 splice:打造微信小程序的灵活图片上传功能✨

文章目录 ✨ 掌握 findIndex、push 和 splice&#xff1a;打造微信小程序的灵活图片上传功能 &#x1f31f;示例场景&#xff1a;小程序图片上传&#x1f33c; 认识 findIndex定义语法在代码中的应用示例当前行为 &#x1f680; 认识 push定义语法在代码中的应用示例特点 ✂️ …

【Java】—— 堆

一、堆的定义 在计算机科学中&#xff0c;堆&#xff08;heap&#xff09;是一种特殊的树状数据结构。用于存储和管理数据。堆通常用于实现优先队列。其中具有最高&#xff08;或最低&#xff09;优先级的元素始终位于堆的根部。 堆分为最小堆和最大堆两种类型&#xff1a; …

Windows 使用 Docker + WSL2 部署 Ollama(AMD 显卡推理)搭建手册‌

Windows 使用 Docker WSL2 部署 Ollama&#xff08;AMD 显卡推理&#xff09;搭建手册‌ ‌手册目标‌ 在 Windows 11 上通过 ‌Docker WSL2‌ 调用 AMD 显卡运行 Ollama 推理服务。 实现 ‌低延迟、高性能的本地模型推理‌&#xff0c;同时不影响 Windows 正常使用。 标记…

【每天认识一个漏洞】shiro反序列化漏洞

&#x1f31d;博客主页&#xff1a;菜鸟小羊 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 以下是在实际业务中遇到的一个漏洞&#xff0c;仅用来学习&#xff0c;通过暴露的 /actuator/heapdump 端点获取 Shiro key…

【AI大模型】DeepSeek + Kimi 高效制作PPT实战详解

目录 一、前言 二、传统 PPT 制作问题 2.1 传统方式制作 PPT 2.2 AI 大模型辅助制作 PPT 2.3 适用场景对比分析 2.4 最佳实践与推荐 三、DeepSeek Kimi 高效制作PPT操作实践 3.1 Kimi 简介 3.2 DeepSeek Kimi 制作PPT优势 3.2.1 DeepSeek 优势 3.2.2 Kimi 制作PPT优…

音频3A测试--AGC(自动增益)和NS(降噪)测试

一、测试前期准备 一台电脑&#xff1a;用于作为控制播放和录制数据&#xff1b; 一台音频处理器(调音台)&#xff1a;控制每个通道播放的数据&#xff0c;如噪声、人工头、模拟设备B输入的数据、收集标准麦克风&#xff0c;设备A处理完成的数据&#xff1b; 四个高保真音响&…

zabbix配置邮件告警

目录 实现步骤&#xff1a; 实现目的&#xff1a; 1.在监控端操作&#xff1a; 2.web界面部署 ​​​​​​​实现步骤&#xff1a; 1、在 zabbix服务端配置邮件发送脚本和修改 zabbix服务端配置文件; 2、在 zabbix前端控制台进行相关设置。 实现目的&#xff1a; Zab…

PHP fastadmin 学习

安装php环境安装mysql插件 修改 php.ini下载 phpstudy、fastadmin 错误 安装FastAdmin could not find driver 参考链接 安装插件 创建1.php <? phpinfo(); ?>运行 http://127.0.0.1/1.php 查看 POD 页面访问404 伪静态 Apache <IfModule mod_rewrite.c> O…

PARETO PROMPT OPTIMIZATION

题目 帕累托提示优化 论文地址&#xff1a;https://openreview.net/forum?idHGCk5aaSvE 摘要 自然语言迅速优化或及时工程已成为一种强大的技术&#xff0c;可以解锁大型语言模型&#xff08;LLMS&#xff09;的各种任务的潜力。尽管现有方法主要集中于最大化LLM输出的单一特…

Agent智能体是什么?

文章目录 一、Agent的起源与发展1.1时间线1.2核心驱动力 二、Agent的定义与架构2.1基本定义2.2典型结构&#xff08;以GPTs为例&#xff09; 三、OpenAI的Agent演进路径3.1关键阶段3.2技术支撑3.3 GPTs生态经济模型 四、其他Agent平台对比五、Agent实践案例5.1文本处理自动化5.…