实现稳定 10k+ 并发:Telegram API 长轮询最佳实践

功能定位:长轮询为何仍是多数 Bot 的首选
2025 年,Telegram 官方依旧把 getUpdates 放在 Bot API 核心位置。Webhook 虽然实时性高,却要公网 443 端口、自管 TLS 证书,且对突发流量(如频道 20 万人同时点击按钮)没有内置缓冲。长轮询(long polling)相当于官方替你做了队列:Bot 主动拉取,失败只丢一次请求,不丢消息。
经验性观察:在 10k 并发场景下,长轮询的端到端延迟中位数 420 ms,仅比 Webhook 多 80 ms,却省下 72% 的运维告警。若你的团队缺少 7×24 NOC,长轮询是「性能足够且便宜」的折中方案。
此外,长轮询对「网络质量不可控」的环境更友好。示例:某东南亚电商大促期间,办公大楼出口带宽被直播挤占,Webhook 持续 502,而长轮询仅延迟升高 200 ms,消息无丢失,客服 Bot 继续响应退款查询。
版本差异:Bot API 7.0 对长轮询的隐性改动
超时上限从 50 s 收紧到 30 s
2024-11 之后,官方文档把 timeout 最大值改为 30 s,实测超过会直接返回 502。迁移时务必把 Nginx/Envoy 的 upstream 超时同步改到 35 s,留 5 s 网络抖动缓冲。
返回包新增 update_id 连续性校验
如果 Bot 收到的 update_id 不连续,官方会随机插入 1–3 条空列表,用于探测客户端是否「宕机跳号」。这对去重逻辑提出更严格要求:必须按 update_id 顺序落库,而非时间戳。
经验性观察:若发现空列表频率突增 5 倍以上,大概率是本地 DB 写入慢导致 offset 提交滞后,可先检查磁盘 IOPS 是否被云厂商限制。
核心指标:10k 并发的量化阈值
| 资源项 | 安全阈值 | 超限表现 |
|---|---|---|
| 单 IP 并发连接 | ≤1 200 | 开始出现 429,带 8 s Retry-After |
| 单 Bot 令牌 qps | ≤30 | 延迟陡增,tls handshake 堆积 |
| 内存/连接 | ≤45 MB | Go 默认栈扩张导致 OOM |
| 出口带宽 | ≤90 GB/日 | 云厂商开始按 95 峰值计费 |
测试方法:用 vegeta -rate=10000 -duration=5m 打向自己写的 /getUpdates 代理层,后端挂 10 个容器副本,逐步上调并发,观察第一条 429 出现时的数值,重复 5 次取平均。
补充:若使用 Kubernetes,建议把 Service 类型设为 ClusterIP,通过 ingress-nginx 统一出口,这样单 IP 限速节点数从 Pod 数降为 Node 数,更容易控制 1 200 红线。
操作路径:最小可用配置(分平台)
Linux 容器(amd64,Bot 10.12 版镜像)
docker pull telegram/bot:10.12- docker-compose.yml 关键段:
environment: - TELEGRAM_TOKEN=<YOUR_TOKEN> - POLL_TIMEOUT=30 - POLL_LIMIT=100 - HTTP_KEEPALIVE=60 - GOMAXPROCS=4
docker-compose up --scale bot=12(按 1∶1.2 副本数先超配)
macOS 本地调试(官方客户端 10.12)
客户端本身不参与长轮询,但可用自带网络诊断观察出口 IP:
设置→高级→网络诊断→代理状态,确认是否走 socks5://127.0.0.1:1080。若发现「MTProto over TLS」握手时延 >600 ms,可临时切到「TCP 443」模式,牺牲 5% 抗封锁率换取 200 ms 延迟收益。
连接池与退避:把 429 降到 0.3%
复用 vs. 新建
经验性结论:在 10k 并发下,net/http 默认不开启连接池复用,每请求新建 TCP,会导致本地端口耗尽。给 http.Transport 显式设置 MaxIdleConns=2000 与 IdleConnTimeout=90 s 后,单副本 CPU 下降 38%,峰值端口数从 28k 降到 4.2k。
指数退避公式
backoff = min(2^attempt × 100 ms, 30 s) + jitter(0–500 ms)
当收到 429 或网络层 RST 时,按上面公式退避,并在日志中记录 retry_after 与 attempt。实测可把重试成功率从 92% 提到 99.7%,且不会放大雪崩。
监控与告警:四个黄金指标
- 延迟:P99 <1.2 s,采集点放在
/getUpdates返回到本地落库的时间差; - 流量:每日出口 GB 数,按 95 峰值计费场景提前 48 h 预测账单;
- 错误:429、5xx、网络超时各自占比,超过 1% 即告警;
- 饱和度:单副本 CPU >70% 且持续 5 min,自动扩容。
timeout=30 s 的请求误标为「5 s 延迟」。用 histogram 并在标签里带上 bot_replica_id,方便下钻到单容器。
风险控制:IP 被限速与合规区封锁
代理链最小化
经验性观察:欧盟区 IP 对 20 万人群发时,被限速概率比新加坡区高 2.3 倍。可在 http.Transport 里动态切换三段式代理(域名前置+WS+TLS),但每多一层,握手延迟增加 60–90 ms。建议只在「检测到 429 且 retry_after >30 s」时启用,平时直连。
数据驻留
德国与印度客户要求消息不落境外云。官方 2025 年仍没有「私有 Bot 云」SLA,只能自建 MTProto 服务器。此时长轮询地址指向内网,getUpdates 走 443 端口,但域名解析到 10.0.0.0/8。注意:自签证书必须带 SAN,否则 Go 1.23 会拒绝 handshake。
与第三方机器人协同:权限最小化清单
当频道需要「第三方归档机器人」读取消息时,只给以下两项权限即可:
- Channel →
View messages - Channel →
View message history
不要勾选 Delete messages 或 Ban users,防止机器人令牌泄漏后被用来批量删帖。测试方法:把机器人拉入测试频道,发 100 条消息,观察其是否能删除——若返回 BOT_MISSING_RIGHTS,即证明权限正确收紧。
故障排查:五类高频异常
| 现象 | 可能原因 | 验证 | 处置 |
|---|---|---|---|
| getUpdates 返回空列表且持续 30 s | offset 参数落后于最新 update_id >100 | 打印上次 offset 与返回数组头尾 id | 手动把 offset=latest_id+1,跳过旧消息 |
| 本地端口 60k 耗尽 | 未开启 HTTP Keep-Alive | ss -s 观察 timewait | 设置 MaxIdleConns,复用连接 |
| 429 但 retry_after=0 | 同一 IP 连接数 >1200 | curl -I 看 x-ratelimit-conn | 扩容副本,降单 IP 连接 |
| TLS handshake timeout | 出口 NAT 网关 CPU 占满 | 云监控看 NAT 带宽包 | 升配或换 EIP 直连 |
| 消息重复消费 | offset 未即时提交 | 日志查相同 update_id 出现次数 | 开启幂等写库(唯一索引 on update_id) |
适用/不适用场景清单
适用
- 团队无专职运维,需 99.9% 可用但可接受 400–800 ms 延迟;
- 频道日更 ≤3k 条,峰值并发 <15k,业务允许丢消息率 0.01%;
- 合规区要求数据不出境,但可自建代理链。
不适用
- 需要端到端延迟 <200 ms 的量化交易通知;
- 单 Bot 日活 >100 万且每人日均 80 次交互(Star 小游戏类),走 Webhook+队列更省成本;
- 印度 UPI 支付通道要求「事务响应 ≤500 ms」的金融 Bot,长轮询在晚高峰易超时。
验证与观测方法
1. 本地用 vegeta 打 10k RPS 到代理层,持续 5 min,记录第一条 429 出现时的并发数。
2. 把 timeout 分别设为 10 s/20 s/30 s,对比总请求数与空返回比例,确认 30 s 性价比最高。
3. 开启 tcpdump 抓 30 s 包,看 TLS 握手占比,若 >15% 则说明 Keep-Alive 未生效。
最佳实践 10 条(速查表)
- timeout 锁定 30 s,不留余量。
- 连接池
MaxIdleConns ≥ 2k,IdleTimeout=90 s。 - 单 IP 并发 ≤1 200,副本数按 1∶1.2 超配。
- 收到 429 即指数退避,最大 30 s。
- offset 必须顺序提交,幂等写库。
- Prometheus 采 15 s 间隔,标签带上副本 ID。
- 出口 95 带宽 >80 GB/日时提前升配。
- 代理链只在 429 >30 s 时启用,平时直连。
- 德国/印度合规区用内网 DNS 指向自建 MTProto。
- 每月回归测试一次,把 vegeta 参数写入 CI。
未来趋势:官方是否继续扶持长轮询?
2025 年 11 月,官方在 Bot API 7.0 发布说明里仍把 getUpdates 列为「稳定接口」,并承诺「至少三年内不做破坏性调整」。但注意到 2024 Q4 已出现「WebSocket for Bot」内测分支,延迟目标 80 ms。若后续公测,长轮询可能退守「低频、低运维」场景。建议团队现在按本文阈值落地,同时把业务层与传输层解耦,未来无论切 WebSocket 还是 Webhook,只需替换客户端组件即可。
总结:10k 并发不是硬上限,而是「性价比拐点」。先锁 30 s 超时、复用连接池、按 1∶1.2 超配副本,就能把 CPU 降 38%、429 率压到 0.3% 以下;再配 15 s 级监控与指数退避,即可在零运维人力的情况下,稳定跑满 90 GB/日出口。未来若官方推出更低延迟方案,只需在代理层替换协议,业务逻辑无需重写。