【Redis】笔记|第7节|大厂生产级Redis高并发分布式锁实战(二)

news2025/6/6 7:14:36

一、Redis主从架构锁失效问题解析

1. 核心问题背景

在Redis主从架构中,分布式锁失效的核心风险源于主从复制的异步特性主节点故障后的角色切换。即使客户端仅操作主节点写入,主节点宕机时未同步的锁数据可能导致新主节点允许重复加锁。

2. 主从切换延迟导致锁失效的原理

  1. 异步复制的天然缺陷
    • 主节点处理写请求(如加锁)后,先在本地内存执行命令,再异步同步到从节点。若主节点在同步完成前宕机,从节点的数据可能不一致。
  2. 典型失效场景
    • 客户端A在主节点加锁成功,但锁数据未同步到从节点。
    • 主节点宕机,从节点(未存储锁数据)升级为新主节点。
    • 客户端B向新主节点请求加锁,因无锁数据而成功获取锁。
    • 结果:客户端A和B同时持有锁,分布式锁失效。

3. 主节点故障的直接影响

  1. 数据丢失风险
    • 主节点宕机时,未同步到从节点的锁数据永久丢失。新主节点因无锁记录,导致后续加锁请求绕过原有锁逻辑。
  2. 故障转移的隐患
    • Redis Sentinel或手动故障转移过程中,若从节点未及时同步锁数据,升级后的新主节点会成为锁失效的根源。

4. 解决方案与优化策略

  1. Redlock算法(多节点方案)
    • 核心思想:基于多个独立Redis实例(至少3个),通过多数派选举机制降低单节点故障影响。
      • 客户端依次向所有节点请求加锁,超过半数成功则认为加锁成功。
      • 即使某个主节点宕机,只要多数节点未同时故障,锁仍有效。
    • 缺点:复杂度较高,性能略低于单节点锁;Redisson官方已废弃RedLock,推荐使用MultiLock替代。
  2. 强制主从同步(配置优化)
    • 通过Redis配置参数限制主节点写入,确保从节点同步:
min-slaves-to-write 1       # 至少1个从节点同步成功,主节点才接受写请求
min-slaves-max-lag 10       # 从节点同步延迟超过10秒时,主节点拒绝写请求
    • 风险:若从节点长时间无法同步,可能导致请求超时,影响系统可用性。
  1. 选择强一致性存储方案
    • 改用ZooKeeper、etcd等支持强一致性的分布式协调工具:
      • ZooKeeper通过ZAB协议保证数据同步,选举出的新主节点必为数据一致的节点。
    • 优点:彻底避免异步复制导致的锁失效问题。

二、从CAP角度剖析Redis与ZooKeeper实现分布式锁的区别

1. CAP 定理核心概念

  1. 一致性(Consistency):所有节点在同一时刻看到相同的数据(强一致性)。
  2. 可用性(Availability):系统在正常响应时间内处理请求,不会因故障拒绝服务。
  3. 分区容错性(Partition tolerance):系统在网络分区(节点间通信中断)时仍能正常运行。

CAP 定理结论:分布式系统无法同时满足三者,必须舍弃其一。

2. Redis 分布式锁的 CAP 定位

        1. 单节点 / 主从架构(默认异步复制)
  • 类型AP 系统(牺牲一致性,保证可用性和分区容错性)
  • 分析
    • 一致性(弱)
      • 主从架构采用异步复制,主节点宕机时未同步的锁数据会丢失,导致新主节点允许重复加锁(如前文主从切换延迟问题)。
      • 单节点模式无此问题,但单机故障会导致锁服务不可用。
    • 可用性(高)
      • 主节点正常时可快速响应加锁 / 释放请求,从节点支持读请求(提升读可用性)。
      • 主节点宕机后,通过 Sentinel 快速选举新主节点,恢复服务(牺牲短暂可用性,但整体可用性较高)。
    • 分区容错性(支持)
      • 主从节点通过异步复制容忍网络分区,允许分区期间主节点继续服务(但可能产生数据不一致)。
  • 典型场景

适用于非强一致性需求、追求高可用的业务(如缓存、非核心业务锁)。

        2. Redlock 算法(多节点方案)
  • 类型尽力而为的 AP 系统(尝试提升一致性,但未完全解决)
  • 分析
    • 通过多个独立 Redis 实例(至少 3 个)实现 “多数派加锁”,降低单节点故障导致的一致性问题。
    • 但本质仍是异步复制,若多数节点同时故障(如网络分区),仍可能出现锁失效(CAP 中偏向 AP)。

3. ZooKeeper 分布式锁的 CAP 定位

        1. 基于 ZAB 协议的 CP 系统
  • 类型CP 系统(牺牲可用性,保证一致性和分区容错性)
  • 分析
    • 一致性(强)
      • 通过 ZAB 协议(类似 Paxos)实现原子广播,确保所有节点数据一致。
      • 锁通过 “有序临时节点” 实现,选举出的 Leader 节点必为数据一致的节点,避免重复加锁。
    • 可用性(低)
      • 当发生网络分区或 Leader 宕机时,需进行 Leader 选举,期间整个集群不可用(直到选举完成)。
    • 分区容错性(支持)
      • 通过 Leader 选举和数据同步机制,在分区恢复后自动修复数据一致性。
    • 典型场景

        适用于强一致性需求、允许短暂不可用的关键业务(如分布式事务、核心资源锁)。

4. 对比总结:Redis vs. ZooKeeper

维度

Redis(主从 / Redlock)

ZooKeeper

CAP 类型

AP(牺牲一致性,保证可用性)

CP(牺牲可用性,保证一致性)

一致性

弱(异步复制可能丢失锁数据)

强(ZAB 协议保证数据一致)

可用性

高(快速故障转移,短暂不可用)

低(Leader 选举期间不可用)

实现复杂度

简单(单节点 / Redlock 需多实例协调)

复杂(需理解 ZAB 协议和节点监听机制)

典型场景

非核心业务锁、高并发读场景

金融级强一致场景、分布式协调中心

5. 实际应用建议

  1. 优先选 Redis 的场景
    • 业务允许短暂锁失效(如缓存击穿防护)。
    • 需要高吞吐量和低延迟(Redis 单节点性能优于 ZK)。
  2. 优先选 ZooKeeper 的场景
    • 强一致性要求(如分布式事务、分布式选举)。
    • 容忍短暂不可用,但必须保证锁的唯一性(如分布式锁核心场景)。
  3. 替代方案
    • 若追求 AP + 强一致性,可考虑 etcd(基于 Raft 协议的 CP 系统,比 ZK 更易用)。

三、RedLock分布式锁原理及存在问题

RedLock 是 Redis 官方提出的分布式锁方案,旨在解决单实例 Redis 锁的 单点故障问题,其核心思想是通过 多个独立的 Redis 实例(通常建议 ≥5 个)实现 “多数派共识”,确保锁的互斥性和容错性。

核心流程

  1. 获取锁
    • 客户端同时向 所有 Redis 实例 发送 SET key value NX PX TTL 命令(NX 表示仅当键不存在时创建,PX 设置过期时间)。
    • 客户端收集各实例的响应结果,若 至少半数实例(≥n/2+1)返回成功,且总耗时 ≤ TTL,则认为获取锁成功。
    • 若失败,需向 所有实例释放锁(即使部分实例获取锁失败,防止残留锁)。
  2. 释放锁
    • 客户端向 所有实例 发送包含 唯一标识符(UUID) 的释放命令,确保仅释放自己持有的锁(避免误释放其他客户端的锁)。

设计目标

  • 互斥性:同一时刻仅一个客户端持有锁。
  • 容错性:部分实例故障时(≤n/2),锁仍可用。
  • 高可用性:通过多实例异步复制提升可用性(Redis 本身为 AP 系统)。

存在的问题

RedLock 的设计基于 异步复制和松散的多数派共识,存在以下核心缺陷:

1. 时钟同步问题(主因)
  • 场景:若某个 Redis 实例的时钟发生 跳跃(如回退或突然变快),可能导致锁提前过期或延迟释放。
    • 例:客户端 A 在实例 1、2、3 成功获取锁(多数派),但实例 3 的时钟突然回退,其锁提前过期,客户端 B 可能在实例 3 上重新获取锁,导致 锁冲突
  • 影响:违反互斥性,可能引发业务逻辑错误(如重复扣款)。
2. 主从切换引发的锁冗余
  • 场景:Redis 主从复制是 异步的,若主节点在写入锁后未同步到从节点就宕机,新主节点(从节点)可能不存在该锁,导致:
    • 客户端 A 在旧主节点获取锁,但未同步到从节点;旧主节点宕机后,从节点升级为主节点,客户端 B 可在新主节点重新获取锁,两个客户端同时持有锁
  • 本质:Redis 的异步复制无法保证强一致性,RedLock 依赖的 “多数派” 未解决主从切换的 数据不一致窗口 问题。
3. 网络分区下的脑裂
  • 场景:当发生网络分区时,客户端可能在 不同的实例子集 上获取到锁。
    • 例:5 个实例分为 3 个和 2 个分区,客户端 A 在 3 个实例获取锁(多数派),客户端 B 在另外 2 个实例(旧主节点所在分区)也可能获取锁(若旧主节点未感知到分区,仍认为自己是主节点),导致 锁冲突
4. 释放锁的原子性风险
  • 释放锁时需向所有实例发送命令,若某实例因网络延迟未收到释放请求,可能导致锁 长时间占用,影响后续客户端获取锁。
5. 性能与复杂度权衡
  • 相比单实例锁,RedLock 需要 多次网络往返(n 次获取 + n 次释放),延迟较高,吞吐量下降。
  • 运维复杂度增加(需管理多个独立实例)。

社区争议与替代方案

  • 争议:分布式系统专家 Martin Kleppmann 曾质疑 RedLock 的安全性,指出其在时钟同步和主从切换场景下无法保证互斥性。
  • Redis 官方回应:承认 RedLock 不适合 强一致性场景,但适用于 非金融级、高可用需求 的业务(如缓存、临时任务锁)。
  • 替代方案
    • 若需 强一致性:选择 ZooKeeper(基于 Zab 协议的 CP 系统)、etcd(Raft 协议)。
    • 若需轻量级方案:单实例 Redis 锁 + 哨兵(适用于非严格互斥场景)。

四、大促高并发下提升缓存性能100倍-读写锁

1.利用 Redisson 读写锁优化热点缓存的实践

1. 热点缓存场景与读写锁适配性

热点缓存(如商品详情、活动配置)的核心特点是 读多写少

  • 读操作:大量并发请求读取同一份缓存数据;
  • 写操作:少量请求更新缓存(如数据库数据变更后刷新缓存)。

普通互斥锁(如 Redis 单实例 SET 锁)会导致读操作串行化,成为性能瓶颈。而 读写锁(ReadWriteLock)允许:

  • 读锁(共享锁):多个线程/进程可同时持有,支持并发读;
  • 写锁(排他锁):仅一个线程/进程持有,写操作时阻塞所有读写。
2. Redisson 读写锁的优势

Redisson 提供的 RReadWriteLock 是基于 Redis 的分布式读写锁实现,具备以下特性:

  • 跨进程互斥:基于 Redis 的分布式特性,保证不同 JVM 或服务实例间的锁互斥;
  • 可重入性:同一线程可多次获取同一读锁/写锁(需释放对应次数);
  • 自动续期(看门狗):默认每 30 秒自动延长锁的过期时间(需业务执行未完成);
  • 公平锁(可选):按请求顺序分配锁(默认非公平,性能更优)。
3. 基于 Redisson 读写锁的热点缓存实现
步骤 1:Redisson 客户端配置

首先配置 Redisson 连接 Redis(以单实例为例):

Config config = new Config();
config.useSingleServer()
    .setAddress("redis://127.0.0.1:6379")
    .setPassword("your-password")
    .setDatabase(0);
RedissonClient redisson = Redisson.create(config);
步骤 2:定义缓存操作逻辑

核心逻辑:读缓存时用读锁(并发读),写缓存时用写锁(排他写),并处理缓存未命中时的加载逻辑(防缓存击穿)。

public class HotCacheManager {
    private final RedissonClient redisson;
    private final Cache<String, Object> localCache; // 本地缓存(可选,如Caffeine)

    public HotCacheManager(RedissonClient redisson) {
        this.redisson = redisson;
        this.localCache = Caffeine.newBuilder().build();
    }

    /**
     * 读取热点缓存(读锁)
     */
    public Object getHotData(String key) {
        // 1. 先查本地缓存(可选,减少Redis访问)
        Object value = localCache.getIfPresent(key);
        if (value != null) {
            return value;
        }

        // 2. 获取分布式读锁(共享锁)
        RReadWriteLock rwLock = redisson.getReadWriteLock("hot_cache_lock:" + key);
        RLock readLock = rwLock.readLock();

        try {
            // 加读锁(带超时,避免死锁)
            boolean locked = readLock.tryLock(5, 30, TimeUnit.SECONDS);
            if (!locked) {
                throw new RuntimeException("获取读锁失败");
            }

            // 3. 读Redis缓存
            value = redisson.getBucket(key).get();
            if (value != null) {
                localCache.put(key, value); // 更新本地缓存(可选)
                return value;
            }

            // 4. 缓存未命中,升级为写锁加载数据(防缓存击穿)
            RLock writeLock = rwLock.writeLock();
            try {
                // 二次检查(避免多线程重复加载)
                value = redisson.getBucket(key).get();
                if (value == null) {
                    value = loadFromDatabase(key); // 从数据库加载
                    redisson.getBucket(key).set(value, 300, TimeUnit.SECONDS); // 写入Redis缓存
                }
                localCache.put(key, value); // 更新本地缓存(可选)
                return value;
            } finally {
                writeLock.unlock(); // 释放写锁
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("获取锁中断");
        } finally {
            readLock.unlock(); // 释放读锁
        }
    }

    /**
     * 更新热点缓存(写锁)
     */
    public void updateHotData(String key, Object newValue) {
        RReadWriteLock rwLock = redisson.getReadWriteLock("hot_cache_lock:" + key);
        RLock writeLock = rwLock.writeLock();

        try {
            // 加写锁(带超时)
            boolean locked = writeLock.tryLock(5, 30, TimeUnit.SECONDS);
            if (!locked) {
                throw new RuntimeException("获取写锁失败");
            }

            // 更新Redis缓存
            redisson.getBucket(key).set(newValue, 300, TimeUnit.SECONDS);
            // 更新数据库(根据业务需求)
            updateDatabase(key, newValue);
            // 清空本地缓存(可选)
            localCache.invalidate(key);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("获取锁中断");
        } finally {
            writeLock.unlock(); // 释放写锁
        }
    }

    private Object loadFromDatabase(String key) {
        // 模拟数据库查询
        return "db_data_" + key;
    }

    private void updateDatabase(String key, Object value) {
        // 模拟数据库更新
    }
}
关键优化点说明
  • 本地缓存(可选):通过 Caffeine 等本地缓存减少 Redis 访问,提升读性能(需权衡一致性,可设置短过期时间)。
  • 读锁防缓存击穿:缓存未命中时,通过写锁互斥加载数据,避免大量请求穿透到数据库。
  • 锁超时控制tryLock(5, 30, TimeUnit.SECONDS) 表示最多等待 5 秒获取锁,锁最长持有 30 秒(防止线程挂起导致锁无法释放)。

2. Redisson 读写锁核心源码分析(3.6.5 版本)

1.读写锁核心特性
  1. 互斥规则
    • 读读不互斥:多个线程可同时持有读锁
    • 读写互斥:写锁阻塞时,新读锁需等待写锁释放
    • 写写互斥:仅允许一个线程持有写锁
  2. 存储结构
    • 使用 Redis Hash 存储锁元数据,Key 为锁名,包含字段:
      • mode:标识锁类型(read/write
      • UUID:threadId:记录读锁线程的重入次数
      • UUID:threadId:write:记录写锁线程的重入次数
    • 额外键 {lockName}:UUID:threadId:rwlock_timeout:{index} 管理每个读锁的超时

2.加读锁源码解析

入口代码RedissonReadLock#tryLockInnerAsync

核心 Lua 脚本逻辑(简化自 3.6.5 源码):

-- 参数:KEYS[1]=lockName, KEYS[2]=timeoutKeyPrefix, ARGV[1]=leaseTime, ARGV[2]=threadId
local mode = redis.call('hget', KEYS[1], 'mode');
if mode == false then -- 锁不存在
    redis.call('hset', KEYS[1], 'mode', 'read');          -- 设置模式为读锁
    redis.call('hset', KEYS[1], ARGV[2], 1);             -- 记录线程重入次数=1
    redis.call('set', KEYS[2] .. ':1', 1);               -- 创建超时控制键
    redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]);     -- 设置超时
    redis.call('pexpire', KEYS[1], ARGV[1]);             -- 设置主锁超时
    return nil; -- 加锁成功
end;
if mode == 'read' or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[2]..':write')) then 
    local counter = redis.call('hincrby', KEYS[1], ARGV[2], 1); -- 重入次数+1
    redis.call('set', KEYS[2] .. ':' .. counter, 1);     -- 为新重入层级创建超时键
    redis.call('pexpire', KEYS[1], ARGV[1]);             -- 续期主锁
    return nil; -- 加锁成功
end;
return redis.call('pttl', KEYS[1]); -- 返回锁剩余时间(需重试)

关键逻辑

  • 首次加锁:初始化 Hash 结构,标识为读模式
  • 重入或同一线程降级(持有写锁时再加读锁):增加重入计数,新建超时键
  • 冲突处理:若存在写锁且非当前线程,返回锁 TTL 触发客户端重试

3. 加写锁源码解析

入口代码RedissonWriteLock#tryLockInnerAsync

核心 Lua 脚本

-- 参数:KEYS[1]=lockName, ARGV[1]=leaseTime, ARGV[2]=writeThreadId
local mode = redis.call('hget', KEYS[1], 'mode');
if mode == false then -- 锁不存在
    redis.call('hset', KEYS[1], 'mode', 'write');         -- 设置模式为写锁
    redis.call('hset', KEYS[1], ARGV[2], 1);             -- 记录写锁重入次数=1
    redis.call('pexpire', KEYS[1], ARGV[1]);             -- 设置超时
    return nil; -- 加锁成功
end;
if mode == 'write' and 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]); -- 存在读锁或其他写锁,返回TTL

关键逻辑

  • 写锁互斥:若当前存在读锁或其他线程的写锁,直接返回 TTL 等待
  • 可重入:同一线程多次获取写锁时增加计数
  • 降级支持:持有写锁的线程可再加读锁(通过检查 write 标识实现)

4.释放锁源码解析

1. 释放读锁RedissonReadLock#unlockInnerAsync

local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); -- 重入次数-1
if counter == 0 then 
    redis.call('hdel', KEYS[1], ARGV[2]);               -- 删除线程条目
end;
redis.call('del', KEYS[3] .. ':' .. (counter+1));       -- 删除对应超时键
if redis.call('hlen', KEYS[1]) > 1 then 
    -- 存在其他读锁:计算最大剩余时间并更新主锁超时
    local maxRemainTime = calculateMaxTime(); 
    redis.call('pexpire', KEYS[1], maxRemainTime); 
    return 0; 
else 
    redis.call('del', KEYS[1]);                         -- 无其他锁则彻底删除
    return 1; 
end;

2. 释放写锁RedissonWriteLock#unlockInnerAsync

local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
if counter == 0 then 
    redis.call('hdel', KEYS[1], ARGV[3]);               -- 删除写锁条目
    if redis.call('hlen', KEYS[1]) == 1 then 
        redis.call('del', KEYS[1]);                     -- 无其他锁则删除
    else 
        redis.call('hset', KEYS[1], 'mode', 'read');    -- 存在读锁则降级为读模式
    end; 
    return 1; 
else 
    redis.call('pexpire', KEYS[1], ARGV[2]);            -- 重入次数未归零,仅续期
    return 0; 
end;

关键逻辑

  • 读锁释放:需同步清理超时键,主锁超时按剩余读锁的最大 TTL 更新
  • 写锁释放:重入计数归零时,若存在等待的读锁,将模式降级为 read

5.看门狗机制(续期)
  • 读锁续期:遍历所有读锁线程,为每个 rwlock_timeout 键续期

见 RedissonReadLock#renewExpirationAsync

for (String key : allKeys) {
    for (int i = 1; i <= reentrantCount; i++) {
        String timeoutKey = timeoutPrefix + ":" + key + ":rwlock_timeout:" + i;
        redis.command("pexpire", timeoutKey, leaseTime);
    }
}
  • 写锁续期:与普通锁相同,仅续期主锁 Key

6.锁升降级规则
  • 支持降级:持有写锁的线程可再获取读锁(避免死锁)
  • 禁止升级:持有读锁时尝试获取写锁会阻塞(因读写互斥,易死锁)

示例:

rwLock.readLock().lock();
rwLock.writeLock().lock(); // 此处永久阻塞!

总结

Redisson 读写锁通过 Redis Hash 结构 和 多键协同 实现互斥规则:

  1. 模式标识mode)决定锁类型,控制读写互斥;
  2. 超时键分离 解决读锁并发续期的一致性;
  3. 写锁降级 通过先释放写锁保留读锁实现,但升级会导致死锁。

建议结合 org.redisson.RedissonReadWriteLock 类调试以深入理解流程。

3. Redisson 读写锁总结

优势
  • 高性能读并发:读锁共享,支持多客户端同时读取热点缓存。
  • 强一致性:基于 Redis 的分布式特性,保证跨进程的锁互斥。
  • 自动续期:内置看门狗机制(默认 30 秒续期),防止业务执行期间锁过期。
注意事项
  • 锁粒度控制:避免锁粒度过大(如全局锁)导致竞争激烈,建议按业务维度(如商品 ID)拆分锁。
  • 写锁优先级:Redisson 读写锁默认非公平(可能导致写锁饥饿),需业务评估是否需要公平锁(RReadWriteLock#fairLock())。
  • 超时设置:需根据业务执行时间设置合理的锁超时时间(tryLock 的 leaseTime),避免锁无法及时释放。
适用场景
  • 读多写少的热点缓存(如商品详情、活动配置);
  • 数据一致性要求高(写锁保证原子性更新);
  • 分布式系统跨进程协调(如多实例缓存更新互斥)。

通过 Redisson 读写锁,可在热点缓存场景下显著提升读性能,同时保证写操作的原子性,是分布式系统中解决读多写少问题的经典方案。

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

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

相关文章

贪心算法应用:硬币找零问题详解

贪心算法与硬币找零问题详解 贪心算法&#xff08;Greedy Algorithm&#xff09;在解决优化问题时表现出简洁高效的特点&#xff0c;尤其适用于特定结构的组合优化问题。本文将用2万字篇幅&#xff0c;深入探讨贪心算法在硬币找零问题中的应用&#xff0c;覆盖算法原理、正确性…

深入理解 x86 汇编中的重复前缀:REP、REPZ/REPE、REPNZ/REPNE(进阶详解版)

一、重复前缀&#xff1a;串操作的 “循环加速器” 如果你写过汇编代码&#xff0c;一定遇到过需要重复处理大量数据的场景&#xff1a; 复制 1000 字节的内存块比较两个长达 200 字符的字符串在缓冲区中搜索特定的特征值 手动用loop指令编写循环&#xff1f;代码冗长不说&a…

Docker 在 AI 开发中的实践:GPU 支持与深度学习环境的容器化

人工智能(AI)和机器学习(ML),特别是深度学习,正以前所未有的速度发展。然而,AI 模型的开发和部署并非易事。开发者常常面临复杂的依赖管理(如 Python 版本、TensorFlow/PyTorch 版本、CUDA、cuDNN)、异构硬件(CPU 和 GPU)支持以及环境复现困难等痛点。这些挑战严重阻…

学习NuxtLink标签

我第一次接触这个标签&#xff0c;我都不知道是干嘛的&#xff0c;哈哈哈哈&#xff0c;就是他长得有点像routerLink&#xff0c;所以我就去查了一下&#xff01;哎&#xff01;&#xff01;&#xff01;真是一样的&#xff0c;哈哈哈哈&#xff0c;至少做的事情是一样的&#…

基于PostGIS的GeoTools执行原生SQL查询制图实践-以贵州省行政区划及地级市驻地为例

目录 前言 一、空间相关表简介 1、地市行政区划表 2、地市驻地信息表 3、空间查询检索 二、GeoTools制图实现 1、数据类型绑定 2、WKT转Geometry 3、原生SQL转SimpleFeatureCollection 4、集成调用 5、成果预览 三、总结 前言 在当今这个信息爆炸的时代&#xff0c…

NLP实战(5):基于LSTM的电影评论情感分析模型研究

目录 摘要 1. 引言 2. 相关工作 3. 方法 3.1 数据预处理 3.2 模型架构 3.3 训练策略 3.4 交叉验证 4. 实验与结果 4.1 数据集 4.2 实验结果 4.3训练日志 4.4 示例预测 5. 讨论 6. 结论 附录代码 展示和免费下载 摘要 本文提出了一种基于双向LSTM的深度学习模…

c++面向对象第4天---拷贝构造函数与深复制

含有对象成员的构造函数深复制与浅复制拷贝&#xff08;复制&#xff09;构造函数 第一部分&#xff1a;含有对象成员的构造函数 以下是一个学生 类包含日期成员出生日期的代码 #include<iostream> using namespace std; class Date { public:Date(int year,int month…

Windows版PostgreSQL 安装 vector 扩展

问题 spring-ai在集成PGVector向量存储的时候会报错如下&#xff0c;那么就需要安装pgsql的vector扩展。 SQL [CREATE EXTENSION IF NOT EXISTS vector]; 错误: 无法打开扩展控制文件 "C:/Program Files/PostgreSQL/9.6/share/extension/vector.control": No such …

KINGCMS被入侵

现象会强制跳转到 一个异常网站,请掉截图代码. 代码中包含经过混淆处理的JavaScript&#xff0c;它使用了一种技术来隐藏其真实功能。代码中使用了eval函数来执行动态生成的代码&#xff0c;这是一种常见的技术&#xff0c;恶意脚本经常使用它来隐藏其真实目的。 这段脚本会检…

完美解决在pycharm中创建Django项目安装mysqlclient报错的问题(windows下)

正常情况下&#xff0c;在Windows安装mysqlclient会报错&#xff1a; 我这里用的是anaconda虚拟环境&#xff0c;安装前必须激活anacoda虚拟环境&#xff0c; 怎么激活虚拟环境&#xff1f;可以参考超详细的pycharmanaconda搭建python虚拟环境_pycharm anaconda环境搭建-CSDN博…

『React』组件副作用,useEffect讲解

在 React 开发中&#xff0c;有时候会听到“副作用”这个词。特别是用到 useEffect 这个 Hook 的时候&#xff0c;官方就明确说它是用来处理副作用的。那什么是副作用&#xff1f;为什么我们要专门管控它&#xff1f;今天就聊聊 React 中的组件副作用。 &#x1f4cc; 什么是“…

使用VSCode在WSL和Docker中开发

通过WSL&#xff0c;开发人员可以安装 Linux 发行版&#xff08;例如 Ubuntu、OpenSUSE、Kali、Debian、Arch Linux 等&#xff09;&#xff0c;并直接在 Windows 上使用 Linux 应用程序、实用程序和 Bash 命令行工具&#xff0c;不用进行任何修改&#xff0c;也无需使用传统虚…

ZooKeeper 命令操作

文章目录 Zookeeper 数据模型Zookeeper 服务端常用命令Zookeeper 客户端常用命令 Zookeeper 数据模型 ZooKeeper 是一个树形目录服务,其数据模型和Unix的文件系统目录树很类似&#xff0c;拥有一个层次化结构。这里面的每一个节点都被称为&#xff1a; ZNode&#xff0c;每个节…

Redis底层数据结构之深入理解跳表(1)

在上一篇文章中我们详细的介绍了一下Redis中跳表的结构以及为什么Redis要引入跳表而不是平衡树或红黑树。这篇文章我们就来详细梳理一下跳表的增加、搜索和删除步骤。 SkipList的初始化 跳表初始化时&#xff0c;将每一层链表的头尾节点创建出来并使用集合将头尾节点进行存储&…

20250529-C#知识:继承、密封类、密封方法、重写

C#知识&#xff1a;继承、密封类、密封方法、重写 继承是面向对象的三大特性之一&#xff0c;通过继承能够减少重复代码的编写&#xff0c;有助于提升开发效率。 1、继承 C#不同于C&#xff0c;只支持单继承当子类出现与父类同名的成员时&#xff0c;父类成员被隐藏&#xff0…

从0到1,带你走进Flink的世界

目录 一、Flink 是什么&#xff1f; 二、Flink 能做什么&#xff1f; 三、Flink 架构全景概览 3.1 分层架构剖析 3.2 核心组件解析 四、Flink 的核心概念 4.1 数据流与数据集 4.2 转换操作 4.3 窗口 4.4 时间语义 4.5 状态与检查点 五、Flink 安装与快速上手 5.1 …

springboot @value

#springboot value value 可以读取 yaml 中 的数据

Dify-5:Web 前端架构

本文档提供了 Dify Web 前端架构的技术概述&#xff0c;包括核心组件、结构和关键技术。它解释了前端如何组织、组件如何通信以及国际化功能如何实现。 技术栈 Dify 的 Web 前端基于现代 JavaScript 技术栈构建&#xff1a; 框架&#xff1a;Next.js&#xff08;基于 React …

深度学习赋能图像识别:技术、应用与展望

论文&#xff1a; 一、引言​ 1.1 研究背景与意义​ 在当今数字化时代&#xff0c;图像作为信息的重要载体&#xff0c;广泛存在于各个领域。图像识别技术旨在让计算机理解和识别图像内容&#xff0c;将图像中的对象、场景、行为等信息转化为计算机能够处理的符号或数据 &am…

八N皇后问题

1 问题的提出 在8X8格的国际象棋上摆放八个皇后&#xff0c;使其不能互相攻击&#xff0c;即任意两个皇后都不能处于同一行、同一列或同一斜线上&#xff0c;问有多少种摆法 我们的任务就是用MATLAB进行求解 2 数学模型的构建 首先我们分析题目就是 任意两个皇后都不能处于…