SpringBoot: Redis 模拟高并发商品秒杀测试
秒杀系统的架构设计
秒杀系统,是典型的短时大量突发访问类问题。对这类问题,有三种优化性能的思路:
- 写入内存而不是写入硬盘
- 异步处理而不是同步处理
- 分布式处理
用上这三招,不论秒杀时负载多大,都能轻松应对, Redis正好能完美满足上述三点。因此,用Redis就能轻松实现秒杀系统。
秒杀测试代码编写:
package com.xiao.springbootredisseckill.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.atomic.AtomicInteger;@RestController
public class TestController {@Autowiredprivate RedisTemplate redisTemplate;// 记录实际卖出的商品数量private AtomicInteger successNum = new AtomicInteger(0);// 设置库存@GetMapping("/setStockNum")public String setStockNum(@RequestParam Integer num){// 初始化库存数量redisTemplate.opsForValue().set("product_stock",num);//初始化实际卖出的商品数量0successNum.set(0);return "Ok";}// 秒杀核心代码@GetMapping("/grabBuy")public String grabBuy() {Integer sku = (Integer)redisTemplate.opsForValue().get("product_stock");sku = sku - 1;if (sku < 0) {return "库存不足";}redisTemplate.opsForValue().set("product_stock", sku);//记录实际卖出的商品数量return "减少库存成功,共减少" + successNum.incrementAndGet();}// 获取库存数量@GetMapping("/getStockNum")public Object getStockNum() {Integer product_stock = Integer.parseInt((String) redisTemplate.opsForValue().get("product_stock"));System.out.println(product_stock);return product_stock;}@GetMapping(value = "/successNum")public String successNum() {return "顾客成功抢到的商品数量:" + successNum.get();}
}
测试:
库存设为 100, 访问 127.0.0.1:8080/grabBuy 进行抢购。为了让测试场景更像实际抢购实际场景,这里使用 Apache 其下的 JMeter 压力测试工具,具体如何使用请自行百度
访问127.0.0.1:8080/setStockNum 设置库存,或者使用 rdm 设置库存
JMeter 配置压力测试脚本
测试结果:
访问 127.0.0.1:8080/successNum, 查看用户实际抢购成功的商品数量:
???? 为什么用户实际抢购数量为 1422 呢? 远超于库存 100 ,出现了超卖情况,如果实际应用中出现这样的错误,那跟用户怕是讲不清了 …
原因分析:
从上面测试结果,我们知道,高并发请求: 127.0.0.1:8080/grabBuy, 就会出现超卖现象。
其原因其实就是是库存数量product_stock的读和写操作不在同一个原子操作上,导致类似不可重复读的现象。可以类比多线程的问题。
什么是原子性呢?
在化学反应中,原子是不能再分的。在计算机中也就是某个操作不可分割的,就叫原子性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。
什么是不可重复读呢?
在学习数据库相关知识的时候,我们知道了事务的隔离级别:丢失更新 、脏读、不可重复读、幻读,其中不可重复读就是在同一事务中,多次读取同一数据但是返回不同的结果,也就是有其他事务更改了这些数据。
解决办法:
提供两种解决方法:
①: 通过redis事务解决超卖问题
@GetMapping("/grabBuy")public String grabBuy() {/** 解决商品超卖 方法一* */redisTemplate.setEnableTransactionSupport(true);List<Object> result = (List<Object>) redisTemplate.execute(new SessionCallback<List<Object>>() {@Overridepublic List<Object> execute(RedisOperations redisOperations) throws DataAccessException {// 监听redisOperations.watch("product_stock");Integer product_stock = (Integer)redisOperations.opsForValue().get("product_stock");// 开启事务redisOperations.multi();// 必要的空查询redisOperations.opsForValue().get("product_stock");product_stock = product_stock - 1;if (product_stock < 0) {return null;}// 剩余库存存入redisOperations.opsForValue().set("product_stock", product_stock);return redisOperations.exec();}});if (result != null && result.size() > 0) {return "减少库存成功,共减少" + successNum.incrementAndGet();}return "库存不足";}
②:通过使用 redisson 的 Rlock 加锁方式解决超卖问题
pom.xml 引入依赖包:
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter --><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.13.6</version></dependency>
秒杀代码编写:
@GetMapping("/grabBuy")public String grabBuy() {/** 解决商品超卖 方法二* */RLock rLock = redissonClient.getLock("product");try {rLock.lock();Integer product_num = (Integer)redisTemplate.opsForValue().get("product_stock");System.out.println(product_num);if (product_num < 1) {return "库存不足!";}// 自减一redisTemplate.opsForValue().decrement("product_stock");return "减少库存成功,共减少" + successNum.incrementAndGet();} finally {rLock.unlock();}}
注意: redissonClient.getLock() 括号内的参数千万不能取redis数据库脸面已经含有的键名,否则报如下错误,我找半天才发现这里的错误。
org.redisson.client.RedisException: ERR Error running script (call to f_3ffe249c16dee540ac8ab32e39bd408626b9aff7): @user_script:1: WRONGTYPE Operation against a key holding the wrong kind of value . channel: [id: 0xfc81693b, L:/127.0.0.1:11732 - R:localhost/127.0.0.1:6379] command: (EVAL), params: [if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil;end; local counter = redis.call('h..., 2, product_stock, redisson_lock__channel:{product_stock}, 0, 30000, 93656223-0013-4a55-a2d6-06ec7df0cda2:71]
结果展示:
成功解决超卖问题!
SpringBoot: Redis 模拟高并发商品秒杀测试相关推荐
- PHP高并发商品秒杀问题的解决方案
前言 秒杀会产生一个瞬间的高并发,使用数据库会增加数据库的访问压力,也会降低访问速度,所以我们应该使用缓存,来降低数据库的访问压力: 可以看出这里的操作和原来的下单是不一样的:产生的秒杀预订单不会马上 ...
- redis解决“高并发定时秒杀”库存误差问题
前言:高并发的秒杀活动中,通过查询数据库判断是否还有库存,然后对库存字段进行增减,极易出现库存超出或者库存为负的情况,一般来说有3中解决办法(数据库表加锁,memche缓存,redis队列): 我们这 ...
- gin redis 链接不上_php + redis 高并发商品秒杀 完整业务模拟流程 实现方案
关于商品秒杀 之前百度了很多关于商品秒杀的业务怎么做,网上的答案真的是五花八门,归纳一下就两种方式 队列或计数器 网上大部分都没有写具体的代码业务 我这里模拟一个业务小场景进行实践 商品:1 每日限量 ...
- Redis解决高并发(秒杀抢红包)
1,Redis 丰富的数据结构(Data Structures) 字符串(String) Redis字符串能包含任意类型的数据 一个字符串类型的值最多能存储512M字节的内容 利用INCR命令簇(IN ...
- SpringBoot +Redis +RabbitMQ 实现高并发限时秒杀
SpringBoot +Redis +RabbitMQ 实现高并发限时秒杀 提示:以下是本篇文章正文内容,下面案例可供参考 一.软件安装 1.安装RabbitMQ docker安装:docker安装R ...
- SpringBoot实现Java高并发秒杀系统之DAO层开发(一)
SpringBoot实现Java高并发秒杀系统之DAO层开发(一) 秒杀系统在如今电商项目中是很常见的,最近在学习电商项目时讲到了秒杀系统的实现,于是打算使用SpringBoot框架学习一下秒杀系统( ...
- Spring Boot集成Redis缓存之模拟高并发场景处理
前言 同样我们以上一篇文章为例子,搭建好环境之后,我欧美可以模拟高并发场景下,我们的缓存效率怎么样,到底能不能解决我们实际项目中的缓存问题.也就是如何解决缓存穿透? Spring Boot集成Redi ...
- SpringBoot实现Java高并发秒杀系统之Service层开发(二)
继上一篇文章:SpringBoot实现Java高并发秒杀系统之DAO层开发 我们创建了SpringBoot项目并熟悉了秒杀系统的表设计,下面我们将讲解一下秒杀系统的核心部分:Service业务层的开发 ...
- php redis下单,redis 队列简单实现高并发抢购/秒杀
redis 队列简单实现高并发抢购/秒杀 2019-03-21 14:34 阅读数 82 前提为每人限购1件 <>开抢前 把秒杀商品库存存进 Redis 队列中 $redis = new ...
最新文章
- Bitcoin ABC首席开发者回应有关比特币现金的提问(二)
- 创建模块化程序(二)
- Linux基础篇之文本、数据流处理命令(sed uniq grep awk wc)
- 2020-11-22(树,森林和二叉树转换)
- 《机器学习与R语言(原书第2版)》一2.3 探索和理解数据
- rocketmq 订阅组_必须先理解的RocketMQ入门手册,才能再次深入解读
- Objective-C 中自动生成 setter getter 方法
- SSM整合(配置文件)
- 个推Node.js 微服务实践:基于容器的一站式命令行工具链
- 799元首发!小米手表Color:14天超长续航、专业运动健康管理
- 解析身份证_你需要知道的与身份证相关的7个函数,让你的工作效率快速提升!...
- mysql escape 注入_php 防mysql注入函数 addslashes和mysql_real_escape_string
- Tomcat logs 目录下各日志文件的含义
- JAVA电影院售票系统毕业设计 开题报告
- linux的网易云音乐界面不显示,linux mint19.1解决网易云音乐安装后打不开的问题...
- treble_Android上的Project Treble是什么,我的手机会收到吗?
- Sqoop导入导出基本操作
- 记成功安装win10+elementary双系统
- Libuv源码分析 —— 9. DNS
- Blender遇到的一些莫名其妙的问题