如果你在 Solana 上做开发,RPC 是你接触的第一块基础设施,也是你最容易忽视、但又最关键的一块。很容易把它当成普通商品对待——Google 上随便挑一个,URL 一插,回头继续写业务逻辑。开发环境凑合够用,到了生产基本都会出问题。"我代码在 devnet 上 sendTransaction 成功了"和"我代码在 mainnet 真实负载下能稳定上链"——这两件事之间的差距,大部分都跟 RPC 选型有关。
这篇是给在 Solana 上做开发的工程师写的指南——RPC 到底做什么、怎么正确设置、有哪些常见模式和反模式、生产出问题时该往哪里查。
Solana RPC 是什么(代码层面)
机制上,Solana RPC 就是一个使用 JSON-RPC 协议的 HTTPS 端点。你的客户端(web3.js、Solana CLI、Anchor 框架、自己写的 Rust 代码)通过 HTTP 调用,RPC 节点要么返回链上状态、要么把交易转发给 validator。
你跟 RPC 打交道,主要做这几件事:
- 读取数据。 账户状态、交易历史、最新 blockhash、模拟交易
- 发送交易。 sendTransaction(或 sendRawTransaction)
- 订阅推送。 基于 WebSocket 的账户、program、签名实时更新
生产环境必须区分清楚:读流量和写流量的优化方向完全不同。读 RPC 强的服务商,写 RPC 不一定行;反过来也一样。
默认设置:Web3.js
大多数教程展示的最简单设置是这样的:
import { Connection, clusterApiUrl } from "@solana/web3.js";
const connection = new Connection(clusterApiUrl("mainnet-beta"));
开发环境凑合用没问题。生产环境完全不够用。clusterApiUrl("mainnet-beta") 返回的是 Solana 公共 RPC——有限流、没 SLA、网络拥堵时数据延迟严重。开发可以,生产绝对不行。
生产环境正确的写法:
import { Connection } from "@solana/web3.js";
const connection = new Connection(
process.env.SOLANA_RPC_URL, // 你 RPC 服务商的 URL
{
commitment: "confirmed",
confirmTransactionInitialTimeout: 30_000,
httpHeaders: {
"Content-Type": "application/json",
},
}
);
URL 必须放环境变量。不要在代码里硬编码 RPC URL——后面想换服务商时会非常麻烦,dev/staging/prod 也会用不同的 URL。
读取数据:能跑得动的模式
常见的读操作和正确处理方式:
// 读单个账户
const account = await connection.getAccountInfo(publicKey);
// 批量读多个账户(一次往返)
const accounts = await connection.getMultipleAccountsInfo([key1, key2, key3]);
// 读 program 下的账户(带过滤条件)
const accounts = await connection.getProgramAccounts(programId, {
filters: [
{ dataSize: 165 }, // SPL token account 大小
{ memcmp: { offset: 0, bytes: ownerKey } },
],
});
批量读用 getMultipleAccountsInfo。 一次往返搞定,不要循环调用 getAccountInfo。循环调用是开发者浪费时间最常见的姿势之一——每次循环都是一次完整的网络往返。
getProgramAccounts 必须加过滤条件。 不加 filter 一次能拉几 MB 不相关数据。filter 是在 RPC 端执行的,比拉到客户端再过滤快得多。
变化慢的状态,能缓存就缓存。 代币 mint 元数据、program owner、ATA(Associated Token Account)地址这些数据并不是每个 slot 都在变。该缓存就缓存。
发送交易:能跑得动的模式
sendTransaction 我们另开了一篇详细写(Solana sendTransaction 最佳实践)。这里简短回顾一下:
const signature = await connection.sendTransaction(tx, signers, {
skipPreflight: true,
maxRetries: 0,
preflightCommitment: "processed",
});
生产环境的几个关键点:
skipPreflight: true—— 客户端已经验证过了,不需要 RPC 再 preflight 一次maxRetries: 0—— 自己拿到新 blockhash 重试,不要让 RPC 帮你重试- 用专门的写 RPC —— 写流量对延迟敏感,跟读流量完全不是一码事
订阅推送:什么时候用
WebSocket 订阅是主动推送,不用你轮询。适合这几种场景:
- 账户变化。 账户数据变了立刻通知你
- program 日志。 program 发出日志(事件检测)立刻通知
- 签名确认。 特定交易确认后立刻通知
const subId = connection.onAccountChange(
publicKey,
(accountInfo, context) => {
console.log("Account changed at slot", context.slot);
},
"confirmed"
);
// 用完记得取消
connection.removeAccountChangeListener(subId);
代价是:订阅比轮询复杂得多,会遇到一堆轮询不会有的失败模式(重连逻辑、订阅泄漏管理)。轮询太慢或太贵的时候才用订阅,否则老老实实轮询。
如果是大流量事件检测(多账户、多 program),标准 WebSocket 订阅模型不太够用,需要专门的流式数据源。这超出本篇范围,但你应该知道有这条路。
不同应用类型的常见模式
不同应用对 RPC 的用法差别很大:
钱包。 读为主(余额、交易历史、代币持仓)+ 偶尔写(用户主动 swap)。需要慷慨的读定价 + 可靠的交易发送。
索引器/分析平台。 大读取量、经常需要订阅推送,对写不敏感。读优化的 RPC 就行。
交易机器人。 重度 sendTransaction、对延迟极度敏感、有 MEV 风险。需要写优化 + 带 Anti-MEV 路由的 RPC。读流量适中。
dApp。 读写混合。面向终端用户,失败比机器人更明显。可靠性和可预测性比峰值性能更重要。
后端服务。 通常混合,看具体用例。
最普遍能跑得通的模式:读用一个 RPC(通常选读优化、定价合理的服务商),写用另一个(写优化、亚秒级落块、带 Anti-MEV 路由)。换个角度想:读 RPC 是数据库,写 RPC 是交易路由层。
常见反模式(按踩坑严重度排)
所有事情用同一个 RPC。 服务商各有强项——有些读得好但写一般,有些专长写但读贵。分开选。
硬编码 URL。 永远用环境变量。后面一定会想换。
不处理限流。 生产 RPC 都会限流,客户端必须做指数退避重试,不要直接崩。
对慢数据高频轮询。 每秒查一次代币元数据是浪费。缓存。
激进重连 WebSocket。 有些机器人每发一笔交易就重连一次。建立连接很贵,别这么干。
生产用 clusterApiUrl。 这是公共 RPC,开发可以,生产绝对不行。
不做监控。 大多数生产 bug 都是"我们不知道 X 在发生"。每笔签名级别的遥测就是解药。
不模拟就直接发。 真发交易前先 simulate 一下,绝大多数逻辑 bug 在花手续费前就能抓到。
把"交易已发送"当成"交易已上链"。 sendTransaction 返回签名不代表已经上链,永远要确认。
这周可以做什么
如果你在开新 Solana 项目:
- 读 RPC 和写 RPC 分别选。优化目标完全不同。
- URL 放环境变量。不要硬编码。
- Connection 设合理默认值。
commitment: "confirmed"、合理的超时。 - 批量读用
getMultipleAccountsInfo,不要循环getAccountInfo。一次往返搞定。 getProgramAccounts永远加过滤条件。- 早期就把每笔签名级别的遥测搭起来。后面一定用得上。
- 拿模拟负载压测。开发环境读得动不代表生产量级也读得动。
BoltTx 给开发者的能力
BoltTx 专门为 Solana 应用的写入场景设计:
- 亚秒级落块,配尾部延迟稳定的工程优化
- 原生 Anti-MEV 路由默认开启
- 专属 SWQoS + 优先级连接,拥堵期也能保证上链
- 每笔签名级别的投递遥测,生产 debug 用得上
- 单一全球端点——不用选 region、运维零负担
- 按 tip 计费——只为成功上链的交易付费
集成方式:
import { Connection } from "@solana/web3.js";
const writeConnection = new Connection(
"https://bolttx.io/?api-key=YOUR_API_KEY",
"processed"
);
// 在你 sendTransaction 的地方用这个 connection
const signature = await writeConnection.sendTransaction(tx, signers, {
skipPreflight: true,
maxRetries: 0,
});
免费档注册。配一个读为主的 RPC,两边都能拿到最优。大多数生产团队跑的就是这种双 RPC 架构。
常见问题
生产里能直接用 Solana CLI 吗? ops 脚本可以。应用代码必须用 SDK(web3.js、anchor、solana-web3.rs)。
web3.js 和 Anchor 的区别? web3.js 是底层 Solana 交互 SDK;Anchor 是写 program、加上结构化账户校验的客户端框架。生产里这两个经常一起用。
能从 Python 用 Solana 吗?
能。solders 和 solana-py 是主要库。生态比 JS 那边不成熟,但够用。
读应该用哪个 commitment 级别?
大部分场景用 "confirmed"。要极致低延迟、能扛住 reorg 用 "processed"。"finalized" 留给"高价值且不可撤销"的决策。
开发要自己跑 RPC 节点吗? 开发不要——直接用托管服务商。生产到大规模时偶尔有团队会自建——但绝大多数团队最后都发现,托管 RPC 在成本和可靠性上都比自托管划算。