进阶
连接保活(Keep-Alive):削减连接建立开销
每建立一次 HTTPS 连接,都要付出一次 TCP 握手加一次 TLS 握手的代价 —— 在你的第一个字节真正发出之前,往往已经过去 40-150 毫秒。对高频场景,复用同一个已建立的连接可以完全消除这部分开销。
为什么重要
TCP 三次握手
任何数据流动之前要先做三次往返。跨洋链路上,仅建立阶段就可能消耗 200 毫秒以上。
TLS 握手
TLS 1.3 还要一次往返(TLS 1.2 要两次),加上证书校验,通常在 30-80 毫秒之间。
TCP 慢启动
全新连接的拥塞窗口是逐步爬升的;已预热的连接可以立刻以满速发送你的请求。
一笔交易可能看不出差别。但当你每分钟跑到上百笔时,是否有一条预热的连接,决定了你的交易是落在当前 slot 还是下一个 slot。
BoltTx 如何支持 Keep-Alive
BoltTx 的每个端点都支持 HTTP/1.1 Keep-Alive 和 HTTP/2 多路复用。连接在 空闲 60 秒 后才会被服务端关闭。如果两次请求之间的间隔更长,建议每 30-45 秒向 health 端点打一次探针来保活:
健康检查端点
/v1/healthcurl https://bolttx.io/v1/health
# -> 200 OK "ok"health 端点无需鉴权、不限速、不计费 —— 不计入 TPS 配额、不影响 Tip 统计。可以随意使用。
各语言配置方式
在所有运行时,核心原则都一样:HTTP 客户端只创建一次,所有交易复用同一个客户端。绝不要在每次发送时新建客户端。
Node.js / Bun —— 原生 fetch
现代 fetch 实现通过全局 agent 自动复用连接。只要在模块层引用同一个实例,所有调用都会复用。
// bolttx-client.ts — created once at module load
const API_KEY = process.env.BOLTTX_API_KEY!;
const ENDPOINT = "https://bolttx.io/v1/send";
export async function sendTx(signedBase64: string) {
// fetch reuses the global undici Agent — no per-call setup cost
const res = await fetch(ENDPOINT, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"Connection": "keep-alive",
},
body: JSON.stringify({ transaction: signedBase64 }),
});
return res.json();
}
// Optional: warm probe every 30s for bursty workloads
setInterval(() => fetch("https://bolttx.io/v1/health"), 30_000);Python —— requests.Session
在模块加载时创建一个 Session 对象并复用。直接用 requests.get/post 不带 session 的话,每次调用都会新建连接。
import requests, threading, time
# One session for the whole process
_session = requests.Session()
_session.headers.update({
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
})
def send_tx(signed_b64: str):
return _session.post(
"https://bolttx.io/v1/send",
json={"transaction": signed_b64},
timeout=5,
).json()
# Optional warm probe
def _keepalive():
while True:
_session.get("https://bolttx.io/v1/health", timeout=5)
time.sleep(30)
threading.Thread(target=_keepalive, daemon=True).start()Rust —— reqwest::Client
clone 一个 Client 即可 —— 内部是 Arc,克隆之间共享同一个连接池。绝不要在热路径里调用 Client::new()。
use reqwest::Client;
use once_cell::sync::Lazy;
// Build once, clone cheaply — shared connection pool.
pub static HTTP: Lazy<Client> = Lazy::new(|| {
Client::builder()
.pool_idle_timeout(std::time::Duration::from_secs(60))
.pool_max_idle_per_host(32)
.http2_prior_knowledge()
.build()
.expect("reqwest client")
});
pub async fn send_tx(signed_b64: &str) -> reqwest::Result<serde_json::Value> {
HTTP.post("https://bolttx.io/v1/send")
.bearer_auth(&*API_KEY)
.json(&serde_json::json!({ "transaction": signed_b64 }))
.send()
.await?
.json()
.await
}Go —— 复用 http.Client
复用同一个 http.Client(或默认的 http.DefaultClient)。Go 默认的 Transport 会自动池化连接。
package bolttx
import (
"bytes"
"encoding/json"
"net/http"
"time"
)
// Package-level client — reuse for every call.
var client = &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 64,
MaxIdleConnsPerHost: 32,
IdleConnTimeout: 60 * time.Second,
ForceAttemptHTTP2: true,
},
}
func SendTx(signedB64 string) (map[string]any, error) {
body, _ := json.Marshal(map[string]string{"transaction": signedB64})
req, _ := http.NewRequest("POST", "https://bolttx.io/v1/send", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var out map[string]any
return out, json.NewDecoder(resp.Body).Decode(&out)
}验证是否生效
连续快速发送多笔请求,对比第 1 笔和第 10 笔的端到端延迟。Keep-Alive 正常工作时,两者的差距应该仅由网络 RTT 决定(毫秒级),而不是连接建立(几十到几百毫秒)。如果第 10 笔仍然慢,说明你的客户端在每次发送时都在新建连接。
常见误区
- •在每次发送函数里新建 fetch / requests / reqwest 客户端 —— 这会让连接池完全失效。
- •使用短生命周期的 serverless 函数(某些 edge runtime),无法跨调用保留连接 —— 考虑使用常驻 worker 架构,或接受这部分开销。
- •你和 BoltTx 之间的激进负载均衡或代理会中断 Keep-Alive —— 建议端到端实测,而不仅仅检查客户端配置。