说明:此压测版本为集群版本,非开源的版本。
1. 测试概要
- 测试目标:评估系统在2000 QPS高并发场景下的吞吐量及用户容量。
- 测试工具:k6(受限于硬件性能,实际未达到理论峰值)。
- 测试时长:100秒(实际因系统处理延迟延长至125秒,MQ堆积)。
- 服务器配置:
-
- 3台机器(4核16GB),中间件与服务混部(非生产推荐架构)。
2. 关键指标
| 指标 | 理论值 | 实际值 | 说明 |
|---|---|---|---|
| 请求发送总量 | 200,000 | 157,252 | 硬件性能限制导致不足 |
| 请求发送成功率 | 100% | 100% | 无请求丢失 |
| 系统处理总量 | - | 157,000 | 每1000条合并记录导致误差 |
| 总处理时间 | 100秒 | ~125秒 | 系统存在处理积压 |
| 实际吞吐量 | 2,000 QPS | 1,258 QPS | 系统稳态处理能力 |
| 峰值处理能力 | - | ~3,200 QPS | 后25秒的追赶速率 |
3. 用户容量估算
- 场景假设:用户每10秒发送1条数据(WebSocket长连接)。
-
- 支持用户数:3,200 QPS × 10秒 ≈ 32,000活跃用户。
- 考虑20%活跃度:32,000 ÷ 0.2 ≈ 160,000总用户。
- 注意事项:
-
- 实际容量受其他HTTP请求、数据库IO等影响,需预留30%~50%冗余。
- 当前测试未模拟混合请求(如API、数据库操作),生产环境需补充测试。
4. 性能瓶颈分析
- 硬件限制:
-
- 中间件混部(MySQL/Redis/MongoDB/NGINX/.rocketMQ),资源争用影响性能。
- 未使用Redis集群,单节点可能成为瓶颈。
- k6测试机性能问题,不能模拟全量并发。
- 处理延迟:
-
- 前100秒积压70,000条,后25秒处理80,000条,表明系统异步处理能力尚可但初始吞吐不足。
-
- 使用的是http测试的并发,大量的http连接会显著影响性能,后25秒因为没有http发送消息,处理性能大幅度提升。
5. 优化建议
- 架构调整:
-
- 中间件独立部署(如MySQL、Redis、rocketMQ、mongoDB、nginx)。
- Redis启用集群模式,提升缓存吞吐量。
- 测试改进:
-
- 补充混合场景测试(HTTP+WebSocket+数据库操作)。
- 使用分布式压测工具(如k6 Cloud)突破单机性能限制。
- 容量规划:
-
- 生产环境按500 QPS/节点规划(保留冗余)。
6. 结论
当前测试环境下,系统稳态吞吐量约1,258 QPS,理论可支持16万用户(20%活跃度)。但实际生产需考虑中间件分离、集群化部署及更复杂的业务场景,建议进一步优化后重新压测。
截图
压测脚本
import http from 'k6/http';
import { check } from 'k6';
import { Rate } from 'k6/metrics';
import execution from 'k6/execution'; // Import the execution module
// --- Configuration Options ---
// Create a custom metric to track failure rate specifically
const errorRate = new Rate('errors');
export const options = {
// Use the constant-arrival-rate executor to maintain a specific request rate
scenarios: {
constant_request_rate: {
executor: 'constant-arrival-rate',
rate: 1500, // Target requests per second
timeUnit: '1s', // Time unit for the rate (500 requests per 1 second)
duration: '100s', // Duration to maintain this rate (e.g., 1 minute)
preAllocatedVUs: 100, // Initial number of VUs to allocate. Adjust based on response times.
// If response time is > 200ms (1000ms / 500 RPS), you'll need more VUs.
// Start with rate / avg_response_time_in_seconds roughly.
maxVUs: 300, // Maximum number of VUs K6 can use to try and meet the rate.
// Increased slightly as a precaution, adjust as needed.
},
},
// IMPORTANT: Ignore TLS verification errors for IP address HTTPS
insecureSkipTLSVerify: true,
// Define thresholds
thresholds: {
http_req_failed: ['rate<0.01'], // http errors should be less than 1%
'errors': ['rate<0.01'], // Using our custom metric for checks failures
http_req_duration: ['p(95)<800'], // 95% of requests should be below 800ms (adjust as needed)
// You might need a threshold for the actual achieved RPS if meeting the target is critical
// 'http_reqs{scenario:constant_request_rate}': ['count>=29500'], // Example: Ensure at least ~500*60 requests in 1 min
},
};
// --- Main Test Logic ---
export default function () {
// Get the globally unique, auto-incrementing iteration number for the current scenario
const contentCounter = execution.scenario.iterationInTest;
// Construct the URL dynamically with the incrementing counter
const baseUrl = 'https://172.1.1.1:8080/vim/sdk/message/send/pushText';
const chatId = 3;
const chatType = 'friend';
const url = `${baseUrl}?chatId=${chatId}&content=${contentCounter}&chatType=${chatType}`;
// --- Define Custom Headers Here ---
const customHeaders = {
'sa-token': 'xxxx', // **重要**: 替换为你的认证 Token
'Content-Type': 'application/json', // Even if payload is empty, keep it if API expects it
'X-Request-ID': `k6-${execution.vu.idInTest}-${execution.scenario.iterationInInstance}`, // Use more specific execution context vars
'User-Agent': 'MyK6LoadTester/1.0-POST-Incremental',
// Add other necessary headers
};
// --- Define the POST Request Body (Payload) ---
// Since the data is in the URL parameters, the payload likely remains empty or null.
// Confirm with your API specification.
const payload = JSON.stringify({}); // Sending an empty JSON object
// OR const payload = null; // If absolutely no body should be sent
// Package headers for the request parameters
const params = {
headers: customHeaders,
// Optional: Add tags for better reporting
tags: {
name: 'PushTextMessageIncremental', // Name this request in results
},
};
// --- Send the HTTP POST Request ---
const res = http.post(url, payload, params);
// --- Check the Response ---
const checkRes = check(res, {
'status is 200 or 201': (r) => r.status === 200 || r.status === 201, // POST often returns 201 Created
// 'response body contains success indicator': (r) => r.body.includes('"code":0'), // Uncomment and adapt if needed
});
// Record errors using our custom rate metric if the check fails
if (!checkRes) {
errorRate.add(1); // Record an error
// Consider logging less frequently on high load to avoid log spam, or log only status/URL
console.error(`Request failed! VU: ${execution.vu.idInTest}, Iter: ${execution.scenario.iterationInInstance}, Status: ${res.status}, URL: ${url}`); // Log failure info
} else {
errorRate.add(0); // Record success for the error rate calculation
}
// No explicit 'sleep' is needed with constant-arrival-rate executor.
}
// --- Teardown Function (Optional) ---
// export function teardown(data) {
// console.log(`Test finished. Total iterations (requests): ${execution.scenario.iterationInTest}`);
// // Note: iterationInTest in teardown might show the final count + 1
// }