简单讲一下笔者为 EdgeCOS 亲手设计的CDN下载架构。

这个架构差不多肝了3-4天,感谢给我提供灵感的朋友们。由于方案确实有些许复杂,我就写篇文章来记录一下!这个架构推翻重来过好几次,目前这个就是 prod 正在跑的版本。

一、用户侧初始请求

用户端向业务后端发起一个带假地址的请求,形如 https://user.edgecos.com/f/[linkCode]。业务后端收到后,首先校验该用户(通过 uid)的剩余流量是否足以支撑本次下载。若流量不足,则直接返回 403;若充足,则业务后端生成一条 EdgeCOS Signature V1 签名的 CDN URL,同时从对象存储或元数据中获取该文件的实际文件名,并按照 RFC 5987 规范编码后放入 filename 参数。

签名过程采用 EdgeCOS Signature V1 规则,参与签名的参数仅包含:e-sign-algorithm=hmac-sha256e-uide-request-ide-expirese-signature。其中 e-signature 由上述参数结合后端存储的 CDN 私钥计算 HMAC-SHA256 得到。filename 不参与签名。签名有效期为 300 秒,通过 e-expires 字段控制。生成完整 URL 后,业务后端返回 302 重定向,将用户重定向到 CDN URL,触发浏览器的原生下载。

二、CDN 节点接收请求并进行远程鉴权

用户跟随 302 跳转访问 CDN 节点,CDN 节点不自行处理鉴权,而是将请求中的 Host、原始 URL 及所有查询参数原样转发至远程鉴权服务器。此时 CDN 节点本身不解析或修改参数,仅透传。

三、远程鉴权服务器校验逻辑

远程鉴权服务器收到转发请求后,依次执行三项校验:

  1. 参数完整性校验:检查 URL 中是否包含 e-sign-algorithme-uide-request-ide-expirese-signature 五个必需参数。若缺少任意一项,立即返回 403,鉴权不通过。
  2. 签名一致性校验:取 URL 中除 e-signature 外的其余四个参数,结合本地存储的 CDN 私钥,重新计算 HMAC-SHA256 签名,并与请求携带的 e-signature 值做比对。若不一致,视为签名被篡改,返回 403。
  3. 过期时间校验:取 e-expires 字段值(时间戳),与服务器当前时间比较。若当前时间已超过该过期时间,则判定签名失效,返回 403。

三项校验全部通过后,进入流量预扣阶段。

四、流量预扣与记录

鉴权服务器根据 e-uid 查询该用户的剩余流量额度,并将其与本次请求的文件大小(通常可从 CDN 节点转发的参数或回源时获取,此处假设鉴权时已知文件大小)进行比较:

· 若剩余流量小于文件大小,则返回 403,拒绝本次请求。
· 若剩余流量充足,则按文件大小冻结用户相应流量(即预扣),同时将本次请求的 e-request-id、uid、预扣流量大小写入 PostgreSQL 数据库。此处不使用 Redis 的过期重命名机制,原因在于 Redis 中若对 key 进行重命名并设置过期,会导致对账阶段无法通过 e-request-id 追溯原始记录,进而产生数据不一致的严重问题。

允许不同请求携带相同的 e-request-id,以兼容 IDM 等多线程下载工具或视频拖拽产生的并发或重复请求场景,此时数据库需按 (request_id, uid) 组合记录多笔预扣明细。

预扣完成后,鉴权服务器向 CDN 节点返回 HTTP 200 OK,表示放行。

五、CDN 节点放行与响应

CDN 节点收到 200 响应后,允许当前请求继续向下处理。随后,CDN 节点从请求 URL 中提取 filename 参数的值,将其拼入响应头 Content-Disposition: attachment; filename=... 中,以此告知浏览器以附件形式下载。

接着,CDN 节点处理数据响应:

· 若节点本地未缓存该文件,则向对象存储发起回源请求,回源时不携带任何查询参数(包括 filename 及其他鉴权参数),仅使用路径获取原始文件内容。(不然会SignatureDoesNotMatch)
· 若节点本地已有缓存,则直接返回缓存内容。缓存键(CacheKey)设计上忽略所有查询参数,仅以路径为基础,确保不同签名参数或 filename 变化不会产生重复缓存。

六、请求结束后的日志推送与对账

CDN 节点在完成本次请求(无论成功或失败)后,将实时生成的访问日志(包含状态码、实际传输流量、e-request-id 等字段)以 HTTP 方式推送给远程日志服务器。

后端只要接收到了实时日志,首先通过状态码 200 筛选出鉴权通过且正常放行的请求。对于每一条有效日志,使用其 e-request-id 去 PostgreSQL 中查询对应的预扣记录,然后进行流量对账:

· 若日志中的实际传输流量 等于 预扣流量,说明用户完整下载了文件(可能存在部分超额,但实际场景中通常不会超过预扣值),此时不做退流量处理,预扣部分全部扣除。
· 若实际传输流量 小于 预扣流量,说明用户未下载完整文件(例如中途取消或网络中断),则需解冻超出部分,即将“预扣流量 − 实际流量”的差额返还至该用户的剩余流量池。
· 若出现实际流量 大于 预扣流量(理论上不会发生),数据库需单独记录该异常的 request_id 和 uid,以供人工核查或后续补偿处理。

至此,整个请求链路就结束了。该方案通过请求开始前先进行预扣、请求结束后返还多扣的流量,确保了我不会被刷流量到破产,也能提供令人惊叹的30s内延迟,给用户一个很好的体验。

最后修改:2026 年 07 月 08 日
如果觉得我的文章对你有用,请随意赞赏!