271 lines
9.0 KiB
TypeScript
271 lines
9.0 KiB
TypeScript
/**
|
||
* 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)
|
||
})
|