Cal.diy 预订流程性能测试指南:基于 Grafana k6 的四阶段压测实战
2026/9/11 15:02:14 网站建设 项目流程

Cal.diy 预订流程性能测试指南:基于 Grafana k6 的四阶段压测实战

【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy

本指南以 Cal.diy 仓库中的 性能测试套件文档 为核心,系统讲解如何利用 Grafana k6 对 Cal.diy 最关键的预订(Booking)流程开展 smoke、load、stress、spike 四类压测。读完本文,你将掌握测试目录的组织方式、命令行与 Docker 两种运行手段、阈值与负载参数的源码级配置,以及在此基础上扩展新压测用例的完整方法。

性能测试套件概览:守护核心预订链路

Cal.diy 是一个开源的日程预订基础设施,其预订流程(Booking Flow)——从用户访问某个可预订页面到提交预订——是最核心、最影响用户体验的链路。为此,仓库在 packages/testing/performance 目录下内置了一套基于Grafana k6的专用性能测试套件,专门用于测量预订流程在不同负载条件下的表现,覆盖从低负载功能验证到每分钟数万请求的高并发场景。

测试目录结构

packages/testing/performance/ ├── smoke/ # 冒烟测试:最小负载,验证功能是否正常 ├── load/ # 负载测试:模拟预期的正常负载(每分钟数千请求) ├── stress/ # 压力测试:模拟重负载,寻找系统瓶颈点(每分钟数万请求) ├── spike/ # 尖峰测试:模拟突发流量(短时间内激增至每分钟数万请求) ├── utils/ # 共享工具与辅助函数 │ ├── config.js # 全局配置:阈值、测试用户、环境变量读取 │ └── helpers.js # 核心场景封装:预订页面访问 └── README.md # 官方使用文档(本指南的原始依据)

每个负载档位目录下都对应一份booking.js,它们复用utils/中的共享配置与辅助函数,仅通过options(虚拟用户数、时长、阶段与阈值)区分压测强度。这种"场景复用、参数分层"的组织方式,使得新增或调整压测档位非常轻量。

前置条件

运行这套测试需要满足两个基本条件:

  1. 安装 k6:在你的机器上安装 Grafana k6(官方支持通过包管理器、二进制或 Docker 镜像安装)。
  2. 准备被测实例:本地运行中的 Cal.diy(默认指向http://localhost:3000),或者一个已部署的 Cal.diy 实例。

此外,若使用 Docker 脚本方式运行,还需要本机安装并启动 Docker。

运行测试:从单条命令到完整压测

设置 BASE_URL

所有测试默认针对http://localhost:3000运行。若要压测其他环境,通过BASE_URL环境变量指定:

BASE_URL=https://your-cal-instance.com k6 run tests/performance/smoke/booking.js

注意:文档中的命令路径(如tests/performance/smoke/booking.js)对应的是测试位于仓库根目录tests/下的原始布局。在当前仓库中,性能测试实际位于 packages/testing/performance,因此直接运行时应使用仓库内实际路径,例如:

k6 run packages/testing/performance/smoke/booking.js

BASE_URL的读取与防护逻辑在 config.js 中实现,细节见下文"共享配置"一节。

直接运行各档位测试

# 运行预订流程冒烟测试(最小负载,验证功能) k6 run packages/testing/performance/smoke/booking.js # 运行预订流程负载测试(每分钟数千请求) k6 run packages/testing/performance/load/booking.js # 运行预订流程压力测试(每分钟数万请求) k6 run packages/testing/performance/stress/booking.js # 运行预订流程尖峰测试(突发流量激增至每分钟数万请求) k6 run packages/testing/performance/spike/booking.js

k6 运行结束后会自动输出完整的指标报告,包括请求吞吐率、错误率、延迟分位数等;若任一阈值被突破,k6 会以非零退出码结束,便于接入 CI。

使用 Docker 脚本运行(推荐方式)

除了直接调用 k6,套件还提供了 run-k6-local.sh 脚本,它会自动检测操作系统(Linux / macOS)并配置合适的 Docker 参数,免去手动拼接docker run命令的麻烦。

前置条件

  • Docker 已安装并启动;
  • Cal.diy 在本地运行(默认http://localhost:3000)。

交互式菜单用法

从仓库根目录执行:

./packages/testing/scripts/run-k6-local.sh

脚本会显示交互式菜单:

Select a test: 1) smoke 2) load 3) stress 4) spike 5) all 0) exit

命令行直传参数

也可以跳过菜单,直接通过 CLI 参数指定档位:

# 运行冒烟测试 ./packages/testing/scripts/run-k6-local.sh smoke # 依次运行 smoke/load/stress/spike 全部档位 ./packages/testing/scripts/run-k6-local.sh all

脚本实现要点(源码级)

阅读 run-k6-local.sh 源码,可以理解脚本的几项关键行为:

  • 操作系统适配:通过uname -s判断平台。在 Linux 下追加--add-host=host.docker.internal:host-gateway,让容器内能通过host.docker.internal访问宿主机上运行的 Cal.diy;macOS 无需额外参数,其他系统直接报错退出。
  • 镜像可定制:默认使用grafana/k6镜像,可通过K6_IMAGE环境变量覆盖。
  • 目录挂载:将本地测试目录挂载到容器内/scripts工作目录(挂载源可用MOUNT_LOCAL覆盖,默认按脚本所在位置推算)。当前仓库中,测试根目录为 packages/testing,因此运行脚本时建议显式指定:
    MOUNT_LOCAL=packages/testing ./packages/testing/scripts/run-k6-local.sh load
  • 环境变量透传:脚本始终注入BASE_URL(Docker 模式下默认http://host.docker.internal:3000),并仅在变量存在时透传TOKENTEST_USER_FREETEST_PASSWORD_FREETEST_USER_PROTEST_PASSWORD_PRO
  • 健壮性检查:若测试目录不存在,脚本会在启动容器前报错退出。

自定义运行参数

BASE_URL=http://localhost:3000 \ TOKEN=your_token_here \ ./packages/testing/scripts/run-k6-local.sh load

脚本支持的环境变量:

  • BASE_URL
  • TOKEN
  • TEST_USER_FREE
  • TEST_PASSWORD_FREE
  • TEST_USER_PRO
  • TEST_PASSWORD_PRO

共享配置:thresholds、测试用户与工具函数

config.js 是整个套件的配置中枢,所有档位的测试脚本都从这里导入常量。

BASE_URL 与生产域名保护

export const BASE_URL = __ENV.BASE_URL || "http://localhost:3000"; if (BASE_URL.includes("cal.com")) { throw new Error("BLOCKED: Do not run against production domain"); }

这里有两层设计意图:默认指向本地http://localhost:3000;同时硬性拦截包含cal.com的域名,一旦误配到生产域名,测试在启动阶段就会抛错终止,从机制上杜绝了误压生产环境的可能。

模拟思考时间

export const SLEEP_DURATION = { SHORT: 1, MEDIUM: 3, LONG: 5, };

SLEEP_DURATION定义了短(1s)、中(3s)、长(5s)三档"思考时间"(用户阅读页面的停顿),用于让请求节奏更接近真实用户行为。

阈值体系

export const THRESHOLDS = { HTTP_ERRORS: ["rate<0.01"], RESPONSE_TIME: { SMOKE: { p95: ["p(95)<500"], p99: ["p(99)<1000"] }, LOAD: { p95: ["p(95)<1000"], p99: ["p(99)<2000"] }, STRESS: { p95: ["p(95)<2000"], p99: ["p(99)<4000"] }, SPIKE: { p95: ["p(95)<3000"], p99: ["p(99)<6000"] }, }, };

核心阈值分为两类:

档位HTTP 错误率p95 延迟p99 延迟阈值严格程度
smoke< 1%< 500ms< 1000ms最严格,用于捕捉性能回归
load< 1%< 1000ms< 2000ms中等,针对正常运营条件
stress< 1%< 2000ms< 4000ms较宽松,针对重负载
spike< 1%< 3000ms< 6000ms最宽松,针对突发流量

rate<0.01表示请求失败率(http_req_failed)须低于 1%;延迟阈值随负载强度逐级放宽,反映"负载越高、容忍度越大"的测试哲学。

测试账号池

export const TEST_USERS = new SharedArray("users", () => [ { username: __ENV.TEST_USER_FREE || "free", password: __ENV.TEST_PASSWORD_FREE || "free" }, { username: __ENV.TEST_USER_PRO || "pro", password: __ENV.TEST_PASSWORD_PRO || "pro" }, ]); export function getRandomUser() { return TEST_USERS[Math.floor(Math.random() * TEST_USERS.length)]; }

测试账号池同时包含免费版(默认free/free)与 Pro 版(默认pro/pro)两组账号,通过SharedArray在所有虚拟用户(VU)间共享,并用getRandomUser()随机抽取,模拟不同套餐用户的混合流量。

防缓存与随机等待

export function randomQueryParam() { return `nocache=${Date.now()}`; } export function randomSleep(min = SLEEP_DURATION.SHORT, max = SLEEP_DURATION.MEDIUM) { if (min > max) { throw new Error("min cannot be greater than max"); } const sleepTime = Math.random() * (max - min) + min; sleep(sleepTime); }

randomQueryParam()为每个请求追加基于时间戳的nocache参数,绕过浏览器/CDN 缓存,保证压测打到真实渲染链路;randomSleep()在给定区间内随机休眠,且内置min > max的参数校验。

核心场景实现:预订页面访问

helpers.js 中封装了本套件唯一的用户旅程场景viewBookingPage

export function viewBookingPage(username, eventSlug) { return group("View Booking Page", () => { const url = `${BASE_URL}/${username}/${eventSlug}?${randomQueryParam()}`; const response = http.get(url, { tags: { name: "Booking Page" }, timeout: "30s", }); check(response, { "Booking page loaded": (r) => r.status === 200, "Has booking form": (r) => r.body.includes('data-testid="day"') || r.body.includes("booking"), "Response time acceptable": (r) => r.timings.duration < 5000, }); randomSleep(); return response; }); }

逐行解读其设计:

  • URL 构造${BASE_URL}/${username}/${eventSlug}对应 Cal.diy 的公开预订页路由(如/pro/30min),并拼接randomQueryParam()防止缓存。
  • 请求参数timeout: "30s"给出宽松的请求超时;tags: { name: "Booking Page" }将指标聚合到统一的标签名下,便于跨档位对比。
  • 三重断言status === 200验证页面可访问;data-testid="day"验证预订表单(日期选择器)确实渲染,data-testid="day"这个测试锚点可以在 DatePicker.tsx 中找到对应实现;timings.duration < 5000给出单请求的兜底延迟红线。
  • 节奏控制:请求后调用randomSleep(),模拟真实用户浏览页面的停顿,避免 VU 变成无停顿的"压测机器人"。
  • 分组统计group("View Booking Page", ...)使 k6 报告按此分组展示指标,语义清晰。

四种压测档位:options 逐档剖析

四份booking.js都复用viewBookingPage("pro", "30min")作为主场景(即压测 Pro 用户30min事件类型的预订页),差异完全体现在options中。

smoke:固定 VU + 固定时长

smoke/booking.js 采用最简单直接的配置:

export const options = { vus: 10, duration: "2m", thresholds: { http_req_failed: THRESHOLDS.HTTP_ERRORS, http_req_duration: THRESHOLDS.RESPONSE_TIME.SMOKE.p95, }, };

10 个虚拟用户恒定运行 2 分钟,配合最严格的 p95 < 500ms 阈值,用作每次改动后的快速回归验证。

load:阶梯式爬坡到 2,000 VU

load/booking.js 使用stages定义五段负载曲线:

阶段时长VU 目标说明
12m500爬坡到 500 VU
23m1000继续爬坡到 1,000 VU
35m2000爬坡到 2,000 VU
410m2000维持 2,000 VU 运行 10 分钟
53m0缓慢归零

主循环sleep(0.1)保持较高请求密度,总测试约 23 分钟,用于评估正常运营高峰(每分钟数千请求)下的稳定性。

stress:逼近并寻找瓶颈点

stress/booking.js 的负载曲线更长、更狠:

阶段时长VU 目标说明
12m1000爬坡到 1,000 VU
23m2000爬坡到 2,000 VU
35m3000爬坡到 3,000 VU
410m3000维持 3,000 VU
55m4000继续爬坡到 4,000 VU
610m4000维持 4,000 VU(数万请求/分钟)
73m0缓慢归零

主循环压缩到sleep(0.05),以每分钟数万请求的强度冲击系统,寻找吞吐与延迟的"拐点"。总时长约 38 分钟,建议在专门的压测环境执行。

spike:模拟突发流量尖峰

spike/booking.js 模拟流量突变场景:

阶段时长VU 目标说明
11m500基线负载
22m50002 分钟内快速激增到 5,000 VU
35m5000维持峰值 5,000 VU
42m500回落至基线
51m0归零

总时长约 11 分钟。这类场景适合验证"营销活动上线、热点事件"等瞬时流量洪峰下系统的弹性与自动扩容表现,阈值也是最宽松的(p95 < 3000ms)。

环境变量参考

变量作用默认值
BASE_URL被测应用的基础 URLhttp://localhost:3000(Docker 脚本模式为http://host.docker.internal:3000
TEST_USER_FREE免费版测试账号用户名free
TEST_PASSWORD_FREE免费版测试账号密码free
TEST_USER_PROPro 版测试账号用户名pro
TEST_PASSWORD_PROPro 版测试账号密码pro
TOKEN透传给 k6 的令牌(脚本模式)
K6_IMAGEDocker 模式使用的 k6 镜像(脚本模式)grafana/k6
MOUNT_LOCALDocker 模式挂载的本地测试目录(脚本模式)脚本位置推算出的默认目录

新增测试的最佳实践

README 给出了新增用例的标准流程,结合源码可以进一步细化:

  1. 创建测试文件:在smoke/load/stress/spike/目录下新建 JS 文件(如packages/testing/performance/load/booking-create.js)。
  2. 导入共享能力:从 utils/helpers.js 导入既有场景封装,或参考其模式用group/check编写新场景;从 utils/config.js 导入BASE_URLTHRESHOLDSrandomSleepgetRandomUser等能力,保证新用例与既有档位共享相同的阈值与账号管理逻辑。
  3. 定义 options:声明 VUs /stages、运行时长与thresholds——务必从THRESHOLDS中按档位选取对应的 p95/p99 阈值,而非硬编码数值,以便全局统一调整。
  4. 实现主函数:在default函数中编排场景调用,并用sleep/randomSleep控制请求节奏。
  5. 验证运行:先用小 VU 数冒烟验证脚本本身无错误,再按档位完整执行,最后根据结果决定是否纳入 CI 回归。

结语:把压测沉淀为常态化质量手段

Cal.diy 的这套性能测试套件在工程实践上有几个值得借鉴的设计:场景与参数分离helpers.js只描述用户旅程,options只描述负载与阈值)、按档位梯度放宽阈值(从 smoke 的 500ms 到 spike 的 3000ms,量化了"不同负载下可接受的体验水位")、以及生产域防护(从根上杜绝误压)。将 smoke 档位接入每次 CI 回归、将 load/stress/spike 按发布节奏定期执行,就能持续量化预订流程的容量水位,在流量洪峰到来之前提前发现瓶颈。

延伸阅读:测试目录的官方说明见 packages/testing/performance/README.md;共享配置与场景封装见 config.js 与 helpers.js;Docker 运行脚本见 run-k6-local.sh;预订表单的测试锚点实现见 DatePicker.tsx。

【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询