4、Redis分布式锁原理解析

news2025/1/16 21:48:48

目录

1、Redisson lock 方法原理解析

1. 如果指定了过期时间

2. 如果没有指定过期时间

3. lock 方法的主要步骤

Redisson lock 方法完整代码

分步骤解释

步骤 1:尝试获取锁

步骤 2:获取锁失败,发起订阅

步骤 3:循环等待锁释放和尝试获取锁

小结

2、Redisson tryLock 方法原理解析

1. 如果指定了过期时间

2. 如果没有指定过期时间

3. tryLock 方法的主要步骤

Redisson tryLock 方法完整代码

分步骤解释

步骤 1:尝试获取锁

步骤 2:获取锁失败,计算剩余时间并发起订阅

步骤 3:循环等待锁释放和尝试获取锁

小结

3、Redisson unlock 方法原理解析

unlock 方法的主要步骤

Redisson unlock 方法完整代码

分步骤解释

步骤 1:调用 unlock 方法

步骤 2:异步解锁操作

步骤 3:等待异步操作完成

小结


1、Redisson lock 方法原理解析

1. 如果指定了过期时间
  • 异步续命机制(Watchdog 机制)不再生效,锁会在指定的时间过期并自动释放。
2. 如果没有指定过期时间
  • 启动 Watchdog 机制,自动续命锁,直到显式调用 unlock() 方法释放锁为止。
3. lock 方法的主要步骤

以下是 Redisson lock 方法的完整代码及其详细分步骤解释。

Redisson lock 方法完整代码

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
    long threadId = Thread.currentThread().getId();  // 获取当前线程ID
    Long ttl = tryAcquire(-1, leaseTime, unit, threadId);  // 尝试获取锁,等待时间为-1,表示无限等待
    if (ttl == null) {  // 如果成功获取到锁,直接返回
        return;
    }

    CompletableFuture<RedissonLockEntry> future = subscribe(threadId);  // 订阅锁释放通知
    pubSub.timeout(future);  // 设置超时回调
    RedissonLockEntry entry;
    if (interruptibly) {
        entry = commandExecutor.getInterrupted(future);  // 获取可中断的锁条目
    } else {
        entry = commandExecutor.get(future);  // 获取锁条目
    }

    try {
        while (true) {
            ttl = tryAcquire(-1, leaseTime, unit, threadId);  // 尝试重新获取锁
            if (ttl == null) {  // 如果成功获取到锁,退出循环
                break;
            }

            if (ttl >= 0) {
                try {
                    entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);  // 等待锁释放通知
                } catch (InterruptedException e) {
                    if (interruptibly) {
                        throw e;
                    }
                    entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);  // 再次等待锁释放通知
                }
            } else {
                if (interruptibly) {
                    entry.getLatch().acquire();  // 等待锁释放通知
                } else {
                    entry.getLatch().acquireUninterruptibly();  // 等待锁释放通知,不可中断
                }
            }
        }
    } finally {
        unsubscribe(entry, threadId);  // 取消订阅
    }
}

分步骤解释

步骤 1:尝试获取锁
  • 方法调用tryAcquire(-1, leaseTime, unit, threadId)
  • 解释
    • 使用 Lua 脚本尝试原子性地获取锁。
    • 如果锁不存在,创建新锁并设置过期时间。
    • 如果锁存在并且由当前线程持有,增加锁的重入计数并重新设置过期时间。
    • 如果成功获取到锁,返回 null,否则返回锁的剩余存活时间。
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    return evalWrite(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
        "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()), unit.toMillis(leaseTime), getLockName(threadId));
}
  • Lua 脚本原理

    1. 检查锁是否存在

      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;
      
      • 如果锁不存在(exists 返回 0),则创建一个新的锁,并将其设置为当前线程持有,同时设置过期时间。
    2. 检查锁是否由当前线程持有

      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;
      
      • 如果锁已经存在并且由当前线程持有(hexists 返回 1),则增加锁的重入计数,并重新设置过期时间。
    3. 返回锁的剩余存活时间

      return redis.call('pttl', KEYS[1]);
      
      • 如果锁存在且不由当前线程持有,则返回锁的剩余存活时间。
步骤 2:获取锁失败,发起订阅

如果初次尝试获取锁失败,Redisson 会订阅锁的释放通知。

  • 方法调用subscribe(threadId)
  • 解释
    • 如果初次尝试获取锁失败,Redisson 会订阅锁的释放通知。
    • 通过 subscribe 方法订阅锁的释放通知,以便在锁被释放时能够及时收到通知。
    • pubSub.timeout(future) 设置超时回调,以防订阅过程中出现问题。
    • 使用 commandExecutor.get 或 commandExecutor.getInterrupted 获取订阅结果,根据是否可中断进行选择。
CompletableFuture<RedissonLockEntry> future = subscribe(threadId);
pubSub.timeout(future);
RedissonLockEntry entry;
if (interruptibly) {
    entry = commandExecutor.getInterrupted(future);
} else {
    entry = commandExecutor.get(future);
}
步骤 3:循环等待锁释放和尝试获取锁

在等待锁释放期间,Redisson 会进入一个循环,不断尝试重新获取锁。

  • 代码块
try {
    while (true) {
        ttl = tryAcquire(-1, leaseTime, unit, threadId);
        if (ttl == null) {
            break;
        }

        if (ttl >= 0) {
            try {
                entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                if (interruptibly) {
                    throw e;
                }
                entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
            }
        } else {
            if (interruptibly) {
                entry.getLatch().acquire();
            } else {
                entry.getLatch().acquireUninterruptibly();
            }
        }
    }
} finally {
    unsubscribe(entry, threadId);
}
  • 解释
    1. 尝试获取锁

      • 在循环中,Redisson 不断调用 tryAcquire 方法尝试获取锁。
      • 如果成功获取到锁,退出循环。
    2. 等待锁释放通知

      • 如果获取锁失败且锁的剩余存活时间大于 0,Redisson 会等待锁释放通知。
      • 使用 entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS) 方法在指定时间内等待锁的释放。
    3. 重复尝试获取锁

      • 在锁释放或等待超时后,Redisson 会继续尝试获取锁,直到成功或显式中断。
    4. 取消订阅

      • 在获取锁成功或最终失败后,调用 unsubscribe(entry, threadId) 取消订阅锁的释放通知。

小结

  • 指定过期时间

    • 锁会在指定的时间过期并自动释放,异步续命机制不再生效。
  • 未指定过期时间

    • 启动 Watchdog 机制,自动续命锁,确保锁在持有期间不会被自动释放,直到显式调用 unlock() 方法释放锁为止。

2、Redisson tryLock 方法原理解析

tryLock 方法与 lock 方法不同的是,tryLock 方法在获取锁失败时不会一直阻塞,而是根据指定的等待时间和租约时间进行尝试,并返回是否成功获取锁。

1. 如果指定了过期时间
  • 异步续命机制(Watchdog 机制)不再生效,锁会在指定的时间过期并自动释放。
2. 如果没有指定过期时间
  • 启动 Watchdog 机制,自动续命锁,直到显式调用 unlock() 方法释放锁为止。
3. tryLock 方法的主要步骤

以下是 Redisson tryLock 方法的完整代码及其详细分步骤解释。

Redisson tryLock 方法完整代码

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
    long time = unit.toMillis(waitTime);
    long current = System.currentTimeMillis();
    long threadId = Thread.currentThread().getId();
    Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);

    if (ttl == null) {
        return true;  // 成功获取到锁
    }

    time -= System.currentTimeMillis() - current;
    if (time <= 0) {
        acquireFailed(waitTime, unit, threadId);
        return false;
    }

    current = System.currentTimeMillis();
    CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
    try {
        subscribeFuture.get(time, TimeUnit.MILLISECONDS);
    } catch (TimeoutException e) {
        if (!subscribeFuture.completeExceptionally(new RedisTimeoutException(
                "Unable to acquire subscription lock after " + time + "ms. " +
                        "Try to increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."))) {
            subscribeFuture.whenComplete((res, ex) -> {
                if (ex == null) {
                    unsubscribe(res, threadId);
                }
            });
        }
        acquireFailed(waitTime, unit, threadId);
        return false;
    } catch (ExecutionException e) {
        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 -= System.currentTimeMillis() - currentTime;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }

            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 -= System.currentTimeMillis() - currentTime;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }
        }
    } finally {
        unsubscribe(commandExecutor.getNow(subscribeFuture), threadId);
    }
}

分步骤解释

步骤 1:尝试获取锁
  • 方法调用tryAcquire(waitTime, leaseTime, unit, threadId)
  • 解释
    • 使用 Lua 脚本尝试原子性地获取锁。
    • 如果锁不存在,创建新锁并设置过期时间。
    • 如果锁存在并且由当前线程持有,增加锁的重入计数并重新设置过期时间。
    • 如果成功获取到锁,返回 null,否则返回锁的剩余存活时间。
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    RFuture<Long> ttlRemainingFuture;
    if (leaseTime > 0) {
        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 -> {
        if (ttlRemaining == null) {
            if (leaseTime > 0) {
                internalLockLeaseTime = unit.toMillis(leaseTime);
            } else {
                scheduleExpirationRenewal(threadId);
            }
        }
        return ttlRemaining;
    });
    return new CompletableFutureWrapper<>(f);
}
  • Lua 脚本原理

    1. 检查锁是否存在

      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;
      
      • 如果锁不存在(exists 返回 0),则创建一个新的锁,并将其设置为当前线程持有,同时设置过期时间。
    2. 检查锁是否由当前线程持有

      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;
      
      • 如果锁已经存在并且由当前线程持有(hexists 返回 1),则增加锁的重入计数,并重新设置过期时间。
    3. 返回锁的剩余存活时间

      return redis.call('pttl', KEYS[1]);
      
      • 如果锁存在且不由当前线程持有,则返回锁的剩余存活时间。
步骤 2:获取锁失败,计算剩余时间并发起订阅

如果初次尝试获取锁失败,Redisson 会订阅锁的释放通知。

  • 方法调用subscribe(threadId)
  • 解释
    • 如果初次尝试获取锁失败,Redisson 会订阅锁的释放通知。
    • 通过 subscribe 方法订阅锁的释放通知,以便在锁被释放时能够及时收到通知。
    • pubSub.timeout(future) 设置超时回调,以防订阅过程中出现问题。
    • 使用 commandExecutor.get 或 commandExecutor.getInterrupted 获取订阅结果,根据是否可中断进行选择。
CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
try {
    subscribeFuture.get(time, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
    if (!subscribeFuture.completeExceptionally(new RedisTimeoutException(
            "Unable to acquire subscription lock after " + time + "ms. " +
                    "Try to increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."))) {
        subscribeFuture.whenComplete((res, ex) -> {
            if (ex == null) {
                unsubscribe(res, threadId);
            }
        });
    }
    acquireFailed(waitTime, unit, threadId);
    return false;
} catch (ExecutionException e) {
    acquireFailed(waitTime, unit, threadId);
    return false;
}
步骤 3:循环等待锁释放和尝试获取锁

在等待锁释放期间,Redisson 会进入一个循环,不断尝试重新获取锁。

  • 代码块
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 -= System.currentTimeMillis() - currentTime;
        if (time <= 0) {
            acquireFailed(waitTime, unit, threadId);
            return false;
        }

        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 -= System.currentTimeMillis() - currentTime;
        if (time <= 0) {
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
    }
} finally {
    unsubscribe(commandExecutor.getNow(subscribeFuture), threadId);
}
  • 解释
    1. 尝试获取锁
      • 在循环中,Redisson 不断调用 tryAcquire 方法尝试获取锁。
      • 如果成功获取到锁,退出循环

并返回 true

  1. 等待锁释放通知

    • 如果获取锁失败且锁的剩余存活时间大于 0,Redisson 会等待锁释放通知。
    • 使用 entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS) 方法在指定时间内等待锁的释放。
  2. 重复尝试获取锁

    • 在锁释放或等待超时后,Redisson 会继续尝试获取锁,直到成功或显式中断。
  3. 取消订阅

    • 在获取锁成功或最终失败后,调用 unsubscribe(entry, threadId) 取消订阅锁的释放通知。

小结

Redisson 的 tryLock 方法提供了一种非阻塞的分布式锁机制,通过以下几个步骤实现:

  1. 尝试获取锁

    • 通过 Lua 脚本进行原子性操作,确保获取锁的过程是线程安全的。
    • 如果成功获取到锁,返回 true
  2. 获取锁失败,计算剩余时间并发起订阅

    • 如果初次获取锁失败,Redisson 会订阅锁的释放通知,并等待一定时间。
  3. 循环等待锁释放和尝试获取锁

    • 在等待锁释放期间,Redisson 进入循环,不断尝试重新获取锁。
    • 使用 entry.getLatch().tryAcquire 方法在指定时间内等待锁的释放。
    • 如果成功获取到锁,退出循环并返回 true,否则在时间用尽后返回 false

3、Redisson unlock 方法原理解析

unlock 方法用于释放已经持有的锁,确保其他线程可以获取锁。Redisson 通过 Lua 脚本原子性地执行解锁操作,以保证解锁过程的安全性和一致性。

unlock 方法的主要步骤

以下是 Redisson unlock 方法的完整代码及其详细分步骤解释。

Redisson unlock 方法完整代码

@Override
public void unlock() {
    long threadId = Thread.currentThread().getId();
    RFuture<Boolean> future = unlockAsync(threadId);
    commandExecutor.get(future);
}

private <T> RFuture<T> unlockAsync(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.<Object>asList(getRawName(), getChannelName()), 
            LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}

分步骤解释

步骤 1:调用 unlock 方法
  • 方法调用unlock()
  • 解释
    • 获取当前线程的 ID。
    • 调用 unlockAsync 方法进行异步解锁操作。
    • 使用 commandExecutor.get(future) 等待异步操作完成。
@Override
public void unlock() {
    long threadId = Thread.currentThread().getId();
    RFuture<Boolean> future = unlockAsync(threadId);
    commandExecutor.get(future);
}
步骤 2:异步解锁操作
  • 方法调用unlockAsync(threadId)
  • 解释
    • 使用 Lua 脚本原子性地执行解锁操作。
    • 如果锁由当前线程持有,减少锁的重入计数。
    • 如果重入计数减到 0,删除锁并发布解锁消息。
private <T> RFuture<T> unlockAsync(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.<Object>asList(getRawName(), getChannelName()), 
            LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}
  • Lua 脚本原理

    1. 检查锁是否由当前线程持有

      if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
          return nil;
      end;
      
      • 如果锁不由当前线程持有(hexists 返回 0),返回 nil,表示解锁失败。
    2. 减少锁的重入计数

      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;
      
      • 如果锁由当前线程持有,减少锁的重入计数(hincrby)。
      • 如果重入计数大于 0,重新设置锁的过期时间,并返回 0,表示锁仍然被持有。
      • 如果重入计数减到 0,删除锁(del),并发布解锁消息(publish),返回 1,表示锁已释放。
步骤 3:等待异步操作完成
  • 方法调用commandExecutor.get(future)
  • 解释
    • 等待异步解锁操作完成。
    • 如果解锁操作失败,抛出异常。
commandExecutor.get(future);

小结

Redisson 的 unlock 方法通过以下几个步骤实现安全可靠的解锁操作:

  1. 调用 unlock 方法

    • 获取当前线程的 ID。
    • 调用 unlockAsync 方法进行异步解锁操作。
    • 使用 commandExecutor.get(future) 等待异步操作完成。
  2. 异步解锁操作

    • 使用 Lua 脚本原子性地执行解锁操作,确保操作的安全性和一致性。
    • 如果锁由当前线程持有,减少锁的重入计数。
    • 如果重入计数减到 0,删除锁并发布解锁消息。
  3. 等待异步操作完成

    • 等待异步解锁操作完成,如果解锁操作失败,抛出异常。

4、流程图

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

64.SAP ME与SAP ERP物料接口增加自定义字段的方法

目录 1.过程介绍 2.配置过程 2.1SAP ME里增加自定义字段 2.2SAP ME修改接口文件 2.3SAP ME刷新接口结构 2.4测试检查接口字段是否传输过来 1.过程介绍 首先&#xff0c;SAP ME与SAP ERP之间的物料主数据接口&#xff0c;采用IDOC的方式。如果需要增加自定…

操作系统精选题(三)(简答题、概念题)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;操作系统 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 前言 简答题 一、对 CPU、内存、外设并…

基于局域网下的服务器连接、文件传输以及内网穿透教程 | 服务器连接ssh | 服务器文件传输scp | 内网穿透frp | 研究生入学必备 | 深度学习必备

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;本篇博客分享的是基于局域网下的服务器连接&#x1f517;、文件传输以及内网穿透教程&#xff0c;内容非常完备✨&#xff0c;涵盖了在服务器上做深度学…

ueditor集成秀米编辑器

ueditor集成秀米编辑器 一、背景二、集成秀米编辑器流程2.1、新增秀米插件的按钮&#xff0c;显示在我们的富文本编辑器上2.2、点击该按钮&#xff0c;可以呼出一个iframe&#xff0c;这个iframe引用的是秀米自己的编辑器页面2.3、要是有图片&#xff0c;需要再修改配置哈2.4、…

密码学:对称加密算法、非对称加密算法、哈希算法

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 这一章节我们需…

【Python】利用代理IP爬取当当网数据做数据分析

前言 在数字化浪潮的推动下&#xff0c;电商平台已经彻底改变了我们的购物方式。从简单的在线交易到复杂的用户交互&#xff0c;电商平台积累了海量的用户数据。这些数据&#xff0c;如同隐藏在深海中的宝藏&#xff0c;等待着被发掘和利用。通过分析用户的浏览、搜索、购买等行…

Redis学习——Redisson 分布式锁集成及其简单使用

文章目录 引言1. Redisson概述1.1 Redisson的基本概念1.2 Redisson的主要功能1.3 Redisson的优点 2. 开发环境3. Redisson的安装与配置3.1 添加依赖3.2 配置Redisson 4. 使用Redisson4.1 可重入锁4.1.1 可重入锁的概念4.1.2 可重入锁的实现原理4.1.3 简单使用锁的获取和释放 4.…

数据恢复篇:如何在电脑上恢复已删除和丢失的音乐文件

尽管流媒体网络非常流行&#xff0c;但许多人仍然选择将音乐下载并保存在 PC 本地。这会使文件面临丢失或意外删除的风险。 幸运的是&#xff0c;您可以使用数据恢复软件恢复已删除的音乐和其他文件类型。这篇文章讨论了这些解决方案以及如何使用奇客数据恢复检索丢失的音乐文…

Java面试题--JVM大厂篇之深入了解G1 GC:高并发、响应时间敏感应用的最佳选择

引言&#xff1a; 在现代Java应用的性能优化中&#xff0c;垃圾回收器&#xff08;GC&#xff09;的选择至关重要。对于高并发、响应时间敏感的应用而言&#xff0c;G1 GC&#xff08;Garbage-First Garbage Collector&#xff09;无疑是一个强大的工具。本文将深入探讨G1 GC适…

抗腐蚀耐腐蚀不锈钢304/316L航空插头插座

不锈钢航空插头是一种专为航空航天、军事、工业等领域设计的连接器&#xff0c;具有高强度、耐腐蚀、耐高温等特点。它们通常用于在高振动、高湿度、高温度等恶劣环境下保持稳定的电气连接。不锈钢航空插头的设计充分考虑了这些极端条件&#xff0c;以确保信号和电源传输的可靠…

机器学习环境搭建

前言 个人笔记&#xff0c;记录框架和小问题&#xff0c;没有太详细记载。。 1、Anaconda安装 下载地址&#xff1a; Free Download | Anaconda &#xff08;慢&#xff09; ​ 国内镜像&#xff1a;https://link.csdn.net/?targethttp%3A%2F%2Fitcxy.xyz%2F241.html 下载…

(PC+WAP)高端大气的装修装潢公司网站模板

(PCWAP)高端大气的装修装潢公司网站模板PbootCMS内核开发的网站模板&#xff0c;该模板适用于装修公司网站、装潢公司网站等企业&#xff0c;当然其他行业也可以做&#xff0c;只需要把文字图片换成其他行业的即可&#xff1b;(PCWAP)&#xff0c;同一个后台&#xff0c;数据即…

【数据结构(邓俊辉)学习笔记】二叉搜索树04——AVL树

文章目录 1.重平衡1.1 AVL BBST1.2 平衡因子1.3 适度平衡1.4 接口1.5 失衡 复衡 2. 插入2.1 单旋2.2 双旋2.3 实现 3. 删除3.1 单旋3.2 双旋3.3 实现 4. &#xff08;3 4&#xff09;-重构4.1 "34"重构4.2 "34"实现4.3 rotateAt4.4 综合评价 1.重平衡 1…

SSZipArchive 解压后 中文文件名乱码问题

不知道什么情况&#xff0c;做为一个三方广泛使用的框架库&#xff0c;会出现这种比较低级的问题&#xff01; 还有中文的文件名解压后显示乱码&#xff01; 经过深入研究排查&#xff0c;发现目录或文件名编码错误&#xff01;但是POD库&#xff0c;不可能直接在里面改&#…

在线疫苗预约小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;工作人员管理&#xff0c;管理员管理&#xff0c;用户管理&#xff0c;疫苗管理&#xff0c;论坛管理&#xff0c;公告管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;公告&#xff0c;疫苗&…

SpringBoot + mkcert ,解决本地及局域网(内网)HTTPS访问

本文主要解决访问SpringBoot开发的Web程序,本地及内网系统,需要HTTPS证书的问题。 我测试的版本是,其他版本不确定是否也正常,测试过没问题的小伙伴,可以在评论区将测试过的版本号留下,方便他人参考: <spring-boot.version>2.3.12.RELEASE</spring-boot.vers…

redis-cluster(集群模式搭建)

redis中间件版本: redis-5.0.5环境介绍 这里使用服务器数量3&#xff0c;分别为172.0.0.1&#xff0c;172.0.0.2&#xff0c;172.0.0.3&#xff0c;每台机器redis节点数量2个&#xff0c;共6个redis节点构成redis-cluster模式。编译安装包 在172.0.0.1的机器上进入安装目录 cd …

【6.26更新】Win10 22H2 19045.4598镜像:免费下载!

当前微软已经发布了六月最新的KB5039299更新补丁&#xff0c;用户完成升级后&#xff0c;系统版本号将更新至19045.4598。此次更新解决了任务栏上应用跳转列表失败、可能导致系统无法从休眠状态恢复等多个问题&#xff0c;推荐大家升级。如果您不知道去哪里才能下载到该版本&am…

【JavaEE】JVM

文章目录 一、JVM 简介二、JVM 运行流程三、JVM 运行时数据区1、堆&#xff08;线程共享&#xff09;2、Java虚拟机栈&#xff08;线程私有&#xff09;3、本地方法栈&#xff08;线程私有&#xff09;4、程序计数器&#xff08;线程私有&#xff09;5、方法区&#xff08;线程…

002-关于Geogebra软件的介绍及与MatLab的区别

为什么要学Geogebra&#xff1f; 因为和MatLab的科学计算相比&#xff0c;GeoGebra重点突出教学展示&#xff0c;对于教师、学生人群来讲再合适不过了&#xff0c;尤其是可以融入到PPT里边呈现交互式动画&#xff0c;想想听众的表情&#xff01;这不就弥补了看到PPT播放数学公…