Solana Geyser 插件:以光速流式传输数据

  • Helius
  • 发布于 2023-10-15 11:21
  • 阅读 8

本文介绍了Solana Geyser Plugins的设计与实现,探讨了其在数据复制和RPC负载管理中的作用。文章深入分析了Geyser Plugins的功能、接口及其开发方法,并总结了当前可用的Geyser Plugins,以及如何创建自己的插件。

13分钟阅读

2023年10月13日

本文是关于什么的?

Geyser Plugins 是模块化组件,旨在将有关账户、槽、区块和交易的数据传递到外部数据存储,从而使开发者能够消除验证器的RPC(远程过程调用)负载。Geyser Plugins 为希望自定义其数据流和处理需求的开发者提供了一种灵活的解决方案。

在本文中,我们将深入探讨 Solana Geyser Plugins 的细节。我们将先探讨 AccountsDB 副本,这是一种被提议的数据复制和负载管理方法,但最终选用了 Geyser Plugins。

接下来,我们将详细分析 Geyser Plugins 的定义、功能以及如何通过插件接口进行结构化。

之后,我们将讨论可用的常见 Geyser Plugins,并指导你完成创建自己的复杂过程。最后,我们将谈论 Helius 以及我们如何简化 Solana 上的数据流。

AccountsDB 副本:被放弃的数据复制和 RPC 负载方法

Solana 探索了多种途径以解决 RPC 负载和数据复制的挑战。其中一种有前途的方法是使用 AccountsDB 副本。这些副本旨在将账户扫描请求从主验证器卸载到 AccountsDB 副本。尽管该系统前景可期,但其本质上复杂,并且需要一套新服务以确保主验证器与副本之间的同步。最终,这一提议被舍弃,转而使用 Geyser Plugin 系统——这一解决方案更容易被验证器客户端支持,并且为开发者在实现其应用时提供了更多的灵活性。

那么,Solana Geyser Plugins 究竟是什么?

Solana Geyser Plugins 是什么?

Solana Geyser Plugins 提供对 Solana 数据的低延迟访问,可以服务于替代需要在验证器上进行的 RPC 调用的应用程序。例如,如果一个验证器必须在快速连续的情况下处理大量 getProgramAccounts 调用,验证器可能由于这种强烈的流量而落后于网络。

Geyser Plugins 通过将有关账户、区块、槽和交易的信息重新定向到外部数据存储(如关系数据库、NoSQL 数据库或 Kafka),来解决此问题。

这种数据的重定向允许 RPC 服务为那些希望从这些外部数据存储中获取数据的用户提供更灵活的优化,例如缓存和索引。

Geyser Plugins 在 Solana 和外部数据存储解决方案之间充当桥梁。它们使开发者能够从验证器中转移大量数据管理任务,从而提高性能并降低潜在瓶颈的风险。

Geyser Plugins 确保验证器与网络保持同步,无论 RPC 流量的大小如何。

Geyser Plugin 接口

开发者可以使用 Solana Geyser Plugin 接口构建 Geyser Plugins。该接口提供对账户、交易、槽、区块元数据和条目的访问。它在 solana-geyser-plugin-interface crate 中声明,并由 GeyserPlugin trait 定义。

该 trait 定义了一些方法,每个方法以 update_ 为前缀,在生成新数据或更新现有数据时被调用。Geyser Plugins 还需指定其在加载和卸载过程中的行为。该 trait 概述了 Geyser Plugin 应实现的基本方法,以确保根据其所需的插件行为进行高效的数据流。

源代码

代码

pub trait GeyserPlugin:Any +Send +Sync +Debug {
    // 必需方法
    fn name(&self) -> &'static str;

    // 提供的方法
    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 Plugin 生态系统中所有插件的基础接口。它被声明为一个公共 trait,具有来自 Rust 标准库的 AnySendSyncDebug trait 边界。这些 trait 边界如下:

  • Any 允许进行类型反射,这使得向下转型为具体类型成为可能
  • Send 表示实现此 trait 的类型的所有权可以在线程之间转移
  • Sync 表示实现此 trait 的类型的引用可以在多个线程之间共享
  • Debug 允许格式化类型以进行输出,特别是为了调试目的

AnyDebug 对我们并不重要。

真正重要的是,GeyserPlugin 需要 SendSync 以确保程序的线程安全。

必需方法

代码

fn name(&self) -> &'static str;

name 方法是任何实现 GeyserPlugin 的类型所必需的。该方法作为 Geyser Plugin 的标识符,返回一个表示 Geyser Plugin 名称的静态字符串切片。

除了 on_loadon_unload,该方法和所有其他方法使用 &self 而不是 &mut self,这是 Solana 1.16 更新的新特性。这显著提高了性能,消除了每次调用其函数时需要将 Geyser Plugin 包装在读写锁中并获取写锁的需要。

提供的方法

该 trait 有多种提供的方法,包含默认实现,可以被 GeyserPlugin 的实现覆盖。

代码

fn on_load(&mut self, _config_file: &str) ->Result<()> { ... }

on_load 方法是在系统加载插件时调用的回调,用于插件所需的任何初始化。它接受一个引用,代表指向配置文件的路径。该配置必须是 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 枚举实例。

SlotStatus 概述了 Solana 中槽的三种状态:

  • Processed - 节点已处理的最高槽。尽管该槽既未确认也未最终确定,但它是验证器认为最有可能成为规范的链的一部分
  • Confirmed - 槽获得足够的投票被视为安全且是链的一部分。该槽得到了 Solana 中超级多数验证器的支持
  • Rooted - 槽现在是区块链的永久部分,所有其他版本或分支的链必须建立在此槽之上。这意味着网络上的所有分支都源于该区块

代码

fn notify_transaction(
        &self,
        transaction:ReplicaTransactionInfoVersions<'_>,
        slot: Slot
    ) ->Result<()> { ... }

notify_transaction 方法在槽中的交易被处理时调用,通知插件交易的详细信息。

ReplicaTransactionInfoVersions 是处理 ReplicaTransactionInfo 的枚举包装类。如果 RepicaTransactionInfo 的结构更改,则会为更新版本添加新的枚举条目。这将迫使插件实现处理该更改,适应一个新的 枚举 条目。目前,该枚举封装了两种变体:

  1. V0_0_1(&'a ReplicaTransactionInfo<'a>)
  2. 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> 变体。

该变体 是一个 结构,包含有关条目的槽、区块中的索引、之前条目的哈希次数、条目的 SHA-256 哈希和条目中执行的交易数量的信息。

代码

fn notify_block_metadata(
        &self,
        blockinfo:ReplicaBlockInfoVersions<'_>
    ) ->Result<()> { ... }

notify_block_metadata 方法在区块的元数据更新时调用。它接受一个 ReplicaBlockInfoVersions 枚举实例以获取其区块信息。枚举 是各种 ReplicaBlockInfo 版本的包装,其中包含有关区块的信息,例如其槽、哈希、奖励、区块时间、区块高度等。

代码

fn account_data_notifications_enabled(&self) ->bool { ... }
fn transaction_notifications_enabled(&self) ->bool { ... }
fn entry_notifications_enabled(&self) ->bool { ... }

这些方法返回布尔值,指示插件是否希望启用账户数据、交易和条目的通知。

关于确认级别的说明

Geyser 会在账户数据和交易被处理后立即发送更新。这对端到端索引速度是有益的,然而,存在被处理的槽可能被跳过的风险。

被跳过的槽 指的是由于领导者离线或包含该槽的分叉被放弃而未产生区块的过去槽。流化到的数据存储系统必须识别这种可能性并相应地管理更新。

常见的 Solana Geyser Plugins

开发者可以使用多种 Solana Geyser Plugins,甚至可以对其进行分叉以满足特定需求。一些值得注意的插件包括:

  • PostgreSQL Plugin:用于使用 PostgreSQL 管理和查询数据
  • gRPC Service Streaming Plugin:用于将 Solana 账户更新流化到 gRPC 服务
  • RabbitMQ Producer Plugin:用于与 RabbitMQ 进行消息排队
  • Kafka Producer Plugin:用于使用 Kafka 流化数据
  • Amazon SQS Plugin:用于利用亚马逊简单队列服务进行消息排队
  • Google BigTable Plugin:用于使用 Google BigTable 管理和查询数据

这些插件可以适应多种用例。

例如,Clockwork 利用 Geyser Plugin 来计划交易和构建自动化、事件驱动的 Solana 程序。尽管该项目已停止,但其开源代码仍然是一个宝贵的资源,可以在他们的 GitHub 查看。

其他可能的用例可能包括使用 Geyser Plugins 监控 DeFi 平台上的账户余额,提供网络健康指标,或实时监控供应链事件。

创建你自己的 Solana Geyser Plugin

以下是一些资源和组件,以构建你自己的插件:

Solana Geyser 插件脚手架

Solana Geyser 插件脚手架是最简单的资源,用于开始你的 Solana Geyser Plugin 开发之旅。这个脚手架充当一个简约模板,记录了 插件管理器 和插件本身之间的交互。这是一个很好的起点,可以熟悉插件工作流以及调试技术。

插件管理器

插件管理器是控制所有 Geyser Plugins 生命周期和交互的核心组件。它能够在运行时动态加载和卸载插件,从而提供更大的灵活性和模块化功能。

在运行时,插件管理器将配置文件的路径传递给你的插件。这使得 Geyser Plugins 中的可自定义设置能够在不改变插件代码的情况下进行修改。

要将插件集成到验证器中,你需要使用 --geyser-plugin-config 参数指定动态库路径。这告诉验证器在何处找到插件及其相关配置。

至少,配置文件必须为 JSON 格式,并包含指向 Geyser Plugin 动态库的路径 - .so 在 Linux 上。一个最小的配置文件大致看起来如下:

代码

{
    "libpath": "/.so"
}

从头创建 Geyser Plugin

如果你想走不寻常的道路,并在不使用脚手架或修改现有插件的情况下创建自己的 Geyser Plugin,你需要使用 Geyser Plugin 接口来编码你的插件。

插件 必须 实现 GeyserPlugin trait 才能在运行时工作。此外,动态库必须导出一个 “C” 函数 _create_plugin,该函数创建插件的实现。

一个示例是创建一个实现了 GeyserPlugin trait 的 Webhook 插件:

代码

#[no_mangle]
#[allow(improper_ctypes_definitions)]
/// # 安全
///
/// 此函数返回指向 WebhookPlugin 的指针作为 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 的新实例,将该实例作为特征对象包装,然后将包装的特征对象转换为原始指针,以便由函数返回。

因此,创建你自己的 Geyser Plugin 的步骤如下:

  • 构建实现 Solana Geyser Plugin 接口的插件
  • target/releasetarget/debug 文件夹获取动态库( .so 文件)
  • 创建一个 geyser-config.json 文件,必须在 “libpath” 字段下包含指向 Geyser Plugin 动态库的路径
  • 使用 --geyser-plugin-config geyser-config.json 标志启动你的验证器

这些步骤听起来相当简单,但实际上运行和维护 Solana Geyser Plugin 的过程可能相当繁琐。

Helius Geyser 流式处理

Helius 以在 Solana 上提供无与伦比的开发者体验而闻名。这一独特的专注于 Solana,赋予了 Helius 丰富的经验,使其能够应对各种挑战并促进众多大规模集成。Helius 独特地处于一个有利位置,可以解决开发者可能面临的任何问题。

在 Helius 中,我们为 Solana 生态系统内的若干高性能团队管理 Geyser Plugins。我们运营着具有额外冗余和故障容忍的专用 Geyser 集群,以确保你不会担心缺失数据或停机的问题。我们的程序化 API 访问允许你动态修改 Geyser 插件,而无需担心可靠性。管理 Geyser Plugins 通常是一项艰巨的任务,因为你必须负责确保数据的一致性、可靠性和可用性。为什么不让 Helius 来为你处理这些呢?

如果你对 Geyser 流式处理感兴趣,请在你的 Helius 控制面板中 订购一个专用节点,或通过 Discord 联系我们以立即开始。

结论

恭喜!

在本文中,我们通过研究 Solana Geyser Plugins,导航了数据复制和 RPC 负载管理的复杂性。理解这个系统并非易事——它是一个几乎没有文档的复杂架构,但为 Solana 开发者提供了大量自定义和性能优化的机会。

本文中获得的知识是无价的,尤其是对于希望在 Solana 上构建或管理高性能应用的开发者或团队来说。Geyser Plugins 是理解的关键,因为它们为 Solana 生态系统提供了一种可扩展且可靠的解决方案。

如果你读到了这里,非常感谢!

其他资源

  • 原文链接: helius.dev/blog/solana-g...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Helius
Helius
https://www.helius.dev/