高并发场景下,基于Redisson实现的分布式锁
mhr18 2024-12-04 14:02 24 浏览 0 评论
分布式锁应用场景
随着互联网应用的高速发展,在电商应用中高并发应用场景涉及很多,例如:
- 秒杀:在大规模的秒杀场景中,需要保证商品数量、限制用户购买数量, 防止用户购买数量的超限、避免出现超卖情况;
- 订单支付:当用户下单付款时,需要对订单信息进行互斥操作以避免订单重复支付;
- 提现操作:需要防止用户重复提现,避免造成财务损失。 **总结:**分布式锁应用场景可以分为两类:
- 共享资源的互斥访问:当多个节点需要对同一个共享资源进行操作时,需要确保同一时刻只有一个节点可以操作,此时就可以使用分布式锁;
- 分布式任务调度:分布式系统往往需要对任务进行调度,确保任务在多个节点的协作下执行。而在并行的任务执行过程中,需要区分哪些任务已经被分配并且正在被执行,哪些任务没有被分配。利用分布式锁来保证任务的正确性、顺序性和稳定性。 概括地说,就是对多线程下,对共享变量操作,线程间是变量不可见,导致出现并发问题,需要通过分布式锁来进行控制,今天就给大家通过案例,分享一下如何使用redisson实现分布式锁。
案例需求描述
库存中有200件商品,通过商品下单购买场景,使用分布式锁避免商品超卖问题。
Redisson环境准备
本地Redis环境安装
windows下安装 默认端口:6379 下载链接 github.com/tporadowski 解压 双击redis-server.exe启动服务端 双击redis-cli.exe启动客户端连接服务端 在客户端输入 “ping”,出现“PONG”,即证明连接成功,部分配置可以在redis.conf文件修改;
Spring boot项目与redis集成
引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.22.0</version>
</dependency>
创建redis连接池代码
package com.zhc.config.redis;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redisson 连接池配置
* @author zhouhengchao
* @since 2023-06-19 20:29:00
* @version 1.0
*/
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.database}")
private Integer dataBase;
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = serializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// key采用String的序列化方式
redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
// value序列化方式采用jackson
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
// hash的key也采用String的序列化方式
redisTemplate.setHashKeySerializer(StringRedisSerializer.UTF_8);
//hash的value序列化方式采用jackson
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
/**
* 此方法不能用@Ben注解,避免替换Spring容器中的同类型对象
*/
public GenericJackson2JsonRedisSerializer serializer() {
return new GenericJackson2JsonRedisSerializer();
}
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setDatabase(dataBase);
// 设置redisson序列化方式,否则打开查看数据可能乱码
Codec codec = new JsonJacksonCodec();
config.setCodec(codec);
return Redisson.create(config);
}
}
redis的yaml文件配置
spring:
redis:
host: localhost
port: 6379
database: 0
扣减库存方法
/**
* 从redis中获取库存,扣减库存数量
*/
private void reduceStock(){
// 从redis中获取商品库存
RBucket<Integer> bucket = redissonClient.getBucket(REDIS_STOCK);
int stock = bucket.get();
if (stock > 0) {
// 库存-1
stock--;
// 更新库存
bucket.set(stock, 2, TimeUnit.DAYS);
log.info("扣减成功,剩余库存:" + stock);
} else {
log.info("扣减失败,库存不足");
}
}
基于synchronized加锁控制
@GetMapping("/test01")
public void test01(){
for (int i = 0; i < 6; i++) {
synchronized (this) {
new Thread(this::reduceStock).start();
}
}
}
我们通过了Synchronized锁,成功解决了多个线程争抢导致的超卖问题,但是有个问题,假设后期公司为了保证服务可用性。
将单击的应用,升级称为了集群的模式,那么是否会有超卖问题呢?
通过nginx搭建负载均衡
下载Nginx: ?nginx.org/download/ng… nginx.conf完整配置
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream redislock{
server localhost:8081 weight=1;
server localhost:8082 weight=1;
}
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
proxy_pass http://redislock;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
启动nginx,双击nginx.exe文件即可; 访问应用:http://localhost/test01 发现存在超卖问题。
使用redis分布式锁
@GetMapping("/test02")
public void test02(){
// 分布式锁名称,关键是多个应用要共享这一个Redis的key
String lockKey = "lockDeductStock";
// setIfAbsent 如果不存在key就set值,并返回1
//如果存在(不为空)不进行操作,并返回0,与redis命令setnx相似,setIfAbsent是java中的方法
// 根据返回值为1就表示获取分布式锁成功,返回0就表示获取锁失败
Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey);
// 加锁不成功,返回给前端错误码,前端给用户友好提示
if (Boolean.FALSE.equals(lockResult)) {
log.info("系统繁忙,请稍后再试!");
return;
}
reduceStock();
// 业务执行完成,删除这个锁
redisTemplate.delete(lockKey);
}
1、主要使用setIfAbsent方法:如果不包含key就set值,并返回1; 如果存在(不为空)不进行操作,并返回0;
2、很明显,比get和set要好。因为先判断get,再set的用法,有可能会重复set值,与setnx类似。 以上redis加锁可以解决并发问题,但是存在问题:
- 如果setIfAbsent加锁成功,但是到业务逻辑代码时,该服务挂掉了,就会导致另一个服务一直获取不到锁,一直在等待中;
- 可以使用 redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey,30,TimeUnit.SECONDS),设置锁的key过期时间,在规定时间后key过期就可以再获取。
redis分布式锁优化
以上分布式锁还是存在问题,如果锁的key过期时间与程序执行时间差问题,例如:
- 如果锁key在程序执行结束前过期,就会导致删除key失败;
- 同时另一个应用获取了锁,又会被其他应用删掉锁,导致锁一直失效,存在并发问题。 可以通过引入UUId来解决锁被其他应用勿释放问题,如下代码:
@GetMapping("/test03")
public void test03(){
// 分布式锁名称,关键是多个应用要共享这一个Redis的key
String lockKey = "lockDeductStock";
// 分布式锁的值
String lockValue = UUID.randomUUID().toString().replaceAll("-", "");
// setIfAbsent 如果不存在key就set值,并返回1
//如果存在(不为空)不进行操作,并返回0,与redis命令setnx相似,setIfAbsent是java中的方法
// 根据返回值为1就表示获取分布式锁成功,返回0就表示获取锁失败
Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 30, TimeUnit.SECONDS);
// 加锁不成功,返回给前端错误码,前端给用户友好提示
if (Boolean.FALSE.equals(lockResult)) {
log.info("系统繁忙,请稍后再试!");
return ;
}
reduceStock();
// 判断是不是当前请求的UUID,如果是则可以正常释放锁。如果不是,则释放锁失败!
if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
还存在锁超时问题:锁超时问题,写一个定时任务,分线程每隔十秒去查看一次主线程是否持有这把锁,如果这个锁存在,重新将这个锁的超时时间设置为30S,对锁延时,比较复杂。
使用redisson实现分布式锁
@GetMapping("/test04")
public void test04(){
// 分布式锁名称,关键是多个应用要共享这一个Redis的key
String lockKey = "lockDeductStock";
// 获取锁对象
RLock redissonLock = redissonClient.getLock(lockKey);
try {
redissonLock.lock();
// boolean result = redissonLock.tryLock();
// 加锁不成功,返回给前端错误码,前端给用户友好提示
// if (!result) {
// log.info("系统繁忙,请稍后再试!");
// return;
// }
reduceStock();
}
finally{
if(redissonLock.isHeldByCurrentThread()){
redissonLock.unlock();
}
}
}
redisson分布式锁原理图:
关键方法介绍:
- lock() 方法是阻塞获取锁的方式,如果当前锁被其他线程持有,则当前线程会一直阻塞等待获取锁,直到获取到锁或者发生超时或中断等情况才会结束等待;
- tryLock() 方法是一种非阻塞获取锁的方式,在尝试获取锁时不会阻塞当前线程,而是立即返回获取锁的结果,如果获取成功则返回 true,否则返回 false. 总结:
- lock()方法获取到锁之后可以保证线程对共享资源的访问是互斥的,适用于需要确保共享资源只能被一个线程访问的场景。Redisson 的 lock() 方法支持可重入锁和公平锁等特性,可以更好地满足多线程并发访问的需求;
- tryLock() 方法支持加锁时间限制、等待时间限制以及可重入等特性,可以更好地控制获取锁的过程和等待时间,避免程序出现长时间无法响应等问题。 在实际应用中需要根据具体场景和业务需求来选择合适的方法,以确保程序的正确性和高效性。 视频中的内容如果对您有所帮助,请给个三连加关注的支持,欢迎在评论区留言讨论,后续会进一步完善文档。
作者:美丽的程序人生
链接:https://juejin.cn/post/7248156337691000890
来源:稀土掘金
- 上一篇:Java三种方式实现redis分布式锁
- 下一篇:深入浅出告诉你Redis的高级特性
相关推荐
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
- 备份软件调用rman接口备份报错RMAN-06820 ORA-17629 ORA-17627
-
一、报错描述:备份归档报错无法连接主库进行归档,监听问题12541RMAN-06820:WARNING:failedtoarchivecurrentlogatprimarydatab...
- 增量备份修复物理备库gap(增量备份恢复数据库步骤)
-
适用场景:主备不同步,主库归档日志已删除且无备份.解决方案:主库增量备份修复dg备库中的gap.具体步骤:1、停止同步>alterdatabaserecovermanagedstand...
- 一分钟看懂,如何白嫖sql工具(白嫖数据库)
-
如何白嫖sql工具?1分钟看懂。今天分享一个免费的sql工具,毕竟现在比较火的NavicatDbeaverDatagrip都需要付费才能使用完整功能。幸亏今天有了这款SQLynx,它不仅支持国内外...
- 「开源资讯」数据管理与可视化分析平台,DataGear 1.6.1 发布
-
前言数据齿轮(DataGear)是一款数据库管理系统,使用Java语言开发,采用浏览器/服务器架构,以数据管理为核心功能,支持多种数据库。它的数据模型并不是原始的数据库表,而是融合了数据库表及表间关系...
- 您还在手工打造增删改查代码么,该神器带你脱离苦海
-
作为Java开发程序,日常开发中,都会使用Spring框架,完成日常的功能开发;在相关业务系统中,难免存在各种增删改查的接口需求开发。通常来说,实现增删改查有如下几个方式:纯手工打造,编写各种Cont...
- Linux基础知识(linux基础知识点及答案)
-
系统目录结构/bin:命令和应用程序。/boot:这里存放的是启动Linux时使用的一些核心文件,包括一些连接文件以及镜像文件。/dev:dev是Device(设备)的缩写,该目录...
- PL/SQL 杂谈(二)(pl/sql developer使用)
-
承接(一)部分。我们从结构和功能这两个方面展示PL/SQL的关键要素。可以看看PL/SQL的优雅的代码。写出一个好的代码,就和文科生写出一篇优秀的作文一样,那么赏心悦目。1、与SQL的集成PL/S...
- 电商ERP系统哪个好用?(电商erp哪个好一点)
-
电商ERP系统哪个好用?做电商的,谁还没被ERP折腾过?有老板说:“我们早就上了ERP,订单、库存、财务全搞定,系统用得飞起。”也有运营吐槽:“系统是上了,可库存老不准,订单漏单错单天天有,财务对账还...
- 汽车检测线系统实例,看集中控制与PLC分布控制
-
PLC可编程控制器,上个世纪70年代初,为取代早期继电器控制线路,开始采取存储指令方式,完成顺序控制而设计的。开始仅有逻辑运算、计时、计数等简单功能。随着微处理的发展,PLC可编程能力日益提高,已经能...
- 苹果五件套成公司年会奖品主角,几大小技巧教你玩转苹果新品
-
钱江晚报·小时新闻记者张云山随着春节的临近,各家大公司的年会又将陆续上演。上周,各大游戏公司的年会大奖,苹果五件套又成了标配。在上海的游戏公司中,莉莉丝奖品列表拉得相当长,从特等奖到九等奖还包含了特...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)