写点什么

【连载 21】性能测试实践——超时结账第一回合

作者:FunTester
  • 2025-03-11
    河北
  • 本文字数:3042 字

    阅读完需:约 10 分钟

3.7.1 超市结账第一回合

让我们把目光转回小八超市。最近生意红火,8 个收银台忙得团团转,早高峰时连上厕所的时间都没有。收银员们叫苦不迭,纷纷建议老板临时增加 2 个收银台。小八思前想后,决定先对现有的 8 个收银台进行一次摸底,看看在满负荷运转的情况下,每分钟能结账多少顾客。根据摸底结果,再决定是否增加临时收银台。


以此为背景,我们来设计一个性能测试用例。根据需求分析,我们选择线程模型,也就是排队模型,总并发数量为 8。测试内容就是模拟顾客结账的流程,简化为三个步骤:扫码计价、付款结账和打包走人。为了给收银员留出热身时间,我们设置了 2 分钟的 Rump-Up 时间。


相信大家对这种场景已经驾轻就熟,下面是我设计的多线程类。为了增加一点挑战性,我分别统计了三个步骤的耗时,并且支持异步输出实时信息,这样可以更快定位系统瓶颈,提升排查效率。


既然要增加额外的统计和异步输出功能,必然需要一个额外的线程来完成这个任务。为了避免线程开销,我在 TaskExecutor 类中增加了一个属性 realTimeThread:


/** * 实时信息输出线程,用于实时统计一段时间的TPS和平均耗时 */public Thread realTimeThread;
复制代码


这样在 start()方法中稍加改造即可使用。当 realTimeThread 未赋值时,默认只统计当前实时 TPS 和 RT。


if (realTimeThread == null) realTimeThread = new Thread() {    @Override    public void run() {        while (realTimeKey) {            // 重复代码,省略        }    }};realTimeThread.start();
复制代码


接下来我们编写多线程任务类代码。增加了三个阶段方法以及对应的统计属性。本次数据统计采用了实时 TPS 和 RT 相同的方案,使用一个全局线程安全对象计算总耗时,然后依据实时 TPS 计算平均耗时。


为了增加统计数据的波动性,在 pay()方法中增加了时间相关变量作为休眠参数。多线程任务类代码如下:


package org.funtester.performance.books.chapter03.section7;
import org.funtester.performance.books.chapter03.common.ThreadTool;import org.funtester.performance.books.chapter03.section3.ThreadTask;
import java.util.ArrayList;import java.util.concurrent.atomic.AtomicLong;
/** * 超市收银台性能测试用例 */public class SupermarketCheckoutTaskFirst extends ThreadTask {
/** * 计价耗时统计 */ public static AtomicLong priceCostTime;
/** * 支付耗时统计 */ public static AtomicLong payCostTime;
/** * 打包耗时统计 */ public static AtomicLong packCostTime;
/** * 构造方法 * @param totalNum 执行的总次数 */ public SupermarketCheckoutTaskFirst(int totalNum) { this.totalNum = totalNum; this.costTime = new ArrayList<>(totalNum); priceCostTime = new AtomicLong(); payCostTime = new AtomicLong(); packCostTime = new AtomicLong(); }
/** * 业务操作,计价、支付、打包 */ @Override public void test() { long start = System.currentTimeMillis(); price(); long price = System.currentTimeMillis(); priceCostTime.addAndGet(price - start); pay(); long pay = System.currentTimeMillis(); payCostTime.addAndGet(pay - price); pack(); long pack = System.currentTimeMillis(); packCostTime.addAndGet(pack - pay); }
/** * 计价 */ public void price() { ThreadTool.sleep(10); }
/** * 支付 */ public void pay() { ThreadTool.sleep((int) (System.currentTimeMillis() % 10000) / 100); }
/** * 打包 */ public void pack() { ThreadTool.sleep(10); }}
复制代码


测试用例如下:


package org.funtester.performance.books.chapter03.section7;
import org.funtester.performance.books.chapter03.common.ThreadTool;import org.funtester.performance.books.chapter03.section3.ThreadTask;import org.funtester.performance.books.chapter03.section4.TaskExecutor;
import java.util.ArrayList;import java.util.List;
/** * 超市收银台性能测试用例 */public class SupermarketCheckoutCase {
public static void main(String[] args) throws InterruptedException { int total = 1000; List<ThreadTask> tasks = new ArrayList<>(); for (int i = 0; i < 8; i++) { SupermarketCheckoutTaskFirst supermarketCheckoutTask = new SupermarketCheckoutTaskFirst(total); tasks.add(supermarketCheckoutTask); } TaskExecutor taskExecutor = new TaskExecutor(tasks, "超市收银台性能测试用例", 120); Thread thread = new Thread() { @Override public void run() { while (taskExecutor.realTimeKey) { ThreadTool.sleep(1000); long sumCost = TaskExecutor.realTimeCostTime.sumThenReset(); long sumTimes = TaskExecutor.realTimeCostTimes.sumThenReset(); System.out.println(String.format("实时统计TPS: %d, 平均耗时: %d", sumTimes, sumTimes == 0 ? 0 : sumCost / sumTimes)); long price = SupermarketCheckoutTaskFirst.priceCostTime.getAndSet(0); long pay = SupermarketCheckoutTaskFirst.payCostTime.getAndSet(0); long pack = SupermarketCheckoutTaskFirst.packCostTime.getAndSet(0); System.out.println(String.format("实时统计各阶段耗时: price: %d, pay: %d, pack: %d", price / sumTimes, pay / sumTimes, pack / sumTimes)); } } }; taskExecutor.realTimeThread = thread; taskExecutor.start(); }}
复制代码


控制台输出信息如下(省略了重复内容):


实时统计各阶段耗时: price: 11, pay: 51, pack: 11实时统计TPS: 12, 平均耗时: 84实时统计各阶段耗时: price: 10, pay: 52, pack: 11实时统计TPS: 23, 平均耗时: 84// 其他Rump-Up阶段信息省略Rump-Up结束,开始执行测试任务!实时统计TPS: 88, 平均耗时: 90实时统计各阶段耗时: price: 11, pay: 67, pack: 11实时统计TPS: 80, 平均耗时: 100实时统计各阶段耗时: price: 11, pay: 78, pack: 11任务执行完毕! 预期执行次数: 1000, 实际执行次数 1000, 错误次数 0, 耗时收集数量: 1000// 此处省略相同多线程任务结束日志测试TPS: 129, 平均耗时: 62测试TPS: 129, 总执行次数: 8000最小值:20最大值:127平均值:6250分位值:5790分位值:10795分位值:11699分位值:124999分位值:126任务执行完毕! 压测时长: 62 秒, 预期执行次数: 8000, 实际执行次数 8000, 错误次数 0, 耗时收集数量: 8000
复制代码


通过这次测试,小八超市的收银台性能一目了然,接下来就是根据数据做决策了。正所谓“磨刀不误砍柴工”,有了这些数据支持,小八的决策会更加科学合理。

发布于: 刚刚阅读数: 4
用户头像

FunTester

关注

公众号:FunTester,800篇原创,欢迎关注 2020-10-20 加入

Fun·BUG挖掘机·性能征服者·头顶锅盖·Tester

评论

发布
暂无评论
【连载 21】性能测试实践——超时结账第一回合_FunTester_InfoQ写作社区