秒杀系统的设计与压测

embedded/2024/11/13 22:13:11/

环境准备

数据库

完成demo至少需要两个数据表,一个customer表示秒杀的用户,一个sec_product表示被秒杀的商品。

create database sec_kill;use sec_kill;
create table customer(id int primary key auto_increment not null,name varchar(20),phone varchar(20)
);create table product(id int primary key,name varchar(20),stock int
);create table product_order
(id int auto_increment primary key,product_id  int null,customer_id int null
);

在customer中添加5000个用户,用于秒杀。用SQL脚本实现:

-- 插入5000个customer记录
delimiter $$create procedure insert_customers()
BEGINdeclare i int default 1;declare max int default 5000;while i <= max do-- 生成name字段,格式为customer_xxxx,xxxx为编号SET @name = concat('customer_', lpad(i, 4, '0'));-- 生成phone字段,格式为1300000xxxx,xxxx为编号SET @phone = concat('1300000', lpad(i, 4, '0'));insert into customer (name, phone) values (@name, @phone);set i = i + 1;end while ;
END $$delimiter ;-- 调用存储过程
call insert_customers();

在这里插入图片描述

JMeter测试

  1. 新建一个测试计划
  2. 在测试计划中添加一个线程组,代表并发用户数,可以设置循环次数
  3. 在线程组下添加HTTP请求
  4. 导入数据库中的customer
  5. 添加聚合报告,用于查看

数据库乐观锁的方式实现

@Service
public class SecKillServiceImpl extends ServiceImpl<ProductMapper, Product> implements SecKillService{@Resourceprivate ProductOrderService productOrderService;@Overridepublic String sec_kill(int customer_id, int product_id) {boolean result = this.update().setSql("stock = stock - 1").eq("id", product_id).gt("stock", 0).update(); // where id = ? and stock > 0if (!result) {return "秒杀失败";}ProductOrder order = new ProductOrder();order.setCustomerId(1);order.setProductId(customer_id);productOrderService.save(order);return "抢购成功";}
}

在这里插入图片描述
吞吐量大概是500/s

用reentrantLock锁整个逻辑

    @Overridepublic String sec_kill(int customer_id, int product_id) {ReentrantLock lock = new ReentrantLock();lock.lock();boolean result = this.update().setSql("stock = stock - 1").eq("id", product_id).gt("stock", 0).update();  if (!result) {return "秒杀失败";}ProductOrder order = new ProductOrder();order.setCustomerId(1);order.setProductId(customer_id);productOrderService.save(order);lock.unlock();return "抢购成功";}

在这里插入图片描述

用synchronized锁整个逻辑

    @Overridepublic synchronized String sec_kill(int customer_id, int product_id) {boolean result = this.update().setSql("stock = stock - 1").eq("id", product_id).gt("stock", 0).update();if (!result) {return "秒杀失败";}ProductOrder order = new ProductOrder();order.setCustomerId(1);order.setProductId(customer_id);productOrderService.save(order);return "抢购成功";}

在这里插入图片描述

用redis异步的方式

    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;static {SECKILL_SCRIPT = new DefaultRedisScript<>();SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));SECKILL_SCRIPT.setResultType(Long.class);}@Autowiredprivate StringRedisTemplate redisTemplate;@Overridepublic String sec_kill(int customer_id, int product_id) {Long result = redisTemplate.execute(SECKILL_SCRIPT, Collections.emptyList(), product_id + "", customer_id + "");int r = result.intValue();if (r != 0) {return "秒杀失败";}return "抢购成功";}

seckill.lua:

-- 1.参数列表
-- 1.1.优惠券id
local voucherId = ARGV[1]
-- 1.2.用户id
local userId = ARGV[2]-- 2.数据key
-- 2.1.库存key
local stockKey = 'seckill:stock:' .. voucherId
-- 2.2.订单key
local orderKey = 'seckill:order:' .. voucherId-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call('get', stockKey)) <= 0) then-- 3.2.库存不足,返回1return 1
end
-- 3.2.判断用户是否下单 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then-- 3.3.存在,说明是重复下单,返回2return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
-- 3.5.下单(保存用户)sadd orderKey userId
redis.call('sadd', orderKey, userId)
-- 3.6.发送消息到队列中, XADD stream.orders * k1 v1 k2 v2 ...
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId)
return 0

在这里插入图片描述
可能由于虚拟机配置较低的原因,提升效果并不明显。


http://www.ppmy.cn/embedded/136863.html

相关文章

蓝桥杯备赛(持续更新)

16届蓝桥杯算法类知识图谱.pdf 1. 格式打印 %03d&#xff1a;如果是两位数&#xff0c;将会在前面添上一位0 %.2f&#xff1a;会保留两位小数 如果是long&#xff0c;必须在数字后面加上L。 2. 进制转化 2.1. 十进制转任意进制&#xff1a; 十进制转任意进制时&#xff…

Android中桌面小部件framework层使用到的设计模式

在Android中&#xff0c;桌面小部件&#xff08;App Widget&#xff09;的Framework层采用了多种设计模式&#xff0c;以实现模块化、可维护性和高效的交互。 以下是Android桌面小部件Framework层中常用的设计模式及其具体应用&#xff1a; 1. 观察者模式&#xff08;Observe…

redis开启远程登录

redis开启远程登录 Redis 默认配置下仅允许本地访问,要开启远程登录,你需要修改 Redis 配置文件中的一些设置。以下步骤将指导你如何配置 Redis 以允许远程访问: 找到 Redis 配置文件: Redis 的配置文件通常名为 redis.conf。在 Linux 系统上,它可能位于 /etc/redis/redi…

重构代码之移动字段

移动字段用于将字段从一个类移动到另一个更合适的类中。通常&#xff0c;当某个字段在当前类中的使用很少&#xff0c;或者更多地被其他类依赖时&#xff0c;可以考虑将其移动到更加合适的类中&#xff0c;以提升代码的聚合性和可维护性。 一、适用场景 字段主要为其他类提供…

【LeetCode】每日一题 2024_11_10 有序数组中的单一元素(二分)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 题目&#xff1a;有序数组中的单一元素 代码与解题思路 先读题&#xff1a; “一个仅由整数组成的有序数组” “你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度。” 看到这里基本上…

论文翻译 | Chain of Hindsight aligns LanguageModels with Feedback

摘要 从人类偏好中学习对于语言模型匹配人类需求并与人类和社会价值观保持一致非常重要。先前的研究通过学习人类的反馈来理解和遵循指令&#xff0c;取得了显著的成功。尽管如此&#xff0c;这些方法要么是建立在人工注释者青睐的精心挑选的模型上&#xff0c;这使得它们在数据…

PySimpleGUI 库 和 pymsql 库

PySimpleGUI 库 PySimpleGUI 是一个用于简化 GUI 编程的 Python 包&#xff0c;它封装了多种底层 GUI 框架&#xff08;如 tkinter、Qt、WxPython 等&#xff09;&#xff0c;提供了简单易用的 API。PySimpleGUI 包含了大量的控件&#xff08;也称为小部件或组件&#xff09;&…

AI笔筒操作说明及应用场景

AI笔筒由来&#xff1a; 在快节奏的现代办公环境中&#xff0c;我们一直在寻找既能提升效率、增添便利&#xff0c;又能融入企业文化、展现个人品味的桌面伙伴。为此&#xff0c;我们特推出专为追求卓越、注重细节的您设计的AI笔筒礼品版&#xff0c;它集高科技与实用性于一身…