Appearance
CDN配置
课程概述
本章节深入探讨CDN(Content Delivery Network)的配置和使用,学习如何通过CDN加速静态资源分发,提升全球用户访问速度。掌握CDN配置是优化应用性能的重要手段。
学习目标
- 理解CDN的工作原理和优势
- 掌握静态资源CDN配置
- 学习CDN域名和缓存策略
- 了解CDN回源和容灾配置
- 掌握主流CDN服务的使用
- 学习CDN性能优化技巧
第一部分:CDN基础
1.1 什么是CDN
CDN(内容分发网络)是一组分布在全球各地的服务器,通过将内容缓存到距离用户最近的节点,加速内容传输。
工作流程:
用户请求 → CDN边缘节点 →
├─ 命中缓存 → 直接返回
└─ 未命中 → 回源服务器 → 缓存并返回性能对比:
javascript
// 不使用CDN
用户(北京) → 源服务器(美国) = 200ms
// 使用CDN
用户(北京) → CDN节点(北京) = 20ms1.2 CDN的优势
javascript
1. 加速访问 - 就近访问,减少延迟
2. 减轻负载 - 分担源站压力
3. 提高可用性 - 多节点容灾
4. 节省带宽 - 边缘缓存减少回源
5. 安全防护 - DDoS防护,WAF1.3 CDN缓存策略
javascript
// 缓存层级
1. 浏览器缓存
2. CDN边缘节点
3. CDN中心节点
4. 源服务器第二部分:静态资源CDN配置
2.1 Vite CDN配置
typescript
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
base: 'https://cdn.example.com/', // CDN域名
build: {
rollupOptions: {
output: {
// 资源文件命名
assetFileNames: 'assets/[name]-[hash][extname]',
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
}
}
}
})环境特定CDN:
typescript
// vite.config.ts
export default defineConfig(({ mode }) => {
const CDN_URLS = {
development: '/',
staging: 'https://staging-cdn.example.com/',
production: 'https://cdn.example.com/',
}
return {
base: CDN_URLS[mode] || '/',
// ...
}
})2.2 Webpack CDN配置
javascript
// webpack.config.js
module.exports = {
output: {
publicPath: 'https://cdn.example.com/',
},
// 或使用环境变量
output: {
publicPath: process.env.CDN_URL || '/',
}
}2.3 动态公共路径
typescript
// src/main.tsx
// 运行时设置公共路径
if (import.meta.env.PROD) {
import.meta.env.BASE_URL = 'https://cdn.example.com/'
}
// 或从配置文件加载
fetch('/config.json')
.then(res => res.json())
.then(config => {
import.meta.env.BASE_URL = config.cdnUrl
})2.4 HTML中引用CDN
html
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- 从CDN加载字体 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
<!-- 从CDN加载库 -->
<script crossorigin src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>第三部分:主流CDN服务
3.1 Cloudflare CDN
bash
# 安装Cloudflare CLI
npm install -g wrangler
# 登录
wrangler login
# 部署到Cloudflare Pages
wrangler pages publish dist配置文件:
toml
# wrangler.toml
name = "my-app"
type = "webpack"
[site]
bucket = "./dist"
[env.production]
route = "https://example.com/*"
zone_id = "your-zone-id"自定义缓存规则:
javascript
// _headers (Cloudflare Pages)
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: public, max-age=31536000, immutable
/*.html
Cache-Control: public, max-age=0, must-revalidate
/static/*
Cache-Control: public, max-age=31536000, immutable3.2 AWS CloudFront
javascript
// AWS CDK配置
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'
import * as s3 from 'aws-cdk-lib/aws-s3'
const bucket = new s3.Bucket(this, 'MyBucket')
const distribution = new cloudfront.CloudFrontWebDistribution(this, 'MyDistribution', {
originConfigs: [{
s3OriginSource: {
s3BucketSource: bucket
},
behaviors: [{
isDefaultBehavior: true,
compress: true,
allowedMethods: cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
cachedMethods: cloudfront.CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS,
forwardedValues: {
queryString: false,
cookies: { forward: 'none' }
},
minTtl: 0,
defaultTtl: 86400,
maxTtl: 31536000,
}]
}]
})3.3 阿里云CDN
javascript
// 阿里云OSS + CDN配置
const OSS = require('ali-oss')
const client = new OSS({
region: 'oss-cn-hangzhou',
accessKeyId: 'your-access-key-id',
accessKeySecret: 'your-access-key-secret',
bucket: 'your-bucket-name'
})
// 上传文件到OSS
async function uploadToOSS(localFile, remotePath) {
try {
const result = await client.put(remotePath, localFile, {
headers: {
'Cache-Control': 'public, max-age=31536000',
'Content-Type': 'application/javascript'
}
})
// CDN URL
const cdnUrl = `https://cdn.example.com/${remotePath}`
console.log('CDN URL:', cdnUrl)
} catch (error) {
console.error('Upload failed:', error)
}
}3.4 腾讯云CDN
javascript
// 腾讯云COS + CDN
const COS = require('cos-nodejs-sdk-v5')
const cos = new COS({
SecretId: 'your-secret-id',
SecretKey: 'your-secret-key'
})
// 上传文件
cos.putObject({
Bucket: 'your-bucket',
Region: 'ap-guangzhou',
Key: 'assets/app.js',
Body: fs.createReadStream('./dist/app.js'),
Headers: {
'Cache-Control': 'max-age=31536000'
}
}, (err, data) => {
if (err) {
console.error(err)
} else {
console.log('CDN URL:', `https://cdn.example.com/assets/app.js`)
}
})第四部分:缓存策略
4.1 HTTP缓存头
javascript
// 不同资源的缓存策略
// HTML - 不缓存或短期缓存
Cache-Control: no-cache
// 或
Cache-Control: public, max-age=300, must-revalidate
// JS/CSS - 长期缓存(使用hash)
Cache-Control: public, max-age=31536000, immutable
// 图片 - 长期缓存
Cache-Control: public, max-age=31536000
// API响应 - 不缓存
Cache-Control: no-store4.2 Vite缓存配置
typescript
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
build: {
rollupOptions: {
output: {
// 使用内容hash,确保缓存失效
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.')
const ext = info[info.length - 1]
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(ext)) {
return `images/[name]-[hash][extname]`
}
if (/woff2?|ttf|otf|eot/i.test(ext)) {
return `fonts/[name]-[hash][extname]`
}
return `assets/[name]-[hash][extname]`
},
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
}
}
}
})4.3 服务端缓存配置
Nginx配置:
nginx
# nginx.conf
server {
listen 80;
server_name example.com;
root /var/www/html;
# HTML - 不缓存
location ~* \.html$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
expires -1;
}
# JS/CSS - 长期缓存
location ~* \.(js|css)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
expires 1y;
}
# 图片 - 长期缓存
location ~* \.(jpg|jpeg|png|gif|ico|svg)$ {
add_header Cache-Control "public, max-age=31536000";
expires 1y;
}
# 字体 - 长期缓存
location ~* \.(woff2?|ttf|otf|eot)$ {
add_header Cache-Control "public, max-age=31536000";
add_header Access-Control-Allow-Origin "*";
expires 1y;
}
}4.4 CDN缓存刷新
javascript
// 阿里云CDN刷新
const Core = require('@alicloud/pop-core')
const client = new Core({
accessKeyId: 'your-access-key-id',
accessKeySecret: 'your-access-key-secret',
endpoint: 'https://cdn.aliyuncs.com',
apiVersion: '2018-05-10'
})
// 刷新URL
async function refreshCDN(urls) {
const params = {
ObjectPath: urls.join('\n'),
ObjectType: 'File'
}
try {
const result = await client.request('RefreshObjectCaches', params)
console.log('CDN刷新成功:', result)
} catch (error) {
console.error('CDN刷新失败:', error)
}
}
// 使用
refreshCDN([
'https://cdn.example.com/js/app-abc123.js',
'https://cdn.example.com/css/main-def456.css'
])第五部分:部署自动化
5.1 部署脚本
javascript
// scripts/deploy-cdn.js
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront'
import fs from 'fs'
import path from 'path'
import glob from 'glob'
const s3Client = new S3Client({ region: 'us-east-1' })
const cfClient = new CloudFrontClient({ region: 'us-east-1' })
const BUCKET = 'my-bucket'
const DISTRIBUTION_ID = 'E1234567890'
const CDN_PATH = 'https://cdn.example.com'
// 上传文件到S3
async function uploadFile(localPath, remotePath) {
const fileContent = fs.readFileSync(localPath)
const ext = path.extname(localPath)
const contentType = {
'.html': 'text/html',
'.js': 'application/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.svg': 'image/svg+xml',
}[ext] || 'application/octet-stream'
const cacheControl = ext === '.html'
? 'no-cache'
: 'public, max-age=31536000, immutable'
const command = new PutObjectCommand({
Bucket: BUCKET,
Key: remotePath,
Body: fileContent,
ContentType: contentType,
CacheControl: cacheControl,
})
await s3Client.send(command)
console.log(`Uploaded: ${remotePath}`)
}
// 批量上传
async function uploadDirectory(dir) {
const files = glob.sync(`${dir}/**/*`, { nodir: true })
for (const file of files) {
const remotePath = file.replace(dir + '/', '')
await uploadFile(file, remotePath)
}
}
// 刷新CDN缓存
async function invalidateCache(paths) {
const command = new CreateInvalidationCommand({
DistributionId: DISTRIBUTION_ID,
InvalidationBatch: {
CallerReference: Date.now().toString(),
Paths: {
Quantity: paths.length,
Items: paths,
},
},
})
await cfClient.send(command)
console.log('CDN cache invalidated')
}
// 执行部署
async function deploy() {
console.log('Starting deployment...')
// 上传文件
await uploadDirectory('dist')
// 刷新关键文件缓存
await invalidateCache([
'/index.html',
'/manifest.json',
])
console.log('Deployment complete!')
console.log(`Site available at: ${CDN_PATH}`)
}
deploy().catch(console.error)5.2 GitHub Actions部署
yaml
# .github/workflows/deploy-cdn.yml
name: Deploy to CDN
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
VITE_CDN_URL: https://cdn.example.com/
- name: Deploy to S3
uses: jakejarvis/s3-sync-action@master
with:
args: --follow-symlinks --delete
env:
AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'us-east-1'
SOURCE_DIR: 'dist'
- name: Invalidate CloudFront
uses: chetan/invalidate-cloudfront-action@v2
env:
DISTRIBUTION: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
PATHS: '/*'
AWS_REGION: 'us-east-1'
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}5.3 部署配置文件
json
// package.json
{
"scripts": {
"build": "vite build",
"build:prod": "vite build --mode production",
"deploy": "npm run build:prod && node scripts/deploy-cdn.js",
"deploy:staging": "vite build --mode staging && node scripts/deploy-cdn.js --env staging"
}
}第六部分:性能优化
6.1 资源压缩
typescript
// vite.config.ts
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
// Gzip压缩
viteCompression({
algorithm: 'gzip',
ext: '.gz',
}),
// Brotli压缩
viteCompression({
algorithm: 'brotliCompress',
ext: '.br',
})
]
})6.2 图片优化
typescript
// vite.config.ts
import viteImagemin from 'vite-plugin-imagemin'
export default defineConfig({
plugins: [
viteImagemin({
gifsicle: {
optimizationLevel: 7,
},
optipng: {
optimizationLevel: 7,
},
mozjpeg: {
quality: 80,
},
pngquant: {
quality: [0.8, 0.9],
speed: 4,
},
svgo: {
plugins: [
{ name: 'removeViewBox', active: false },
{ name: 'removeEmptyAttrs', active: true },
],
},
})
]
})6.3 CDN预热
javascript
// scripts/cdn-preheat.js
// 阿里云CDN预热
const Core = require('@alicloud/pop-core')
const client = new Core({
accessKeyId: 'your-access-key-id',
accessKeySecret: 'your-access-key-secret',
endpoint: 'https://cdn.aliyuncs.com',
apiVersion: '2018-05-10'
})
async function preheatCDN(urls) {
const params = {
ObjectPath: urls.join('\n'),
Area: 'domestic' // domestic | overseas | global
}
try {
const result = await client.request('PushObjectCache', params)
console.log('CDN预热成功:', result)
} catch (error) {
console.error('CDN预热失败:', error)
}
}
// 预热重要资源
preheatCDN([
'https://cdn.example.com/js/app-abc123.js',
'https://cdn.example.com/css/main-def456.css',
'https://cdn.example.com/images/hero.jpg'
])第七部分:监控和故障处理
7.1 CDN监控
javascript
// src/utils/cdn-monitor.ts
export function monitorCDN() {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name.includes('cdn.example.com')) {
// 记录CDN资源加载性能
console.log('CDN Resource:', {
url: entry.name,
duration: entry.duration,
transferSize: entry.transferSize,
})
// 发送到监控服务
analytics.track('cdn_performance', {
url: entry.name,
duration: entry.duration,
})
}
}
})
observer.observe({ entryTypes: ['resource'] })
}
}7.2 CDN降级
typescript
// src/utils/cdn-fallback.ts
export function loadWithFallback(cdnUrl: string, fallbackUrl: string): Promise<string> {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = cdnUrl
script.onload = () => resolve(cdnUrl)
script.onerror = () => {
console.warn('CDN failed, using fallback')
// 降级到备用URL
const fallbackScript = document.createElement('script')
fallbackScript.src = fallbackUrl
fallbackScript.onload = () => resolve(fallbackUrl)
fallbackScript.onerror = () => reject(new Error('Both CDN and fallback failed'))
document.head.appendChild(fallbackScript)
}
document.head.appendChild(script)
})
}
// 使用
loadWithFallback(
'https://cdn.example.com/lib.js',
'/local/lib.js'
)7.3 多CDN策略
typescript
// src/config/cdn.ts
const CDN_PROVIDERS = [
'https://cdn1.example.com',
'https://cdn2.example.com',
'https://cdn3.example.com',
]
export function getCDNUrl(path: string): string {
// 根据用户地理位置选择CDN
const userRegion = getUserRegion()
const cdnMap: Record<string, string> = {
'asia': CDN_PROVIDERS[0],
'europe': CDN_PROVIDERS[1],
'america': CDN_PROVIDERS[2],
}
const cdn = cdnMap[userRegion] || CDN_PROVIDERS[0]
return `${cdn}/${path}`
}
function getUserRegion(): string {
// 根据IP或其他方式判断用户地区
return 'asia'
}第八部分:CDN高级优化
8.1 智能CDN选择
typescript
// 根据用户位置选择最优CDN
function selectOptimalCDN(userLocation: string): string {
const cdnEndpoints = {
'asia': 'https://asia.cdn.example.com',
'europe': 'https://eu.cdn.example.com',
'americas': 'https://us.cdn.example.com',
'oceania': 'https://au.cdn.example.com'
};
const region = getRegion(userLocation);
return cdnEndpoints[region as keyof typeof cdnEndpoints] || cdnEndpoints.americas;
}
// 自动检测最快CDN
async function detectFastestCDN(cdnList: string[]): Promise<string> {
const pingResults = await Promise.all(
cdnList.map(async (cdn) => {
const start = performance.now();
try {
await fetch(`${cdn}/health-check`, { method: 'HEAD' });
const latency = performance.now() - start;
return { cdn, latency };
} catch {
return { cdn, latency: Infinity };
}
})
);
const fastest = pingResults.sort((a, b) => a.latency - b.latency)[0];
return fastest.cdn;
}
// 使用
const cdnUrl = await detectFastestCDN([
'https://cdn1.example.com',
'https://cdn2.example.com',
'https://cdn3.example.com'
]);
window.CDN_BASE_URL = cdnUrl;8.2 CDN回源优化
nginx
# Nginx配置回源优化
upstream origin_servers {
least_conn;
server origin1.example.com:443 weight=5;
server origin2.example.com:443 weight=3;
server origin3.example.com:443 backup;
check interval=3000 rise=2 fall=3 timeout=1000;
}
server {
listen 443 ssl http2;
server_name cdn.example.com;
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
proxy_pass https://origin_servers;
proxy_cache cdn_cache;
proxy_cache_valid 200 30d;
proxy_cache_valid 404 10m;
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
proxy_cache_background_update on;
add_header X-Cache-Status $upstream_cache_status;
}
}8.3 边缘计算
typescript
// Cloudflare Workers边缘计算
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
const width = url.searchParams.get('w');
const quality = url.searchParams.get('q') || '80';
const format = url.searchParams.get('f') || 'webp';
const originalUrl = `https://origin.example.com${url.pathname}`;
const response = await fetch(originalUrl);
if (!response.ok) {
return response;
}
const options = {
cf: {
image: {
width: width ? parseInt(width) : undefined,
quality: parseInt(quality),
format: format as 'webp' | 'jpeg' | 'png'
}
}
};
return fetch(originalUrl, options);
}
};第九部分:多CDN容灾
9.1 多CDN切换
typescript
class MultiCDNManager {
private cdns = [
'https://cdn1.example.com',
'https://cdn2.example.com',
'https://cdn3.example.com'
];
private currentIndex = 0;
private failureCount: Map<string, number> = new Map();
private readonly MAX_FAILURES = 3;
getResourceUrl(path: string): string {
const cdn = this.cdns[this.currentIndex];
return `${cdn}${path}`;
}
async loadWithFallback(path: string): Promise<Response> {
for (let i = 0; i < this.cdns.length; i++) {
const cdn = this.cdns[(this.currentIndex + i) % this.cdns.length];
if ((this.failureCount.get(cdn) || 0) >= this.MAX_FAILURES) {
continue;
}
try {
const response = await fetch(`${cdn}${path}`);
if (response.ok) {
this.failureCount.set(cdn, 0);
this.currentIndex = (this.currentIndex + i) % this.cdns.length;
return response;
}
} catch (error) {
console.warn(`CDN ${cdn} failed:`, error);
this.failureCount.set(cdn, (this.failureCount.get(cdn) || 0) + 1);
}
}
throw new Error('All CDNs failed');
}
startHealthCheck() {
setInterval(async () => {
for (const cdn of this.cdns) {
try {
const response = await fetch(`${cdn}/health`, { method: 'HEAD' });
if (response.ok) {
this.failureCount.set(cdn, 0);
}
} catch {
// 健康检查失败
}
}
}, 60000);
}
}
const cdnManager = new MultiCDNManager();
cdnManager.startHealthCheck();
async function cdnFetch(path: string): Promise<Response> {
return cdnManager.loadWithFallback(path);
}9.2 P2P CDN
typescript
import PeerCDN from '@peercdn/client';
const peerCDN = new PeerCDN({
cdn: 'https://cdn.example.com',
p2pEnabled: true,
p2pRatio: 0.5,
trackerUrls: [
'wss://tracker1.example.com',
'wss://tracker2.example.com'
]
});
function loadResource(url: string) {
return peerCDN.load(url);
}
peerCDN.on('stats', (stats) => {
console.log('P2P Stats:', {
httpDownloaded: stats.httpDownloaded,
p2pDownloaded: stats.p2pDownloaded,
p2pRatio: (stats.p2pDownloaded / (stats.httpDownloaded + stats.p2pDownloaded) * 100).toFixed(2) + '%',
peers: stats.connectedPeers
});
});第十部分:CDN安全配置
10.1 防盗链配置
nginx
location ~* \.(jpg|jpeg|png|gif|mp4|mp3)$ {
valid_referers none blocked example.com *.example.com;
if ($invalid_referer) {
return 403;
}
}typescript
import crypto from 'crypto';
function generateSignedUrl(
path: string,
expiresIn: number = 3600
): string {
const expires = Math.floor(Date.now() / 1000) + expiresIn;
const secret = process.env.CDN_SECRET!;
const stringToSign = `${path}${expires}`;
const signature = crypto
.createHmac('sha256', secret)
.update(stringToSign)
.digest('hex');
return `https://cdn.example.com${path}?expires=${expires}&signature=${signature}`;
}
const imageUrl = generateSignedUrl('/images/product.jpg', 3600);10.2 DDoS防护
typescript
const cloudflare = {
security: {
ddos_protection: true,
waf: {
enabled: true,
rules: [
{
expression: '(http.request.uri.path contains "admin")',
action: 'challenge'
},
{
expression: '(cf.threat_score > 30)',
action: 'block'
}
]
},
rate_limiting: {
threshold: 100,
period: 60,
action: 'challenge'
},
bot_management: {
enabled: true,
fight_mode: true
}
}
};
const cloudfront = {
webACL: {
rules: [
{
name: 'RateLimitRule',
priority: 1,
statement: {
rateBasedStatement: {
limit: 2000,
aggregateKeyType: 'IP'
}
},
action: { block: {} }
},
{
name: 'GeoBlockRule',
priority: 2,
statement: {
geoMatchStatement: {
countryCodes: ['CN', 'RU']
}
},
action: { block: {} }
}
]
}
};第十一部分:CDN成本优化
11.1 智能缓存策略
typescript
function getCacheDuration(path: string, accessCount: number): string {
if (accessCount > 1000) {
return 'public, max-age=31536000, immutable';
}
if (accessCount > 100) {
return 'public, max-age=86400';
}
return 'public, max-age=3600';
}
function getDynamicCacheHeaders(data: any): HeadersInit {
const etag = generateETag(data);
const lastModified = new Date(data.updatedAt).toUTCString();
return {
'ETag': etag,
'Last-Modified': lastModified,
'Cache-Control': 'public, max-age=0, must-revalidate',
'CDN-Cache-Control': 'public, max-age=60'
};
}11.2 压缩优化
typescript
function getOptimalCompression(
userAgent: string,
acceptEncoding: string
): 'br' | 'gzip' | 'none' {
if (acceptEncoding.includes('br')) {
return 'br';
}
if (acceptEncoding.includes('gzip')) {
return 'gzip';
}
return 'none';
}
const compressionPlugin = {
apply(compiler) {
compiler.hooks.emit.tapAsync('CompressionPlugin', (compilation, callback) => {
Object.keys(compilation.assets).forEach(filename => {
if (filename.match(/\.(js|css|html|svg)$/)) {
const source = compilation.assets[filename].source();
const brCompressed = brotliCompressSync(source);
compilation.assets[`${filename}.br`] = {
source: () => brCompressed,
size: () => brCompressed.length
};
const gzipCompressed = gzipSync(source);
compilation.assets[`${filename}.gz`] = {
source: () => gzipCompressed,
size: () => gzipCompressed.length
};
}
});
callback();
});
}
};第十二部分:性能监控和分析
12.1 CDN性能监控
typescript
class CDNPerformanceMonitor {
private metrics: {
url: string;
loadTime: number;
transferSize: number;
cached: boolean;
}[] = [];
init() {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const resource = entry as PerformanceResourceTiming;
if (resource.name.includes('cdn.example.com')) {
this.metrics.push({
url: resource.name,
loadTime: resource.duration,
transferSize: resource.transferSize,
cached: resource.transferSize === 0
});
}
}
});
observer.observe({ entryTypes: ['resource'] });
}
}
getReport() {
const total = this.metrics.length;
const cached = this.metrics.filter(m => m.cached).length;
const avgLoadTime = this.metrics.reduce((sum, m) => sum + m.loadTime, 0) / total;
const totalTransfer = this.metrics.reduce((sum, m) => sum + m.transferSize, 0);
return {
totalRequests: total,
cachedRequests: cached,
cacheHitRate: (cached / total * 100).toFixed(2) + '%',
avgLoadTime: avgLoadTime.toFixed(2) + 'ms',
totalTransfer: (totalTransfer / 1024 / 1024).toFixed(2) + 'MB',
slowestResources: this.metrics
.sort((a, b) => b.loadTime - a.loadTime)
.slice(0, 10)
};
}
sendReport() {
const report = this.getReport();
navigator.sendBeacon('/api/cdn-performance', JSON.stringify(report));
}
}
const cdnMonitor = new CDNPerformanceMonitor();
cdnMonitor.init();
window.addEventListener('beforeunload', () => {
cdnMonitor.sendReport();
});12.2 真实用户监控(RUM)
typescript
interface CDNMetrics {
region: string;
cdn: string;
ttfb: number;
downloadTime: number;
totalTime: number;
cacheStatus: 'HIT' | 'MISS' | 'EXPIRED';
}
function collectCDNMetrics(): CDNMetrics {
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[];
const cdnResource = resources.find(r => r.name.includes('cdn.example.com'));
return {
region: getUserRegion(),
cdn: getCDNProvider(),
ttfb: navigation.responseStart - navigation.requestStart,
downloadTime: cdnResource ? cdnResource.responseEnd - cdnResource.responseStart : 0,
totalTime: cdnResource ? cdnResource.duration : 0,
cacheStatus: getCacheStatus(cdnResource)
};
}
function getCacheStatus(resource: PerformanceResourceTiming | undefined): 'HIT' | 'MISS' | 'EXPIRED' {
if (!resource) return 'MISS';
if (resource.transferSize === 0) return 'HIT';
if (resource.transferSize < resource.encodedBodySize) return 'EXPIRED';
return 'MISS';
}
window.addEventListener('load', () => {
setTimeout(() => {
const metrics = collectCDNMetrics();
fetch('https://analytics.example.com/rum', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(metrics)
});
}, 1000);
});第十三部分:实战案例
13.1 全球化电商CDN部署
typescript
const cdnConfig = {
regions: {
'asia-east': {
cdn: 'https://asia.cdn.example.com',
origins: ['origin-tokyo.example.com', 'origin-singapore.example.com'],
caching: {
static: '30d',
dynamic: '5m'
}
},
'europe': {
cdn: 'https://eu.cdn.example.com',
origins: ['origin-london.example.com', 'origin-frankfurt.example.com'],
caching: {
static: '30d',
dynamic: '5m'
}
},
'americas': {
cdn: 'https://us.cdn.example.com',
origins: ['origin-virginia.example.com', 'origin-california.example.com'],
caching: {
static: '30d',
dynamic: '5m'
}
}
},
router: (userIP: string) => {
const location = geoIP(userIP);
const region = mapToRegion(location);
return cdnConfig.regions[region];
}
};
// 性能提升:
// 亚洲用户: TTFB 500ms -> 50ms (↓90%)
// 欧洲用户: TTFB 600ms -> 60ms (↓90%)
// 美洲用户: TTFB 100ms -> 30ms (↓70%)总结
本章全面介绍了CDN配置:
- CDN基础 - 理解CDN原理和优势
- 资源配置 - 静态资源CDN部署
- CDN服务 - 主流CDN服务使用
- 缓存策略 - HTTP缓存和CDN缓存
- 自动部署 - CI/CD集成CDN部署
- 性能优化 - 压缩、预热、降级
- 监控处理 - CDN性能监控和容灾
- 高级优化 - 边缘计算、多CDN、P2P
- 安全配置 - 防盗链、DDoS防护
- 成本优化 - 智能缓存、压缩策略
- 性能监控 - RUM监控和分析
- 实战案例 - 全球化部署经验
合理使用CDN能够显著提升应用的全球访问速度。