Skip to content

Commit 5d768a1

Browse files
committed
ci(release): 发版后自动预热 CDN 缓存
实测发现 CDN 接入后仍慢的真因:阿里云 CDN 对大文件按分片缓存,未被请求 过的分片不在缓存中,首批用户退化为实时回源而受源站带宽限制。同一文件 同一节点(RTT 5ms)实测:已缓存分片 10.5MB/s、未缓存分片仅 106KB/s, 相差约 95 倍——预热不是可选优化,而是大文件分发的必要步骤。 - release.yml 在安装包同步至源站后调用阿里云 PushObjectCache 预热 .exe 与 .blockmap;latest.yml/json 按设计不缓存故无需预热 - 仅在 DOWNLOAD_BASE_URL 与 ALIYUN_ACCESS_KEY_ID 均配置时执行, 未配置则跳过;continue-on-error 确保预热失败不推翻已成功的发布 - 元数据步骤加 id/output 复用解析出的文件名,避免重复解析 latest.yml - 技术方案文档补充预热章节与诊断提示(勿只看 X-Cache: HIT 下结论, 文件头部分片可能已 HIT 而中段仍需回源)
1 parent 286e36d commit 5d768a1

2 files changed

Lines changed: 82 additions & 5 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ jobs:
104104
# latest.json 供官网下载页动态展示版本/直链;latest.yml 供 electron-updater
105105
# ---------------------------------------------------------------
106106
- name: Generate latest.json for website
107+
id: meta
107108
run: |
108109
YML=release-windows-latest/latest.yml
109110
VERSION=$(grep -m1 '^version:' "$YML" | awk '{print $2}')
@@ -114,6 +115,8 @@ jobs:
114115
printf '{"version":"%s","fileName":"%s","size":%s,"sha256":"%s","releaseDate":"%s"}\n' \
115116
"$VERSION" "$FILE" "$SIZE" "$SHA256" "$DATE" > release-windows-latest/latest.json
116117
cat release-windows-latest/latest.json
118+
# 供下方 CDN 预热步骤拼接 URL
119+
echo "file=$FILE" >> "$GITHUB_OUTPUT"
117120
118121
- name: Ensure downloads directory on server
119122
uses: appleboy/ssh-action@v1
@@ -157,3 +160,37 @@ jobs:
157160
ls -t -- *.exe 2>/dev/null | tail -n +4 | while read -r f; do
158161
rm -f -- "$f" "$f.blockmap"
159162
done
163+
164+
# ---------------------------------------------------------------
165+
# CDN 预热:大文件在 CDN 上按分片缓存,未被请求过的分片不在缓存中,
166+
# 首批用户会退化为实时回源而受源站带宽限制。
167+
# 实测:已缓存分片 10.5MB/s,未缓存分片仅 106KB/s(相差约 95 倍),
168+
# 故预热不是可选优化而是大文件分发的必要步骤。
169+
# 预热为异步任务,API 返回后后台继续拉取,此处不等待完成。
170+
# ---------------------------------------------------------------
171+
- name: Prefetch CDN cache
172+
# 仅在已切换至 CDN 且配置了预热凭证时执行;未配置则跳过
173+
if: ${{ secrets.DOWNLOAD_BASE_URL != '' && secrets.ALIYUN_ACCESS_KEY_ID != '' }}
174+
# 预热失败不应推翻已成功的发布,仅标记警告
175+
continue-on-error: true
176+
env:
177+
ALIBABA_CLOUD_ACCESS_KEY_ID: ${{ secrets.ALIYUN_ACCESS_KEY_ID }}
178+
ALIBABA_CLOUD_ACCESS_KEY_SECRET: ${{ secrets.ALIYUN_ACCESS_KEY_SECRET }}
179+
DOWNLOAD_BASE_URL: ${{ secrets.DOWNLOAD_BASE_URL }}
180+
FILE_NAME: ${{ steps.meta.outputs.file }}
181+
run: |
182+
set -eo pipefail
183+
curl -sL https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz -o /tmp/aliyun.tgz
184+
tar -xzf /tmp/aliyun.tgz -C /tmp
185+
186+
BASE="${DOWNLOAD_BASE_URL%/}"
187+
# 预热安装包与 blockmap(差量更新依赖);
188+
# latest.yml/json 按设计不缓存,且仅几百字节,无需预热
189+
OBJECTS="$BASE/$FILE_NAME"$'\n'"$BASE/$FILE_NAME.blockmap"
190+
echo "Prefetching:"
191+
echo "$OBJECTS"
192+
193+
/tmp/aliyun cdn PushObjectCache \
194+
--region cn-hangzhou \
195+
--ObjectPath "$OBJECTS" \
196+
--Area domestic

‎docs/knowledge/solutions/2026-07-installer-cdn-distribution.md‎

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,50 @@ const DOWNLOAD_BASE = (
9191

9292
1. 阿里云 CDN 添加加速域名,如 `dl.entropydecrease.com`;
9393
2. **业务类型**选「大文件下载加速」;
94-
3. **源站**填 ECS 公网 IP,**回源 Host 填 `entropydecrease.com`**——这样命中 nginx 现有 `server_name`,**无需改动 nginx 配置,也无需为子域申请证书**(CDN 侧可免费签发 HTTPS 证书);
95-
4. 缓存规则:
94+
3. **源站**填 ECS 公网 IP,**端口必须选 443(不能选 80)**——nginx 的 80 端口是纯 `return 301` 重定向,回源到 80 只会拿到重定向而非文件内容;
95+
4. **回源 Host 填 `entropydecrease.com`**——命中 nginx 现有 `server_name`,**无需改动 nginx 配置,也无需为子域申请证书**(CDN 侧可免费签发 HTTPS 证书,并以回源 Host 作 SNI);
96+
5. 缓存规则:
9697
- `.exe` / `.blockmap` → 长缓存(如 30 天,文件名含版本号,天然唯一)
9798
- `.yml` / `.json` → **不缓存**(更新元数据必须实时;源站已设 `Cache-Control: no-cache`)
98-
5. DNS 添加 CNAME 指向 CDN 提供的地址;
99-
6. 在 GitHub 仓库 Secrets 添加 `DOWNLOAD_BASE_URL = https://dl.entropydecrease.com`;
100-
7. 触发官网部署与一次发版,验证生效。
99+
6. DNS 添加 CNAME 指向 CDN 提供的地址;
100+
7. 在 GitHub 仓库 Secrets 添加
101+
`DOWNLOAD_BASE_URL = https://dl.entropydecrease.com/downloads`
102+
⚠️ **必须带 `/downloads` 路径**:安装包位于 nginx 的 `/downloads/` 下,而前端与
103+
electron-updater 均以 `{BASE}/文件名` 拼接。若 BASE 不带该路径,回源会变成
104+
`/文件名`,nginx 会去官网根目录寻找而返回 404。
105+
8. 触发官网部署与一次发版,验证生效。
106+
107+
## 预热:大文件分发的必要步骤(非可选优化)
108+
109+
阿里云 CDN 对大文件采用**分片缓存**,只有被请求过的分片才进入缓存。未预热时,首批用户会退化为**实时回源**,速度被源站带宽拖垮——此时 CDN 几乎毫无效果。
110+
111+
**实测对比(同一文件、同一节点、RTT 5ms)**:
112+
113+
| 分片状态 | 速率 | 172MB 耗时 |
114+
|---------|------|-----------|
115+
| 已缓存(HIT) | **10.5 MB/s** | **约 16 秒** |
116+
| 未缓存(回源) | 106 KB/s | 约 27 分钟 |
117+
118+
相差约 **95 倍**。因此每次发版必须预热新版本,否则“接了 CDN 却仍然很慢”。
119+
120+
### 已在 CI 中自动化
121+
122+
`release.yml` 在同步安装包至源站后调用阿里云 `PushObjectCache` 接口预热:
123+
124+
```yaml
125+
- name: Prefetch CDN cache
126+
if: ${{ secrets.DOWNLOAD_BASE_URL != '' && secrets.ALIYUN_ACCESS_KEY_ID != '' }}
127+
continue-on-error: true # 预热失败不推翻已成功的发布
128+
run: |
129+
/tmp/aliyun cdn PushObjectCache --region cn-hangzhou \
130+
--ObjectPath "$OBJECTS" --Area domestic
131+
```
132+
133+
要点:
134+
- 预热对象仅需 `.exe` 与 `.blockmap`;`latest.yml/json` 按设计不缓存且仅几百字节,无需预热。
135+
- 预热是**异步任务**,API 返回后后台继续拉取,CI 不等待完成。
136+
- 预热会从源站回源拉取全量文件,**会占用 ECS 带宽**,可能短暂影响 API;属一次性行为。
137+
- 需额外两个 Secrets:`ALIYUN_ACCESS_KEY_ID` / `ALIYUN_ACCESS_KEY_SECRET`,建议专用 RAM 用户仅授予 CDN 刷新预热权限。
101138

102139
## 优缺点
103140

@@ -122,6 +159,9 @@ OSS/存储侧可忽略(保留 3 个版本约 0.5GB)。流量稳定后可购
122159

123160
## 注意事项
124161

162+
- **不预热等于 CDN 白接**:大文件按分片缓存,未预热时首批用户实时回源,速度与直连源站无异(实测 106KB/s vs 已缓存 10.5MB/s)。诊断时切勿只看 `X-Cache: HIT` 就下结论——文件头部分片可能已 HIT 而中段仍需回源,应分别测量不同偏移量的 Range 速率。
163+
- **采购防踩坑**:本方案**不需要 OSS**,切勿购买「OSS 资源包」(尤其是「标准-同城冗余存储」容量包,与下载速度无关)。若要预付,应选「CDN/全站加速/ESA 资源包 → CDN/DCDN 下行流量 → 全国通用(中国内地)」。资源包**额度可叠加**,故先买最小规格、用完再加即可,无需预估准确;资源包过期不退。
164+
- **买包不等于费用封顶**:资源包用完后会**自动转为按量付费**,故费用预警与防盗链仍属必选。
125165
- **必须先设防再上线**:费用预算提醒 + 资源用量预警 + Referer 防盗链 + 单 IP 频次限制;可选用量封顶。公开大文件按量计费若被恶意刷取,账单会飙升。
126166
- **`latest.yml` 绝不可被 CDN 缓存**:否则客户端长期读到旧版本,表现为「明明发布了新版却检测不到更新」。
127167
- **存量客户端更新源已固化**:已安装版本的 `app-update.yml` 指向旧地址,故切换 CDN 后 **ECS 与 GitHub 两侧资产仍需继续发布**,不可下线。

0 commit comments

Comments
 (0)