AniBT Wiki
公开 API

限流与上限

各端点的 limit、候选扫描上限、速率限制、客户端兼容矩阵与延迟拆解。

各端点的上限一览

端点limit 默认limit 上限后端候选扫描上限速率限制(默认)
/rss/anime.xml100200Typesense filter120 次 / 60 秒 / IP
/rss/group/<slug>.xml100200120 次 / 60 秒 / IP
/rss/magnets.xml100200120 次 / 60 秒 / IP
/api/bgm/search525Typesense search60 次 / 60 秒 / IP
/api/animes/search2450Typesense search120 次 / 60 秒 / IP
/api/animes/torrent-match1050Typesense search180 次 / 60 秒 / IP
/api/anime/lookupConvex lookup60 次 / 60 秒 / IP
/api/mikan/group-lookupMikan upstream + Redis60 次 / 60 秒 / IP
/api/subtitle-groups全量返回60 次 / 60 秒 / IP
/api/seasons/anime30 次 / 60 秒 / IP
/api/anime/groups30 次 / 60 秒 / IP

limit 越界处理:超过上限会被自动截断到上限,不会报错。例如 ?limit=5000/rss/anime.xml 实际返回 200 条。

公开搜索翻页深度

公开搜索和磁力列表使用 Typesense 页码分页,服务端最多读取 100 页

  • /magnets?page=<n> 会把页码规范化到 1–100;例如 ?page=10000 会重定向到 ?page=100
  • /api/animes/searchcursor 只接受 ts_page:<页码>。非法前缀会返回 400 VALIDATION_ERROR;超大页码会被规范化到第 100 页并返回空页或最后一页结果。
  • limit 与页码上限是两个独立保护:limit 控制单页大小,cursor / page 控制最多翻到第几页。

过滤模型(/rss/anime.xml

/rss/anime.xml 由主站 Web runtime 查询 Typesense release index。bgmIdgroupSlugresolutionlanguagesubtitleformat 等过滤会尽量下推到 Typesense;episode / episodeKey 这类展示字段可能在 Web runtime 做小窗口后过滤。不会回源 Convex 扫库。

极冷门组合如果叠加过多过滤,可能返回少于 limit 条;这是索引窗口和性能保护的结果。需要更多历史数据时,优先放宽过滤或按字幕组短链分页式轮询。

提高上限

  1. 拉满 limit:把 URL 的 ?limit= 改成端点上限(200)。一次请求拿更多,等于把 N 次轮询合成 1 次。
  2. 用 304 替代轮询:每次请求带 If-None-Match。服务端确认数据没变就返回 304 空 body,不计入很多客户端的"流量统计",但仍会消耗 1 次速率配额。
  3. 拉长间隔:默认建议 ≥ 5 分钟。番剧发布频率本身按"集数"计算,每分钟扫一次没有意义。
  4. 合并并发:不要对同一 URL 并发发多个请求,缓存层会让后续请求拿到一样的结果,但白白消耗速率额度。

通过 Web runtime / 部署平台的环境变量调整(重启后生效):

env
# 把每 IP 60 秒 120 次提高到 60 秒 600 次
RSS_ANIME_RATE_LIMIT_MAX=600
RSS_GROUP_RATE_LIMIT_MAX=600
RSS_MAGNETS_RATE_LIMIT_MAX=600

# 或者改窗口为 10 秒(变成 120 次 / 10 秒)
RSS_RATE_LIMIT_WINDOW_MS=10000

# 关闭 CDN 边缘缓存(不推荐,仅测试用)
RSS_CACHE_TTL_SECONDS=0

/rss/anime.xmllimit 上限是 Web 路由源码常量(src/routes/rss/anime[.]xml.ts),不通过环境变量调整。如果私有部署需要更高,改源码后重新构建并部署 Web runtime。

提高之后需要观察:

  • 单次响应 XML 大小:200 条通常已经足够 RSS reader 使用,继续提高要注意带宽。
  • Typesense 查询 P95 与 Web SSR cache hit/miss:公开 RSS 不应出现 Convex RSS 查询或扫库 fallback。
  • 客户端解析时长:很多 RSS reader 默认只保留最近 N 条,请同步调整。
  • 响应包含 Retry-After: <seconds>,按这个退避,不要立即重试
  • 短期内被打 429 通常是配置错(轮询太快 / 多个 reader 用同 URL)。把间隔放大到 5 分钟 + 启用 304,几乎不会再触发。

1000 用户场景下的真实负载

核心结论:origin 真实承受的回源率与并发用户数几乎无关,只与 s-maxage 和查询参数多样性有关。

请求流转:

用户 ──► 浏览器 / RSS 客户端缓存 (max-age)
        ──► Cloudflare CDN (s-maxage + SWR)
            ──► origin (TanStack Start Web + Redis) ──► Typesense
  • CDN 内未过期 → 0 origin 请求
  • CDN 过期但仍在 SWR 窗口 → 用户拿旧版本(0 延迟),origin 收到 1 个异步刷新请求
  • 完全过期 → origin 收到 1 个同步请求(~50–200ms 回源)
  • 客户端带 If-None-Match → ETag 命中时 origin 仅返回 304(无 body,几乎零开销)

各端点在 1000 用户、客户端默认 15 分钟轮询 假设下的预估负载:

端点客户端日请求量origin 真实回源(次/小时)备注
/rss/anime.xml(全站)~96k~125min CDN 窗口,全部用户共享同一缓存
/rss/group/<slug>.xml取决于 slug 数~12 × slug 数50 个 slug ≈ 600 次/小时
/rss/magnets.xml(简单)~30全部用户用相同筛选时
/rss/magnets.xml(复杂、按 query 分缓存)接近 1× 唯一查询数 / 60s每人查询条件不同时 CDN 命中率 ≈ 0,SWR 缓解延迟
/api/subtitle-groups~96k~12数据极少变化,几乎全 304
/api/seasons/anime~48k~6季度数据周更,长缓存
/api/anime/groups(按 bgmId)~12 × 热门番剧数冷门番剧首次回源较多

综合:1000 用户全套订阅 + 前端使用,origin 一小时实际承受 < 1000 次请求,其中 90%+ 是 304。SWR 让"用户感知延迟"和"origin 回源压力"完全解耦——回源慢不会拖慢用户。

给客户端的轮询建议

属性

类型

绝对不要做的事

  • 多线程并发拉同一 URL(白白消耗速率配额,CDN 会让结果完全相同)
  • 关闭 ETag / If-None-Match(每次都拿全量响应,浪费带宽)
  • 间隔 < 1 分钟(必触发 429,且对你没任何收益)

主流 RSS 客户端的实际行为

不同客户端默认的轮询频率、ETag/304 支持差异很大。好消息:本站对所有客户端都返回正确的 ETag + Last-Modified + Cache-Control,符合 HTTP 标准的客户端会自动走 304,你不用做任何配置

客户端默认轮询If-None-Match尊重 Cache-Control: max-age备注
NetNewsWire(macOS/iOS)1 小时表现最好,几乎全 304
Inoreader(云端)~30 分钟自带服务端聚合,1000 用户共享同一上游请求
Feedly(云端)~1 小时同上,对 origin 极友好
FreshRSS(自建)用户配置(默认 1h)推荐配置 ≥ 15min
Tiny Tiny RSS(自建)用户配置⚠ 部分版本无视建议手动设 ≥ 15min
Reeder(macOS/iOS)跟随 Inoreader/Feedly透传第三方聚合服务
qBittorrent RSS 自动下载默认 30 分钟❌(每次拉全量)对 origin 最不友好,但配合 CDN 仍 OK
Transmission + Flexget用户配置✅(Flexget 支持)推荐
自建脚本(Python requests你写多少就多少取决于实现取决于实现Python 客户端最小实现

针对场景的预期

  • 个人订阅 1–2 个番剧 RSS(NetNewsWire / Inoreader):每小时 1 个请求,全部 304,origin 几乎感知不到。
  • qBittorrent 用 RSS 规则自动下载:每 30 分钟拉一次全量 XML(约 50–200 KB),但因为 CDN 命中,实际打到 origin 也只是 5 分钟一次。
  • 公开聚合站(Inoreader / Feedly):1000 个订阅者后端共享同一份订阅,对 origin 等同 1 个用户。

调优建议

  • Inoreader / Feedly / NetNewsWire iCloud 这类支持云端聚合的客户端 → 你和其他用户共享缓存
  • qBittorrent / Transmission 直接拉的话,把规则间隔调到 ≥ 15 分钟(默认 30 分钟也很合理)
  • 自建脚本必须实现 If-None-Match 复用,否则等于浪费带宽

后端发布 → 用户看到的完整延迟拆解

以"字幕组发布一个新种子"到"订阅者的 RSS 客户端弹出通知"为例,逐段算一遍最坏情况:

延迟拆解
T=0       字幕组提交 release 到 AniBT Web API
          (Web 解析请求体 / 种子,Convex mutation 写入 releases / files 表)
T=0       数据库立即可读(Convex 强一致)
          Web 写侧事件会触发外部 Worker 清 Redis RSS cache + purge CDN

T≤120s    事件 purge 缺失时的兜底 CDN / Web Redis 短缓存窗口内:
          客户端请求被 CDN 拦截,仍拿旧版本
          → 这一段是用户感知延迟的主要来源

T=120s    CDN 缓存进入 SWR 期:
          下一个用户请求触发 CDN 异步回源
          原 origin 拿到新数据 → CDN 缓存更新

T=120~300s 客户端的下次轮询周期(取决于客户端):
          - 若客户端轮询间隔 = 5min → 最快下一次拿到新版本
          - 若间隔 = 15min → 平均再等 7.5 min
          - 若间隔 = 1h → 平均再等 30 min

典型场景的端到端延迟(中位数)

客户端配置后端发布 → 用户看到
自建脚本 5 min 轮询 + ETag3–5 分钟
NetNewsWire 默认 1h 轮询30–35 分钟(瓶颈在客户端)
Inoreader 云端聚合15–30 分钟(瓶颈在 Inoreader 自己的轮询频率)
qBittorrent 默认 30min15–30 分钟

优化哪一段最有效

  1. 缩短客户端轮询间隔(最有效):从 1h 改成 5min,立刻把延迟从 30min 砍到 5min。
  2. 缩短 s-maxage(次要):从 300s 改成 60s,origin 压力涨 5 倍但用户延迟只少 4 分钟,性价比低。
  3. 强制 origin 立即推送(不可行):HTTP RSS 是 pull 模型,没办法。

如果你需要实时(< 1 分钟)通知,RSS 不是合适方案,应该用 webhook / Telegram bot / 站内推送。RSS 的设计目标就是"最终一致 + 极低成本",5–30 分钟的延迟是它的功能不是 bug。

本页目录