秒杀功能(3)压测

第三部分

从这部分开始,我就要做秒杀的功能优化啦。这篇简单讲一下压测实战,需要结合之前的博文JMeter压测(1)、(2)一起看。这边先展示下在当前基础功能的初步实现下,在高并发情况下系统的性能问题和出现的业务逻辑问题。

1. 生成测试用例

我们先写个函数,在数据库中自动创建5000个用户信息,在Redis中自动创建用户的token信息,方便之后模拟高并发的情况。下面先贴代码。

public class UserUtil {

    private static void createUser(int count) throws Exception {
        List<MiaoshaUser> users = new ArrayList<MiaoshaUser>(count);
        //生成用户
        for (int i = 0; i < count; i++) {
            MiaoshaUser user = new MiaoshaUser();
            user.setId(13000000000L + i);
            user.setLoginCount(1);
            user.setNickname("user" + i);
            user.setRegisterDate(new Date());
            user.setSalt("123456");
            user.setHead("111");
            user.setLastLoginDate(new Date());
            user.setPassword(MD5Util.inputPassToDbPass(String.valueOf(user.getId()), user.getSalt()));
            users.add(user);
        }
        System.out.println("create user");
		/*//插入数据库
		Connection conn = DBUtil.getConn();
		String sql = "insert into user(login_count, nickname, register_date, salt, password, id,head,last_login_date)values(?,?,?,?,?,?,?,?)";
		PreparedStatement pstmt = conn.prepareStatement(sql);
		for(int i=0;i<users.size();i++) {
			MiaoshaUser user = users.get(i);
			pstmt.setInt(1, user.getLoginCount());
			pstmt.setString(2, user.getNickname());
			pstmt.setTimestamp(3, new Timestamp(user.getRegisterDate().getTime()));
			pstmt.setString(4, user.getSalt());
			pstmt.setString(5, user.getPassword());
			pstmt.setLong(6, user.getId());
			pstmt.setString(7,user.getHead());
			pstmt.setTimestamp(8,new Timestamp(user.getLastLoginDate().getTime()));
			pstmt.addBatch();
		}
		pstmt.executeBatch();
		pstmt.close();
		conn.close();
		System.out.println("insert to db");*/
        //登录,生成token
        String urlString = "http://localhost:8080/login/do_login";
        File file = new File("E:/miaosha_imooc/yace/config.txt");
        if (file.exists()) {
            file.delete();
        }
        RandomAccessFile raf = new RandomAccessFile(file, "rw");
        file.createNewFile();
        raf.seek(0);
        for (int i = 0; i < users.size(); i++) {
            MiaoshaUser user = users.get(i);
            URL url = new URL(urlString);
            HttpURLConnection co = (HttpURLConnection) url.openConnection();
            co.setRequestMethod("POST");
            co.setDoOutput(true);
            OutputStream out = co.getOutputStream();
            String params = "mobile=" + user.getId() + "&password=" + MD5Util.inputPassToFormPass(String.valueOf(user.getId()));
            out.write(params.getBytes());
            out.flush();
            InputStream inputStream = co.getInputStream();
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            byte buff[] = new byte[1024];
            int len = 0;
            while ((len = inputStream.read(buff)) >= 0) {
                bout.write(buff, 0, len);
            }
            inputStream.close();
            bout.close();
            String response = new String(bout.toByteArray());
            JSONObject jo = JSON.parseObject(response);
            String token = jo.getString("data");
            System.out.println("create token : " + user.getId());

            String row = user.getId() + "," + token;
            raf.seek(raf.length());
            raf.write(row.getBytes());
            raf.write("\r\n".getBytes());
            System.out.println("write to file : " + user.getId());
        }
        raf.close();

        System.out.println("over");
    }

    public static void main(String[] args) throws Exception {
        createUser(5000);
    }
}

可以看到,这里主要是分四步:1.先创建用户;2.再插入数据库;3.插入到Redis中;4.将用户和token对存放到text文件中,这个文件供压测使用(需要读这个文件)。这里要注意的是:数据库插入一次就可以,但redis由于设置了过期时间(我设了2天),如果超时了需要再运行一下程序以插入redis。

2. JMeter基本设置

这部分最基础的部分在之前的博文JMeter(1)、(2)中写过啦,这里就不重复了,主要写一下这次压测相关的步骤。
首先说明一点的是,测试用户全在我的笔记本上访问页面,并且本机同时运行JMeter和Java程序,是不能真正模拟真实的并发的,这里由于设备限制也没有条件(本人比较穷 ),只是大概体验一下高并发,并且之后所有的优化都基于这个条件,主要保持环境条件一致,还是能比较出优化效果的。

  • 新建线程组,这里采用5000个线程循环10次
    在这里插入图片描述
  • 设置一下默认配置,之后就不用反复填写了
    在这里插入图片描述
  • 设置配置文件
    这个具体功能在JMeter(2)中已介绍了,就是读text文件并且设置变量的作用。
    在这里插入图片描述
  • 设置HTTP 请求
    我们这次直接对秒杀功能进行压测,填写的路径如图所示,这个要参见之前的代码。访问这个路径时需要两个变量,其中token是从之前的文本文件中读取的,注意Value的语法(如何写的)。
    在这里插入图片描述
    全部设置好之后,就可以运行了。运行前保证Redie和Tomcat启动。

3. 结果展示

  • 第一次运行的结果
  1. 压测报告
    在这里插入图片描述
    TPS:630.9/sec(不高)
    错误率:64.73%(太高了)
    错误率太高了,看一下程序,抛异常了。
  2. Redis异常
    在这里插入图片描述
    很明显,这样的并发下Redis读取超时了。贴出Redis的配置:
#redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.lettuce.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.timeout=3000ms
spring.redis.jedis.pool.max-wait=3000ms
  1. 数据库超卖现象
    在这里插入图片描述
    这是秒杀商品表,我们是对商品1进行秒杀的,库存成为负值,问题大了。我之前设置的秒杀商品个数给了9件,现在超卖了22件。
  • 第二次运行结果

为了客观表现结果,重新再运行一次。注意把数据库信息清空的清空,恢复的恢复;把JMeter上次结果清空。

  1. 压测报告
    在这里插入图片描述
    TPS:808.5/sec(不高)
    错误率:67.16%(太高了)
  2. Redis异常
    和第一次一样,就不贴图了。
  3. 数据库超卖现象
    在这里插入图片描述
    情况有所好转,还是超卖。

4. 总结

这次实战结果就是:1. Redis读取超时抛异常导致压测的错误率太高;2. TPS不高,说明系统性能不佳;3. 数据库出现超卖现象,严重的业务逻辑错误。

5. 解决异常

程序抛异常,这是不应该产生的,先将抛异常的问题解决。
我一开始先把redis连接池重新配置:

#redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.jedis.pool.max-active=1000
spring.redis.jedis.pool.max-idle=500
spring.redis.timeout=5000ms
spring.redis.jedis.pool.max-wait=5000ms

运行后发现程序不抛异常了,但是压测的错误率并没有下降,依然在60%以上,意识到这可能不是简单改动程序就行了。
查看了压测的日志,也并没有报错,如果大家有遇到类似的问题,参考博文:
https://blog.csdn.net/chenyun19890626/article/details/80645740
这个方法我试过了,用了这个方法可以把错误率大大降低,但是如果把并发调大还是会出现错误。

所以我想是不是本身就是我单机性能有限。。可以把并发线程和循环次数适当调小。

如何性能调优,下次再写。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值