Redis 学习笔记 5:分布式锁
Redis 学习笔记 5:分布式锁
在前文中学习了如何基于 Redis 创建一个简单的分布式锁。虽然在大多数情况下这个锁已经可以满足需要,但其依然存在以下缺陷:
事实上一般而言,我们可以直接使用 Redisson 提供的分布式锁而非自己创建。
Redisson
添加 Redisson 依赖:
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version>
</dependency>
创建 Redisson 客户端实例:
@Configuration
public class RedisConfig {@AutowiredRedisProperties redisProperties;@Beanpublic RedissonClient redissonClient(){Config config = new Config();String address = String.format("redis://%s:%s", redisProperties.getHost(), redisProperties.getPort());config.useSingleServer().setAddress(address).setPassword(redisProperties.getPassword());return Redisson.create(config);}
}
这里的RedisProperties
是 Spring-data-redis 自带的一个配置类,可以借助其直接从配置文件中读取 redis 相关配置信息。
也可以通过修改配置文件的方式配置 Redisson 客户端,但缺点是会变更 spring-data-redis 对 Redis 客户端的默认配置,所以不建议那样做。
使用 Redisson 提供的分布式锁限制优惠券抢购:
// ...
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {// ...@Autowiredprivate RedissonClient redissonClient;// ...@Overridepublic Result createOrder(Long voucherId) {// ...// 使用用户标识进行加锁String lockName = "lock:voucher-order:" + userId.toString();RLock lock = redissonClient.getLock(lockName);boolean isLock = lock.tryLock();if (!isLock) {return Result.fail("同一用户不能重复抢购");}try {return proxy.doCreateOrder(voucherId);} finally {lock.unlock();}}// ...
}
Redisson 提供的分布式锁RLock
有多种加锁方式,这里展示的tryLock()
是非阻塞式的加锁,如果获取锁失败,会立即返回。如果需要阻塞式获取锁(获取锁失败时等待并尝试获取),可以:
isLock = lock.tryLock(1,10, TimeUnit.SECONDS);
这里第一个参数是等待时长,第二个参数是 Redis 锁的过期时长。
可重入锁原理
Redisson 提供的分布式锁是可重入的,其原理和 JDK 提供的用于处理并发的可重入锁ReentrantLock
是类似的。即在锁内部使用一个计数器,当一个线程多次获取同一个锁时,将计数器自增以记录已经重复获取锁的次数。在释放锁的时候将计数器减1,当计数器为0时才真正释放锁。
下面通过改造前文的 Redis 分布式锁,让其支持再入以说明 Redisson 锁可再入的实现原理。
在改造 Redis 锁前,先编写一个测试用例来证明目前的 Redis 锁不支持重入:
// ...
@Log4j2
@SpringBootTest
public class RedisLockTests {@Autowiredprivate StringRedisTemplate redisTemplate;/*** 测试 Redis 锁是否可以再入*/@Testpublic void test() {SimpleRedisLock lock = new SimpleRedisLock(redisTemplate, "test");boolean tryLock = lock.tryLock(2000);if (!tryLock) {log.error("test获取锁失败");return;}try {log.info("test获取锁成功");log.info("test执行业务代码");test2(lock);} finally {lock.unlock();log.info("test释放锁");}}private void test2(SimpleRedisLock lock) {boolean tryLock = lock.tryLock(2000);if (!tryLock) {log.error("test2获取锁失败");return;}try {log.info("test2获取锁成功");log.info("test2执行业务代码");} finally {lock.unlock();log.info("test2释放锁");}}
}
因为像前面说的,重入锁需要有一个计数器,同时还需要持有一个线程 ID 以检查是否当前线程持有的锁,因此不能再使用 Redis 中的 key-value 结构作为锁,改为使用 Hash 来同时保存这两个信息:
[外链图片转存中…(img-otcOkwkN-1747619754081)]
这里将线程 ID 直接作为字典的 key 以节省存储空间。
现在的问题就是在获取锁和释放锁的部分加入计数器维护的逻辑。但就像在前文引入 Lua 脚本时讨论的那样,显然这些操作不能通过 Java 实现,因为那样做不能保证操作的原子性,因此需要用 Lua 脚本来实现:
--[[@描述: Redis 锁获取脚本(支持再入)@版本: 1.0.0
]] --
local key = KEYS[1] -- Redis 锁的对应的 key
local threadId = ARGV[1] -- 持有锁的线程标识
local timeoutSec = ARGV[2] -- 锁的自动过期时长(单位秒)
-- 检查锁是否已经存在
local exists = redis.call('exists', key)
if (exists == 0) then-- 如果锁不存在,添加(正常获取到锁)redis.call('hset', key, threadId, 1)-- 更新锁的过期时间redis.call('expire', key, timeoutSec)return 1
end
-- 如果锁存在,检查是否当前线程的锁
if (redis.call('HEXISTS', key, threadId) == 0) then-- 如果不是当前线程的锁,返回错误信息(互斥,没有获取到锁)return 0
end
-- 是当前线程的锁(再入)
-- 计数器+1
redis.call('HINCRBY', key, threadId, 1)
-- 更新过期时长
redis.call('expire', key, timeoutSec)
return 1
--[[@描述:Redis 锁释放(支持再入)
]] --
local key = KEYS[1] -- Redis 锁的对应的 key
local threadId = ARGV[1] -- 持有锁的线程标识
local timeoutSec = ARGV[2] -- 锁的自动过期时长(单位秒)
-- 检查锁是否已经存在
if (redis.call('exists', key) == 0) then-- 锁不存在,返回错误信息return 0
end
-- 锁存在,检查是否当前线程持有的锁
if (redis.call('HEXISTS', key, threadId) == 0) then-- 不是当前线程持有的锁,返回错误信息return 0
end
-- 是当前线程持有的锁,计数器-1
redis.call('HINCRBY', key, threadId, -1)
-- 如果计数器小于等于0,删除锁
if (tonumber(redis.call('HGET', key, threadId)) <= 0) thenredis.call('del', key)return 1
end
-- 如果计数器还未归0,更新锁的有效时长
redis.call('expire', key, timeoutSec)
return 1
需要注意的是,与之前不同的是,再次获取锁和释放锁的时候都需要更新锁的有效时长,以确保之后的业务能在锁生效期内正常执行完毕。
修改锁的实现类,用 Lua 脚本获取和释放锁:
// ...
public class SimpleRedisLock implements ILock {// ...// Redis 锁获取脚本private static final DefaultRedisScript<Long> LOCK_SCRIPT;static {LOCK_SCRIPT = new DefaultRedisScript<>();// 指定脚本的位置LOCK_SCRIPT.setLocation(new ClassPathResource("reentrant-lock.lua"));// 指定脚本的返回值类型LOCK_SCRIPT.setResultType(Long.class);}// Redis 锁释放脚本private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;static {UNLOCK_SCRIPT = new DefaultRedisScript<>();// 指定脚本的位置UNLOCK_SCRIPT.setLocation(new ClassPathResource("reentrant-unlock.lua"));// 指定脚本的返回值类型UNLOCK_SCRIPT.setResultType(Long.class);}// ...@Overridepublic boolean tryLock(long timeoutSec) {final String jvmThreadId = getJvmThreadId();Long res = stringRedisTemplate.execute(LOCK_SCRIPT,Collections.singletonList(redisKey),jvmThreadId,Long.toString(timeoutSec));return res != null && res > 0;}// ...@Overridepublic void unlock(long timeoutSec) {// 使用 lua 脚本删除 Redis 锁stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(redisKey),getJvmThreadId(),Long.toString(timeoutSec));}@Overridepublic void unlock() {unlock(200);}
}
查看 Redisson 源码可以发现,其实现方式和上文描述的是类似的。
重试机制
Redisson 提供的分布式锁具备获取锁失败后进行重试的机制,且这种机制是基于 Redis 订阅和信号量的方式实现的,会有效避免 CPU 计算资源的浪费。此外,调用 API 时如果指定锁的过期时长为 -1,Redisson 会将锁的在 Redis 中的有效时长设置一个默认值(30秒),并启动一个守护进程(WatchDog)来定期重新刷新其有效时长,以保证该锁的长期有效。在释放锁的时候,该守护进程会被终止。
整个过程可以用下图表示:
这里的
ttl
指获取锁的 Lua 脚本的返回值,如果锁成功获取,会返回 null,获取锁失败,会返回锁的剩余有效时长。
详细的源码分析和说明可以参考这个视频。
联锁
如果 Redisson 实现的锁是基于单个 Redis 的,那么是没有问题的,反之,如果是主从同步的集群,之前所使用的锁就会存在问题:
如果从主节点获取锁成功,但还未将锁同步到其他从节点时主节点宕机,锁就会“丢失”。
这个问题可以用联锁来解决:
即不使用主从,而是使用多台独立的 Redis 获取锁,只有从所有 Redis 获取锁成功,才算是成功,否则视为获取锁失败。在这种情况下,任意 Redis 宕机都不会导致锁失效。
为了演示,额外启动两个 Redis 实例:
docker run --name my-redis -p 6380:6379 -d redis
docker run --name my-redis2 -p 6381:6379 -d redis
关于如何用 Docker 部署 Redis 可以参考这里。
在配置为文件中添加两个 Redis 服务的配置信息:
my-config:redis1:host: 192.168.0.88port: 6380redis2:host: 192.168.0.88port: 6381
创建配置类读取该信息:
@Configuration
@ConfigurationProperties(prefix = "my-config")
@Data
public class MyConfigProperties {@Datapublic static class RedisConfig{private String host;private String port;}private RedisConfig redis1;private RedisConfig redis2;
}
创建对应的 Redisson 客户端:
@Configuration
public class RedisConfig {// ...@AutowiredMyConfigProperties myConfig;// ...@Beanpublic RedissonClient redissonClient2(){Config config = new Config();String address = String.format("redis://%s:%s",myConfig.getRedis1().getHost(),myConfig.getRedis1().getPort());config.useSingleServer().setAddress(address).setPassword(redisProperties.getPassword());return Redisson.create(config);}@Beanpublic RedissonClient redissonClient3(){Config config = new Config();String address = String.format("redis://%s:%s",myConfig.getRedis2().getHost(),myConfig.getRedis2().getPort());config.useSingleServer().setAddress(address).setPassword(redisProperties.getPassword());return Redisson.create(config);}
}
修改测试用例,使用联锁:
// ...
@Log4j2
@SpringBootTest
public class RedisLockTests {// ...@Resourceprivate RedissonClient redissonClient;@Resourceprivate RedissonClient redissonClient2;@Resourceprivate RedissonClient redissonClient3;/*** 测试 Redis 锁是否可以再入*/@Testpublic void test() throws InterruptedException {// 创建 Redisson 联锁String lockName = "lock:test";RLock lock1 = redissonClient.getLock(lockName);RLock lock2 = redissonClient2.getLock(lockName);RLock lock3 = redissonClient3.getLock(lockName);RLock lock = redissonClient.getMultiLock(lock1, lock2, lock3);boolean tryLock = lock.tryLock(10, TimeUnit.SECONDS);// ...}private void test2(RLock lock) throws InterruptedException {// ...}
}
和单个锁类似,联锁同样可以指定等待时间以进行重试。
关于 Redisson 联锁的源码分析可以看这里。
本文的所有示例代码可以从这里获取。
The End.
参考资料
- 黑马程序员Redis入门到实战教程
相关文章:
Redis 学习笔记 5:分布式锁
Redis 学习笔记 5:分布式锁 在前文中学习了如何基于 Redis 创建一个简单的分布式锁。虽然在大多数情况下这个锁已经可以满足需要,但其依然存在以下缺陷: 事实上一般而言,我们可以直接使用 Redisson 提供的分布式锁而非自己创建。…...
【硬核数学】2. AI如何“学习”?微积分揭秘模型优化的奥秘《从零构建机器学习、深度学习到LLM的数学认知》
在上一篇中,我们探索了线性代数如何帮助AI表示数据(向量、矩阵)和变换数据(矩阵乘法)。但AI的魅力远不止于此,它最核心的能力是“学习”——从数据中自动调整自身,以做出越来越准确的预测或决策…...
[Java][Leetcode middle] 151. 反转字符串中的单词
思路挺简单的 自己想的,步骤挺复杂的 先统计处开头和结尾的空格数跳过开头这些空格,将单词放到数组中统计最后一个可能漏过的单词(例如:“hello word”,没有空格退出)倒序输出 public String reverseWor…...
力扣每日一题5-18
class Solution { public int colorTheGrid(int m, int n) { // 每一列可能的状态总数 每个单元有3可能 int totalState 1; for (int i 0; i < m; i) totalState * 3; // pre[k] 代表前一轮dp 状态为k 的方案总数 int [] pre new int [totalState]; // 初始化合法填色 的…...
leetcode 74. Search a 2D Matrix
题目描述 要求时间复杂度必须是log(m*n)。那么对每一行分别执行二分查找就不符合要求,这种做法的时间复杂度是m*log(n)。 方法一,对每一行分别执行二分查找: class Solution { public:bool searchMatrix(vector<vector<int>>&a…...
养生指南:重塑健康生活的实用方案
一、饮食:均衡膳食,滋养身心 三餐以 “轻盐、轻油、轻糖” 为准则。早餐搭配全麦三明治、无糖酸奶和一小把蓝莓,补充优质碳水与抗氧化物质;午餐选用糙米饭、白灼虾及蒜蓉西蓝花,保证蛋白质与膳食纤维摄入;…...
IPTABLES四表五链祥解
在Linux中,iptables 是一个强大的防火墙工具,用于管理和过滤网络流量。iptables 使用四个不同的表,每个表都包含多个链,来控制流量的处理。 一、iptables四个表 表名功能说明filter默认表,负责对进出数据包的过滤操作…...
嵌入式学习--江协51单片机day8
这个本来应该周末写的,可是一直想偷懒,只能是拖到周一了,今天把51结个尾,明天开始学32了。 学习内容LCD1602,直流电机,AD/DA,红外遥控 LCD1602 内部的框架结构 屏幕小于数据显示区ÿ…...
内网穿透与内网映射是什么?
在互联网技术快速迭代的当下,网络通信架构日益复杂,内网穿透与内网映射作为实现公网访问内网资源的核心技术,在企业办公、个人开发、智能家居等领域发挥着关键作用。尽管两者都致力于打通公网与内网的连接通道,但它们在底层原理、…...
51单片机点亮一个LED介绍
LED介绍 LED就是发光二极管,一般来说如果是直插式的,那就是长正短负,如果是贴片式的,那就带彩色标记是阴极,如果是三角形的,水平箭头指的就是阴极,通常一般的工作电压在3mA~20mA,当…...
WebRTC技术EasyRTC嵌入式音视频通信SDK助力智能电视搭建沉浸式实时音视频交互
一、方案概述 EasyRTC是一款基于WebRTC技术的开源实时音视频通信解决方案,具备低延迟、高画质、跨平台等优势。将EasyRTC功能应用于智能电视,能够为用户带来全新的交互体验,满足智能电视在家庭娱乐、远程教育、远程办公、远程医疗等多种场…...
uniapp 小程序 CSS 实现多行文本展开收起 组件
效果 组件 <template><!-- 最外层弹性盒子 --><div class"box" :style"boxStyle"><!-- 文本区域,动态类名控制展开/收起状态 --><div ref"textRef" :class"[text-cont, btnFlag ? text-unfold : t…...
嵌入式51单片机:C51
sbit TISCON^1的意思是定义TI为SCON的次低位(最低位标记为0,其次为1,再次为2)...
【回眸】香橙派zero2 嵌入式数据库SQLite
前言 SQLite介绍 安装SQLite3 SQLite 使用 创建数据库 创建一张表格 插入数据 查看数据库的记录 删除一条记录 更改一条记录 删除一张表 增加一列(性别) SQLite编程操作 前言 还有2个项目没更新完...披星戴月更新中... SQLite介绍 基于嵌入…...
vue3个生命周期解析,及setup
合理使用各生命周期,切勿乱用,不是所有东西都需要,合理使用可以提高效率和性能。 Vue 3 生命周期钩子详解 Vue 3的生命周期钩子分为以下几个阶段: onBeforeMount 调用时机:在组件挂载到DOM之前调用。使用场景…...
MySQL死锁:面试通关“三部曲”心法
想象一下,你的MySQL数据库里有两张桌子(数据表),比如一张“产品库存表”,一张“订单表”。现在来了两个顾客(并发事务),都想同时操作这两张桌子上的东西: 顾客A 先锁住了…...
Spring Boot 与 RabbitMQ 的深度集成实践(四)
实战案例 业务场景描述 在电商系统中,用户下单是一个核心业务操作。当用户成功下单后,系统需要执行一系列后续任务,如发送邮件通知用户订单已成功提交,更新库存信息以确保商品库存的准确性,以及记录订单相关的日志信…...
ES6详解
一、变量声明 let 与 const 块级作用域:替代 var 的函数作用域 const 声明常量(不可重新赋值,但对象属性可修改) if (true) {let x 10const PI 3.14 } console.log(x) // 报错 二、箭头函数 简写语法与 this 绑定 // 传统函数…...
C语言—字符函数和字符串函数
1.字符分类函数 字符控制函数:int iscntrl ( int c ); 控制字符通常不是可打印字符,该函数是用来判断参数是否为控制字符,需要的头文件为<ctype.h>标准ASCII码中,不可打印字符主要包括以下两类: 控制字符&…...
【LeetCode】大厂面试算法真题回忆(93)--优雅数组
题目描述 如果一个数组中出现次数最多的元素出现大于等于k次,被称为k-优雅数组,k也可以被称为优雅阈值。 例如,数组[1, 2, 3, 1, 2, 3, 1],它是一个3-优雅数组,因为元素1出现次数大于等于3次。数组[1, 2, 3, 1, 2]就不是一个3-优雅数组,因为其中出现次数最多的元素是1和…...
【MySQL成神之路】MySQL常用语法总结
目录 MySQL 语法总结 数据库操作 表操作 数据操作 查询语句 索引操作 约束 事务控制 视图操作 存储过程和函数 触发器 用户和权限管理 数据库操作 创建数据库: CREATE DATABASE database_name; 选择数据库: USE database_name; 删除数…...
机器学习第十六讲:K-means → 自动把超市顾客分成不同消费群体
机器学习第十六讲:K-means → 自动把超市顾客分成不同消费群体 资料取自《零基础学机器学习》。 查看总目录:学习大纲 关于DeepSeek本地部署指南可以看下我之前写的文章:DeepSeek R1本地与线上满血版部署:超详细手把手指南 K-me…...
多商户1.8.1版本前端问题优化集合指南
1、逛逛社区上传一张图时,进入详情页面显示不出来 修改路径:pages ---> discover ---> components ---> discoverDetails.vue 解读:这里是因为图片高度没有定义,图片没显示出来。修改如下: <!--逛逛类型为…...
基于正点原子阿波罗F429开发板的LWIP应用(1)——网络ping通
说在开头 正点原子F429开发板主芯片采用的是STM32F429IGT6,网络PHY芯片采用的是LAN8720A(V1)和YT8512C(V2),采用的是RMII连接,PHY_ADDR为0;在代码中将会对不同的芯片做出适配。 CubeMX版本:6.6.1; F4芯片组…...
第 1 章:数字 I/O 与串口通信(GPIO UART)
本章目标: 掌握 GPIO 的硬件原理、寄存器配置与典型驱动框架 深入理解 UART/USART 的帧格式、波特率配置、中断与 DMA 驱动 通过实战案例,将 GPIO 与 UART 结合,实现 AT 命令式外设控制 章节结构 GPIO 概述与硬件原理 GPIO 驱动实现:寄存器、中断与去抖 UART/USART 原理与帧…...
MCU 温度采样理论(-ADC Temperature sensor)
温度传感器可以使用ADC来测量芯片温度。 为了准确测量运行时的芯片温度,请使用在生产过程中运行的参考测量值,此参考值与其他校准数据一起存放在SFlash中。 一、温度测量流程 1、ADC校准:关于偏移和增益调整的实例,见9.3。 2、检查CREFH和VREL:参见8.2。 3、设置参考…...
stm32week16
stm32学习 十一.中断 4.使用中断 EXTI的配置步骤: 使能GPIO时钟设置GPIO输入模式使能AFIO/SYSCFG时钟设置EXTI和IO对应关系设置EXTI屏蔽,上/下沿设置NVIC设计中断服务函数 HAL库的使用: 使能GPIO时钟:__HAL_RCC_GPIOx_CLK_EN…...
隨筆 20250519 基于MAUI Blazor整合SQLite数据库与Star打印机的详细步骤
以下是基于MAUI Blazor整合SQLite数据库与Star打印机的详细步骤,包含必要的NuGet包引入及核心代码实现: 零、目錄結構 一、整合SQLite数据库 1. 安装NuGet包 # SQLite核心库 Install-Package sqlite-net-pcl # SQLite平台适配库&am…...
电子电路原理第十六章(负反馈)
1927年8月,年轻的工程师哈罗德布莱克(Harold Black)从纽约斯塔顿岛坐渡轮去上班。为了打发时间,他粗略写下了关于一个新想法的几个方程式。后来又经过反复修改, 布莱克提交了这个创意的专利申请。起初这个全新的创意被认为像“永动机”一样愚蠢可笑,专利申请也遭到拒绝。但…...
推客小程序系统开发:全栈式技术解决方案与行业赋能实践
在数字化营销深度渗透各行业的当下,传统推广模式已难以满足企业精细化运营与高效获客的需求。专业的推客小程序系统凭借其强大的裂变传播能力与灵活的推广机制,成为企业构建私域流量池、提升推广效能的核心工具。我们基于多年技术沉淀与行业洞察&…...
【prometheus+Grafana篇】基于Prometheus+Grafana实现Oracle数据库的监控与可视化
💫《博主主页》: 🔎 CSDN主页 🔎 IF Club社区主页 🔥《擅长领域》:擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控;并对SQLserver、NoSQL(MongoDB)有了…...
【Android构建系统】Soong构建系统,通过.bp + .go定制编译
背景介绍 本篇是一篇实操内容,是对【Android构建系统】如何在Camera Hal的Android.bp中选择性引用某个模块的优化与改进。本篇内容主要想通过一个具体例子介绍Soong构建系统较复杂的定制化方法和步骤,以便在今后的工作学习中更好的使用Soong构建系统。 …...
Qt开发:QUdpSocket的详解
文章目录 一、QUdpSocket 简介二、常用函数的介绍和使用三、接收端完整示例四、发送端完整示例 一、QUdpSocket 简介 在 Qt 中,UDP(User Datagram Protocol,用户数据报协议)是通过 QUdpSocket 类实现的。UDP 是一种无连接的、轻量…...
【android bluetooth 协议分析 01】【HCI 层介绍 9】【ReadLocalSupportedCommands命令介绍】
1. HCI_Read_Local_Supported_Commands 命令介绍 1. 命令介绍(Description) HCI_Read_Local_Supported_Commands 是 HCI 层中非常重要的查询命令。它允许 Host(如 Android 系统中的 Bluetooth stack)获取 Controller(…...
Model 速通系列(一)nanoGPT
这个是新开的一个系列用来手把手复现一些模型工程,之所以开这个系列是因为有人留言说看到一个工程不知道从哪里读起,出于对自身能力的提升与兴趣,故新开了这个系列。由于主要动机是顺一遍代码并提供注释。 该系列第一篇博客是 nanoGPT &…...
星际争霸小程序:用Java实现策略模式的星际大战
在游戏开发的世界里,策略模式是一种非常实用的设计模式,它允许我们在运行时动态地选择算法或行为。今天,我将带你走进一场星际争霸的奇幻之旅,用Java实现一个简单的星际争霸小程序,通过策略模式来模拟不同种族单位的战…...
网络Tips20-007
网络威胁会导致非授权访问、信息泄露、数据被破坏等网络安全事件发生, 其常见的网络威胁包括窃听、拒绝服务、病毒、木马、( 数据完整性破坏 )等, 常见的网络安全防范措施包括访问控制、审计、身份认证、数字签名、( 数据加密 )、 包过滤和检测等。 AE…...
2.微服务-配置
引入springcloud的pom配置 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.12</version><relativePath/></parent> <dependencyManagemen…...
python实现pdf转图片(针对每一页)
from pdf2image import convert_from_path import ospdf_file rC:\Users\\Desktop\拆分\产权证.pdf poppler_path rC:\poppler-24.08.0\Library\bin # 这里改成你自己的路径output_dir rC:\Users\\Desktop\拆分\output_images os.makedirs(output_dir, exist_okTrue)image…...
Python编程从入门到实践 PDF 高清版
各位程序员朋友们,还在为找不到合适的Python学习资料而烦恼吗?还在为晦涩难懂的编程书籍而头疼吗?今天,就给大家带来一份重磅福利——237完整版PDF, 我用网盘分享了「Python编程:从入门到实践__超清版.pdf…...
CVE-2015-3934 Fiyo CMS SQL注入
CVE-2015-3934 Fiyo CMS SQL注入 页面抓登录数据包 构造延时注入语句在user处’%2B(select(0)from(select(sleep(5)))v)%2B’ 存在延时注入,使用脚本即可...
【Pandas】pandas DataFrame mode
Pandas2.2 DataFrame Computations descriptive stats 方法描述DataFrame.abs()用于返回 DataFrame 中每个元素的绝对值DataFrame.all([axis, bool_only, skipna])用于判断 DataFrame 中是否所有元素在指定轴上都为 TrueDataFrame.any(*[, axis, bool_only, skipna])用于判断…...
(思维题、贪心)洛谷 P11232 CSPS2024 超速检测 题解
这一题在 2024 将我击败,但我怎么现在才补题解 …… 题意 原题 思路 对于每一辆车,我们可以算出,其在距离左端点哪段位置会超速 [ l , r ] [l,r] [l,r],那么这辆车会被 l l l 右侧最近的测速仪到 r r r 左侧最近的测速仪检…...
C#:多线程
一.线程常用概念 线程(Thread):操作系统执行程序的最小单位 进程(Process):程序在内存中的运行实例 并发(Concurrency):多个任务交替执行(单核CPU࿰…...
虚拟币制度钱包开发:功能设计与成本全解析
虚拟币制度钱包开发:功能设计与成本全解析 ——从基础架构到合规风控的完整解决方案 一、开发成本:分层定价与关键影响因素 根据2024-2025年行业数据显示,虚拟币钱包App开发成本跨度较大,主要受功能复杂度、技术架构与合规要求三…...
TransmittableThreadLocal实现上下文传递-笔记
1.TransmittableThreadLocal简介 com.alibaba.ttl.TransmittableThreadLocal(简称 TTL)是阿里巴巴开源的一个工具类,旨在解决 ThreadLocal 在线程池中无法传递上下文变量 的问题。它是对 InheritableThreadLocal 的增强,尤其适用…...
应对WEEE 2025:猎板PCB的区块链追溯与高温基材创新
在全球电子产业加速向循环经济转型的背景下,欧盟《绿色新政》与《WEEE指令》对PCB行业提出更高要求。作为行业先行者,猎板PCB(Hunter PCB)以生物降解基材为核心,结合全球合规体系与产业链协同创新,构建从材…...
大陆资产在香港发行RWA的合规路径与核心限制
大陆资产在香港发行RWA的合规路径与核心限制 ——从“双重合规原则”到资产准入边界的全景解读 一、法律框架:双重合规原则的刚性约束 根据香港金管局Ensemble沙盒项目要求,大陆资产在香港发行RWA需遵循“双重合规原则”,即底层资产需同时符…...
爬虫攻防战:从入门到放弃的完整对抗史与实战解决方案
爬虫攻防战:从入门到放弃的完整对抗史与实战解决方案 这张有趣的图片生动描绘了爬虫开发者与反爬工程师之间的"军备竞赛"。作为技术博主,我将基于这张图的各个阶段,深入分析爬虫技术的演进与对应的反制措施,提供一套完整的反爬解决方案,包括技术原理、实施方法…...
Fabric初体验(踩坑笔记)
搭建fabric部署合约学习笔记 环境准备CURl安装docker 参照官网文档实现(2025.05.19)根据前言交代的文章去尝试(失败版)安装fabric-samples安装指定2.2.0版本Fabric二进制文件和配置文件直接手动下载(不建议)…...