gongxiangp2/scripts/test-s3-network-vs-storage.ts

271 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* S3存储桶速度对比测试
* 区分网络问题和存储桶性能问题
*/
import { S3Client, PutObjectCommand, ListObjectsV2Command, DeleteObjectCommand } from '@aws-sdk/client-s3'
import { randomBytes } from 'crypto'
const S3_ENDPOINT = process.env.S3_ENDPOINT || '' // 公网域名(通过FRP)
const S3_BUCKET = process.env.S3_BUCKET || ''
const S3_ACCESS_KEY_ID = process.env.S3_ACCESS_KEY_ID || ''
const S3_SECRET_ACCESS_KEY = process.env.S3_SECRET_ACCESS_KEY || ''
const MINIO_INTERNAL_IP = process.env.MINIO_INTERNAL_IP || '192.168.58.20'
const MINIO_INTERNAL_PORT = process.env.MINIO_INTERNAL_PORT || '9000'
console.log('='.repeat(70))
console.log('S3存储桶速度对比测试 - 网络 vs 存储桶性能')
console.log('='.repeat(70))
console.log(`公网端点(FRP): ${S3_ENDPOINT}`)
console.log(`内网端点: http://${MINIO_INTERNAL_IP}:${MINIO_INTERNAL_PORT}`)
console.log(`存储桶: ${S3_BUCKET}`)
console.log('='.repeat(70))
console.log('')
// 创建两个S3客户端:公网和内网
const s3ClientPublic = new S3Client({
endpoint: S3_ENDPOINT,
region: process.env.S3_REGION || 'us-east-1',
credentials: {
accessKeyId: S3_ACCESS_KEY_ID,
secretAccessKey: S3_SECRET_ACCESS_KEY,
},
forcePathStyle: true,
requestHandler: {
requestTimeout: 60000,
connectionTimeout: 10000,
},
})
const s3ClientInternal = new S3Client({
endpoint: `http://${MINIO_INTERNAL_IP}:${MINIO_INTERNAL_PORT}`,
region: process.env.S3_REGION || 'us-east-1',
credentials: {
accessKeyId: S3_ACCESS_KEY_ID,
secretAccessKey: S3_SECRET_ACCESS_KEY,
},
forcePathStyle: true,
requestHandler: {
requestTimeout: 60000,
connectionTimeout: 10000,
},
})
// 测试上传速度
async function testUploadSpeed(
client: S3Client,
label: string,
sizeKB: number
): Promise<{ duration: number; speed: number; success: boolean; key?: string }> {
const key = `speed-test-${Date.now()}-${Math.random().toString(36).substring(7)}.bin`
const testData = randomBytes(sizeKB * 1024)
const startTime = Date.now()
try {
const command = new PutObjectCommand({
Bucket: S3_BUCKET,
Key: key,
Body: testData,
ContentType: 'application/octet-stream',
})
await client.send(command)
const duration = Date.now() - startTime
const speed = (sizeKB / duration) * 1000 // KB/s
return { duration, speed, success: true, key }
} catch (error: any) {
const duration = Date.now() - startTime
console.error(` ${label} 上传失败:`, error.message)
return { duration, speed: 0, success: false, key }
}
}
// 测试连接延迟
async function testConnectionLatency(client: S3Client, label: string): Promise<number> {
const startTime = Date.now()
try {
const command = new ListObjectsV2Command({
Bucket: S3_BUCKET,
MaxKeys: 1,
})
await client.send(command)
return Date.now() - startTime
} catch (error: any) {
return Date.now() - startTime
}
}
// 并发上传测试
async function testConcurrentUploads(
client: S3Client,
label: string,
concurrency: number,
sizeKB: number,
count: number
) {
const durations: number[] = []
let success = 0
let failed = 0
const keys: string[] = []
const startTime = Date.now()
for (let i = 0; i < count; i += concurrency) {
const batch = []
for (let j = 0; j < concurrency && i + j < count; j++) {
batch.push(testUploadSpeed(client, label, sizeKB))
}
const results = await Promise.allSettled(batch)
for (const result of results) {
if (result.status === 'fulfilled') {
const r = result.value
if (r.success) {
durations.push(r.duration)
success++
if (r.key) keys.push(r.key)
} else {
failed++
}
} else {
failed++
}
}
process.stdout.write(`\r${label} 进度: ${Math.min(i + concurrency, count)}/${count} (成功: ${success}, 失败: ${failed})`)
}
console.log('')
const totalTime = Date.now() - startTime
const totalSizeMB = (success * sizeKB) / 1024
const throughput = totalTime > 0 ? (totalSizeMB / totalTime) * 1000 : 0
// 清理测试文件
if (keys.length > 0) {
await Promise.allSettled(
keys.map(key =>
client.send(
new DeleteObjectCommand({
Bucket: S3_BUCKET,
Key: key,
})
)
)
)
}
const sorted = [...durations].sort((a, b) => a - b)
const stats = {
min: sorted[0] || 0,
max: sorted[sorted.length - 1] || 0,
avg: durations.reduce((a, b) => a + b, 0) / durations.length || 0,
p95: sorted[Math.floor(sorted.length * 0.95)] || 0,
p99: sorted[Math.floor(sorted.length * 0.99)] || 0,
}
return {
total: count,
success,
failed,
throughput,
stats,
}
}
// 主测试函数
async function runComparisonTests() {
console.log('开始对比测试...\n')
// 1. 连接延迟对比
console.log('1. 连接延迟对比测试...')
const publicLatency = await testConnectionLatency(s3ClientPublic, '公网(FRP)')
const internalLatency = await testConnectionLatency(s3ClientInternal, '内网(直连)')
console.log(` 公网(FRP)延迟: ${publicLatency}ms`)
console.log(` 内网(直连)延迟: ${internalLatency}ms`)
if (publicLatency > 0 && internalLatency > 0) {
const latencyDiff = ((publicLatency - internalLatency) / internalLatency * 100).toFixed(1)
console.log(` 延迟差异: 公网比内网慢 ${latencyDiff}%`)
}
console.log('')
// 2. 单文件上传速度对比
console.log('2. 单文件上传速度对比(100KB)...')
const publicUpload = await testUploadSpeed(s3ClientPublic, '公网', 100)
const internalUpload = await testUploadSpeed(s3ClientInternal, '内网', 100)
console.log(` 公网(FRP): ${(publicUpload.speed / 1024).toFixed(2)} MB/s (${publicUpload.duration}ms)`)
console.log(` 内网(直连): ${(internalUpload.speed / 1024).toFixed(2)} MB/s (${internalUpload.duration}ms)`)
if (publicUpload.speed > 0 && internalUpload.speed > 0) {
const speedup = (internalUpload.speed / publicUpload.speed).toFixed(2)
console.log(` 速度提升: 内网比公网快 ${speedup}x`)
}
// 清理
if (publicUpload.key) {
await s3ClientPublic.send(new DeleteObjectCommand({ Bucket: S3_BUCKET, Key: publicUpload.key })).catch(() => {})
}
if (internalUpload.key) {
await s3ClientInternal.send(new DeleteObjectCommand({ Bucket: S3_BUCKET, Key: internalUpload.key })).catch(() => {})
}
console.log('')
// 3. 并发上传对比(小规模)
console.log('3. 并发上传对比(10并发,20次,100KB)...')
console.log('\n公网(FRP)测试:')
const publicConcurrent = await testConcurrentUploads(s3ClientPublic, '公网', 10, 100, 20)
console.log(` 成功率: ${((publicConcurrent.success / publicConcurrent.total) * 100).toFixed(1)}%`)
console.log(` 吞吐量: ${publicConcurrent.throughput.toFixed(2)} MB/s`)
console.log(` 延迟: avg=${publicConcurrent.stats.avg.toFixed(1)}ms, p95=${publicConcurrent.stats.p95}ms`)
console.log('\n内网(直连)测试:')
const internalConcurrent = await testConcurrentUploads(s3ClientInternal, '内网', 10, 100, 20)
console.log(` 成功率: ${((internalConcurrent.success / internalConcurrent.total) * 100).toFixed(1)}%`)
console.log(` 吞吐量: ${internalConcurrent.throughput.toFixed(2)} MB/s`)
console.log(` 延迟: avg=${internalConcurrent.stats.avg.toFixed(1)}ms, p95=${internalConcurrent.stats.p95}ms`)
if (publicConcurrent.throughput > 0 && internalConcurrent.throughput > 0) {
const throughputSpeedup = (internalConcurrent.throughput / publicConcurrent.throughput).toFixed(2)
console.log(`\n 吞吐量提升: 内网比公网快 ${throughputSpeedup}x`)
}
console.log('')
// 4. 分析结论
console.log('='.repeat(70))
console.log('分析结论:')
console.log('='.repeat(70))
if (internalUpload.speed > publicUpload.speed * 2) {
console.log('✓ 主要问题是网络瓶颈(FRP穿透)')
console.log(` - 内网速度是公网的 ${(internalUpload.speed / publicUpload.speed).toFixed(2)}x`)
console.log(' - 建议:')
console.log(' 1. 优化FRP配置,增加带宽')
console.log(' 2. 使用CDN加速公网访问')
console.log(' 3. 内网用户直接使用内网地址访问')
} else if (internalUpload.speed < 1) {
console.log('✓ 主要问题是存储桶/MinIO服务器性能')
console.log(' - 即使内网直连速度也很慢')
console.log(' - 建议:')
console.log(' 1. 检查MinIO服务器配置和资源')
console.log(' 2. 优化MinIO存储后端(磁盘IO)')
console.log(' 3. 考虑升级服务器硬件')
} else {
console.log('✓ 网络和存储桶都有性能问题')
console.log(' - 建议同时优化网络和存储桶配置')
}
console.log('='.repeat(70))
}
// 运行测试
runComparisonTests()
.then(() => {
process.exit(0)
})
.catch((error) => {
console.error('测试失败:', error)
process.exit(1)
})