秒杀系统设计与实现

秒杀系统

在高并发的项目中抢一定数据量。springboot +mybatis 解决方案

秒杀系统概括

1.1秒杀场景

  • 电商抢购限量商品
  • 买相关明星演唱会的门票
  • 火车票12306

1.2为什么要做一个系统

如果你的项目流量非常小,完全不用担心有并发购买请求,那么这个系统意义不大。但是如果要像12306 那样,接受高并发和下单考验,那么你就需要一套完整的流程保护措施,来保证你系统在用户流量高峰期时不会挂掉。

  • 严防超卖
  • 防止黑客
  • 保护用户体验

1.3保护措施有哪些

  • 乐观锁防止 超卖 --核心
  • 令牌桶限流
  • Redis 缓存
  • 消息队列异步处理订单

2.如何防止超卖

重点,秒杀系统最重要的是防止超卖

2.1 数据库表

-- table structure  for stock \stock_order

DROP TABLE IF EXISTS `stock`;
CREATE TABLE `stock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL COMMENT '名称',
  `count` int(11) NOT NULL COMMENT '库存',
  `sale` int(11) NOT NULL COMMENT '已售',
  `version` int(11) NOT NULL COMMENT '乐观锁,版本号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `stock_order`;
CREATE TABLE `stock_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sid` int(11) NOT NULL COMMENT '库存id',
  `name` varchar(30) NOT NULL COMMENT '商品信息',
  `ceate_time` timestamp NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.2秒杀系统业务模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YqAgahWh-1622335410980)(/Users/fanyuanxiang/Library/Application Support/typora-user-images/image-20210218234527442.png)]

2.3正常测试

在没有高并发的情况下不会出现任何问题。

2.4 使用Jmeter进行压力测试

官网:https://jmeter.apache.org/

1. 介绍

Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域。 它可以用于测试静态和动态资源,例如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库、FTP 服务器, 等等。JMeter 可以用于对服务器、网络或对象模拟巨大的负载,来自不同压力类别下测试它们的强度和分析整体性能。另外,JMeter能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证你的程序返回了你期望的结果。为了最大限度的灵活性,JMeter允许使用正则表达式创建断言。

2. 安装 Jmeter(需要jdk java运行环境)
# 概括
下载地址:官网download
解压运行bin下可执行文件即可
配置相关环境变量(配置参见附录)
JMeter 下载安装

1)JMeter安装

  1. http://jmeter.apache.org/下载最新版本的JMeter,解压文件到任意目录

  2. 安装JDK,配置环境变量JAVA_HOME.

  3. 系统要求:JMeter2.11 需要JDK1.6以上的版本支持运行

  4. JMeter可以运行在如下操作系统上:Unix,Windows和Open VMS.

  5. 避免在一个有空格的路径安装JMeter,这将导致远程测试出现问题。

JMeter插件安装
  1. 插件下载地址: http://jmeter-plugins.org/downloads/all/

  2. 插件下载后解压:找到JMeterPlugins-Extras.jar,把JMeterPlugins-Extras.jar放到apache-jmeter-2.12\lib\ext目录。

JMeter运行
  1. 进入bin目录运行jmeter.bat(win)/jmeter ( mac ) 启动jmeter

注意:打开的时候会有两个窗口,JMeter的命令窗口和JMeter的图形操作界面,不可以关闭命令窗口。

  1. JMeter的classpath:

如果你想添加其他JAR文件到JMeter的classpath中,你必须复制他们到lib目录中;

如果你开发了一个JMeter特定组件或有效的jar文件,复制到lib目录下的ext目录中。

  1. 打开之后显示的是中文,如果你想使用其他语言,比如英文,那么通过菜单选项->选择语言->英文即可,当然转为中文也是同样操作。

使用GUI运行参见博客:link

CLI运行Jmeter
jmeter -n -t [jmx file](jmx压力测试文件) -l [results file](结果输出的文件) -e -o [Path to web report folder](生成一个html压力测试的报告)

使用:

#把配置好的参数使用jmeter保存到文件

jmeter -n -t Desktop/flashsale.jmx -l Desktop/flashsale.txt -e -o Desktop/html
#注意测试报告html 要是一个空目录。

2.5 秒杀防止超卖优化

2.5.1悲观锁
1. 悲观锁
使用synchronized锁,加给访问临界资源的方法。
# OrderServiceImpl.java
@Service
@Transactional
public class OrderServiceImpl implements OrderService {
    //校验库存,查看已售与库存是否相等,已售等于库存则商品被销售完毕
    //注意这里库存数据是不变的,通过已售去操作数据
    @Autowired
    private StockDAO stockDAO;
    @Autowired
    private OrderDAO orderDAO;
    @Override
public synchronized int kill(Integer id) {
        //根据商品id校验库存
        Stock checked = stockDAO.check(id);
        if(checked.getSale().equals(checked.getCount())){
            throw new RuntimeException("库存不足");

        }else {
        //扣除库存(已售)
            checked.setSale(checked.getSale()+1);
           //更新持久层到数据库
           stockDAO.updateSale(checked);
        //创建订单
            Order order = new Order();
            order.setSid(checked.getId()).setName(checked.getName()).setCreateDate(new Date());
            orderDAO.createOrder(order);
            //执行了上面这行代码mybatis会把自动增长到数据库主键id赋值给Order对象
            return order.getId();
        }
    }
}
--问题:会出现相关部分超卖,除非关掉@Transaction 注解。(但是无法保证service原子性)
- 解释:
 	这种方式使用的话给这个方法加一个锁,此时2000个并发来到了只能一个个的访问,只要抢到了锁才执行,释放了其他的线程就竞争锁,每次只能一个线程获得到锁。
- 说明:
	这种方式效率十分低下。且非常耗费性能。
注意:
   如果在这个OrderServiceImpl中加了事务注解@Transactional 这个事务比较大,包括了synchronized(事务会自动添加)的事务,则会造成事务的内部多提交。 仍会造成超卖情况。
   解决方案:
   
   		1.给OrderServiceImpl.kill()方法加synchronized 、但是不要在OrderServiceImpl类中加@Transactional注解开启事务。只有内部dao事务则不会发生超卖现象。
-这样也会出现问题,如果外部service没有事务,库存、订单业务无法保证原子性操作。  

   		2.解决上面的问题 使用synchronized加载方法的调用处(StockController), Service类中仍然加事务。
synchronized(this){
//根据秒杀商品id 去调用秒杀业务
  int orderid = orderService.kill(id);
  return "秒杀成功获得的订单id"+ String.valueOf(orderid);      
}
2.5.2乐观锁

说明:使用乐观锁解决商品超卖问题
实际上是把主要防止超卖问题交给数据库解决,利用了数据库中的version字段 以及数据库中的事务实现实现了防止商品超卖情况。

#分析
10个线程
10 thread 
第一个线程 1-thread   select * stock where id=1 and version
				
				update set 
						sale=sale +1,
						version=version+1
				where id =#{id}		
								and
								version=#{version}
此时其他线程 访问version等0 则更新失败。								
			
1.更新库存方法改造
//扣除库存 OrderServiceImpl.java
    private void updateSale (Stock stock){
      stockDAO.updateSale(sale)
    }
//StockDAO.java
		void updateSale(Stock stock);
//改造后---->

//扣除库存 OrderServiceImpl.java 
    private void updateSale(Stock stock){
      int udpateRows=stockDAO.updateSale(stock);
      if(udpateRows==0){
        throw new RuntimeException("购买失败请重试!!");
      }
    }
//StockDAO.java
		int updateSale(Stock stock);  
2.mapper.xml 配置文件改造
-- StockMapper.xml
UPDATE stock set sale=sale+1 where id=#{id}
-- 修改后
UPDATE stock set sale =sale+1 , version=version+1 where id=#{id} and version=#{version}

其他不变业务方法。

注意:交给服务器做乐观锁一次并发量不要超过万级别。

3.接口限流

秒杀做成一个系统,巨大接口流量请求涌入防止接口压力太大,导致其他服务调用级连问题。甚至服务雪崩现象。

限流:对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统缓慢或宕机。

3.1接口限流
# 在面临高并发的抢购请求时,我们如果不对接口进行限流,可能会对后台系统造成极大的压力,大量的请求抢购成功时需要调用下单的接口,过多的请求打到数据库会对系统的稳定性造成影响。
3.2如何解决接口限流

高并发系统时有三把利器用来保护系统**:缓存、降级和限流**。缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹;而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流

- 缓存: 提升系统的访问速度,和增大系统的处理容量
- 降级: 降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
- 限流: 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求限速来保护系统,一旦达到限速速率则可以拒绝服务、排队或等待、降级处理。
3.3限流算法

常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。

令牌桶算法

令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:

  • 假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌;
  • 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝;
  • 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上;
  • 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

img

漏桶算法

漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:

  • 一个固定容量的漏桶,按照常量固定速率流出水滴;
  • 如果桶是空的,则不需流出水滴;
  • 可以以任意速率流入水滴到漏桶;
  • 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

img

令牌桶和漏桶对比:

  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;
  • 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
  • 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量;
  • 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;
  • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;
  • 两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。

另外有时候我们还使用计数器来进行限流,主要用来限制总并发数,比如数据库连接池、线程池、秒杀的并发数;只要全局总请求数或者一定时间段的总请求数设定的阀值则进行限流,是简单粗暴的总数量限流,而不是平均速率限流。

算法具体使用
1.令牌桶使用:

引入依赖

<!--使用google 的guava 实现令牌桶接口限流-->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.2-jre</version>
</dependency>
2.使用令牌桶算法
//#OrderController.java
//创建令牌桶实例,初始化令牌数10.(可以处理突发请求的量)

private RateLimiter rateLimiter =RateLimiter.create(10);
    @GetMapping("sale")
    public String sale(Integer id ){
//1.当并发量大于初始流量时,数据获取请求后会继续等待令牌的生成。
        //直接获取令牌,如果桶内不足则等待
        //log.info("获取令牌请求等待时间"+rateLimiter.acquire());
      
//2.设置一个等待时间,如果在等待时间内获取到了token 令牌,则处理业务,若果等待时间没有回去到另令牌则抛弃。
        //在一定时间内尝试获取令牌,如果超时为获得到则抛弃该请求。
        if(!rateLimiter.tryAcquire(2, TimeUnit.SECONDS)){
            System.out.println("获取令牌失败,请重新尝试获取令牌。");
            return "获取令牌失败";
        }
        System.out.println("处理业务。。。");
        return "测试令牌";
    }

3.4乐观锁+令牌桶算法实现限流
1.使用令牌桶改造controller实现乐观锁+限流
//#OrderController.java
@RestController
@RequestMapping("stock")
@Slf4j
public class StockController {
    @Autowired
    private OrderService orderService;
    private RateLimiter rateLimiter2=RateLimiter.create(10);

    //开发秒杀方法 使用了令牌桶+乐观锁
    @GetMapping("kill")
    public  String kill(Integer id ){
        System.out.println("秒杀商品的id = " + id);
        //使用令牌桶
        if(!rateLimiter2.tryAcquire(2,TimeUnit.SECONDS)){
            log.info("抢购失败,当前商品过于火爆,请重试!!!");
            return "抢购失败!";
        }
        try {
            //根据秒杀商品id 去调用秒杀业务
                int orderid = orderService.kill(id);
                return "秒杀成功获得的订单id" + String.valueOf(orderid);

        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

//#OrderServiceImpl.java
@Service
@Transactional
public class OrderServiceImpl implements OrderService {
    //校验库存,查看已售与库存是否相等,已售等于库存则商品被销售完毕
    //注意这里库存数据是不变的,通过已售去操作数据

    @Autowired
    private StockDAO stockDAO;
    @Autowired
    private OrderDAO orderDAO;

    @Override
    public int kill(Integer id) {
        //根据商品id校验库存(如果已经售卖完了会直接抛出运行时异常)
        Stock checked = checkStock(id);
        //扣除库存(已售)
        updateSale(checked);
        //创建订单
        return createOrder(checked);

    }

    //校验库存
    private Stock checkStock(Integer id){
        Stock checked = stockDAO.check(id);
        if(checked.getSale().equals(checked.getCount())){
            throw new RuntimeException("库存不足");
        }
            return checked;
    }

    //扣除库存
    private void updateSale(Stock stock){
        stock.setSale(stock.getSale()+1);
        //更新持久层到数据库,结合乐观锁判断服务
        int updateRows = stockDAO.updateSale(stock);
        if (updateRows==0){
            throw new RuntimeException("抢购失败,请重试!");
        }
    }

    //创建订单
    private Integer createOrder(Stock stock ){
        Order order = new Order();
        order.setSid(stock.getId()).setName(stock.getName()).setCreateDate(new Date());
        orderDAO.createOrder(order);
        //执行了上面这行代码mybatis会把自动增长到数据库主键id赋值给Order对象
        return order.getId();
    }

问题分析

以上系统存在的问题:

  • 我们应该在一定时间内执行秒杀处理,不能在任意时间都接受秒杀请求,如何加入时间验证?
  • 对于稍微懂点电脑的人,通过抓包方式获取我们的接口地址,通过脚本进行抢购怎么办。
  • 秒杀开始之后如何限制单个用户的请求频率,即单位时间内同一个用户ip限制访问次数。
 # 主要内容:
1.限时抢购
2.抢购接口隐藏
3.单用户限制频率(单位时间内限制访问次数)

4.1限时抢购的实现

1.将秒杀商品放入redis 并设置超时

这里我们使用String 类型 以kill+商品id作为key 商品id作为value设置180秒超时时间

set kill1 1 EX 180
2.抢购中加入redis 服务,这里使用spring-boot-starter-data-redis

加入相关redis依赖

<dependency>
		<groupId>org.springframework.boot</groupId>
  	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

修改配置连接redis

spring.redis.port= 6379
spring.redis.host=localhost
#redis 中有0-11号库
spring.redis.database=0
RedisTemplate 主要是对对象进行友好操作的
StringRedisTemplate 主要是对String进行友好操作
package com.chiry.service;
import com.chiry.dao.OrderDAO;
import com.chiry.dao.StockDAO;
import com.chiry.entity.Order;
import com.chiry.entity.Stock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
 * @program: itlaoge
 * @description:
 * @author: Chiry
 * @create: 2021-02-19 00:27
 **/
@Service
@Transactional
public class OrderServiceImpl implements OrderService {
    //校验库存,查看已售与库存是否相等,已售等于库存则商品被销售完毕
    //注意这里库存数据是不变的,通过已售去操作数据

    @Autowired
    private StockDAO stockDAO;
    @Autowired
    private OrderDAO orderDAO;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Override
    public int kill(Integer id) {
        //使用redis设置过期时间(所有请求调用秒杀方法验证redis中设置过期时间)
        if (!redisTemplate.hasKey("kill"+id))
            throw new RuntimeException("当前活动已经过期啦!!!");
        //根据商品id校验库存(如果已经售卖完了会直接抛出运行时异常)
        Stock checked = checkStock(id);
        //扣除库存(已售)
        updateSale(checked);
        //创建订单
        return createOrder(checked);

    }

    //校验库存
    private Stock checkStock(Integer id){
        Stock checked = stockDAO.check(id);
        if(checked.getSale().equals(checked.getCount())){
            throw new RuntimeException("库存不足");
        }
            return checked;
    }

    //扣除库存
    private void updateSale(Stock stock){
        stock.setSale(stock.getSale()+1);
        //更新持久层到数据库
        int updateRows = stockDAO.updateSale(stock);
        if (updateRows==0){
            throw new RuntimeException("抢购失败,请重试!");
        }
    }
    //创建订单
    private Integer createOrder(Stock stock ){
        Order order = new Order();
        order.setSid(stock.getId()).setName(stock.getName()).setCreateDate(new Date());
        orderDAO.createOrder(order);
        //执行了上面这行代码mybatis会把自动增长到数据库主键id赋值给Order对象
        return order.getId();
    }
}

5. 抢购接口的隐藏

由于人为发送请求不像脚本,为了防止像脚本请求发送,所以我们要将接口进行隐藏,抢购接口隐藏的具体做法

  • 每次点击秒杀按钮,先从服务器获取一个秒杀验证值,(接口内判断是否到秒杀时间)
  • Redis 以缓存用户id和商品ID为key ,秒杀地址为value缓存验证值
  • 用户请求秒杀商品的时候,带上秒杀验证值进行校验

在这里插入图片描述

1.添加用户表

CREATE TABLE `user`  (
  `id` int(11) NOT NULL COMMENT '主键',
  `name` varchar(80) NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(40) NULL DEFAULT NULL COMMENT '用户密码',
  PRIMARY KEY (`id`)
)ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

1.1user实体类

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
    Integer id;
    String name ;
    String password ;

}

1.2 mapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chiry.dao.UserDAO">
    <select id="findById" parameterType="int" resultType="user">
        SELECT  id ,name ,password FROM  user WHERE id=#{id}
    </select>

</mapper>

2.获取生成md5接口

 
    //生成md5
    @RequestMapping("md5")
    public String getMd5(Integer id ,Integer userid){
        String md5;
        try{
            md5=orderService.getMd5(id,userid);
            
        }catch(Exception e){
            e.printStackTrace();
            return "获取md5失败"+e.getMessage();
        }
        return "获取 md5 信息为:" + md5;
    }

3.业务层代码

 //业务层代码生成md5,防止脚本接口请求。
    @Override
    public String getMd5(Integer id, Integer userid) {
        //验证用户合法性
        User user=userdao.findById(id);
        if(user==null) throw new RuntimeException("用户信息不存在!");
        log.info("用户信息:{ }" ,user.toString() );

        //验证商品的合法性
        Stock check = stockDAO.check(id);
        if(check==null ) throw new RuntimeException("商品信息不合法");
        log.info("商品信息:{ }",check.toString());

        //生成hashkey \value
        String hashKey ="Key_"+userid+"_"+id;
        String str = DigestUtils.md5DigestAsHex((userid + id + "!ewrt#").getBytes());

        redisTemplate.opsForValue().set(hashKey,str,3600, TimeUnit.SECONDS);
        log.info("Redis 写入 :「{}」[{}]",hashKey,str);
        return str;
    }

6.单个用户的限制频率

总有无聊的人会写一个复杂的脚本,先请求hash(md5)值,再立即请求抢购,如果下单按钮做的很差,可能会让脚本在大家前面抢购成功,因此我们需要限制大家个抢购频率。

具体流程:

在这里插入图片描述

实现
1.controller
2.service

附录

普通springboot项目搭建环境

#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.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.chiry</groupId>
	<artifactId>flashsale</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>flashsale</name>
	<description>flash sale demo project for spring boot .</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
    <!--web依赖支持-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
     <!--mybatis依赖支持-->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.4</version>
		</dependency>
    <!--mysql连接器依赖支持-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.20</version>
		</dependency>
        <!--lombok 依赖支持-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.16</version>
			<optional>true</optional>
		</dependency>
        <!--alibaba的druid连接池依赖支持-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.19</version>
		</dependency>
    <!--令牌桶算法封装实现的依赖支持-->
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>28.2-jre</version>
		</dependency>

    <!--springboot测试环境的依赖支持-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

#application.properties

# 服务配置
server.port=8989
server.servlet.context-path=/ms

#mysql 配置

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/flash_sale
spring.datasource.username=root
spring.datasource.password=fyx0909I


#mybatis 配置
#mybatis 配置小配置文件位置
mybatis.mapper-locations=classpath:com/chiry/mapper/*Mapper.xml
#给实体类起别名
mybatis.type-aliases-package=com.chiry.entity

#日志配置,指定包下配置文件级别
logging.level.root=info
logging.level.com.chiry.dao=debug

在mac 下配置环境变量

vim ~/.bash_profile

# 添加配置mac环境变量
配置jmeter环境变量:
export JAVA_HOME=/Library/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home
export JMETER_HOME=/Users/fanyuanxiang/tool/apache-jmeter-5.4.1
export PATH=$JAVA_HOME/bin:$PATH:$JMETER_HOME/bin

# 修改完变量source 使配置生效:
source ~/.bash_profile

# 查看配置生效
jmeter -v
# GUI 详细使用
参见博客地址:https://www.cnblogs.com/yangxia-test/p/3964881.html

​ 注意

Don't use GUI mode for load testing !, only for Test creation and Test debugging.
For load testing, use CLI Mode (was NON GUI):
   jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]
& increase Java Heap to meet your test requirements:
   Modify current env variable HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m" in the jmeter batch file

# jmeter 不建议我们使用jmeter GUI去压力测试因为仅仅建议我们使用他去做脚本参数配置
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值