Java秒杀系统及优化---(4)

本文详细介绍JMeter的安装与使用,包括自定义变量模拟多用户、命令行操作、SpringBoot应用压测及WAR包部署。通过实例演示如何进行性能测试,分析系统瓶颈。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

四、JMeter压测(主要是用来学习JMeter的使用,测试数据不具有参考意义)

  • JMeter入门
  • 自定义变量模拟多用户
  • JMeter命令行使用
  • SpringBoot打war包

1、JMeter入门

  • 官网:http://jmeter.apache.org/
  • 下载: http://jmeter.apache.org/download_jmeter.cgi
  • 用户手册: http://jmeter.apache.org/usermanual/index.html

JMeter压测验证:

win下压测

1.1)先下载jmeter

1.2)解压之后,找到bin目录下的jmeter.bat,运行(jmeter就是用java写的图形界面的应用程序)。

1.3)jmeter非常人性化,选项—选择语言,可以选择中文简体。

Options->Choose Language->Chinese(Simplified)

1.4)现在,我们先找个页面压测一下,压测前,我们先把程序都启动:

我们先来压测商品列表:/goods/to_list

右击测试计划,依次点击:添加---线程(用户)---线程组

  • 线程数:就是我们的并发数,我们先设置为10
  • Ramp-Up时间(秒):表示我们这10个线程是用了多长时间启动起来,如果是10,表示10秒钟把这10个线程启动起来,假如是1,表示1秒将10个线程启动,如果是0,那么就是10个线程一块启动起来。我们设置为0。
  • 循环次数:表示我们这10个线程,访问列表页面的时候循环多少次,如果是1,就是1次。

在线程组上右击---添加---配置元件---HTTP请求默认值,配置了这个之后,其他的元件中就不用重复在配置了。

在线程组上右击---添加---取样器---HTTP请求

在线程组上右击---添加---监听器---聚合报告,这个显示的比较粗略,详细的可以添加图形结果等元件。

点击运行按钮,点击YES,保存执行计划,暂时保存到桌面。

这就是结果,主要看这个Error和Throughput就行。

现在将线程数设置为1000,再来查看结果:

比原来大了好多,并发量在1000的时候,370左右的吞吐量(Throughput吞吐量——默认情况下表示每秒完成的请求数)。

现在将线程数设置为5000,再来查看结果:

好吧,出现错误了,看一下后台,并没有报错,这就是一个最基本的压测。

我们来看一下代码:这里其实什么也没做,只是查询了数据库,所以说瓶颈还是在数据库。

    @GetMapping("/to_list")
    public String list(Model model, SecKillUser user) {
        model.addAttribute("user", user);

        //查询商品列表
        List<GoodsVo> goodsList = goodsService.listGoodsVo();
        model.addAttribute("goodsList", goodsList);
        return "goods_list";
    }

如何证明瓶颈在数据库上?这里我就换成虚拟机中的数据库,使用top命令来查看一下:

这是还没有进行测试时的情况,下面我们把并发量设置为1000,循环10次,进行压测:

这里可以很明显的看到,当压测开始时,我们的mysql进程直接排到前面来,这就说明了瓶颈是在数据库。

我是在自己电脑上装的虚拟机,只是简单做个测试,演示一下压测的效果。

2、自定义变量模拟多用户

这次我们压测另一个接口:因为我们说,我们系统很多时候都要获取用户这个对象,我们来看一下,假如说我们什么也不做,单纯的来获取这个对象看一看我们系统的QPS能够到达多少。为此新建一个UserController进行测试

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/info")
    @ResponseBody
    public Result<SecKillUser> info(Model model, SecKillUser user) {
        return Result.success(user);
    }
}

如何进行压测???

这里我们使用token来传递参数,实际也能用cookie来传递参数,先禁用商品列表,然后新建一个HTTP请求,改名为:获取用户信息,填入相关信息,那么这个token的值从哪里获取?

我们就用这个token就行。其他的跟原来一样,并发量1000,循环10次:

这个的吞吐量超过1000,虽然出现了错误,但后台并没有报错(可能是电脑资源被占用的太多了,反应不过来吧,仅供学习演示)。

毕竟一直这样......,凑活用吧!

算了,调大redis和mysql的参数试试

# spring.datasource.maxActive=2
spring.datasource.maxActive=1000
# spring.datasource.initialSize=1
spring.datasource.initialSize=100
spring.datasource.maxWait=60000
# spring.datasource.minIdle=1
spring.datasource.minIdle=500

# redis.poolMaxTotal=10
redis.poolMaxTotal=1000
# redis.poolMaxIdle=10
redis.poolMaxIdle=500
# 秒
# redis.poolMaxWait=3
redis.poolMaxWait=500

然而还是有错,但后台没报错,跟之前一样......

关于这个Error,先不追究了,先来看看这个1000+的吞吐量,那段程序做了什么,可以有1000+的吞吐量?

对用户信息的处理在UserArgumentResolver这个类,其他的不占用时间,只有getByToken这个方法占用时间多一些。

这里就是从redis中读取了一个值,再写入一个cookie,因为是对redis的操作,使用的是缓存,所以吞吐量更高点。而之前的商品列表,涉及到数据库的操作,所以吞吐量比较低些。

当然,这样测试也不太合适,毕竟只用一个用户,进行多次测试。现在尝试多用户的测试。

右击线程组---添加---配置元件---CSV数据文件设置

新建一个文件,随便起个名config,内容是:userId,userToken

引用文件中的变量:

再测试一下(其实这里是看不出来什么的......,要不就在程序中添加输出语句,看看效果......)

3、JMeter命令行使用(linux下的JMeter)

因为有时候我们就像直接在我们的服务器上做压测,因为我们的服务器大多数情况下都是linux的,没有图形界面的,我们来看一下,在命令行下面我们是如何使用的。

  • 3.1)在windows上录好jmx
  • 3.2)命令行:sh jmeter.sh -n -t XXX.jmx -l result.jtl
  • -n表示不使用图形界面,-t表示测试的脚本,-l表示输出的结果到后面的文件。
  • 3.3)把result.jtl导入到jmeter

这里做测试时,为了方便,还是用jar包。后面会简单说一下打war包。

先把jdk、redis都安装配置好,然后上传jmeter、jar包和需要的配置文件

我先上传了jmeter和jar包,使用nohup和java来运行jar包,nohup的作用就是:忽略输入,并把输出追加到"nohup.out"。

我们看一下这个文件的内容:实际上就是IDEA控制台输出的内容

打开浏览器,这里使用的是虚拟机的地址来访问:

程序正常运行。

我们还是先来压测商品列表,这次设置并发量5000,循环10次

将这个文件另存为goods_list,上传到服务器

从图中可以看到负载11.51,最高达到21+,而处理器只有两个,严重超载,当然,这并不是恒定不变的,有一段时间是两个java线程使用了更多的cpu,一个是secondkill,一个是jmeter本身。这个测试只是为了练习,不要太在意结果...

我们把测试结果下载下来,导入到jmeter中,看看内容:

在并发量5000,循环10次的情况下,Error为0,QPS:495.1,那我们再跑一次看看:

这次更高些(第一次算是预热)966。我们就把这次的数据作为一个参考,优化完之后再次进行压测,作比较。

下面我们来压测/miaosha/do_miaosha,这个算是我们的核心代码了,这里先改一下数据库时间,使其可以秒杀,库存都设置为10

因为“秒杀”这个方法需要user和goodsId,这个user我们使用token来模拟。我们先来生成5000个用户和token,这个生成的程序就不贴代码了,就是做循环......

要说的是:在生成数据时,调用doLogin有个小问题

doLogin本身返回的是Boolean类型,但是我们想要它返回一个token,是String类型,所以这里先改成String类型

    //生成用户信息时用
    @PostMapping("/do_login")
    @ResponseBody
    public Result<String> doLogin(HttpServletResponse response, @Valid LoginVo loginVo){
        log.info(loginVo.toString());
        //登陆
        String token = userService.login(response, loginVo);
        //如果出现异常,则直接抛出了,所有直接返回true就行
        return Result.success(token);

    }
public String login(HttpServletResponse response, LoginVo loginVo) {
    //public boolean login(HttpServletResponse response, LoginVo loginVo) {
        ......
        return token;
        //return true;
    }

改完后,启动工程,再启动生成用户信息的程序,将用户信息插入数据库的同时,保存一份token用于压测。

生成的token,从0到4999:

数据库也插入了5000条数据(还包括之前用来测试的那条数据):

再来设置JMeter脚本:

上传文件到服务器:

修改脚本文件中,引用tokens.txt的路径:

再进行压测,下载结果,导入JMeter查看:

这次负载达到40多了...

这个秒杀的压测,只有176(Throughput),确实挺低的,不过也是意料之中,因为秒杀这个接口做的事情比较多:

先是判断库存,然后是判断是否重复秒杀,最后是减库存下订单,都是与数据库进行交互,吞吐量低也正常。

我们跟之前一样,再重新压测一次:

并发量5000,循环10次,吞吐量555.

好了,测试先到此为止。

这是虚拟机的配置,其他的还有centos7.4

 

4、SpringBoot打war包

到目前为止,我们的程序都是打jar包,以main函数方式运行,但是实际项目中,我们很有可能是要把程序打成war包,放到我们的tomcat服务器下面,这样来跑,于是顺带介绍一下如何打war包。

修改packaging为war包:

导入依赖:
 

<!--部署成war包时开启-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-tomcat</artifactId>
	<scope>provided</scope>
</dependency>
<dependency>
	<groupId>org.apache.tomcat.embed</groupId>
	<artifactId>tomcat-embed-jasper</artifactId>
	<scope>provided</scope>
</dependency>

继承SpringBootServletInitializer

@SpringBootApplication
public class SecondkillApplication extends SpringBootServletInitializer {
	public static void main(String[] args) {
		SpringApplication.run(SecondkillApplication.class, args);
	}
	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		return builder.sources(SecondkillApplication.class);
	}
}

使用idea 工具生成war包,可以先clean,然后直接package。

这就打包成功了。

先放到本地tomcat下,然后启动tomcat试试:

tomcat启动后,会自动解压war包。打开浏览器,这样访问时,需要加上包名才能访问:

为了方便,直接把工程放到ROOT目录下:可以把刚才解压完的文件中的内容都拷贝到ROOT目录下:

完成!当然,如果你想打包的名字是工程名,就在pom文件中添加:

打包的名字就是工程artifactId了。

java实现秒杀系统@Controller @RequestMapping("seckill")//url:/模块/资源/{id}/细分 /seckill/list public class SeckillController { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private SeckillService seckillService; @RequestMapping(value="/list",method = RequestMethod.GET) public String list(Model model){ //获取列表页 List list=seckillService.getSeckillList(); model.addAttribute("list",list); //list.jsp+model = ModelAndView return "list";//WEB-INF/jsp/"list".jsp } @RequestMapping(value = "/{seckillId}/detail",method = RequestMethod.GET) public String detail(@PathVariable("seckillId") Long seckillId, Model model){ if (seckillId == null){ return "redirect:/seckill/list"; } Seckill seckill = seckillService.getById(seckillId); if (seckill == null){ return "forward:/seckill/list"; } model.addAttribute("seckill",seckill); return "detail"; } //ajax json @RequestMapping(value = "/{seckillId}/exposer", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"}) @ResponseBody public SeckillResult exposer(@PathVariable("seckillId") Long seckillId){ SeckillResult result; try { Exposer exposer =seckillService.exportSeckillUrl(seckillId); result = new SeckillResult(true,exposer); } catch (Exception e) { logger.error(e.getMessage(),e); result = new SeckillResult(false,e.getMessage()); } return result; } @RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"} ) @ResponseBody public SeckillResult execute(@PathVariable("seckillId")Long seckillId,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值