Solana 机器人扩容时,最先撞上什么

从每分钟十笔到一千笔:那些限制到来的顺序是可预测的,而人们最先优化的那个,往往不是正在卡你的那个。

BoltTx Team··14 min read
solana扩容限流吞吐交易机器人交易上链

一个每分钟处理十笔交易的机器人,不是一个每分钟一千笔的缩小版。卡住它们的东西不同,而且开始卡住的顺序相当可预测。

★这个顺序很重要,因为优化一个你还没撞上的限制,不会带来任何改善。★

它们到来的顺序

顺序 限制 症状
1 ★RPC 限流★ ★429,通常来自轮询★
2 确认轮询 请求数随在途交易数增长
3 ★账户写入争用★ ★不管发多少,吞吐都不动★
4 你自己的 CPU 事件循环延迟、GC 停顿
5 ★手续费预算★ ★是经济问题,不是技术墙★

★几乎每个团队都先优化自己的代码,然后照样撞上第一条。★ 前两条是请求数问题,而它们的到来远早于"你的进程怎么样"变得重要。

限流是通过轮询到来的

让人意外的是,耗尽配额的很少是发送。 ★是确认轮询。★

100 笔在途交易
  × 每个 slot 轮询一次
  × 30 秒才收敛
  = 为 100 次发送产生 ★7,500 个请求★

两个修法,按影响排序:

// ★1. 把轮询批量化 —— 单次最多 256 个签名。★
const { value } = await connection.getSignatureStatuses(pendingSignatures);

// ★2. 关掉 preflight —— 它让发送的请求数翻倍。★
await connection.sendRawTransaction(raw, { skipPreflight: true, maxRetries: 0 });

★在这个阶段,把确认批量化是杠杆率最高的单项改动★ —— 因为它把占主导的请求来源压掉了两个数量级。

然后把读路径和发送路径分开。 一次繁重的 getProgramAccounts 扫描和一次时间敏感的发送抢同一份配额,结果是扫描赢了、发送失败了 —— 而且恰好在量最大的时候。

如果你的请求预算已经控制住、交易还是会错过,免费领个 BoltTx key 改一行就能测测提交路径。

写入争用:那堵不会移动的墙

★过了请求数问题之后,这是那堵不会移动的墙。★

Solana 并行执行交易,除非它们写同一个账户。 写同一个账户的交易会被串行化 —— 所以一个把一切都汇聚到单个账户的设计,不管你发多少都有硬上限。

★一个共享账户★        →  吞吐 ≈ 每个 slot 一次写入
★按用户分开的账户★    →  吞吐随并行度扩展

诊断很简单: ★如果不管你发多少吞吐都不动、而且没看到 429,就去找一个共享的可写账户。★

对交易机器人来说,这常常表现为"所有人都在交易同一个热门池子" —— 而那不是你能控制的。你能控制的是你自己的状态账户,而把它们汇聚到一个账户上,等于自己给自己砌了同一堵墙。

你的进程要到后面才变得相关

只有在前三条之后,本地资源才要紧 —— 而即便到那时,要紧的也很少是原始速度:

// ★该看的指标是事件循环延迟,不是 CPU 占用率。★
let last = Date.now();
setInterval(() => {
  const lag = Date.now() - last - 100;
  if (lag > 50) metrics.eventLoopLag.observe(lag);
  last = Date.now();
}, 100);

★一次垃圾回收停顿可以超过一个 slot,而且它恰好发生在高负载、队列最深的时候。★ 那才是"用一门没有 GC 停顿的语言"的论据 —— 不是原始执行速度,那是微秒级的东西,而网络时间按 slot 计。

重写任何东西之前: 把 JSON 解析挪出热路径、停止在紧循环里分配内存,并检查是不是有一个慢的 handler 卡住了它后面的一切。

手续费预算也是一个天花板

★到了规模上,卡住你的往往是经济而不是技术。★

十倍的量就是十倍的手续费,再加上"竞争更频繁"带来的更高失败率。一个在低量下赚钱的策略,可能在高量下不赚钱 —— 而与此同时每一个技术指标看起来都很健康。

metrics.record({ outcome, feeSpent, netPerAttempt });   // ★按尝试,不是按成功★

★如果每次尝试的净利随着量增长而缩小,你就找到了真正的天花板★ —— 而再多的基础设施工作也抬不高它。

按正确的顺序扩容

1. 先测出你实际撞上的是哪一个限制。 429 比例、吞吐曲线、事件循环延迟、每次尝试净利。★在这里靠猜,浪费的时间最多。★

2. 把确认轮询批量化。 通常是单项最大的收益。

3. 把读和发送分开。 消除掉一整类相互干扰。

4. 给你自己的可写状态分片。 只在吞吐曲线是平的时候才做。

5. 修你的进程。 GC、阻塞的 handler、内存分配。

6. 重新核算经济性。 ★如果每次尝试的利润在下降,就停止扩容。★

★跳过第一步是最常见、也最昂贵的错误。★ 用更快的语言重写,对一个被限流的机器人毫无帮助;而给状态分片,对一个问题出在轮询的机器人也毫无帮助。

上链应该是什么水平

我们投递节点上的真实交易:中位确认 336 毫秒 —— 不到一个 slot。

★到了规模上,决定队列行为的是尾巴。★ 一个"大部分交易很快落块、但有相当一部分慢得多"的系统,恰恰会在它最忙的那些时段积累积压。

BoltTx 在这里的位置

我们负责提交。你的架构、批量策略和状态设计都留在你的代码里。

提交走我们自建的四区域投递节点,带 SWQoS 路由,不暴露公共 mempool —— 交易在落块之前、传输途中观察不到。★因为发送路径和你读取用的东西是分开的,一次繁重的扫描耗尽不了你发送依赖的那份配额★ —— 这从构造上就移除了清单上的第二个瓶颈。

你在本地签名。我们不托管资金、不代签、不修改交易内容。tip 带在交易里、从你自己的钱包链上支付,交易失败时跟着一起 revert —— 这是 Solana 原子交易的机制决定的。只有到达链上的交易才计费。

免费领 API key,没有月费:

const sender = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");

常见问题

Solana 机器人扩容时最先撞上什么? RPC 限流,而且几乎总是来自确认轮询而不是发送。 本地性能和写入争用要到那之后才变得相关。

确认轮询为什么消耗那么多请求? 因为它随"在途交易数 × 轮询频率 × 持续时长"增长。一百笔交易逐个轮询,可能为一百次发送产生几千个请求。

怎么减少确认请求? 批量化。 getSignatureStatuses 单次最多收 256 个签名,把占主导的请求来源压掉两个数量级。

skipPreflight 对限流有帮助吗? 有,它去掉了模拟那次往返,把发送请求数砍一半。而且它避免了对着一个你不会落块的 slot 做模拟。

读和发送该用同一个端点吗? 不该。一次繁重扫描和一次时间敏感的发送抢同一份配额,结果是扫描赢、发送失败 —— 恰好在量最大的时候。

不管我发多少,吞吐为什么都不动? 多半是写入争用。写同一个账户的交易会串行化,所以一个共享账户会封住吞吐,不管提交量多大。

怎么发现写入争用? 吞吐是平的、而且没有 429。 如果你没被限流、发更多也没变化,就去找一个每笔交易都在写的账户。

我自己的代码什么时候变成瓶颈? 在限流和争用都解决之后。 而即便到那时,通常也是 GC 停顿或某个阻塞的 handler,而不是原始执行速度。

该为了扩容用 Rust 重写机器人吗? 不该为了原始速度,那相对网络时间可以忽略。真正的论据是没有 GC 停顿 —— 而且要在你确认那正是卡住你的东西之后。

怎么测量事件循环延迟? 按固定间隔安排一个定时器,记录它实际晚了多久触发。这比 CPU 占用率更能暴露阻塞的 handler 和 GC 停顿。

扩大交易量会降低盈利能力吗? 会。十倍的量是十倍的手续费,加上竞争更多带来的更高失败率。 追踪每次尝试的净利就能看见。

哪个指标显示经济天花板? 每次尝试的净利,不是每次成功的。 如果它随量增长而缩小,你就找到了一个基础设施工作抬不高的限制。

该跑多少并发发送? 由你端点的限流决定,不是由 Solana 决定。 往上加到出现 429 再退回来 —— 而且用工作池而不是分块批次。

加钱包能提高吞吐吗? 在被争用的账户上不能,因为争用是按账户而不是按钱包的。它还会在不提高胜算的情况下让你的手续费翻倍。

扩容时第一件该测的事是什么? 到底是哪个限制在卡你: 429 比例、吞吐曲线、事件循环延迟、每次尝试净利。在这里靠猜,比任何别的失误浪费的时间都多。

拥堵时我的积压为什么会涨? 因为落块的尾巴变长了,而你的流入没变。按排空速率而不是队列深度告警 —— 正在排空的深队列没问题。

延伸阅读

← 返回博客列表