必须用 redis + lua 做包级时间窗口限流,因 cdn 回源致 ip 失真、静态路径不触发 access_by_lua、限流目标为包而非 ip,故 limit_req_zone 无效;需按 package_name 或 dist_hash 构建 key,结合 resty.limit.count 原子计数与 ttl,并设计 redis 不可用时的本地降级及业务感知策略。

必须用 Redis + Lua 做包级时间窗口限流,单机 limit_req 在 Composer 镜像场景下完全失效——它既无法跨节点统计,又因 CDN 回源导致 IP 失真,更关键的是,静态资源路径(如 /d/[hash].tar)根本不进 access_by_lua 阶段,压根不触发限流逻辑。
为什么 limit_req_zone 对 Composer 镜像无效
Composer 客户端请求高度集中于三个语义化路径:/packages.json、/p/[vendor]/[package].json、/d/[hash].tar。这些路径携带明确的业务标识,但限流目标不是“防 IP 爬虫”,而是防止某个热门包(如 laravel/framework)被 CI 流水线反复拉取压垮后端存储。
-
$binary_remote_addr在 CDN 回源或负载均衡后已不是真实客户端 IP,计数分散 -
$http_x_forwarded_for可被伪造,不适合作为限流依据 -
/d/xxx.tar这类静态资源由 Nginx 的static模块直接处理,access_by_lua不执行,limit_req彻底失活 - 限频维度必须是
package_name或dist_hash,而非 IP 或全局 QPS
用 resty.limit.count 构造包级限流 key
避免手写 INCR+EXPIRE 组合带来的竞态和过期不一致问题。resty.limit.count 封装了原子性计数与自动 TTL,还能返回剩余配额供透传。
-
/packages.json→ key ="pkg:global",统一控制元数据刷新频率 -
/p/(.+?)\/(.+?)\.json→ key ="pkg:" .. ngx.var[1] .. "/" .. ngx.var[2],每包独立限频(需在location中用rewrite捕获 vendor/name) -
/d/([0-9a-f]{64})\.tar→ key ="dist:" .. ngx.var[1],按 dist hash 限频,避免同一 tar 包被重复压制 - 所有 key 设置 TTL = 60s,配合 count = 30,即“每分钟最多 30 次请求”
降级不是关服务,而是有损响应
当 Redis 连接失败或限流模块异常时,不能直接 500,而应降级为宽松策略或缓存兜底:
- Redis 不可用 → 切换至本地内存计数(
resty.shared_dict),仅限单机维度,避免雪崩 - 检测到某包
dist请求突增(如 1 分钟内 >100 次)→ 返回 302 重定向到备用镜像源,或返回带X-RateLimit-Reset的 429,并附带推荐使用composer install --no-plugins减少元数据请求 -
/packages.json请求超时 → 返回上次成功缓存的副本(Nginxproxy_cache_use_stale error timeout updating),并异步触发后台刷新
真正难的不是写 Lua 脚本,而是 key 的语义设计和降级路径的业务感知——比如 /p/symfony/console.json 和 /p/symfony/console(无 .json 后缀)可能对应同一包,但路径规则不同,key 必须归一化;再比如 CI 场景下的 User-Agent(Composer/2.9.6)应比普通用户放宽 3 倍配额,这些细节不靠业务理解,光靠通用限流组件没法落地。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











