上一篇 下一篇 分享链接 返回 返回顶部

如何在香港服务器上搭建跨境电商平台,用 Nginx + 缓存加速提升海外买家访问体验

发布人:Minchunlin 发布时间:2025-09-10 10:56 阅读量:736


周五晚上 10 点,我们的跨境电商站要在周末做一次东南亚 + 拉美的联合促销。下午的预压里,我看着巴西和智利的买家加载首页要 5~7 秒,结账页更是卡在 3 秒以上,眼看着 A/B 组转化差了 18%——原因很简单:跨海链路抖动 + 源站没做“适配跨境”的缓存策略。

我当场拍板:不再只做“静态资源 CDN + 源站直出”那一套,而是在香港源站前面加上 Nginx 反向代理 + 分层缓存 + 微缓存,把源站变成“Origin Shield”,让全世界的边缘(或者没有边缘时的直连买家)都更稳更快。下面是我在机房那一夜的完整笔记。

场景与目标

  • 场景:电商前台(Next.js/Node 或 PHP)+ 后台 API(Go/Java/PHP)+ MySQL/Redis,源站部署在 香港(CN/HK/SEA/ANZ/美西/拉美都要访问)。
  • 痛点:跨洋 RTT 高、链路波动大;动态页面占比高;源站计算昂贵;静态资源已上 CDN 但命中率有限。

目标:

  • 首页/类目/商品详情实现 < 2 秒首屏(海外主流地区);
  • 结账等敏感页面保持一致性与正确性;
  • 高并发下源站稳定,避免“回源风暴”;
  • 方案可回滚、可观测、可扩展。

拓扑与分层缓存设计

[海外买家] ──(公网/或CDN)──> [HK Nginx 反代+缓存层] ──> [应用集群]
                                                      ├── PHP-FPM/Node
                                                      ├── API(Go/Java)
                                                      ├── MySQL/Redis

策略关键词:

  • 静态:强缓存 + 指纹化(1 年),Brotli/Gzip 压缩。
  • 动态:微缓存 1~5 秒(列表/详情),严格绕过缓存(购物车、结账、用户态接口)。
  • 回源保护:cache lock、stale-while-revalidate、use_stale on error/timeout、限流。
  • 键设计:按 Host + URI + 语言/货币 + 设备类型 + 参数白名单。
  • 观测:$upstream_cache_status、RTT、TTFB、命中率。

现场硬件与系统参数

硬件(HK 机柜内)

角色 型号/CPU 内存 存储 网卡 备注
Nginx 缓存层 x2 Xeon Silver 4314 64 GB 2 x 1.92TB NVMe (RAID1) + 64GB tmpfs 2 x 10GbE Keepalived 做 VIP
应用 x4 AMD EPYC 7313P 128 GB 2 x NVMe 1.92TB 2 x 10GbE Node/PHP/Java
DB/Cache EPYC 7443P 256 GB 4 x NVMe 2 x 25GbE MySQL/Redis

说明:热数据放 tmpfs(命中极热 Key),温数据走 NVMe,长尾回源。

操作系统与版本

组件 版本/来源 关键能力
CentOS 7 最小化安装 兼容老业务(用户曾要求用 7)
Nginx 1.24+(nginx.org repo) 链接 OpenSSL 1.1.1 TLS1.3、http_v2、动态模块
OpenSSL 1.1.1 Nginx 官方包自带 TLSv1.3/OCSP Stapling
ngx_brotli(可选) 动态模块编译 Brotli 压缩
Keepalived VRRP 双机漂移
GoAccess/Promtail 日志/指标 可视化与告警

预热数据与“前后对比”

样本数据(不同网络会有差异;仅作参考)

地区 优化前首页 TTFB 优化后首页 TTFB 优化前首屏 优化后首屏
新加坡 420 ms 120 ms 1.9 s 1.1 s
东京 380 ms 110 ms 1.7 s 1.0 s
悉尼 520 ms 180 ms 2.2 s 1.3 s
洛杉矶 680 ms 220 ms 2.6 s 1.5 s
圣保罗 950 ms 310 ms 3.4 s 1.9 s

步骤 1|系统与内核调优(CentOS 7)

文件句柄与 backlog:

# /etc/security/limits.conf
* soft nofile 1048576
* hard nofile 1048576
# /etc/sysctl.d/99-tuning.conf
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 250000
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
# 可选:TFO(内核支持情况下)
net.ipv4.tcp_fastopen = 3

BBR 提示:CentOS 7 默认内核不带 BBR,如需启用建议用 ELRepo 新内核(谨慎评估变更窗口)。

步骤 2|安装 Nginx(开启 http_v2/TLS1.3/Brotli)

cat >/etc/yum.repos.d/nginx.repo <<'EOF'
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
EOF

yum install -y nginx
# 可选:编译并加载动态 ngx_brotli 模块(略,按环境而定)

systemd:

systemctl enable nginx
systemctl start nginx

步骤 3|缓存目录与分层(tmpfs + NVMe)

# 极热缓存(1~5 分钟):内存 tmpfs
mkdir -p /dev/shm/ngcache_hot
chmod 700 /dev/shm/ngcache_hot

# 常温缓存:NVMe
mkdir -p /data/ngcache_warm/{meta,body}
chown -R nginx:nginx /data/ngcache_warm

步骤 4|Nginx 核心配置(/etc/nginx/nginx.conf)

user  nginx;
worker_processes auto;
worker_rlimit_nofile 1048576;

events {
    use epoll;
    worker_connections 65535;
    multi_accept on;
    accept_mutex off;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;
    keepalive_timeout  65;
    keepalive_requests 10000;

    # 压缩
    gzip on;
    gzip_comp_level 6;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript application/xml+rss image/svg+xml;
    # 可选:Brotli(装了模块才生效)
    # brotli on;
    # brotli_comp_level 5;
    # brotli_types text/plain text/css application/json application/javascript application/xml+rss image/svg+xml;

    # 日志:记录缓存状态
    log_format  main  '$remote_addr - $host [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" "$http_user_agent" '
                      'rt=$request_time urt=$upstream_response_time '
                      'u_status=$upstream_status cache=$upstream_cache_status';

    access_log  /var/log/nginx/access.log  main;

    # 缓存键:按语言/货币/设备拆分
    map $http_accept_language $lang {
        default en; ~*zh zh; ~*es es; ~*pt pt; ~*ja ja; ~*ko ko;
    }
    map $cookie_currency $currency { default USD; ~*CNY CNY; ~*JPY JPY; ~*BRL BRL; }
    map $http_user_agent $device {
        ~*mobile|iphone|android mobile;
        default desktop;
    }

    # 会话判断(示例:有登录/购物车 Cookie 则绕过缓存)
    map $http_cookie $bypass_cache {
        default 0;
        ~*"(session|cart|sid|auth|token)" 1;
    }

    # 缓存区(热缓存在内存,常温在 NVMe)
    proxy_cache_path /dev/shm/ngcache_hot levels=1:2 keys_zone=HOT:128m max_size=48g inactive=10m use_temp_path=off;
    proxy_cache_path /data/ngcache_warm levels=1:2 keys_zone=WARM:1g max_size=1200g inactive=1h use_temp_path=off;

    # 避免回源风暴
    proxy_cache_lock on;
    proxy_cache_lock_timeout 10s;
    proxy_cache_revalidate on;

    # 上游连接池
    upstream app_pool {
        zone upstream_zone 64k;
        server 10.0.0.11:8080 max_fails=3 fail_timeout=10s;
        server 10.0.0.12:8080 max_fails=3 fail_timeout=10s;
        keepalive 256;
    }

    # HTTPS 终止(TLS1.3)
    server {
        listen 443 ssl http2 reuseport;
        server_name shop.example.com;

        ssl_certificate     /etc/nginx/ssl/fullchain.pem;
        ssl_certificate_key /etc/nginx/ssl/privkey.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:50m;
        ssl_session_timeout 1d;
        ssl_stapling on; ssl_stapling_verify on;

        # 静态资源:强缓存
        location ~* \.(css|js|woff2?|ttf|otf|svg|gif|png|jpg|jpeg|webp|avif)$ {
            expires 365d;
            add_header Cache-Control "public, max-age=31536000, immutable";
            # 一般直接走 CDN;若仍经由源站,也可用 proxy_cache 做二级缓存
            proxy_pass http://app_pool;
            proxy_set_header Host $host;
            proxy_cache WARM;
            proxy_cache_key "$scheme://$host$uri?ver=$arg_v";
            proxy_cache_valid 200 206 301 302 365d;
            add_header X-Cache-Status $upstream_cache_status always;
        }

        # 动态页面:微缓存(列表/详情)
        location / {
            proxy_pass http://app_pool;
            proxy_set_header Host $host;
            proxy_http_version 1.1;
            proxy_set_header Connection "";

            # 缓存选择:登录/购物车绕过;其他命中
            proxy_no_cache $bypass_cache;
            proxy_cache_bypass $bypass_cache;

            # 分层:极热->HOT,次热->WARM
            proxy_cache HOT;
            proxy_cache_background_update on;
            proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;

            # Key 设计:语言/货币/设备/参数白名单
            set $cache_key "$scheme://$host$uri|l=$lang|c=$currency|d=$device|q=$arg_page,$arg_sort,$arg_filter";
            proxy_cache_key $cache_key;

            # 微缓存时长(动态)
            proxy_cache_valid 200 301 10s;
            proxy_cache_valid 404 1s;

            add_header X-Cache-Status $upstream_cache_status always;
            add_header Vary "Accept-Encoding, Accept-Language, User-Agent, Cookie" always;

            # 超时
            proxy_connect_timeout 3s;
            proxy_send_timeout 30s;
            proxy_read_timeout 30s;
        }

        # 结账、用户态 API:强制绕过
        location ^~ /checkout/ {
            proxy_pass http://app_pool;
            proxy_set_header Host $host;
            proxy_no_cache 1;
            proxy_cache_bypass 1;
            add_header X-Cache-Status BYPASS always;
        }
        location ^~ /api/ {
            proxy_pass http://app_pool;
            proxy_set_header Host $host;
            proxy_no_cache 1;
            proxy_cache_bypass 1;
            add_header X-Cache-Status BYPASS always;
        }
    }

    # HTTP -> HTTPS
    server {
        listen 80 reuseport;
        server_name shop.example.com;
        return 301 https://$host$request_uri;
    }
}

要点

  • proxy_cache_lock 抑制“回源风暴”。
  • proxy_cache_background_update 让用户继续拿旧副本,后台悄悄更新。
  • proxy_cache_use_stale 在上游抖动时兜底。
  • 利用 $bypass_cache 精准绕过用户态。
  • cache_key 控制“语言/币种/设备/参数”维度,既保证命中,又避免串缓存。

步骤 5|图片与自适应(可选)

如果业务图片多、体积大,建议上游引入图服务(如 imgproxy/thumbor),在 Nginx 前端做二级缓存:

location /img/ {
    proxy_pass http://img_service;
    proxy_cache WARM;
    proxy_cache_key "$scheme://$host$uri|w=$arg_w|q=$arg_q|fmt=$arg_fmt";
    proxy_cache_valid 200 7d;
    add_header Cache-Control "public, max-age=604800";
    add_header X-Cache-Status $upstream_cache_status always;
}

前端以 ?w=&,fmt=webp/avif 请求,Nginx 缓住不同变体。

步骤 6|清缓存与回滚

6.1 PURGE(有条件)

生产上我尽量避免第三方模块,但如果必须做精准清理,可编译 ngx_cache_purge,或以“版本号参数”替代(推荐):

  • 推荐:静态资源文件名指纹(app.abcd1234.js)或 ?v=timestamp,不需要 PURGE,发版即“新 Key”。
  • 动态页面:发布后将影响页的 版本号写入白名单参数(如 ?ver=20250910),由 Nginx proxy_cache_key 携带。

6.2 回滚

Nginx 配置用 原子切换(/etc/nginx/conf.d + symlink),nginx -t && nginx -s reload。

观察命中率、500/timeout、RTT,异常则回滚上一个 symlink。

步骤 7|防御与限流

# 限制爬虫/恶请求
map $http_user_agent $is_badbot {
    default 0;
    ~*(crawler|spider|scrapy|curl) 1;
}
limit_req_zone $binary_remote_addr zone=req_perip:20m rate=10r/s;

server {
    # ...
    location / {
        if ($is_badbot) { return 429; }
        limit_req zone=req_perip burst=100 nodelay;
        # ...
    }
}

步骤 8|观测与日志

关键字段:$upstream_cache_status(MISS/HIT/BYPASS/STALE/UPDATING)、rt/urt/u_status。

GoAccess 快速看命中率与慢请求;Promtail/Tempo/Loki + Grafana 做看板。

建议监控:缓存命中率、TTFB、后端 5xx、回源 QPS、锁等待/超时。

真实坑位 & 现场解决

结账页被缓存
症状:个别买家看到别人的购物车。
解决:增加 bypass_cache 规则,匹配 session/cart/auth Cookie;关键页面设置 Cache-Control: private, no-store 并在 Nginx 中 proxy_no_cache 1。

多语言串缓存
症状:西语买家看到英文。
解决:cache_key 引入 $lang;同时保证应用对 Accept-Language 的解析稳定(避免随机化)。

币种切换不生效
症状:BRL 被缓存成 USD。
解决:取 $cookie_currency 进入 Key;对于未设置的用户,以 Geo/IP 决定默认,落 Cookie。

参数爆炸导致命中低
症状:?utm_* 导致每个 URL 都是新 Key。
解决:参数白名单入 Key(page/sort/filter/ver),其他参数全部忽略;或 proxy_ignore_headers 配合。

CDN 与源站缓存冲突
症状:CDN HIT 但源站 MISS,或相反。
解决:让 CDN 以源站 Cache-Control 为准;源站作为 Origin Shield,时长稍长于 CDN;对比 Age 与 X-Cache-Status 校准。

回源风暴
症状:节点重启后某热点详情页 QPS 飙升打爆应用。
解决:proxy_cache_lock on + 热点页预热(脚本批量 curl),并设置 use_stale updating。

TLS1.3 在 CentOS 7
症状:老 OpenSSL 不支持。
解决:使用 nginx.org 官方包(已链接 1.1.1);如自编译,务必链至 1.1.1。

压测与验证

工具与命令

静态/动态:wrk、h2load(HTTP/2)。

示例:

# HTTP/2
h2load -n 20000 -c 200 -m 100 https://shop.example.com/collection/sale

# HTTP/1.1
wrk -t8 -c200 -d60s --latency https://shop.example.com/product/12345

结果样本(优化前 vs 优化后)

指标 优化前 优化后
平均 TTFB(SG) 420 ms 120 ms
99 线 TTFB(SG) 980 ms 260 ms
源站 QPS 峰值 5.2k 1.3k(命中率上升)
缓存命中率(首页/类目) 35% 78%
结账页错误率 0.4% 0.12%(绕缓存后稳定)

FAQ|几个常被问到的决策

要不要上 HTTP/3?
如果前面有全球 CDN,CDN 负责 H3 即可;源站用 H2 足够。若直连海外且链路抖动大,可评估 Nginx QUIC 分支,但 上线成本高。

Nginx 和 Varnish 选哪个?
我偏向 Nginx 一体化(TLS 终止 + 反代 + 缓存 + 压缩),维护面更小;极限场景 Varnish 也非常强,但要多一层 TLS 代理。

微缓存是否会“缓存错内容”?
核心在 Key 设计 + 绕过规则。把“用户态、价格、库存、个性化”一律绕过或缩短 TTL,列表/详情的“无状态部分”微缓存 5~10 秒,一般就够了。

收尾|凌晨 3 点的日志

夜里 3 点,我在机房的走道坐下,看着面前的 GoAccess 仪表盘:HIT 从 30% 爬到了 75%,圣保罗用户的首页 TTFB 从 900 多毫秒落到 300 出头。促销页面刚刚推送完新版本,参数版本号一生效,缓存里“旧世界”立刻退场——没有清理命令,没有全站刷新,只有冷静的 Key 变更。

我合上笔记本,给同事发了条消息:“跨境电商不是只拼 CDN,源站也要有脑子。”Nginx + 分层缓存,把香港这台“心脏”变成了更强的 Origin Shield。等周一复盘时,运营把转化的曲线放出来,所有人都笑了——而我只想回机房把那台风扇调小一点。

可复用清单(Checklist)

  •  CentOS 7 基础调优(limits/sysctl/TFO)。
  •  Nginx 1.24+(TLS1.3、http2、Brotli 可选)。
  •  分层缓存:HOT=tmpfs、WARM=NVMe。
  •  proxy_cache_lock / use_stale / background_update。
  •  Key 设计:Host+URI+语言+币种+设备+参数白名单。
  •  绕过规则:Session/Cart/Auth/API。
  •  静态资源强缓存 + 指纹化,以版本号驱动刷新。
  •  观测:命中率、TTFB、回源 QPS、5xx。
  •  回滚与灰度:symlink + nginx -t && reload。
  •  压测校验:wrk/h2load,对比前后数据。
目录结构
全文