秒杀系统的架构设计

秒杀系统,是典型的短时大量突发访问类问题。对这类问题,有三种优化性能的思路:

  1. 写入内存而不是写入硬盘
  2. 异步处理而不是同步处理
  3. 分布式处理

用上这三招,不论秒杀时负载多大,都能轻松应对, 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 模拟高并发商品秒杀测试相关推荐

  1. PHP高并发商品秒杀问题的解决方案

    前言 秒杀会产生一个瞬间的高并发,使用数据库会增加数据库的访问压力,也会降低访问速度,所以我们应该使用缓存,来降低数据库的访问压力: 可以看出这里的操作和原来的下单是不一样的:产生的秒杀预订单不会马上 ...

  2. redis解决“高并发定时秒杀”库存误差问题

    前言:高并发的秒杀活动中,通过查询数据库判断是否还有库存,然后对库存字段进行增减,极易出现库存超出或者库存为负的情况,一般来说有3中解决办法(数据库表加锁,memche缓存,redis队列): 我们这 ...

  3. gin redis 链接不上_php + redis 高并发商品秒杀 完整业务模拟流程 实现方案

    关于商品秒杀 之前百度了很多关于商品秒杀的业务怎么做,网上的答案真的是五花八门,归纳一下就两种方式 队列或计数器 网上大部分都没有写具体的代码业务 我这里模拟一个业务小场景进行实践 商品:1 每日限量 ...

  4. Redis解决高并发(秒杀抢红包)

    1,Redis 丰富的数据结构(Data Structures) 字符串(String) Redis字符串能包含任意类型的数据 一个字符串类型的值最多能存储512M字节的内容 利用INCR命令簇(IN ...

  5. SpringBoot +Redis +RabbitMQ 实现高并发限时秒杀

    SpringBoot +Redis +RabbitMQ 实现高并发限时秒杀 提示:以下是本篇文章正文内容,下面案例可供参考 一.软件安装 1.安装RabbitMQ docker安装:docker安装R ...

  6. SpringBoot实现Java高并发秒杀系统之DAO层开发(一)

    SpringBoot实现Java高并发秒杀系统之DAO层开发(一) 秒杀系统在如今电商项目中是很常见的,最近在学习电商项目时讲到了秒杀系统的实现,于是打算使用SpringBoot框架学习一下秒杀系统( ...

  7. Spring Boot集成Redis缓存之模拟高并发场景处理

    前言 同样我们以上一篇文章为例子,搭建好环境之后,我欧美可以模拟高并发场景下,我们的缓存效率怎么样,到底能不能解决我们实际项目中的缓存问题.也就是如何解决缓存穿透? Spring Boot集成Redi ...

  8. SpringBoot实现Java高并发秒杀系统之Service层开发(二)

    继上一篇文章:SpringBoot实现Java高并发秒杀系统之DAO层开发 我们创建了SpringBoot项目并熟悉了秒杀系统的表设计,下面我们将讲解一下秒杀系统的核心部分:Service业务层的开发 ...

  9. php redis下单,redis 队列简单实现高并发抢购/秒杀

    redis 队列简单实现高并发抢购/秒杀 2019-03-21 14:34 阅读数 82 前提为每人限购1件 <>开抢前 把秒杀商品库存存进 Redis 队列中 $redis = new ...

最新文章

  1. Bitcoin ABC首席开发者回应有关比特币现金的提问(二)
  2. 创建模块化程序(二)
  3. Linux基础篇之文本、数据流处理命令(sed uniq grep awk wc)
  4. 2020-11-22(树,森林和二叉树转换)
  5. 《机器学习与R语言(原书第2版)》一2.3 探索和理解数据
  6. rocketmq 订阅组_必须先理解的RocketMQ入门手册,才能再次深入解读
  7. Objective-C 中自动生成 setter getter 方法
  8. SSM整合(配置文件)
  9. 个推Node.js 微服务实践:基于容器的一站式命令行工具链
  10. 799元首发!小米手表Color:14天超长续航、专业运动健康管理
  11. 解析身份证_你需要知道的与身份证相关的7个函数,让你的工作效率快速提升!...
  12. mysql escape 注入_php 防mysql注入函数 addslashes和mysql_real_escape_string
  13. Tomcat logs 目录下各日志文件的含义
  14. JAVA电影院售票系统毕业设计 开题报告
  15. linux的网易云音乐界面不显示,linux mint19.1解决网易云音乐安装后打不开的问题...
  16. treble_Android上的Project Treble是什么,我的手机会收到吗?
  17. Sqoop导入导出基本操作
  18. 记成功安装win10+elementary双系统
  19. Libuv源码分析 —— 9. DNS
  20. Blender遇到的一些莫名其妙的问题

热门文章

  1. 吕鑫MFC学习系列五
  2. Win7补丁对比环境搭建
  3. gre模考软件java.exe_5款GRE模考软件神器大汇总
  4. AE2020 TypeMonkey V1.18脚本安装
  5. 如何在线快速把json数据转excel表格
  6. jqweui.com
  7. 为什么比尔·盖茨可以有大成就?
  8. 高级软件工程师证书有用吗_bim工程师证书有用吗?bim好在哪里?
  9. Apache Benchmark(ab)使用
  10. 一个python开发者的修炼之路