微服务项目【商品秒杀接口压测及优化】
生成测试用户
将UserUtils工具类导入到zmall-user模块中,运行生成测试用户信息,可根据自身电脑情况来生成用户数量。
UserUtils:
package com.xujie.zmall.utils;import com.alibaba.nacos.common.utils.MD5Utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xujie.zmall.model.User;
import com.xujie.zmall.util.JsonResponseBody;import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;public class UserUtils {private static void createUser(int count) throws Exception {List<User> lst=new ArrayList<User>();//循环添加用户数据for(int i=0;i<count;i++){User user=new User();user.setLoginName("user"+i);user.setUserName("测试用户"+i);user.setPassword(MD5Utils.md5Hex("123456".getBytes()));user.setType(0);user.setMobile((17700000000L+i)+"");user.setEmail("user"+i+"@139.com");user.setIdentityCode((430104199912120000L+i)+"");lst.add(user);}System.out.println("create users");//获取数据库连接Connection conn=getConn();//定义SQLString sql="insert into zmall_user(loginName,userName,password,identityCode,email,mobile,type) values(?,?,?,?,?,?,?)";//执行SQLPreparedStatement ps=conn.prepareStatement(sql);//赋值for (User user : lst){ps.setString(1,user.getLoginName());ps.setString(2,user.getUserName());ps.setString(3,user.getPassword());ps.setString(4,user.getIdentityCode());ps.setString(5,user.getEmail());ps.setString(6,user.getMobile());ps.setInt(7,user.getType());ps.addBatch();}ps.executeBatch();ps.clearParameters();ps.close();conn.close();System.out.println("insert to db");//登录,生成UserTicketString urlString="http://localhost:8010/userLogin";File file=new File("C:\\Users\\xj\\DeskTop\\config.txt");if(file.exists()){file.delete();}RandomAccessFile accessFile=new RandomAccessFile(file,"rw");//设置光标位置accessFile.seek(0);for (User user : lst) {URL url=new URL(urlString);HttpURLConnection co = (HttpURLConnection) url.openConnection();co.setRequestMethod("POST");co.setDoOutput(true);OutputStream out=co.getOutputStream();String params="loginName="+user.getLoginName()+"&password=123456";out.write(params.getBytes());out.flush();InputStream in=co.getInputStream();ByteArrayOutputStream bout=new ByteArrayOutputStream();byte[] buffer=new byte[1024];int len=0;while((len=in.read(buffer))>=0){bout.write(buffer,0,len);}in.close();bout.close();String response=new String(bout.toByteArray());ObjectMapper mapper=new ObjectMapper();JsonResponseBody jsonResponseBody=mapper.readValue(response, JsonResponseBody.class);String token=jsonResponseBody.getData().toString();System.out.println("create token:"+token);accessFile.seek(accessFile.length());accessFile.write(token.getBytes());accessFile.write("\r\n".getBytes());//System.out.println("write to file:"+token);}accessFile.close();System.out.println("over");}private static Connection getConn() throws Exception {String url="jdbc:mysql://localhost:3306/zmall?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&characterEncoding=UTF8";String driver="com.mysql.jdbc.Driver";String username="root";String password="1234";Class.forName(driver);return DriverManager.getConnection(url,username,password);}public static void main(String[] args) throws Exception {createUser(100);}
}
1)必须保证zmall-user模块处于运行状态下,在进行测试用户数据生成操作;
2)注意修改UserUtils中的用户登录接口地址及端口;同时请修改用户登录接口,将生成的token令牌存入响应封装类中;//5.通过UUID生成token令牌并保存到cookie中 String token= UUID.randomUUID().toString().replace("-",""); ... return new JsonResponseBody<>(token);3)设置生成登录令牌存储位置;
4)修改数据库名、登录账号及密码;
5)设置生成测试用户数量;
jmeter压测
线程组:1000个线程,1秒之内发送,循环1次。测试结果如下:吞吐量为139/s
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OeLKcW66-1676465432384)(images\2022-08-18_172645.png)]](https://img-blog.csdnimg.cn/8b127a6a92dd4016ba911c516a757932.png)
数据库中的秒杀商品表中的商品出现了库存为负数的问题。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G3BKywcE-1676465432388)(images\20220818214728.jpg)]](https://img-blog.csdnimg.cn/8cd8c97b9205478d86f283f3c006b7b7.jpeg)
订单表和订单项表中出现了秒杀商品超卖问题。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IN7oYGQ0-1676465432390)(images\20220818214649.jpg)]](https://img-blog.csdnimg.cn/1262e55e2dd247c3912a680c1b0e1cef.jpeg)
秒杀接口优化
优化第一步:解决超卖
更新秒杀商品库存的sql语句,只有当库存大于0才能更新库存;修改更新秒杀库存方法updateKillStockById的返回类型为boolean,用于判断是否更新成功。
KillServiceImpl
@Service
public class KillServiceImpl extends ServiceImpl<KillMapper, Kill> implements IKillService {@Transactional@Overridepublic boolean updateKillStockById(Integer pid) {return this.update(new UpdateWrapper<Kill>().setSql("total=total-1").eq("item_id",pid).gt("total",0)); //修改点}
}
OrderServiceImpl
@Transactional
@Override
public JsonResponseBody<?> createKillOrder(User user, Integer pid) {...//4.秒杀商品库存减一boolean flag=killService.updateKillStockById(pid);if(!flag)throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);...//生成秒杀订单等操作return new JsonResponseBody<>();
}
优化第二步:Redis重复抢购
在zmall_order表中将用户ID+商品ID设置为唯一组合索引。
可以再zmall_order表中新增一个商品ID字段,用于与用户ID一起控制用户重复抢购问题。
在RedisService中新增以下两个方法,用于Redis重复抢购的判断操作。
- 根据用户ID和秒杀商品ID为Key,将秒杀订单保存到Redis中;
- 根据用户ID和秒杀商品ID从Redis中获取对应的秒杀商品;
RedisServiceImpl
/*** 将秒杀订单保存到Redis* @param pid 商品ID* @param order 秒杀订单
*/
@Override
public void setKillOrderToRedis(Integer pid, Order order) {redisTemplate.opsForValue().set("order:"+order.getUserId()+":"+pid,order,1800, TimeUnit.SECONDS);
}/*** 根据用户ID和商品ID从Redis中获取秒杀商品,用于重复抢购判断* @param uid 用户ID* @param pid 商品ID* @return 返回Redis中存储的秒杀订单
*/
@Override
public Order getKillOrderByUidAndPid(Integer uid, Integer pid) {return (Order) redisTemplate.opsForValue().get("order:"+uid+":"+pid);
}
这里用户抢购的秒杀订单保存到Redis默认设置是1800秒,即30分钟;可视情况具体调整。
OrderServiceImpl
@Transactional
@Override
public JsonResponseBody<?> createKillOrder(User user, Integer pid) {.../***********在库存判断是否为空之后***********///6.根据秒杀商品ID和用户ID判断是否重复抢购Order order = redisService.getKillOrderByUidAndPid(user.getId(), pid);if(null!=order)throw new BusinessException(JsonResponseStatus.ORDER_REPART);/***********在根据商品ID获取商品之前***********///4.秒杀商品库存减一boolean flag=killService.updateKillStockById(pid);if(!flag)throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);...//生成秒杀订单等操作//重点,重点,重点,在此处将生成的秒杀订单保存到Redis中,用于之后的重复抢购判断redisService.setKillOrderToRedis(pid,order);return new JsonResponseBody<>();
}
此处第二步优化完毕,再次进行JMeter压测,并查看测试情况。
优化第三步:Redis预减库存
商品初始化
将参与秒杀活动且秒杀状态、秒杀活动时间有效的商品推送到Redis中,并对秒杀商品设置超时时间。
超时时间的设定取至于活动结束时间减去活动开始时间的差值,但必须是有效活动时间,也就是当前时间在活动开始时间与结束时间范围之内。
IRedisService
/**
* 设置秒杀商品库存到Redis中
* @param pid 秒杀商品ID
* @param total 秒杀商品数量
* @param expires 秒杀商品存储过期时间
*/
void setKillTotaltoRedis(Integer pid,Integer total,long expires);
RedisServiceImpl
@Override
public void setKillTotaltoRedis(Integer pid, Integer total,long expires) {redisTemplate.opsForValue().set("goods:"+pid,total,expires,TimeUnit.DAYS);
}
OrderController
在zmall-order订单模块中的OrderController类上实现InitializingBean,完成秒杀商品预加载。
@Controller
public class OrderController implements InitializingBean {@Autowiredprivate IRedisService redisService;/*** 秒杀商品初始化* @throws Exception*/@Overridepublic void afterPropertiesSet() throws Exception {List<Kill> list =killService.list(new QueryWrapper<Kill>()//秒杀活动必须是激活状态.eq("is_active", 1)//秒杀活动结束时间必须>=当前时间,小于证明活动已结束.ge("end_time",new Date().toLocaleString()));list.forEach(kill -> {//计算秒杀商品存入Redis的过期时间,此处以天为单位Instant start = kill.getStartTime().toInstant();Instant end = kill.getEndTime().toInstant();long days = Duration.between(start, end).toDays();redisService.setKillTotaltoRedis(kill.getItemId(),kill.getTotal(),days);});}
}
预减库存
第一步:在RedisService中定义库存预减和递增方法。预减方法是在用户抢购商品成功后对商品进行库存预减;递增方法是在高并发情况下Redis库存预减可能会出现负数情况,通过递增方法进行库存回滚为0
IRedisService
/**
* 根据秒杀商品ID实现Redis商品库存递增
* @param pid
* @return
*/
long increment(Integer pid);/**
* 根据秒杀商品ID实现Redis商品库存递减
* @param pid
* @return
*/
long decrement(Integer pid);
RedisServiceImpl
@Override
public long increment(Integer pid) {return redisTemplate.opsForValue().increment("goods:"+pid);
}@Override
public long decrement(Integer pid) {return redisTemplate.opsForValue().decrement("goods:"+pid);
}
第二步:修改订单生成方法,加入Redis库存预减判断
请在Redis重复抢购判断的下面加入Redis库存预减操作。
//7.Redis库存预减
long stock = redisService.decrement(pid);
if(stock<0){redisService.increment(pid);throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);
}
第三步:还原测试数据,重新使用jmeter压测,这时可以发现明显压测效率要提升很多。
但是还是要根据不同电脑配置情况来决定,配置太低,效率也提升不了多少。
相关文章:
微服务项目【商品秒杀接口压测及优化】
生成测试用户 将UserUtils工具类导入到zmall-user模块中,运行生成测试用户信息,可根据自身电脑情况来生成用户数量。 UserUtils: package com.xujie.zmall.utils;import com.alibaba.nacos.common.utils.MD5Utils; import com.fasterxml.j…...
1997. 访问完所有房间的第一天
题目 你需要访问 n 个房间,房间从 0 到 n - 1 编号。同时,每一天都有一个日期编号,从 0 开始,依天数递增。你每天都会访问一个房间。 最开始的第 0 天,你访问 0 号房间。给你一个长度为 n 且 下标从 0 开始 的数组 n…...
通达信交易接口以什么形式执行下单的?
通达信程交易接口 以API形式来执行下单接口,一般不再需要通过接口系统之间进行连接,通过直接调用通达信dll交易函数的方式直接进行交易,包括下单,撤单,查询资金股份、当日委托、当日成交等方面都能很快的执行出来。以a…...
CobaltStrike上线微信通知
CobaltStrike上线微信通知 利用pushplus公众号(每天免费发送200条消息) http://www.pushplus.plus/push1.html 扫码登录后需要复制token 可以测试一下发送一下消息,手机会受到如下消息。可以在微信提示里将消息免打扰关闭(默认…...
喜茶、奈雪的茶“花式”寻生路
配图来自Canva可画 疫情全面开放不少人“阳了又阳”,电解质饮品成为热销品,梨子、橘子、柠檬等水果被卖断货,凉茶、黄桃罐头被抢购一空,喜茶的“多肉大橘”、奈雪的“霸气银耳炖梨”、蜜雪冰城的“棒打鲜橙”、沪上阿姨的“鲜炖整…...
Xstream使用教程
1.Xstream介绍 官网:https://x-stream.github.io/tutorial.html 介绍:XStream 对象序列化和反序列化为 XML的一个JAVA类库。JDK 1.4以上适用。 PS:与JAXB相比,Xstream更好用一些,像XStreamImplicit这种注解,我在JAX…...
【正点原子FPGA连载】第十一章PL SYSMON测量输入模拟电压 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南
1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id692450874670 3)全套实验源码手册视频下载地址: http://www.openedv.com/thread-340252-1-1.html 第十一章PL SYSM…...
纷享销客百思特 | 数字化营销赋能企业新增长沙龙圆满落幕
为进一步帮助企业客户实现数字化转型,纷享销客联合百思特管理咨询集团,于2月10日举办 “数字化营销赋能企业新增长”主题沙龙。本次活动以“新变革新增长”为主题,现场30余位制造企业高管齐聚一堂,共同探讨企业如何在当前复杂的宏…...
oracle查看具体表占用空间 oracle查看表属于哪个用户
文章目录前言oracle查看具体表占用空间1、查看表空间总大小、使用率、剩余空间2、查看具体表的占用空间大小3、查看表空间对应日志文件oracle查看表属于哪个用户1、oracle怎么查看表属于哪个用户2、Oracle查询视图所属用户3、Oracle查询存储过程所属用户总结前言 表空间是数据…...
2.Visual Studio下载和安装
Visual Studio 是微软提供的一个集成开发环境(IDE),主要用于为 Windows 系统开发应用程序。Visual Studio 提供了构建 .Net 平台应用程序的一站式服务,可以使用 Visual Studio 开发、调试和运行应用程序。 1、Visual Studio下载 …...
「4」线性代数(期末复习)
🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀 目录 第四章 向量组的线性相关性 &2)向量组的线性相关性 &3)向…...
IDEA中使用tomcat8-maven-plugin插件
第一种方式 pom.xml <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.or…...
2023年妇女节是哪一天 妇女节是2023年几月几日?
2023年妇女节是哪一天是2023年几月几日? 2023年妇女节是2023年3月8日 三八妇女节是国家法定节假日吗? 妇女节不是国家法定节假日,而国家法定节假日包括:元旦、春节、清明节、劳动节、端午节、中秋节、国庆节; 关于三…...
如何运维多集群数据库?58 同城 NebulaGraph Database 运维实践
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SktQW2qn-1676450580889)(https://www-cdn.nebula-graph.com.cn/nebula-website-5.0/images/blogs/58.%20Com%20Inc/58%E5%90%8C%E5%9F%8E_%E7%94%BB%E6%9D%BF%201.jpg)] 图计算业务背景介绍 我们为什…...
尚医通(十四)Spring Cloud GateWay网关 | 跨域 | 权限认证
目录一、网关基本概念1、API网关介绍2、Spring Cloud Gateway3、Spring Cloud Gateway核心概念二、创建service_gateway模块(网关服务)1、创建service_gateway模块2、在pom.xml引入依赖3、编写application.properties配置文件4、编写启动类5、前端端口号…...
PO模式在Selenium中简单实践
初识PO模式 PO(PageObject)是一种设计模式。简单来说就是把一些繁琐的定位方法、元素操作方式等封装到类中,通过类与类之间的调用完成特定操作。 PO被认为是自动化测试项目开发实践的最佳设计模式之一。 在学习PO模式前,可以先…...
KubeSphere
文章目录一、概述二、最小化安装 KubeSphere2.1 前提2.2 安装 nfs 服务器一、概述 KubeSphere是在Kubernetes之上构建的以应用为中心的企业级分布式容器平台,提供简单易用的操作界面以及向导式操作方式,在降低用户使用容器调度平台学习成本的同时&#…...
JAVA基础阶段面试题(关键点)必备
1、简述什么是 JDK、JRE 和 JVM? JDK : 开发工具包JRE : 运行时环境JVM : java虚拟机2、写出Java的四类八种基本数据类?整数 byte short int long小数(浮点) float double布尔 boolean字符 char3、& 和 && 的区别 ?& 符号的左右两边,无…...
Shiro简介
介绍 ApacheShiro 是一个功能强大且易于使用的 Java 安全(权限)框架。Shiro 可以完成:认证、授权、加密、会话管理、与 Web集成、缓存等。借助Shiro 您可以快速轻松地保护任何应用程序一一从最小的移动应用程序到最大的 Web 和企业应用程序。 1.2:为什么要用 shiro 自2003年以…...
cmu 445 poject 3笔记
2022年的任务 https://15445.courses.cs.cmu.edu/fall2022/project3/ task1, 从磁盘读取数据的算子 task2, 聚合和join算子 task3, sort,limit,topn算子,以及sortlimit->TopN优化 leaderboard没做 本文不写代码,只记录遇到的一些思维盲点 Task1 scan…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
