你开始看到 429。最直接的解读是:该升级套餐了。
有时候确实是。但很多时候 429 只是别的问题的症状 —— 一个悄悄长大的轮询循环、一个没加过滤的订阅、或者一套在"你最付不起"的时候把请求量翻倍的重试逻辑。
升级之前值得先诊断。
服务商实际在限什么
"限流"这个词至少涵盖三种不同机制,而你遇到的是哪一种,决定了 429 意味着什么。
每秒请求数。 调用量的硬上限。好理解:要么装得下,要么装不下。
Credit 或计算单元。 每个方法从月度池子里扣的量不同。getSlot 很便宜;对一个大程序调 getProgramAccounts 极贵。★如果你的调用都很重,请求速率很低也能撞上 credit 上限。★
并发连接数。 限的是同时打开的连接数,不是吞吐量。通常是"每个请求新建一条连接"而不复用导致的。
最容易搞混的是第二种。一个每秒 5 个请求的机器人,如果那 5 个都是重查询,烧 credit 可能比每秒 50 个请求的还快。
哪些调用贵
RPC 方法的成本差别很大,大到值得为此重构代码。
| 调用 | 典型成本 |
|---|---|
getSlot、getBlockHeight |
非常便宜 |
getAccountInfo |
便宜 |
getSignatureStatuses |
便宜 |
getMultipleAccounts |
★一次调用顶 N 次★ |
getParsedTransaction |
中等 |
getProgramAccounts |
★贵,而且随程序规模增长★ |
★大多数代码库里最大的单项收益,是把循环调用 getAccountInfo 换成一次 getMultipleAccounts。★
// 贵:N 个请求,N 倍的额度消耗。
// const accounts = await Promise.all(
// pubkeys.map((pk) => connection.getAccountInfo(pk)),
// );
// 便宜:一个请求,最多 100 个账户。
const accounts = await connection.getMultipleAccountsInfo(pubkeys);
超过 100 个账户时,按 100 个一批切分,而不是退回去逐个调用。
如果这些你已经调过了、瓶颈在上链而不在读取,免费领个 BoltTx key 改一行就能拿来做对比。
重试放大陷阱
把限流问题搞得更糟的最常见方式,就是处理得不对。
// 错:429 触发立即重试,重试又触发 429。
async function fetchWithRetry(fn) {
for (let i = 0; i < 5; i++) {
try {
return await fn();
} catch {
// 没有延迟。你刚刚在"已经超限"的那一刻,
// 把自己的请求速率乘了五倍。
}
}
}
★不带退避地重试 429,会把一次小超额变成持续超额。★ 正确写法是指数退避加抖动:
async function fetchWithBackoff<T>(fn: () => Promise<T>, max = 5): Promise<T> {
for (let i = 0; i < max; i++) {
try {
return await fn();
} catch (e) {
if (i === max - 1) throw e;
// 指数退避,加抖动,这样并发的 worker 不会
// 在同一个时刻一起重试、把尖峰重新造出来。
const base = 200 * 2 ** i;
await new Promise((r) => setTimeout(r, base + Math.random() * base));
}
}
throw new Error("unreachable");
}
抖动比大多数人以为的更重要。没有它,所有收到 429 的 worker 会同时重试,把造成问题的那个尖峰原样复现一遍。
读和发的约束不一样
这个区分,决定了你面对限流该做什么。
读的限流是容量问题。 升档就有更多容量,关系是直接的。
发送不是容量问题。 你的交易会不会被验证节点接收,取决于转发方背后的质押权重,而不是你的套餐每秒允许多少请求。
★更大的套餐给你更多轮询空间。它不会让你的交易在拥堵时上链。★
有些团队因为读取撞 429 而升级,然后发现上链率纹丝不动 —— 因为那本来就不是同一个约束。
升级之前先诊断
三个检查,按顺序:
到底是什么在消耗额度? 按方法名计数并打日志。大多数代码库里有一个循环产生了大部分调用,而且通常不是人们猜的那个。
const counts = new Map<string, number>();
function track(method: string) {
counts.set(method, (counts.get(method) ?? 0) + 1);
}
setInterval(() => {
console.log([...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10));
counts.clear();
}, 60_000);
能不能改成批量或订阅? 定时轮询账户状态,往往可以换成 onAccountChange —— 用一条持久连接换掉请求量。
尖峰是不是重试造成的? 把 429 时间窗内的请求数和基线对比。如果它是上升的,说明你的重试逻辑在放大而不是吸收。
订阅上的过滤不是可选项
在繁忙程序上不加过滤地用 onProgramAccountChange,是最快撞上限流的方式之一 —— 因为服务商会把每一个匹配的账户变化都推给你。
// 在服务端过滤。之后服务商只发匹配的。
connection.onProgramAccountChange(programId, handler, "processed", [
{ dataSize: 165 },
{ memcmp: { offset: 32, bytes: owner.toBase58() } },
]);
★过滤在服务商那一侧执行,所以不加过滤的订阅是双方都要付代价。★
上链应该是什么水平
我们投递节点上的真实交易:中位确认 336 毫秒 —— 不到一个 slot。
如果你的读取额度还很宽裕、交易却仍然在拥堵时迟到,那约束在路由而不在容量 —— 升多少档都解决不了。
BoltTx 在这里的位置
我们做发送,不卖读取容量。读的那一半,你按自己的负载配一家就行。
提交走我们自建的四区域投递节点,带 SWQoS 路由,不暴露公共 mempool —— 交易在落块之前,传输途中观察不到。你在本地签名,我们不托管资金、不代签、不修改交易内容。
tip 带在交易里、从你自己的钱包链上支付,交易失败时跟着一起 revert —— 这是 Solana 原子交易的机制决定的。只有到达链上的交易才计费。
免费领 API key,没有月费:
// 读取那边不用动,只换发送端点。
const sender = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");
常见问题
Solana RPC 返回 429 是什么意思? 你超过了当前档位的限制 —— 每秒请求数、credit、或并发连接数,取决于服务商的模型。它是关于读取的容量信号,完全不说明你的交易有没有上链。
为什么请求速率不高也会撞限流?
因为很多服务商按加权 credit 计费,而不是按原始请求数。几次 getProgramAccounts 消耗的额度,可能比几百次 getSlot 还多。
哪些 Solana RPC 调用最贵?
getProgramAccounts 遥遥领先,而且成本随程序规模增长。getParsedTransaction 中等。getSlot、getAccountInfo、getSignatureStatuses 相对便宜。
怎么在不损失功能的前提下降低 RPC 用量?
用 getMultipleAccounts 批量取代循环调 getAccountInfo、把适合推送的数据从轮询换成订阅、以及给所有程序订阅加服务端过滤。
收到 429 该立即重试吗? 不该。立即重试会在你已经超限的那一刻把请求速率成倍放大。用指数退避加抖动,这样并发的 worker 不会在同一时刻一起重试。
重试为什么要加抖动? 不加的话,所有收到 429 的 worker 会同时重试,把造成问题的尖峰原样复现。随机化延迟能把负载摊开,而不是让它同步。
升级 RPC 套餐能解决交易不上链吗? 通常不能。读取容量和交易上链是两个不同的约束。上链取决于通往出块方的路径和背后的质押权重,而这两样都不会因为读取额度变大而改变。
怎么找出是什么在消耗我的 RPC 额度? 给客户端埋点,按方法名计数并定期打印排名。大多数代码库里有一个循环占了大部分请求,而且通常不是人们以为的那个。
WebSocket 订阅算限流额度吗? 各家政策不同,但有一点是共通的:不加过滤的高流量订阅,是最快撞到任何限制的方式 —— 因为每一个匹配的变化都会被推给你。
请求数限制和 credit 限制有什么区别? 请求数限制管你调用了多少次。credit 限制管加权消耗,重方法扣得多。在 credit 模型下,你可能远没到请求上限却已经用光了额度。
getMultipleAccounts 一次能取多少个账户?
每次最多 100 个。超过就按 100 个一批切,不要退回去逐个 getAccountInfo —— 那会成倍增加消耗。
免费档能跑生产环境的交易机器人吗? 小规模读取有时可以。但拥堵时发送,共享的免费端点是最糟的情况 —— 因为它在你最在意的那些时刻恰好最忙。
为什么只有行情剧烈时才出现 429? 因为那时候你自己的请求量通常也在涨 —— 更多价格查询、更多重试、更多要解析的活动 —— 而共享容量同时正被所有其他人挤压。
读和发该用两家服务商吗? 很多生产环境就是这么配的。两种负载约束不同:读要吞吐和宽松限额,发要路由质量。拆开之后,其中一个不会把另一个卡住。
连接复用对限流有帮助吗? 对并发连接数这一类限制有帮助,而且能降延迟。但它不减少请求数或 credit 消耗 —— 那两个算的是调用,不是连接。
怎么判断该优化还是该升级? 先埋点。如果一个循环产生了大部分调用、或者你的重试逻辑在 429 窗口里放大请求,那优化比升档便宜。如果用量已经很高效、确实到了容量上限,那就升级。
延伸阅读
- Solana WebSocket 订阅
- Solana RPC 免费档指南
- Solana 私有 RPC 讲清楚
- Solana RPC 监控
- Solana 交易上链完全指南