那崩溃了怎么办啊
重要提示
不要等到比赛开始时再来看这一段。比赛前尽量做好压力测试。
不要过度优化,切勿按照本页说明从上到下全部调整一遍。选择适合服务器当前瓶颈的进行优化,优化后再次测试,观察是否有新的瓶颈,再针对性优化。谨记过犹不及。
系统能承载的最大并发数低,很可能是配置错误。我们首先需要检查这些配置是不是正确。
当然,如果配置正确的前提下,很可能就是硬件资源真的不够用了。
Linux 系统优化
1. 检查并调整文件描述符上限
查看当前文件描述符限制:
# 查看软限制和硬限制
ulimit -n
ulimit -Hn
# 查看系统级限制
cat /proc/sys/fs/file-max
修改文件描述符上限,编辑 /etc/security/limits.conf
:
# 添加以下内容(用户名请根据实际情况修改)
* soft nofile 65536
* hard nofile 65536
root soft nofile 65536
root hard nofile 65536
www-data soft nofile 65536
www-data hard nofile 65536
修改系统级文件描述符上限,编辑 /etc/sysctl.conf
:
# 添加以下内容
fs.file-max = 1000000
应用配置:
# 重新加载 sysctl 配置
sudo sysctl -p
# 重启服务或重新登录以使 limits.conf 生效
2. 调整网络连接数上限
编辑 /etc/sysctl.conf
,添加以下配置:
# 增加端口范围
net.ipv4.ip_local_port_range = 1024 65535
# 增加 TCP 连接队列长度
net.core.somaxconn = 65535
# 调整 TCP 时间戳
net.ipv4.tcp_timestamps = 1
# 快速回收 TIME_WAIT 连接
net.ipv4.tcp_tw_reuse = 1
# 减少 TIME_WAIT 超时时间
net.ipv4.tcp_fin_timeout = 30
# 增加网络缓冲区大小
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 131072 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
# Redis 优化
vm.overcommit_memory = 1 # 允许内存过量分配
应用配置:
sudo sysctl -p
Nginx 优化配置
编辑 Nginx 配置文件(通常在 /etc/nginx/nginx.conf
):
# 在 main 块中
user www-data;
worker_processes auto; # 自动设置为 CPU 核心数
worker_rlimit_nofile 65535; # 工作进程能打开的文件描述符数量
events {
worker_connections 65535; # 每个工作进程的最大连接数
use epoll; # 使用 epoll 事件模型(Linux 推荐)
multi_accept on; # 允许一次接受多个连接
}
http {
# 基本设置
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000; # 增加 keep-alive 请求数
# 客户端请求大小限制
client_max_body_size 100M; # 允许上传的最大文件大小
client_body_buffer_size 128k;
client_header_buffer_size 32k;
large_client_header_buffers 4 32k;
# 超时设置
client_body_timeout 60s;
client_header_timeout 60s;
send_timeout 60s;
# 压缩设置
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# 缓存设置
open_file_cache max=10000 inactive=300s;
open_file_cache_valid 360s;
open_file_cache_min_uses 2;
open_file_cache_errors off;
# 限制请求频率(防止 DDoS)
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
# 在 server 块中应用限制
server {
# 对登录接口限制
location /api/auth/ {
limit_req zone=login burst=10 nodelay;
# 其他配置...
}
# 对 API 接口限制
location /api/ {
limit_req zone=api burst=50 nodelay;
# 其他配置...
}
}
}
检验 Nginx 配置并重启:
# 检查配置文件语法
sudo nginx -t
# 重新加载配置
sudo nginx -s reload
# 或重启 Nginx
sudo systemctl restart nginx
数据库优化
MySQL/MariaDB 配置优化
编辑 /etc/mysql/mysql.conf.d/mysqld.cnf
或 /etc/my.cnf
:
[mysqld]
# 连接数设置
max_connections = 1000
max_user_connections = 800
# 缓冲池设置(根据服务器内存调整)
innodb_buffer_pool_size = 2G # 设置为内存的 70-80%
innodb_buffer_pool_instances = 8
# 查询缓存
query_cache_type = 1
query_cache_size = 256M
# 连接超时
wait_timeout = 600
interactive_timeout = 600
# 临时表设置
tmp_table_size = 256M
max_heap_table_size = 256M
# 日志设置
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
重启 MySQL:
sudo systemctl restart mysql
Redis 配置优化
编辑 /etc/redis/redis.conf
或 /usr/local/etc/redis.conf
:
# 基本配置
bind 127.0.0.1 ::1 # 绑定本地地址,如需远程访问请修改
port 6379
timeout 300 # 客户端空闲超时时间
# 内存配置
maxmemory 4gb # 根据服务器内存设置,建议不超过总内存的50%
maxmemory-policy allkeys-lru # 内存满时的淘汰策略
# 持久化配置(根据需求调整)
# RDB 快照设置
save 900 1 # 900秒内至少1个key变化时保存
save 300 10 # 300秒内至少10个key变化时保存
save 60 10000 # 60秒内至少10000个key变化时保存
# AOF 配置(可选,影响性能但提高数据安全性)
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec # 每秒同步一次
# 网络和连接配置
tcp-keepalive 300
tcp-backlog 511 # TCP连接队列长度
maxclients 10000 # 最大客户端连接数
# 性能优化
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
# 慢查询日志
slowlog-log-slower-than 10000 # 记录执行时间超过10ms的命令
slowlog-max-len 128
# 其他优化
stop-writes-on-bgsave-error no # 后台保存失败时不停止写入
rdbcompression yes # 启用RDB压缩
rdbchecksum yes # 启用RDB校验
hz 10 # 后台任务执行频率
应用层优化
Node.js 应用优化
如果使用 PM2 管理 Node.js 应用,可以这样配置:
// ecosystem.config.js
module.exports = {
apps: [{
name: 'ccxc-sync-server',
script: './dist/app.js',
instances: 'max', // 启动与 CPU 核心数相等的实例
exec_mode: 'cluster',
max_memory_restart: '1G',
env: {
NODE_ENV: 'production',
UV_THREADPOOL_SIZE: 128 // 增加线程池大小
}
}]
}
监控和诊断命令
实时监控系统状态
# 查看当前连接数
ss -tuln | wc -l
# 查看 Nginx 状态
curl http://localhost/nginx_status
# 查看系统负载
top
htop
# 查看内存使用
free -h
# 查看磁盘 I/O
iotop
# 查看网络连接状态
netstat -ant | awk '{print $6}' | sort | uniq -c | sort -n
# 查看文件描述符使用情况
lsof | wc -l
cat /proc/sys/fs/file-nr
重要提醒
- 在生产环境修改这些配置前,请务必备份原始配置文件
- 根据你的服务器硬件配置(CPU、内存、磁盘)适当调整数值
- 建议在内测阶段进行压力测试,验证配置效果
- 监控系统资源使用情况,避免过度优化导致资源耗尽
压力测试建议
根据 CCXC Engine 的实际使用场景,压力测试应该重点关注以下核心 API 接口:
主要测试接口
高负载接口:
API Base: https://api.yourdomain.com/api
POST /v1/start
- 比赛开始POST /v1/play/get-main-info
- 获取主要信息POST /v1/play/get-puzzle-info
- 获取分区信息POST /v1/play/get-detail
- 获取题目信息
WebSocket 连接:
wss://api.yourdomain.com/ws-api/notify
- 实时通知
使用 Apache Bench (ab) 进行测试
首先创建认证头部生成脚本:
# create_auth_headers.sh
#!/bin/bash
USER_TOKEN="your_user_token_here"
USER_SK="your_user_secret_key_here"
TS=$(date +%s%3N) # 毫秒时间戳
BODY_STRING="$1"
# 计算签名
SIGN_DATA="token=${USER_TOKEN}&ts=${TS}&bodyString=${BODY_STRING}"
SIGN=$(echo -n "$SIGN_DATA" | openssl dgst -sha1 -hmac "$USER_SK" -binary | base64)
echo "User-Token: ${USER_TOKEN}"
echo "X-Auth-Token: Ccxc-Auth ${TS} ${SIGN}"
echo "Content-Type: application/json"
生成带认证的测试命令:
# 设置认证信息
USER_TOKEN="your_user_token_here"
USER_SK="your_user_secret_key_here"
TS=$(date +%s%3N)
# 为每个接口生成签名和头部
# 测试启动接口
BODY="{}"
SIGN_DATA="token=${USER_TOKEN}&ts=${TS}&bodyString=${BODY}"
SIGN=$(echo -n "$SIGN_DATA" | openssl dgst -sha1 -hmac "$USER_SK" -binary | base64)
ab -n 5000 -c 200 -p start_payload.json -T application/json \
-H "User-Token: ${USER_TOKEN}" \
-H "X-Auth-Token: Ccxc-Auth ${TS} ${SIGN}" \
https://api.yourdomain.com/api/v1/start
# 测试主信息接口
BODY="{}"
SIGN_DATA="token=${USER_TOKEN}&ts=${TS}&bodyString=${BODY}"
SIGN=$(echo -n "$SIGN_DATA" | openssl dgst -sha1 -hmac "$USER_SK" -binary | base64)
ab -n 10000 -c 300 -p main_info_payload.json -T application/json \
-H "User-Token: ${USER_TOKEN}" \
-H "X-Auth-Token: Ccxc-Auth ${TS} ${SIGN}" \
https://api.yourdomain.com/api/v1/play/get-main-info
# 测试题目信息接口
BODY='{"pgid": 1}'
SIGN_DATA="token=${USER_TOKEN}&ts=${TS}&bodyString=${BODY}"
SIGN=$(echo -n "$SIGN_DATA" | openssl dgst -sha1 -hmac "$USER_SK" -binary | base64)
ab -n 8000 -c 250 -p puzzle_info_payload.json -T application/json \
-H "User-Token: ${USER_TOKEN}" \
-H "X-Auth-Token: Ccxc-Auth ${TS} ${SIGN}" \
https://api.yourdomain.com/api/v1/play/get-puzzle-info
# 测试详细信息接口
BODY='{"pid": 1}'
SIGN_DATA="token=${USER_TOKEN}&ts=${TS}&bodyString=${BODY}"
SIGN=$(echo -n "$SIGN_DATA" | openssl dgst -sha1 -hmac "$USER_SK" -binary | base64)
ab -n 6000 -c 200 -p detail_payload.json -T application/json \
-H "User-Token: ${USER_TOKEN}" \
-H "X-Auth-Token: Ccxc-Auth ${TS} ${SIGN}" \
https://api.yourdomain.com/api/v1/play/get-detail
使用 Node.js 进行测试
// ab_test.js
const crypto = require('crypto');
const https = require('https');
const { performance } = require('perf_hooks');
// 配置
const USER_TOKEN = "your_user_token_here";
const USER_SK = "your_user_secret_key_here";
const BASE_URL = "https://api.yourdomain.com/api";
// 生成认证头部
function generateAuthHeaders(bodyString) {
const ts = Date.now();
const signData = `token=${USER_TOKEN}&ts=${ts}&bodyString=${bodyString}`;
const sign = crypto.createHmac('sha1', USER_SK).update(signData).digest('base64');
return {
'User-Token': USER_TOKEN,
'X-Auth-Token': `Ccxc-Auth ${ts} ${sign}`,
'Content-Type': 'application/json'
};
}
// 执行HTTP请求
function makeRequest(path, body, callback) {
const bodyString = JSON.stringify(body);
const headers = generateAuthHeaders(bodyString);
const options = {
hostname: 'api.yourdomain.com',
port: 443,
path: path,
method: 'POST',
headers: headers
};
const startTime = performance.now();
const req = https.request(options, (res) => {
const endTime = performance.now();
callback(null, {
statusCode: res.statusCode,
responseTime: endTime - startTime
});
});
req.on('error', callback);
req.write(bodyString);
req.end();
}
// 并发测试函数
async function concurrentTest(path, body, concurrency, totalRequests) {
const results = [];
const startTime = performance.now();
let completed = 0;
let errors = 0;
return new Promise((resolve) => {
for (let i = 0; i < concurrency; i++) {
const requestsPerWorker = Math.floor(totalRequests / concurrency);
const worker = async () => {
for (let j = 0; j < requestsPerWorker; j++) {
makeRequest(BASE_URL + path, body, (err, result) => {
completed++;
if (err) {
errors++;
} else {
results.push(result);
}
if (completed >= totalRequests) {
const endTime = performance.now();
const totalTime = endTime - startTime;
resolve({
totalRequests,
totalTime,
errors,
successRate: ((totalRequests - errors) / totalRequests * 100).toFixed(2),
avgResponseTime: results.reduce((sum, r) => sum + r.responseTime, 0) / results.length
});
}
});
// 控制请求频率
await new Promise(resolve => setTimeout(resolve, 10));
}
};
worker();
}
});
}
// 运行测试
async function runTests() {
console.log('开始压力测试...');
// 测试启动接口
console.log('\n测试 /v1/start 接口...');
const startResult = await concurrentTest('/v1/start', {}, 200, 5000);
console.log('结果:', startResult);
// 测试主信息接口
console.log('\n测试 /v1/play/get-main-info 接口...');
const mainInfoResult = await concurrentTest('/v1/play/get-main-info', {}, 300, 10000);
console.log('结果:', mainInfoResult);
// 测试题目信息接口
console.log('\n测试 /v1/play/get-puzzle-info 接口...');
const puzzleInfoResult = await concurrentTest('/v1/play/get-puzzle-info', {pgid: 1}, 250, 8000);
console.log('结果:', puzzleInfoResult);
// 测试详细信息接口
console.log('\n测试 /v1/play/get-detail 接口...');
const detailResult = await concurrentTest('/v1/play/get-detail', {pid: 1}, 200, 6000);
console.log('结果:', detailResult);
}
// 运行测试
runTests().catch(console.error);
运行 Node.js 压力测试:
node ab_test.js
WebSocket 连接压力测试
使用 Node.js 脚本测试 WebSocket 连接:
// websocket_test.js
const WebSocket = require('ws');
const CONCURRENT_CONNECTIONS = 1000;
const WS_BASE_URL = 'wss://api.yourdomain.com/ws-api/notify';
// 认证配置
const USER_TOKEN = "your_user_token_here";
let connectedCount = 0;
let failedCount = 0;
// 生成带认证的 WebSocket URL
function generateWebSocketURL() {
return `${WS_BASE_URL}?sessionId=${USER_TOKEN}`;
}
// 批量创建连接的函数
async function createConnections() {
for (let i = 0; i < CONCURRENT_CONNECTIONS; i++) {
try {
const wsUrl = generateWebSocketURL();
const ws = new WebSocket(wsUrl);
ws.on('open', () => {
connectedCount++;
console.log(`Connected: ${connectedCount}/${CONCURRENT_CONNECTIONS}`);
// 模拟保持连接
const pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
} else {
clearInterval(pingInterval);
}
}, 30000);
// 5分钟后关闭连接
setTimeout(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.close();
}
clearInterval(pingInterval);
}, 300000);
});
ws.on('error', (error) => {
failedCount++;
console.error(`Connection ${i} failed:`, error.message);
});
ws.on('close', (code, reason) => {
console.log(`Connection ${i} closed: ${code} ${reason}`);
});
ws.on('message', (data) => {
console.log(`Connection ${i} received:`, data.toString());
});
// 避免同时发起太多连接,每批50个连接间隔100ms
if (i % 50 === 0 && i > 0) {
await new Promise(resolve => setTimeout(resolve, 100));
}
} catch (error) {
failedCount++;
console.error(`Failed to create connection ${i}:`, error.message);
}
}
}
// 开始测试
createConnections().then(() => {
console.log('所有连接创建请求已发送');
// 每30秒输出一次统计
const statsInterval = setInterval(() => {
console.log(`\n=== 统计信息 ===`);
console.log(`已连接: ${connectedCount}`);
console.log(`失败: ${failedCount}`);
console.log(`成功率: ${(connectedCount / CONCURRENT_CONNECTIONS * 100).toFixed(2)}%`);
console.log(`================\n`);
}, 30000);
// 10分钟后停止统计并结束程序
setTimeout(() => {
clearInterval(statsInterval);
console.log(`\n=== 最终统计 ===`);
console.log(`总连接尝试: ${CONCURRENT_CONNECTIONS}`);
console.log(`成功连接: ${connectedCount}`);
console.log(`连接失败: ${failedCount}`);
console.log(`成功率: ${(connectedCount / CONCURRENT_CONNECTIONS * 100).toFixed(2)}%`);
console.log(`=================`);
process.exit(0);
}, 600000);
}).catch(console.error);
运行 WebSocket 测试:
# 安装 WebSocket 依赖
npm install ws
# 运行测试
node websocket_test.js
测试结果评估标准
HTTP 接口性能目标:
- 响应时间:P95 < 500ms,P99 < 1000ms
- 错误率:< 1%
- 吞吐量:> 1000 requests/second
WebSocket 连接目标:
- 同时连接数:> 5000
- 连接建立成功率:> 99%
- 消息传输延迟:< 100ms
测试建议
- 逐步增加负载:从小并发开始,逐步增加到目标值
- 持续时间测试:不仅要测试峰值,还要测试持续负载能力
- 监控资源使用:同时监控 CPU、内存、磁盘 I/O、网络带宽
- 真实数据测试:使用真实的用户认证 token 和数据进行测试
注意事项
- 替换认证信息:将
your_user_token_here
和your_user_secret_key_here
替换为实际的认证信息 - 替换测试域名:将
yourdomain.com
替换为实际域名 - 控制测试强度:避免对生产环境造成实际影响
- 清理测试数据:测试完成后清理产生的测试数据
测试时注意:
User-Token
与sk
:用户登录后从前端LocalStorage
中查看- 确保测试账号具有足够的权限访问所有测试接口
应急处理
如果系统在比赛中出现问题,可以采取以下应急措施:
- 重启相关服务:
sudo systemctl restart nginx
sudo systemctl restart mysql
sudo systemctl restart redis
sudo pm2 restart all
- 临时调整限制:
# 临时增加文件描述符限制
ulimit -n 100000
# 清理系统缓存(谨慎使用)
echo 3 > /proc/sys/vm/drop_caches
- 查看错误日志:
# Nginx 错误日志
tail -f /var/log/nginx/error.log
# MySQL 错误日志
tail -f /var/log/mysql/error.log
# 系统日志
journalctl -f