一、CDN与源站协同的整体架构

当我们把一个网站或者应用部署到互联网上时,如果所有用户的请求都直接打到自己的服务器(也就是源站),那源站很容易就会被打爆,尤其是在高峰期。这时候就需要CDN(内容分发网络)来帮忙。CDN的作用就是把你的内容提前缓存到离用户更近的节点上,用户访问时就近获取,而不是每次都跑去源站拿数据。

但CDN并不是孤立工作的,它和源站之间需要有一套协同机制。当CDN上缓存过期了、或者用户请求的是动态内容时,CDN就需要向源站"回源"获取数据。这个过程如果调度不好,源站还是会承受巨大压力。所以我们需要一套负载均衡策略来管理这些回源请求,同时还需要容错机制来应对CDN节点故障、源站宕机等突发情况。

1.1 CDN与源站的配合关系

简单来说,CDN和源站的关系就像是"前台和后台"。CDN是前台,负责接待用户、提供缓存内容;源站是后台,负责处理复杂业务、存储原始数据。用户的请求先到达CDN节点,如果CDN上已经有缓存就直接返回,没有的话才去源站取数据。

整个过程可以理解为:用户发起请求 → CDN节点判断是否有缓存 → 有缓存直接返回 → 没有缓存则回源 → 源站返回数据 → CDN缓存数据 → 返回给用户。

# 技术栈:Nginx + Lua (OpenResty)
# 模拟CDN节点的回源判断逻辑

upstream origin_servers {
    # 配置三个源站服务器,作为后端服务池
    server 10.0.0.1:8080 weight=5;
    server 10.0.0.2:8080 weight=3;
    server 10.0.0.3:8080 weight=2;
}

server {
    listen 80;
    server_name cdn.example.com;

    location / {
        # 使用Lua脚本判断是否需要回源
        access_by_lua_block {
            local cache_key = ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.uri
            local cache = require("resty.redis").cache  -- 伪代码,表示本地缓存

            -- 先查本地缓存
            local cached = cache:get(cache_key)
            if cached then
                ngx.status = 200
                ngx.say(cached)
                ngx.exit(200)
            end

            -- 缓存未命中,继续回源
        }

        proxy_pass http://origin_servers;
    }
}

1.2 流量调度的基本流程

流量调度的核心目标就是"把合适的请求发给合适的服务器"。在CDN和源站协同的场景中,流量调度主要解决两个问题:一是CDN节点之间的流量分配,二是回源请求在多个源站之间的分配。

举个生活中的例子:一个连锁餐厅有多个分店(CDN节点)和一个中央厨房(源站)。顾客(用户请求)会去离自己最近的分店吃饭(访问CDN节点)。如果分店没有现成的菜(缓存未命中),就向中央厨房要一份(回源)。中央厨房有多个灶台(多个源站服务器),调度员(负载均衡器)需要把要菜的请求分配给最空闲的灶台。

二、负载均衡策略详解

负载均衡是流量调度的核心手段。不同的业务场景需要不同的负载均衡策略,选对了策略,系统才能高效稳定地运行。

2.1 轮询策略

轮询是最简单的策略,就像排队一样,一个接一个地分配请求。第一个请求给服务器A,第二个给服务器B,第三个给服务器C,第四个又回到服务器A,如此循环。

# 技术栈:Nginx
# 轮询策略配置示例

upstream backend_pool {
    # 三个源站服务器,Nginx默认使用轮询策略
    server 10.0.1.1:80;
    server 10.0.1.2:80;
    server 10.0.1.3:80;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend_pool;
    }
}

轮询策略的优缺点很明确:优点是简单、公平,每个服务器都能均匀处理请求;缺点是它不关心服务器的实际负载情况,如果某台服务器比较慢(比如磁盘IO高),它照样会分过去很多请求,可能导致响应变慢。

2.2 加权轮询策略

加权轮询是在轮询的基础上给每个服务器一个"权重"。权重高的服务器分到更多请求,权重低的分到更少。这就像餐厅里经验多的厨师(高性能服务器)能同时做更多菜,而新手厨师(低性能服务器)只能做几道。

# 技术栈:Nginx
# 加权轮询策略配置示例

upstream weighted_pool {
    # 根据服务器的处理能力分配权重
    server 10.0.2.1:80 weight=10 max_fails=3 fail_timeout=30s;  # 高性能服务器,权重最高
    server 10.0.2.2:80 weight=5;                                 # 中等性能服务器
    server 10.0.2.3:80 weight=3 backup;                           # 备用服务器,主服务器全挂时才使用
}

server {
    listen 80;
    location / {
        proxy_pass http://weighted_pool;
    }
}

注意上面的配置里有一个 backup 参数。标记为backup的服务器只有在所有正常服务器都不可用时才会接收请求,这是一种很好的容错设计。

2.3 IP哈希策略

IP哈希策略是根据用户IP地址来计算该请求应该发给哪台服务器。同一个用户的请求会始终被分配到同一台服务器。这个策略在需要保持会话状态的场景中非常有用,比如用户登录后需要保持session。

# 技术栈:Nginx
# IP哈希策略配置示例

upstream ip_hash_pool {
    ip_hash;  # 启用IP哈希算法

    server 10.0.3.1:80;
    server 10.0.3.2:80;
    server 10.0.3.3:80;
}

server {
    listen 80;
    location /api/ {
        # 所有API请求通过IP哈希分配到固定源站,保持会话一致性
        proxy_pass http://ip_hash_pool;
    }
}

IP哈希的问题是,如果某台服务器挂了,原来分配给它的那批用户就需要重新分配,可能导致会话丢失。所以通常会配合sticky session等机制一起使用。

2.4 最少连接数策略

最少连接数策略比较"聪明",它会实时统计每台服务器当前的活跃连接数,把新请求分配给连接数最少的服务器。这就像餐厅里,服务员会把新客人带到空桌最多的那桌。

# 技术栈:Nginx
# 最少连接数策略配置示例

upstream least_conn_pool {
    least_conn;  # 启用最少连接数算法

    server 10.0.4.1:80;
    server 10.0.4.2:80;
    server 10.0.4.3:80;
}

server {
    listen 80;
    location / {
        proxy_pass http://least_conn_pool;
    }
}

最少连接数策略特别适合处理请求耗时差异大的场景。比如有的请求只需要10毫秒,有的需要5秒,用轮询的话慢请求会把服务器占满,而最少连接数策略会自动把新请求分配给空闲的服务器。

三、流量调度机制

流量调度是负载均衡的"上层建筑"。负载均衡解决的是"怎么分配"的问题,而流量调度解决的是"什么时候分配、从哪里分配"的问题。

3.1 智能DNS调度

智能DNS是流量调度的第一步。用户访问你的域名时,DNS服务器会根据用户的地理位置、运营商等信息,返回一个离用户最近的CDN节点IP。

# 技术栈:Shell命令
# 查询DNS解析结果,观察不同地区的解析差异

# 查询当前域名解析到的CDN节点IP
dig +short cdn.example.com

# 模拟从不同地区查询(需要借助VPN或专用工具)
# 美国地区解析结果
dig +short cdn.example.com @8.8.8.8

# 检查域名解析的TTL(生存时间),TTL越短切换越快
dig cdn.example.com | grep TTL

# 使用nslookup查看完整解析信息
nslookup cdn.example.com

智能DNS的TTL值设置很关键。TTL太短的话,DNS查询压力大,服务器负载高;TTL太长的话,当CDN节点切换时,很多用户可能还在用旧的IP。一般建议设置为60-300秒之间。

3.2 全局负载均衡器(GSLB)

GSLB是在DNS之上的更智能的调度层。它不仅能根据地理位置调度,还能根据CDN节点的健康状态、负载情况、带宽成本等多维度因素来做决策。

{
  "技术栈": "GSLB配置(JSON格式示例)",
  "gslb_config": {
    "enabled": true,
    "health_check": {
      "interval": 5,
      "timeout": 3,
      "unhealthy_threshold": 3,
      "healthy_threshold": 2
    },
    "scheduling_policy": "geo_proximity",
    "regions": [
      {
        "region_id": "cn-north",
        "priority": 1,
        "nodes": [
          {"ip": "203.0.113.10", "weight": 100, "status": "active"},
          {"ip": "203.0.113.11", "weight": 80, "status": "active"}
        ]
      },
      {
        "region_id": "cn-south",
        "priority": 2,
        "nodes": [
          {"ip": "198.51.100.20", "weight": 100, "status": "active"},
          {"ip": "198.51.100.21", "weight": 90, "status": "active"}
        ]
      },
      {
        "region_id": "us-west",
        "priority": 3,
        "nodes": [
          {"ip": "192.0.2.30", "weight": 70, "status": "active"}
        ]
      }
    ],
    "fallback_policy": {
      "strategy": "nearest_healthy_node",
      "max_fallback_distance_km": 500
    }
  }
}

GSLB的健康检查是它工作的基础。它会定期探测每个CDN节点是否存活,如果某个节点连续多次检查失败,就会把它从调度池中暂时移除,等新节点恢复后再加回来。

3.3 动态调度与回源策略

CDN的回源策略决定了什么时候去源站取数据、怎么取。常见的回源策略包括:

# 技术栈:Nginx + Lua (OpenResty)
# 动态回源策略配置示例

server {
    listen 80;
    server_name api.example.com;

    # 定义回源策略:缓存优先,过期后回源
    location /static/ {
        # 静态资源:缓存时间长,回源频率低
        proxy_cache cdn_cache;
        proxy_cache_valid 200 24h;          # 正常响应缓存24小时
        proxy_cache_valid 404 10m;           # 404响应缓存10分钟(防止缓存穿透)
        proxy_cache_key "$scheme$proxy_host$request_uri";
        proxy_cache_lock on;                 # 等待回源完成,防止并发回源雪崩
        expires 1d;
        add_header Cache-Control "public, max-age=86400";
    }

    location /api/ {
        # 动态API:缓存时间短或不缓存,频繁回源
        proxy_cache api_cache;
        proxy_cache_valid 200 10s;           # API响应只缓存10秒
        proxy_cache_lock on;

        # 通过Lua实现动态回源逻辑
        access_by_lua_block {
            -- 检查用户请求是否携带缓存版本号
            local cache_version = ngx.var.arg_cache_version

            if not cache_version or cache_version == "0" then
                -- 未带版本号,允许使用缓存
                ngx.ctx.use_cache = true
            else
                -- 带了版本号,强制回源
                ngx.ctx.use_cache = false
                ngx.var.proxy_cache_bypass = 1
            end
        }

        proxy_pass http://origin_pool;
        proxy_set_header X-Cache-Version $arg_cache_version;
    }
}

四、容错机制设计

在真实的互联网环境中,故障是不可避免的。CDN节点可能宕机、源站可能崩溃、网络可能中断。没有容错机制的系统,一旦发生故障就会导致大面积服务中断。

4.1 健康检查机制

健康检查是容错机制的基础。它的作用是定期"探活",确认CDN节点和源站服务器是否正常工作。

# 技术栈:Nginx
# 源站健康检查配置示例

upstream origin_pool {
    # 每个源站配置健康检查参数
    server 10.0.5.1:8080 max_fails=3 fail_timeout=60s;  # 连续失败3次,标记为不可用60秒
    server 10.0.5.2:8080 max_fails=3 fail_timeout=60s;
    server 10.0.5.3:8080 max_fails=3 fail_timeout=60s;

    # 备用源站(当主源站全部不可用时启用)
    server 10.0.5.4:8080 backup max_fails=3 fail_timeout=60s;
}

server {
    listen 80;

    location / {
        proxy_pass http://origin_pool;

        # 设置回源超时时间,防止请求长时间挂起
        proxy_connect_timeout 5s;    # 连接超时
        proxy_read_timeout 30s;      # 读取超时
        proxy_send_timeout 15s;      # 发送超时

        # 如果主源站全部失败,自动切换到备用源站
        proxy_next_upstream error timeout http_502 http_503 http_504;
        proxy_next_upstream_tries 3;  # 最多重试3次
    }
}

除了Nginx自带的被动健康检查(基于请求失败判断),还可以配置主动健康检查:

# 技术栈:Nginx(需要NGINX Plus或第三方模块)
# 主动健康检查配置示例

upstream origin_pool {
    server 10.0.6.1:8080;
    server 10.0.6.2:8080;
    server 10.0.6.3:8080;

    # 主动健康检查:每10秒发一个HTTP请求检查源站是否存活
    health_check interval=10s timeout=3s fails=3 passes=2;
    health_check uri=/healthz;       # 检查特定的健康检查接口
    health_check method=GET;
}

4.2 故障转移与自动恢复

故障转移是指当主服务不可用时,自动切换到备用服务。自动恢复是指故障修复后,自动把流量切回正常服务。

# 技术栈:Shell脚本
# 模拟源站故障转移脚本

#!/bin/bash
# 源站故障转移脚本:监控源站状态,自动切换流量

# 配置参数
PRIMARY_ORIGIN="10.0.7.1:8080"
STANDBY_ORIGIN="10.0.7.2:8080"
CHECK_INTERVAL=5        # 检查间隔(秒)
FAIL_THRESHOLD=3        # 连续失败次数阈值
RECOVERY_THRESHOLD=5    # 恢复阈值(连续成功次数)

fail_count=0
recovery_count=0
current_origin="$PRIMARY_ORIGIN"

check_origin() {
    local origin=$1
    # 发送HEAD请求检查源站是否存活
    local http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 "http://$origin/healthz" 2>/dev/null)

    if [ "$http_code" = "200" ]; then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] $origin 状态正常 (HTTP $http_code)"
        return 0
    else
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] $origin 状态异常 (HTTP $http_code)"
        return 1
    fi
}

switch_to_standby() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ⚠️ 触发故障转移:从 $PRIMARY_ORIGIN 切换到 $STANDBY_ORIGIN"
    # 这里调用实际的DNS切换或负载均衡配置更新接口
    # 例如调用云厂商API更新DNS记录
    # curl -X PUT "https://api.cloud.com/dns/records" -d "{\"origin\": \"'$STANDBY_ORIGIN'\"}"
    current_origin="$STANDBY_ORIGIN"
    fail_count=0
}

try_recovery() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] 🔧 尝试恢复:从 $STANDBY_ORIGIN 切回 $PRIMARY_ORIGIN"
    # 执行恢复操作
    current_origin="$PRIMARY_ORIGIN"
    recovery_count=0
}

while true; do
    if [ "$current_origin" = "$PRIMARY_ORIGIN" ]; then
        if check_origin "$PRIMARY_ORIGIN"; then
            fail_count=0
        else
            fail_count=$((fail_count + 1))
            if [ "$fail_count" -ge "$FAIL_THRESHOLD" ]; then
                switch_to_standby
            fi
        fi
    else
        # 当前在备用源站,尝试恢复主源站
        if check_origin "$PRIMARY_ORIGIN"; then
            recovery_count=$((recovery_count + 1))
            if [ "$recovery_count" -ge "$RECOVERY_THRESHOLD" ]; then
                try_recovery
            fi
        else
            recovery_count=0
        fi
    fi

    sleep $CHECK_INTERVAL
done

4.3 限流与熔断

限流和熔断是保护系统的最后一道防线。当流量超出系统承受能力时,限流会拒绝多余的请求;当系统连续出现大量错误时,熔断会暂时断开与故障服务的连接,避免级联故障。

# 技术栈:Nginx + Lua (OpenResty)
# 限流与熔断配置示例

# 限流配置:基于IP的限流
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

server {
    listen 80;
    server_name api.example.com;

    location /api/ {
        # 应用限流规则:每秒10个请求
        limit_req zone=api_limit burst=20 nodelay;

        # 熔断逻辑:使用Lua实现简易熔断器
        access_by_lua_block {
            local circuit_breaker = require("resty.circuit_breaker")

            -- 获取熔断器实例(每个源站一个)
            local cb = circuit_breaker.new({
                name = "origin_breaker",
                failure_threshold = 5,     -- 连续失败5次触发熔断
                recovery_timeout = 30,     -- 熔断30秒后尝试恢复
                success_threshold = 2,     -- 连续成功2次恢复
                half_open_max_requests = 3  -- 半开状态最多允许3个探测请求
            })

            -- 检查熔断器状态
            local allowed, state = cb:is_allowed()

            if not allowed then
                -- 熔断状态,直接返回503
                ngx.status = 503
                ngx.header["X-Circuit-State"] = state
                ngx.header["Retry-After"] = cb:get_retry_after()
                ngx.say('{"error": "Service temporarily unavailable"}')
                ngx.exit(503)
            end
        }

        # 回源请求
        proxy_pass http://origin_pool;

        # 在响应后记录成功/失败状态
        body_filter_by_lua_block {
            local circuit_breaker = require("resty.circuit_breaker")
            local cb = circuit_breaker.get("origin_breaker")

            if ngx.status >= 500 then
                cb:on_failure()  -- 记录失败
            else
                cb:on_success()  -- 记录成功
            end
        }
    }
}

五、应用场景分析

了解了原理之后,我们来看看CDN与源站协同在实际业务中的典型应用场景。

5.1 电商大促场景

电商大促(比如双十一、黑五)是流量峰值最极端的场景之一。在零点瞬间,流量可能是平时的几十倍甚至上百倍。如果所有流量都打到源站,数据库和服务器会瞬间崩溃。

# 技术栈:Nginx
# 电商大促场景的CDN+源站协同配置

upstream origin_ecommerce {
    # 源站集群:处理动态请求(商品详情、下单、支付等)
    server 10.1.0.1:8080 weight=10;
    server 10.1.0.2:8080 weight=10;
    server 10.1.0.3:8080 weight=10;
    server 10.1.0.4:8080 weight=5 backup;   # 备用节点
}

server {
    listen 80;
    server_name shop.example.com;

    # 静态资源:图片、CSS、JS全部走CDN缓存
    location ~* \.(jpg|jpeg|png|gif|css|js|woff2)$ {
        proxy_cache static_cache;
        proxy_cache_valid 200 7d;              # 静态资源缓存7天
        proxy_cache_lock on;                   # 防止缓存雪崩
        expires 7d;
        add_header Cache-Control "public, max-age=604800";
        proxy_pass http://origin_ecommerce;
    }

    # 商品列表API:短时间缓存,减少回源
    location /api/products/ {
        proxy_cache api_cache;
        proxy_cache_valid 200 30s;             # 商品列表缓存30秒
        proxy_cache_lock on;
        proxy_cache_use_stale error timeout;   # 源站异常时使用过期缓存
        limit_req zone=api_limit burst=50;     # 限制API请求速率
        proxy_pass http://origin_ecommerce;
    }

    # 下单和支付接口:完全不缓存,直接回源
    location /api/order/ {
        proxy_pass http://origin_ecommerce;
        proxy_cache_bypass 1;                  # 强制回源,不使用缓存
    }
}

大促场景的关键是"分层缓存":静态资源缓存时间长,动态API缓存时间短,核心交易接口不缓存。这样既保证了用户体验,又避免了源站过载。

5.2 音视频流媒体场景

音视频流媒体的特点是流量大、带宽消耗高。CDN在这里的角色是"内容分发"——把视频内容分发到全球各地的节点,用户就近拉取,避免跨地域传输导致的高延迟和高带宽成本。

# 技术栈:Nginx
# 音视频流媒体CDN配置示例(HTTP-FLV/RTMP场景)

server {
    listen 80;
    server_name live.example.com;

    # 直播流媒体:通过HTTP代理转发
    location /live/ {
        proxy_pass http://origin_live;

        # 直播流特殊的缓存策略:几乎不缓存,但保留少量缓冲
        proxy_cache live_cache;
        proxy_cache_valid 200 2s;              # 直播流缓存2秒
        proxy_cache_lock_timeout 5s;

        # 流媒体特有配置
        proxy_http_version 1.1;
        proxy_set_header Connection "";         # 保持长连接
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;

        # 超时配置:直播流连接时间长
        proxy_read_timeout 3600s;              # 1小时超时
        proxy_send_timeout 60s;

        # 错误处理:源站断开时尝试其他节点
        proxy_next_upstream error timeout;
        proxy_next_upstream_tries 2;
    }

    # 点播视频:大文件分段缓存
    location /vod/ {
        proxy_cache vod_cache;
        proxy_cache_valid 200 30d;             # 点播视频缓存30天
        proxy_cache_lock on;
        proxy_cache_min_uses 2;                # 至少2次访问才缓存(避免冷启动)

        # 支持Range请求(视频拖动播放)
        proxy_pass http://origin_vod;
        proxy_set_header Range $http_range;
        proxy_set_header If-Range $http_if_range;

        # 大文件优化
        proxy_buffering on;
        proxy_buffer_size 8k;
        proxy_buffers 16 8k;
    }
}

# 定义流媒体源站
upstream origin_live {
    least_conn;  # 流媒体用最少连接数策略,避免单节点过载
    server 10.2.0.1:1935;
    server 10.2.0.2:1935;
    server 10.2.0.3:1935;
}

upstream origin_vod {
    weighted;
    server 10.2.1.1:8080 weight=10;
    server 10.2.1.2:8080 weight=10;
}

5.3 企业级应用加速

企业级应用(比如ERP系统、SaaS平台)的特点是用户固定、请求模式可预测、对数据一致性要求高。在这种场景下,CDN主要用于加速静态资源加载,减少应用启动时间。

# 技术栈:Nginx
# 企业级应用加速配置示例

server {
    listen 443 ssl;
    server_name app.example.com;

    ssl_certificate /etc/ssl/certs/app.crt;
    ssl_certificate_key /etc/ssl/private/app.key;

    # 前端静态资源(React/Vue等构建产物)
    location /static/ {
        proxy_cache frontend_cache;
        proxy_cache_valid 200 30d;              # 带hash的静态文件缓存30天
        proxy_cache_key "$host$request_uri$is_args$args";
        expires 30d;
        add_header Cache-Control "public, immutable";
        proxy_pass http://origin_app;
    }

    # API网关:根据用户权限和请求类型决定缓存策略
    location /api/ {
        proxy_cache api_cache;
        proxy_cache_key "$scheme$proxy_host$request_uri$http_authorization";  # 包含认证信息

        # 根据用户角色设置不同缓存时间
        proxy_cache_valid 200 5m;               # 默认缓存5分钟
        proxy_cache_bypass $http_x_no_cache;    # 特殊头强制回源

        # 企业应用需要保持会话
        proxy_set_header Cookie $http_cookie;
        proxy_set_header X-Forwarded-User $remote_user;

        proxy_pass http://origin_app;
    }

    # 文件上传:不缓存,大文件直传
    location /upload/ {
        proxy_pass http://origin_app;
        proxy_cache_bypass 1;
        client_max_body_size 100m;              # 允许100MB上传
        proxy_request_buffering off;            # 请求不缓冲,直接转发
    }
}

upstream origin_app {
    # 企业应用源站:通常部署在私有网络中
    server 172.16.0.1:8443;
    server 172.16.0.2:8443;
    server 172.16.0.3:8443 backup;
}

六、技术优缺点

CDN与源站协同的负载均衡和容错机制,在实际应用中既有很多优势,也面临一些挑战。了解这些优缺点有助于做出更合理的技术决策。

6.1 优势分析

第一,用户体验显著提升。 CDN把内容缓存到离用户更近的节点,减少了网络传输距离,页面加载速度可以快2-5倍。用户不需要等待漫长的数据传输,体验感完全不同。

第二,源站压力大幅降低。 大部分请求在CDN层就被缓存命中并返回了,不需要到达源站。据统计,配置合理的CDN可以减少源站70%-90%的流量。这意味着你可以用更少的源站服务器支撑同样规模的业务。

第三,容错能力增强。 当某个CDN节点故障时,DNS和GSLB会自动把流量切到健康节点。当某个源站宕机时,负载均衡器会自动跳过它,把请求发给正常节点。这种自动化的故障转移大大降低了人工介入的需求。

第四,带宽成本降低。 CDN把流量分散到各个节点,避免了所有流量都走源站的骨干网络。特别是跨地域、跨运营商的流量,通过CDN就近分发可以显著降低带宽成本。

6.2 劣势与挑战

第一,缓存一致性问题。 这是CDN最大的挑战之一。源站数据更新了,CDN缓存还是旧的,用户看到的可能是过期数据。解决这个问题的方法包括设置合理的TTL、提供缓存刷新接口、使用版本号机制等,但都会带来额外的复杂性和成本。

第二,配置复杂度增加。 传统的单服务器部署配置相对简单,而CDN+负载均衡+容错机制的配置复杂得多。需要管理多个节点、多种策略、各种参数。一个配置错误可能导致大面积故障。

第三,调试困难。 当问题出现时,需要判断是CDN节点的问题、源站的问题、还是网络中间环节的问题。涉及多个组件,排查起来比较困难。需要完善的日志和监控体系来支撑。

第四,成本投入。 虽然CDN能降低带宽成本,但CDN服务本身是有费用的。节点越多、缓存内容越大、回源频率越高,费用就越高。需要做好成本估算和优化。

七、注意事项

在实际部署CDN与源站协同方案时,有一些关键注意事项需要特别关注,避免踩坑。

7.1 配置避坑指南

避免缓存雪崩。 缓存雪崩是指大量缓存同时过期,导致大量请求同时回源,压垮源站。解决方法是在TTL基础上增加随机偏移量:

# 技术栈:Nginx + Lua (OpenResty)
# 缓存TTL随机偏移,防止雪崩

proxy_cache_valid 200 3600s;  # 基础TTL为1小时

# 通过Lua在响应头中设置带随机偏移的Cache-Control
header_filter_by_lua_block {
    local math_random = math.random
    -- 在基础TTL上增加0-300秒的随机偏移
    local random_offset = math_random(0, 300)
    local ttl = 3600 + random_offset
    ngx.header["Cache-Control"] = "public, max-age=" .. ttl
    ngx.header["X-TTL-Offset"] = random_offset  -- 记录偏移量,方便调试
}

注意缓存穿透问题。 缓存穿透是指请求的数据在缓存和数据库中都不存在,导致每次都打到数据库。解决方法是缓存空结果(但设置较短TTL):

# 技术栈:Nginx
# 缓存穿透防护配置

server {
    listen 80;
    location /api/ {
        proxy_cache api_cache;

        # 缓存所有响应(包括404),防止缓存穿透
        proxy_cache_valid 200 10m;
        proxy_cache_valid 404 1m;          # 404只缓存1分钟
        proxy_cache_valid any 1m;           # 其他状态码缓存1分钟

        # 缓存空结果
        proxy_cache_lock on;
        proxy_cache_use_stale error timeout updating;

        proxy_pass http://origin_pool;
    }
}

关注回源连接数。 源站如果配置了最大连接数限制,CDN同时回源过多可能导致连接被拒绝。需要在CDN侧做回源并发控制:

# 技术栈:Nginx
# 回源连接数控制配置

server {
    listen 80;

    # 限制每个源站的并发连接数
    limit_conn_zone $proxy_addr zone=origin_conn:10m;

    location / {
        # 每个源站最多50个并发连接
        limit_conn origin_conn 50;
        limit_conn_status 429;          # 超过限制返回429状态码

        proxy_pass http://origin_pool;
    }
}

7.2 性能优化建议

合理设置回源协议。 如果CDN和源站都在同一数据中心,可以使用HTTP而非HTTPS回源,减少SSL加解密的开销。如果跨地域回源,建议使用HTTPS保证数据安全:

# 技术栈:Nginx
# 回源协议优化配置

server {
    listen 443 ssl;  # CDN对用户使用HTTPS

    location / {
        # 内网回源使用HTTP,减少加密开销
        proxy_pass http://origin_pool_internal;
        # 如果需要HTTPS回源:
        # proxy_pass https://origin_pool;
        # proxy_ssl_server_name on;
        # proxy_ssl_verify off;
    }
}

启用HTTP/2和HTTP/3。 新版协议支持多路复用,可以同时发送多个请求,减少连接建立开销。在CDN层面启用后,用户体验会有明显提升。

使用HTTP Keepalive。 保持CDN到源站的长连接,减少TCP握手和TLS握手的开销:

# 技术栈:Nginx
# 回源Keepalive配置

upstream origin_pool {
    server 10.0.8.1:8080;
    server 10.0.8.2:8080;

    # 保持长连接配置
    keepalive 32;          # 每个源站最多保持32个空闲连接
    keepalive_requests 1000;  # 每个连接最多处理1000个请求
    keepalive_timeout 60s;    # 空闲连接60秒后关闭
}

server {
    listen 80;
    location / {
        proxy_pass http://origin_pool;
        proxy_http_version 1.1;            # 必须使用HTTP/1.1才能Keepalive
        proxy_set_header Connection "";     # 清空Connection头,保持连接
    }
}

八、文章总结

CDN与源站协同的负载均衡和容错机制,是现代互联网应用不可或缺的组成部分。从智能DNS调度到全局负载均衡,从多种负载均衡策略到多层次容错设计,这套机制帮助我们在保证用户体验的同时,有效保护源站、降低带宽成本、提升系统可用性。

在实际部署中,有几个核心原则值得记住:

第一,分层缓存。不同类型的资源应该设置不同的缓存策略。静态资源长时间缓存,动态API短时间缓存,核心交易接口不缓存。这样可以在缓存命中率和数据一致性之间取得平衡。

第二,渐进式容错。不要期望一种机制解决所有问题。从健康检查到故障转移,从限流到熔断,多层防护才能应对各种异常情况。每一层都有自己的职责,层层递进。

第三,可观测性。配置监控和日志是必须的。没有监控的CDN就像盲人过马路,出了问题也不知道原因。建议至少监控缓存命中率、回源率、响应时间、错误率等关键指标。

第四,灰度验证。任何配置变更都应该先在小范围验证,确认没问题再全量发布。CDN配置错误的影响是全局性的,一个小错误可能导致所有用户受影响。

技术选型方面,Nginx配合Lua脚本(OpenResty)是一个灵活且强大的组合,几乎可以满足各种复杂的调度需求。对于云原生环境,可以考虑使用Kubernetes的Ingress控制器或Service Mesh(如Istio)来管理流量。无论选择什么工具,核心原理是相通的:通过合理的负载均衡和容错机制,让系统在面对流量波动和突发故障时依然稳定可靠。

记住,没有"最好"的方案,只有"最合适"的方案。根据你自己的业务特点、用户规模、预算限制来做出选择,才是正确的做法。