
Solana Geyser 插件:以光速流式传输数据
本文介绍什么?
Geyser 插件是一种模块化组件,旨在将账户、时隙、区块和交易的数据传输到外部数据存储,使开发者能够从验证者中移除 RPC(远程过程调用)负载。对于希望自定义数据流式传输和处理需求的开发者,Geyser 插件提供了一种灵活的解决方案。
本文将深入探讨 Solana Geyser 插件的复杂机制。我们会先介绍 AccountsDB 副本,这是一种曾被提出用于数据复制和负载管理的方法,但最终被放弃,转而采用 Geyser 插件。
接下来,我们会详细解析 Geyser 插件是什么、如何运作,以及如何通过插件接口进行组织。
随后,我们会讨论常见的 Geyser 插件,并指导你完成自行创建插件的复杂流程。最后,我们会介绍 Helius,以及我们如何简化 Solana 上的数据流式传输。
AccountsDB 副本:被放弃的数据复制与 RPC 负载解决方案
Solana 探索了多种方式,以应对高 RPC 负载和数据复制的挑战。其中一种很有前景的方法是使用 AccountsDB 副本。这些副本旨在将账户扫描请求从主验证者转移到 AccountsDB 副本。尽管前景可观,但该系统本身十分复杂,并且需要一套新的服务来确保主验证者与副本之间保持同步。最终,这项提案被放弃,转而采用 Geyser 插件系统。后者更易于验证者客户端支持,也让开发者在实现应用时拥有更大的灵活性。
那么,Solana Geyser 插件究竟是什么?
什么是 Solana Geyser 插件?
Solana Geyser 插件提供对 Solana 数据的低延迟访问,可为应用提供服务,从而免去向验证者发起 RPC 调用的需要。例如,如果验证者必须在短时间内连续处理大量 getProgramAccounts 调用,巨大的流量可能导致验证者落后于网络进度。
Geyser 插件通过将账户、区块、时隙和交易信息重定向到关系型数据库、NoSQL 数据库或 Kafka 等外部数据存储来解决这一问题。
通过重定向数据,RPC 服务可以针对从这些外部存储获取数据的需求,提供缓存和索引等更灵活、更有针对性的优化。
Geyser 插件充当 Solana 与外部数据存储解决方案之间的桥梁。它让开发者能够从验证者卸载大量数据管理任务,从而提高性能并降低出现潜在瓶颈的风险。
无论 RPC 流量多大,Geyser 插件都能确保验证者与网络保持同步。
Geyser 插件接口
开发者可以使用 Solana Geyser 插件接口构建 Geyser 插件。该接口提供对账户、交易、时隙、区块元数据和条目的访问。它声明于 solana-geyser-plugin-interface crate,并由 GeyserPlugin trait 定义。
该 trait 定义了一组方法,每个方法都以 update_ 为前缀,并在创建新数据或更新现有数据时调用。Geyser 插件还必须指定其在加载和卸载过程中的行为。该 trait 概述了 Geyser 插件应实现的核心方法,以便根据所需的插件行为高效地流式传输数据。
源代码
pub trait GeyserPlugin:Any +Send +Sync +Debug {
// Required method
fn name(&self) -> &'static str;
// Provided methods
fn on_load(&mut self, _config_file: &str) ->Result<()> { ... }
fn on_unload(&mut self) { ... }
fn update_account(
&self,
account:ReplicaAccountInfoVersions<'_>,
slot: Slot,
is_startup:bool
) ->Result<()> { ... }
fn notify_end_of_startup(&self) ->Result<()> { ... }
fn update_slot_status(
&self,
slot: Slot,
parent:Option,
status:SlotStatus
) ->Result<()> { ... }
fn notify_transaction(
&self,
transaction:ReplicaTransactionInfoVersions<'_>,
slot: Slot
) ->Result<()> { ... }
fn notify_entry(&self, entry:ReplicaEntryInfoVersions<'_>) ->Result<()> { ... }
fn notify_block_metadata(
&self,
blockinfo:ReplicaBlockInfoVersions<'_>
) ->Result<()> { ... }
fn account_data_notifications_enabled(&self) ->bool { ... }
fn transaction_notifications_enabled(&self) ->bool { ... }
fn entry_notifications_enabled(&self) ->bool { ... }
}Trait 声明
GeyserPlugin trait 是 Solana Geyser 插件生态系统中所有插件的基础接口。它被声明为公共 trait,并带有 Rust 标准库中的 Any、Send、Sync 和 Debug trait 约束。这些 trait 约束如下:
Any支持类型反射,从而可以向下转换为具体类型Send表示实现此 trait 的类型的所有权可以在线程之间转移Sync表示实现此 trait 的类型引用可以在线程之间共享Debug允许格式化该类型以便输出,尤其用于调试
Any 和 Debug 对我们来说并不那么重要。
真正重要的是,GeyserPlugin 需要 Send 和 Sync 来确保程序的线程安全。
必需方法
fn name(&self) -> &'static str;任何实现 GeyserPlugin 的类型都必须实现 name 方法。该方法用作 Geyser 插件的标识符。它会返回一个表示 Geyser 插件名称的静态字符串切片。
除了 on_load 和 on_unload 之外,该方法及所有其他方法都使用 &self 而不是 &mut self,这是 Solana 1.16 更新中的新变化。由于不再需要将 Geyser 插件包装在读写锁中,也无需在每次调用其函数时获取写锁,因此性能得到大幅提升。
已提供的方法
该 trait 提供了多个带有默认实现的方法,GeyserPlugin 的实现可以覆盖这些方法。
fn on_load(&mut self, _config_file: &str) ->Result<()> { ... }on_load 方法是系统加载插件时调用的回调,用于执行插件所需的任何初始化。它接收一个 string 引用,表示配置文件的路径。配置必须采用 JSON5 格式,并包含 libpath 字段,用于指明实现此接口的共享库的完整路径名。
fn on_unload(&mut self) { ... }on_unload 方法是一个回调,在系统卸载插件之前调用,用于执行必要的清理工作。
fn update_account(
&self,
account:ReplicaAccountInfoVersions<'_>,
slot: Slot,
is_startup:bool
) ->Result<()> { ... }当账户在已处理确认级别更新时,会调用 update_account 方法,而这种情况在一个时隙内可能发生多次。此时,必须跟踪已确认的时隙,才能获取提交到规范链的账户更新。
ReplicaAccountInfoVersions 结构体包含所传输账户的元数据和数据。
slot 参数指向账户发生更新的时隙。
当 is_startup 为 true 时,表示验证者启动时从快照加载账户。当 is_startup 为 false 时,账户会在交易处理期间更新。
fn notify_end_of_startup(&self) ->Result<()> { ... }notify_end_of_startup 方法用于发出启动阶段结束的信号。这会在验证者从快照恢复账户数据库,并相应更新所有账户后发生。
fn update_slot_status(
&self,
slot: Slot,
parent:Option,
status:SlotStatus
) ->Result<()> { ... }当时隙状态更新时,会调用 update_slot_status 方法。它接收一个 Slot、一个用于父时隙的 Option<u64>,以及一个 SlotStatus enum 实例。
SlotStatus 定义了 Solana 中时隙的三种状态:
Processed- 节点已处理的最高时隙。虽然该时隙尚未确认或最终确定,但它属于验证者认为最有可能成为规范链的链Confirmed- 该时隙已获得足够多的投票,可以被视为安全并成为链的一部分。该时隙获得了 Solana 验证者绝对多数的支持Rooted- 该时隙现已成为区块链的永久组成部分,链的所有其他版本或分叉都必须基于此时隙继续构建。这意味着网络上的所有分支都源自该区块
fn notify_transaction(
&self,
transaction:ReplicaTransactionInfoVersions<'_>,
slot: Slot
) ->Result<()> { ... }当一个时隙中处理了某笔交易时,会调用 notify_transaction 方法,将交易详情通知插件。
ReplicaTransactionInfoVersions 是一个用于处理 ReplicaTransactionInfo 的 enum 包装器。如果 RepicaTransactionInfo 的结构发生变化,enum 就会为新版本添加一个新条目。这会迫使插件实现通过适配新的 enum 条目来处理变化。目前,enum 包装了两个变体:
V0_0_1(&'a ReplicaTransactionInfo<'a>)V0_0_2(&'a ReplicaTransactionInfoV2<'a>)
pub struct ReplicaTransactionInfo<'a> {
pub signature: &'a Signature,
pub is_vote: bool,
pub transaction: &'a SanitizedTransaction,
pub transaction_status_meta: &'a TransactionStatusMeta,
}
pub struct ReplicaTransactionInfoV2<'a> {
pub signature: &'a Signature,
pub is_vote: bool,
pub transaction: &'a SanitizedTransaction,
pub transaction_status_meta: &'a TransactionStatusMeta,
pub index: usize,
}这些变体的主要区别在于,第二个变体会存储交易在区块中的索引。
fn notify_entry(&self, entry:ReplicaEntryInfoVersions<'_>) ->Result<()> { ... }notify_entry 会向插件通知新条目。它接收一个 ReplicaEntryInfoVersions 实例,该实例是一个包装器,用于确保 ReplicaEntryInfo 处理能够兼容未来变化。目前,它包含 V0_0_1(&'a ReplicaEntryInfo<'a>) 变体。
此变体是一个 struct,包含条目的时隙、在区块中的索引、自上一个条目以来的哈希数量、条目的 SHA-256 哈希,以及该条目中已执行的交易数量。
fn notify_block_metadata(
&self,
blockinfo:ReplicaBlockInfoVersions<'_>
) ->Result<()> { ... }当区块的元数据更新时,会调用 notify_block_metadata 方法。它接收一个 ReplicaBlockInfoVersions enum 实例作为区块信息。此 enum 是不同 ReplicaBlockInfo 版本的包装器,其中包含区块的时隙、哈希、奖励、区块时间和区块高度等信息。
fn account_data_notifications_enabled(&self) ->bool { ... }
fn transaction_notifications_enabled(&self) ->bool { ... }
fn entry_notifications_enabled(&self) ->bool { ... }这些方法分别返回布尔值,表示插件是否希望启用账户数据、交易和条目的通知。
关于承诺级别的说明
账户数据和交易一经处理,Geyser 就会立即发送更新。这有助于提升端到端索引速度,但已处理的时隙也可能被跳过。
跳过的时隙是指过去未生成区块的时隙,原因可能是领导者离线,也可能是包含该时隙的分叉被放弃,转而采用更优的分叉。接收流式数据的数据存储系统必须意识到这种可能性,并相应地管理更新。
常见的 Solana Geyser 插件
开发者可以使用各式各样的 Solana Geyser 插件,甚至可以对其进行分叉以满足特定需求。一些值得关注的插件包括:
PostgreSQL Plugin:使用 PostgreSQL 管理和查询数据gRPC Service Streaming Plugin:将 Solana 账户更新流式传输到 gRPC 服务RabbitMQ Producer Plugin:使用 RabbitMQ 实现消息队列Kafka Producer Plugin:使用 Kafka 流式传输数据Amazon SQS Plugin:利用 Amazon Simple Queue Service 实现消息队列Google BigTable Plugin:使用 Google BigTable 管理和查询数据
这些插件可以进行调整,以满足各种各样的使用场景。
例如,Clockwork 曾利用 Geyser 插件调度交易,并构建自动化、事件驱动的 Solana 程序。尽管该项目已经停止运营,其开源代码仍是宝贵的资源,可以在其 GitHub 上查看。
其他可能的使用场景包括:使用 Geyser 插件监控 DeFi 平台上的账户余额、提供网络健康指标,或实时监控供应链事件。
创建你自己的 Solana Geyser 插件
以下是构建自有插件所需的一些资源和组件:
Solana Geyser 插件脚手架
Solana Geyser 插件脚手架是开启 Solana Geyser 插件开发之旅最易用的资源。该脚手架是一个精简模板,用于记录插件管理器与插件本身之间的交互。它是熟悉插件工作流和调试技术的绝佳起点。
插件管理器
插件管理器是负责管理所有 Geyser 插件生命周期和交互的核心组件。它可以在运行时动态加载和卸载插件,从而提供更高的灵活性和模块化能力。
运行时,插件管理器会将配置文件的路径传递给你的插件。这样,你就能在不更改插件代码的情况下修改 Geyser 插件的自定义设置。
要将插件集成到验证者中,你需要使用 --geyser-plugin-config 参数指定动态库路径。该参数会告诉验证者到哪里查找插件及其相关配置。
配置文件至少必须采用 JSON 格式,并包含 Geyser 插件动态库的路径,在 Linux 上即 .so 文件。最精简的配置文件如下所示:
{
"libpath": "/.so"
}从零创建 Geyser 插件
如果你想另辟蹊径,在不使用脚手架或修改现有插件的情况下创建自己的 Geyser 插件,就需要使用 Geyser 插件接口编写插件。
插件必须实现 GeyserPlugin trait,才能与运行时配合工作。此外,动态库必须导出一个用于创建插件实现的“C”函数 _create_plugin。
例如,可以创建一个实现 GeyserPlugin trait 的 Webhook 插件:
#[no_mangle]
#[allow(improper_ctypes_definitions)]
/// # Safety
///
/// This function returns the WebhookPlugin pointer as trait GeyserPlugin.
pub unsafe extern "C" fn _create_plugin() -> *mut dyn GeyserPlugin {
let plugin = WebhookPlugin::new();
let plugin: Box = Box::new(plugin);
Box::into_raw(plugin)
}这里,我们创建了一个使用 C 调用约定 extern "C" 的不安全公共函数,使其与 C 和其他语言兼容。函数 fn _create*_*plugin() -> *mut dyn GeyserPlugin 本身返回一个指向 dynGeyserPlugin 的可变裸指针,而后者就是 GeyserPlugin trait。函数体会创建一个新的 WebhookPlugin 实例,将该实例装箱为 trait 对象,然后把装箱后的 trait 对象转换为裸指针,以便函数返回。
因此,创建自己的 Geyser 插件需要执行以下步骤:
- 构建实现 Solana Geyser 插件接口的插件
- 从
target/release或target/debug文件夹中获取动态库(.so文件) - 创建一个
geyser-config.json文件,该文件必须在“libpath”字段中包含 Geyser 插件动态库的路径 - 使用
--geyser-plugin-config geyser-config.json标志启动验证者
这些步骤听起来相当简单,但实际运行和维护 Solana Geyser 插件的过程可能非常繁琐。
Helius Geyser 流式传输
Helius 以在 Solana 上提供无与伦比的开发者体验而闻名。对 Solana 的专注让 Helius 积累了丰富经验,成功应对各种挑战,并促成了众多大规模集成。Helius 拥有独特优势,能够解决开发者可能遇到的任何问题。
在 Helius,我们为 Solana 生态系统中的多个高绩效团队管理 Geyser 插件。我们运营专用的 Geyser 集群,并增加了冗余和容错能力,确保你无需担心数据丢失或服务中断。通过我们的编程式 API 访问,你可以动态修改 Geyser 插件,而不必担心可靠性。管理 Geyser 插件往往是一项艰巨的任务,因为你需要负责确保数据的一致性、可靠性和可用性。何不让 Helius 替你完成这些工作?
如果你对 Geyser 流式传输感兴趣,可以在 Helius 控制面板中订购专用节点,或通过 Discord 联系我们,立即开始使用。
总结
恭喜!
本文通过研究 Solana Geyser 插件,带你了解了数据复制和 RPC 负载管理的复杂机制。理解这个系统并非易事——其架构十分复杂,相关文档却寥寥无几,但它为 Solana 开发者提供了丰富的自定义和性能优化机会。
本文介绍的知识非常宝贵,尤其适合希望在 Solana 上构建或管理高性能应用的开发者或团队。理解 Geyser 插件至关重要,因为它为 Solana 生态系统提供了一种可扩展且可靠的解决方案。
如果你已经读到这里,匿名朋友,谢谢你!
其他资源
相关文章
订阅 Helius
及时了解 Solana 开发的最新动态,并在我们发布新内容时收到更新


