Redis-Redis 高并发分布式锁

集群分布式场景高并发

1.negix配置代理和路由

高并发场景超卖问题

1.使用原生redis控制超卖时(若是商品,则可以将商品id作为锁对象),会遇到的问题

问题一:若直接使用:将获取锁的对象和设置的超时的时间分开,则不能控制原子性,如下所示

         Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge");
        stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);

 问题二:若直接使用:将获取锁的对象和设置的超时的时间放在一个原子操作里执行时,在临界条件下,当程序执行到最后准备释放锁时候,锁的超时时间已到,则此时的锁成为已过期,则释放不了锁而当下一个线程也来执行任务时,前一个任务将这个任务所拿的所给释放掉了(释放掉不属于自己的锁对象);则引入redisson分布式锁来解决当前的问题,redisson具有锁续命机制

@RestController
public class IndexController {@Autowiredprivate Redisson redisson;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate RedisTemplate redisTemplate;@RequestMapping("/deduct_stock")public String deductStock() {String lockKey = "lock:product_101";//Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge");//stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);String clientId = UUID.randomUUID().toString();Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); //jedis.setnx(k,v)if (!result) {return "error_code";}try {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}} finally {if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {stringRedisTemplate.delete(lockKey);}}return "end";}

使用分布式锁redisson

redisson使用

引入对应的redission的jar包

        <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.6.5</version></dependency>

 设置redission配置


@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}@Beanpublic Redisson redisson() {// 此为单机模式Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);return (Redisson) Redisson.create(config);}}

 redission的基本使用


@RestController
public class IndexController {@Autowiredprivate Redisson redisson;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate RedisTemplate redisTemplate;@RequestMapping("/deduct_stock")public String deductStock() {String lockKey = "lock:product_101";//Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge");//stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);/*String clientId = UUID.randomUUID().toString();Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); //jedis.setnx(k,v)if (!result) {return "error_code";}*///获取锁对象RLock redissonLock = redisson.getLock(lockKey);//加分布式锁redissonLock.lock();  //  .setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);try {int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)System.out.println("扣减成功,剩余库存:" + realStock);} else {System.out.println("扣减失败,库存不足");}} finally {/*if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {stringRedisTemplate.delete(lockKey);}*///解锁redissonLock.unlock();}return "end";}

Redission执行的逻辑流程

 Redission分布式锁加锁源码分析

        redissonLock.lock(); 

加锁 

 @Overridepublic void lockInterruptibly() throws InterruptedException {lockInterruptibly(-1, null);}

执行 lockInterruptibly加锁逻辑

@Overridepublic void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {long threadId = Thread.currentThread().getId();Long ttl = tryAcquire(leaseTime, unit, threadId); //尝试去加锁,返回的时加锁后的过期时间// lock acquiredif (ttl == null) {  //若ttl为null ,则表示加锁成功 ;若ttl不为null ,则往下走return;}RFuture<RedissonLockEntry> future = subscribe(threadId); //发布订阅,订阅前者执行的任务若提前执行完,则唤醒机制,去重新获取锁commandExecutor.syncSubscription(future);try {while (true) { //进入循环ttl = tryAcquire(leaseTime, unit, threadId); //再次尝试获取锁,返回加锁成功后的过期时间// lock acquiredif (ttl == null) {  //若ttl为null ,则表示加锁成功 ;若ttl不为null ,则往下走break;}// waiting for messageif (ttl >= 0) { 若ttl大于ogetEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
//进行间歇性锁自旋逻辑,不占用cpu资源} else {getEntry(threadId).getLatch().acquire();}}} finally {unsubscribe(future, threadId);}
//        get(lockAsync(leaseTime, unit));}

执行tryAcquire(leaseTime, unit, threadId)尝试加锁逻辑

 private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {if (leaseTime != -1) {return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);}
//leaseTime 默认设置为-1RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
//执行加锁逻辑ttlRemainingFuture.addListener(new FutureListener<Long>() { //异步执行,锁续命逻辑@Overridepublic void operationComplete(Future<Long> future) throws Exception {if (!future.isSuccess()) { //若加锁不成功,则退出return;}Long ttlRemaining = future.getNow(); //若加锁成功,则ttlRemaining 为null// lock acquiredif (ttlRemaining == null) {scheduleExpirationRenewal(threadId); //加锁成功,则执行锁续命逻辑}}});return ttlRemainingFuture;}

执行tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); 加锁逻辑

  <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
//通过lua脚本来执行加锁逻辑,来保证原子性internalLockLeaseTime = unit.toMillis(leaseTime);return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,//执行加锁逻辑 (key,argv与下面所传参数一一对应)"if (redis.call('exists', KEYS[1]) == 0) then " +      //KEY[1]表示下面的getName(),"redis.call('hset', KEYS[1], ARGV[2], 1); " +    //ARGV[2]表示下面的getLockName(threadId)"redis.call('pexpire', KEYS[1], ARGV[1]); " + //ARGV[1]表示下面的internalLockLeaseTime"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.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));}

执行scheduleExpirationRenewal(threadId);锁续命逻辑

private void scheduleExpirationRenewal(final long threadId) {if (expirationRenewalMap.containsKey(getEntryName())) {return;}Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {//执行锁续命逻辑RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
//KEYS[1]表示getName(),ARGV[2]表示锁对象getLockName(threadId), ARGV[1]表示过期时间
//判断当前锁的对象,为当前的线程对象,那么则当前的锁的对象设置原始的过期时间,以达到续命效果"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return 1; " +"end; " +"return 0;",Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));//异步监听执行        future.addListener(new FutureListener<Boolean>() {@Overridepublic void operationComplete(Future<Boolean> future) throws Exception {expirationRenewalMap.remove(getEntryName());if (!future.isSuccess()) {log.error("Can't update lock " + getName() + " expiration", future.cause());return;}//判断是否任然持有锁,是的话,则getNow()为nullif (future.getNow()) {// reschedule itselfscheduleExpirationRenewal(threadId);//再次执行续命逻辑}}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {task.cancel();}}

在外层未获取到锁的线程  getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);间隙自旋获取锁对象

getLatch()表示信号量,信号量为1,则表示阻塞状态,最终通过发布订阅方式来唤醒当前被阻塞的线程,唤醒后则执行获取锁的逻辑 doAcquireSharedNanos(arg, nanosTimeout);

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

web:[ZJCTF 2019]NiZhuanSiWei1

题目 点进题目&#xff0c;网页显示如下&#xff0c;需要代码审计 $_GET["text"]和$_GET["file"]来获取传入的两个参数text和file。使用isset()函数来检查$text变量是否已设置并且不为null。如果设置了并且不为null&#xff0c;则执行下面的逻辑。在下面的…

Linux内核--内存管理(一)任务空间管理

目录 一、引言 二、基本概念梳理 三、用户态进程内存管理 ------>3.1、用户态 ------>3.2、内核态 ------>3.3、内存管理结构 ------>3.4、mm_struct ------>4.5、vm_area_struct 四、内核态结构 ------>4.1、32位内核态结构 ------>4.2、64位…

交叉编译 和 软硬链接 的初识(面试重点)

目录 交叉编译的初认识Q&A Q1: 编译是什么&#xff1f; Q2: 交叉编译是什么&#xff1f; Q3: 为什么要交叉编译 Q3.1&#xff1a;树莓派相对于C51大得多&#xff0c;可以集成编译器比如gcc&#xff0c;那么树莓派就不需要交叉编译了吗&#xff1f; Q4: 什么是宿主机和…

2023年05月 Scratch(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 关于变量,下列描述错误的是?( ) A:只能建一个变量 B:变量可以隐藏 C:变量可以删除 D:变量的值可以修改 答案:A 考察变量相关操作,包括新建、删除变量,可以设定变量值以…

JavaScript基础—运算符、表达式和语句、分支语句、循环语句、综合案例-ATM存取款机

版本说明 当前版本号[20231125]。 版本修改说明20231125初版 目录 文章目录 版本说明目录JavaScript 基础 - 第2天运算符算术运算符赋值运算符自增/自减运算符比较运算符逻辑运算符运算符优先级 语句表达式和语句分支语句if 分支语句if双分支语句if 多分支语句三元运算符&am…

Python大数据考题

Python大数据考题&#xff1a; 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle&#xff0c;尤其sql要…

摆脱无用代码的负担:TreeShaking 的魔力

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

智能优化算法应用:基于蝗虫算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于蝗虫算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于蝗虫算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蝗虫算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

Typescript基础面试题 | 02.精选 ts 面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

#define例题

我们已经学了#define的所有知识&#xff0c;让我们来看这道题&#xff0c;可不要又陷入陷阱 题目要求&#xff1a; #define N 4 #define Y(n) ((N2)*n) int main() {int z 2 * (N Y(5 1));printf("z%d\n", z);return 0; } 求这个z的值是多少&#xff1f; 我们直接…

项目去除git版本控制

我 | 在这里 &#x1f575;️ 读书 | 长沙 ⭐软件工程 ⭐ 本科 &#x1f3e0; 工作 | 广州 ⭐ Java 全栈开发&#xff08;软件工程师&#xff09; &#x1f383; 爱好 | 研究技术、旅游、阅读、运动、喜欢流行歌曲 ✈️已经旅游的地点 | 新疆-乌鲁木齐、新疆-吐鲁番、广东-广州…

详解Rust编程中的生命周期

1.摘要 生命周期在Rust编程中是一个重要概念, 它能确保引用像预期的那样一直有效。在Rust语言中, 每一个引用都有其生命周期, 通俗讲就是每个引用在程序执行的过程中都有其自身的作用域, 一旦离开其作用域, 其生命周期也宣告结束, 值不再有效。幸运的是, 在绝大多数时间里, 生…