(四)库存超卖案例实战——优化redis分布式锁

2023-10-31 22:30

本文主要是介绍(四)库存超卖案例实战——优化redis分布式锁,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

在上一节内容中,我们已经实现了使用redis分布式锁解决商品“超卖”的问题,本节内容是对redis分布式锁的优化。在上一节的redis分布式锁中,我们的锁有俩个可以优化的问题。第一,锁需要实现可重入,同一个线程不用重复去获取锁;第二,锁没有续期功能,导致业务没有执行完成就已经释放了锁,存在一定的并发访问问题。本案例中通过使用redis的hash数据结构实现可重入锁,使用Timer实现锁的续期功能,完成redis分布式锁的优化。最后,我们通过集成第三方redisson工具包,完成分布式锁以上俩点的优化内容。Redisson提供了简单易用的API,使得开发人员可以轻松地在分布式环境中使用Redis。

正文

  • 加锁的lua脚本:使用exists和hexists指令判断是否存在锁,如果不存在或者存在锁并且该锁下面的field有值,就使用hincrby指令使锁的值加1,实现可重入,否则直接返回0,加锁失败。
if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
"then " +
"   redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
"   redis.call('expire', KEYS[1], ARGV[2]) " +
"   return 1 " +
"else " +
"   return 0 " +
"end"
  • 解锁的lua脚本: 使用hexists指令判断是否存在锁,如果为0,代表没有对应field字段的锁,直接返回nil;如果使用hincrby指令使锁field字段锁的值减少1之后值为0,代表锁已经不在占用,可以删除该锁;否则直接返回0,代表是可重入锁,锁还没有释放。
if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +
"then " +
"   return nil " +
"elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " +
"then " +
"   return redis.call('del', KEYS[1]) " +
"else " +
"   return 0 " +
"end"
  •  实现续期的lua脚本:使用hexists指令判断锁的field值是否存在,如果值为1存在,则将该锁的过期时间更新,否则直接返回0,代表没有找到该锁,续期失败。
if redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
"then " +
"   return redis.call('expire', KEYS[1], ARGV[2]) " +
"else " +
"   return 0 " +
"end";
  • 创建一个自定义的锁工具类MyRedisDistributeLock,实现加锁、解锁、续期功能

- MyRedisDistributeLock实现

package com.ht.atp.plat.util;import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;public class MyRedisDistributeLock implements Lock {public MyRedisDistributeLock(StringRedisTemplate redisTemplate, String lockName, long expire) {this.redisTemplate = redisTemplate;this.lockName = lockName;this.expire = expire;this.uuid = getId();}/*** redis工具类*/private StringRedisTemplate redisTemplate;/*** 锁名称*/private String lockName;/*** 过期时间*/private Long expire;/*** 锁的值*/private String uuid;@Overridepublic void lock() {this.tryLock();}@Overridepublic void lockInterruptibly() {}@Overridepublic boolean tryLock() {try {return this.tryLock(-1L, TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}return false;}@Overridepublic boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException {if (time != -1) {this.expire = unit.toSeconds(time);}String script = "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +"then " +"   redis.call('hincrby', KEYS[1], ARGV[1], 1) " +"   redis.call('expire', KEYS[1], ARGV[2]) " +"   return 1 " +"else " +"   return 0 " +"end";while (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))) {Thread.sleep(50);}
//        //加锁成功后,自动续期this.renewExpire();return true;}@Overridepublic void unlock() {String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +"then " +"   return nil " +"elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " +"then " +"   return redis.call('del', KEYS[1]) " +"else " +"   return 0 " +"end";Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);if (flag == null) {throw new IllegalMonitorStateException("this lock doesn't belong to you!");}}@NotNull@Overridepublic Condition newCondition() {return null;}/*** 给线程拼接唯一标识** @return*/private String getId() {return UUID.randomUUID() + "-" + Thread.currentThread().getId();}private void renewExpire() {String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +"then " +"   return redis.call('expire', KEYS[1], ARGV[2]) " +"else " +"   return 0 " +"end";new Timer().schedule(new TimerTask() {@Overridepublic void run() {System.out.println("-------------------");Boolean flag = redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire));if (flag) {renewExpire();}}}, this.expire * 1000 / 3);}
}

- 实现加锁功能

- 实现解锁功能


 - 使用Timer实现锁的续期功能

  • 使用MyRedisDistributeLock实现库存的加锁业务 

- 使用自定义MyRedisDistributeLock工具类实现加锁业务

public void checkAndReduceStock() {//1.获取锁MyRedisDistributeLock myRedisDistributeLock = new MyRedisDistributeLock(stringRedisTemplate, "stock", 10);myRedisDistributeLock.lock();try {// 2. 查询库存数量String stockQuantity = stringRedisTemplate.opsForValue().get("P0001");// 3. 判断库存是否充足if (stockQuantity != null && stockQuantity.length() != 0) {Integer quantity = Integer.valueOf(stockQuantity);if (quantity > 0) {// 4.扣减库存stringRedisTemplate.opsForValue().set("P0001", String.valueOf(--quantity));}} else {System.out.println("该库存不存在!");}} finally {myRedisDistributeLock.unlock();}}

- 启动服务7000、7001、7002,压测优化后的自定义分布式锁:平均访问时间362ms,吞吐量每秒246,库存扣减为0,表明优化后的分布式锁是可用的。

  • 集成redisson工具包,使用第三方工具包实现分布式锁,完成并发访问“超卖”问题案例演示
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.11.6</version>
</dependency>
  • 创建一个redisson配置类,引入redisson客户端工具
package com.ht.atp.plat.config;import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyRedissonConfig {@BeanRedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://192.168.110.88:6379");//配置看门狗的默认超时时间为30s,供续期使用config.setLockWatchdogTimeout(30000);return Redisson.create(config);}
}
  • 使用Redisson锁实现“超卖”业务方法 
//可重入锁@Overridepublic void checkAndReduceStock() {// 1.加锁,获取锁失败重试RLock lock = this.redissonClient.getLock("lock");lock.lock();try {// 2. 查询库存数量String stockQuantity = stringRedisTemplate.opsForValue().get("P0001");// 3. 判断库存是否充足if (stockQuantity != null && stockQuantity.length() != 0) {Integer quantity = Integer.valueOf(stockQuantity);if (quantity > 0) {// 4.扣减库存stringRedisTemplate.opsForValue().set("P0001", String.valueOf(--quantity));}} else {System.out.println("该库存不存在!");}} finally {// 4.释放锁lock.unlock();}}
  • 开启7000、7001、7002服务,压测扣减库存接口 

- 压测结果:平均访问时间222ms,吞吐量为384每秒

- 库存扣减结果为0

结语

综上所述,无论是自定义分布式锁还是使用redisson工具类,都能实现分布式锁解决并发访问的“超卖问题”,redisson工具使用集成更加方便简洁,推荐使用redisson工具包。本节内容到这里就结束了,我们下期见。。。。。。

这篇关于(四)库存超卖案例实战——优化redis分布式锁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/318558

相关文章

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Pandas使用SQLite3实战

《Pandas使用SQLite3实战》本文主要介绍了Pandas使用SQLite3实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1 环境准备2 从 SQLite3VlfrWQzgt 读取数据到 DataFrame基础用法:读

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

一文详解SpringBoot响应压缩功能的配置与优化

《一文详解SpringBoot响应压缩功能的配置与优化》SpringBoot的响应压缩功能基于智能协商机制,需同时满足很多条件,本文主要为大家详细介绍了SpringBoot响应压缩功能的配置与优化,需... 目录一、核心工作机制1.1 自动协商触发条件1.2 压缩处理流程二、配置方案详解2.1 基础YAML

Redis 中的热点键和数据倾斜示例详解

《Redis中的热点键和数据倾斜示例详解》热点键是指在Redis中被频繁访问的特定键,这些键由于其高访问频率,可能导致Redis服务器的性能问题,尤其是在高并发场景下,本文给大家介绍Redis中的热... 目录Redis 中的热点键和数据倾斜热点键(Hot Key)定义特点应对策略示例数据倾斜(Data S

MySQL中慢SQL优化的不同方式介绍

《MySQL中慢SQL优化的不同方式介绍》慢SQL的优化,主要从两个方面考虑,SQL语句本身的优化,以及数据库设计的优化,下面小编就来给大家介绍一下有哪些方式可以优化慢SQL吧... 目录避免不必要的列分页优化索引优化JOIN 的优化排序优化UNION 优化慢 SQL 的优化,主要从两个方面考虑,SQL 语