进阶

连接保活(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 端点打一次探针来保活:

健康检查端点

GET/v1/health
curl 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 —— 建议端到端实测,而不仅仅检查客户端配置。

延伸阅读