什么是排序服务?排序服务为什么重要?在前不久的文章中,我曾经介绍在Fabric生产线蓝图中其V3.0将重推BFT排序服务。但是,V3.0是仍处于未正式发行阶段的版本。
本文中,我们将介绍排序的概念、排序节点是如何与 Peer 节点交互的、它们在交易流程中如何发挥作用以及当前版本的Fabric中可用的排序服务的实现方式。此外,我们尤其关注V2.X阶段官方高度建议的 Raft 排序服务实现。
什么是排序?
许多分布式区块链,如Ethereum和Bitcoin,都是非许可的。这意味着,任何节点都可以参与共识过程,在共识过程中,交易被排序并打包成区块。因此,这些系统依靠概率共识算法(probabilistic consensus algorithms )最终保证账本一致性高的概率,但仍容易出现相异的账本(也称为账本“分叉”),这造成在网络中不同的参与者对于交易顺序存在不同的观点。
相比之下,Hyperledger Fabric采取与上面不同的工作方式。它使用了一种称为排序节点(Orderer)的节点使交易有序,并与其他排序节点一起形成一个排序服务。因为 Fabric 的设计依赖于确定性的共识算法(deterministic consensus algorithms);所以, Peer 节点所验证的区块都是最终的和正确的。账本不会像其他分布式的以及无需许可的区块链中那样产生分叉。
除了(1)促进确定性之外,排序节点还(2)将链码执行的背书(发生在节点)与排序分离,这在性能和可伸缩性方面给 Fabric网络提供了优势,因为这有利于消除由同一个节点执行和排序时可能出现的瓶颈。
排序节点和通道配置
排序节点还(3)强制执行通道的基本访问控制,限制谁可以向通道读取和写入数据,以及谁可以配置通道。
请记住,有权修改通道中配置元素的人员受相关管理员在创建联合体或通道时设置的策略的约束。配置交易由排序节点处理,因为它需要知道当前的一组策略才能执行其基本形式的访问控制。在这种情况下,排序节点会处理配置更新,以确保请求者具有适当的管理权限。如果是,排序节点将根据现有配置验证更新请求,生成一个新的配置交易,并将其打包到一个块中,该块将中继到通道上的所有Peer节点。然后,Peer节点处理配置交易,以验证排序节点批准的修改是否确实满足通道中定义的策略。
[补注]在1.X版本中,排序节点还维护着允许创建通道的组织列表。此组织列表称为“联盟”,列表本身保存在“排序节点系统通道”(也称为“排序系统通道”)的配置中。默认情况下,此列表及其所在的通道只能由排序节点管理员编辑。
当前2.X版本中已经取消这一管理机制!!!
排序节点和身份
与区块链网络交互的所有东西,包括节点、应用程序、管理员和排序节点,都从它们的数字证书和成员服务提供者(MSP)定义中获取它们的组织身份。
与 Peer 节点一样,排序节点也属于某一个组织。像 Peer 节点一样,每个组织也都是使用单独的证书授权中心(CA)。这个 CA 是否将作为根 CA 发挥作用,或者您是否选择部署根 CA,然后部署与该根 CA 关联的中间 CA,这全部取决于Fabric区块链开发者(当然根本上还是取决于应用程序的最终用户)。
排序节点和交易流程
阶段一:交易提案和背书¶
Peer 节点在整个Fabric网络中,构成网络的基础,主要是负责托管账本,应用程序可以通过智能合约查询和更新这些账本。
具体来说,更新账本的应用程序涉及到三个阶段,该过程确保区块链网络中的所有节点保持它们的账本彼此一致。
在第一阶段中,客户端应用程序通过可信任的Peer节点向Fabric网关服务发送交易建议。该Peer节点执行所提议的交易或将其转发给其组织中的另一Peer节点以供执行。
网关还将交易转发给认可策略所需的组织中的Peer节点。这些认可的Peer节点运行交易并将交易响应返回给网关服务。他们目前没有将拟议的更新应用于他们的分类账副本。认可的交易提案最终将在第二阶段被分为多个区块,然后在第三阶段分发给所有同行进行最终验证并提交给分类账。
注意:Fabric v2.3 SDK将v2.4 Fabric Gateway服务的逻辑嵌入到客户端应用程序中。也就是说,V2.4已经独立出Fabric Gateway服务,但V2.3客户端应用开发时需要使用Fabric v2.3 SDK+本版本提供的Fabric Gateway服务。有关详情,读者可参考李晓黎老师图书《Go语言Hyperledger区块链开发实战》(人民邮电出版社,2022-12)10.4节有关内容。
阶段二:交易提交与排序
随着第一个交易阶段(提案)的成功完成,客户端应用程序已经从Fabric网关服务接收到用于签名的认可的交易提案响应。对于经背书认可的交易,网关服务将交易转发给排序服务,排序服务将交易与其他认可的交易一起排序,并将它们打包到一个块中。
排序服务创建这些交易块,这些交易块最终将分发给通道上的所有Peer 节点,以便在第三阶段进行验证并提交给分类账。区块本身也是有序的,是区块链账本的基本组成部分。
排序服务节点同时接收来自许多不同应用程序客户端的交易(通过网关)。这些排序服务节点共同形成排序服务,排序服务可以由多个通道共享。
区块中的交易数量取决于区块的期望大小和最大间隔时间相关的通道配置参数(确切地说,是 BatchSize
和 BatchTimeout
参数)。然后将这些区块保存到排序节点的账本中,并分发给已经加入通道的所有节点。如果此时恰好有一个 Peer 节点关闭,或者稍后加入通道,它将在重新连接到排序服务节点或与另一个 Peer 节点通信之后接收到这些区块。我们将在第三阶段看到节点如何处理这个区块。
排序节点的第一个角色是打包提案的账本更新。在本例中,应用程序 A1 向排序节点 O1 发送由 E1 和 E2 背书的交易 T1。同时,应用程序 A2 将 E1 背书的交易 T2 发送给排序节点 O1。O1 将来自应用程序 A1 的交易 T1 和来自应用程序 A2 的交易 T2 以及来自网络中其他应用程序的交易打包到区块 B2 中。我们可以看到,在 B2 中,交易顺序是 T1、T2、T3、T4、T6、T5,但这可能不是这些交易到达排序节点的顺序!(这个例子显示了一个非常简单的排序服务配置,只有一个排序节点。)
值得注意的是,一个区块中交易的顺序不一定与排序服务接收的顺序相同,因为可能有多个排序服务节点几乎同时接收交易。重要的是,排序服务将交易放入严格的顺序中,并且 Peer 节点在验证和提交交易时将使用这个顺序。
区块内交易的严格排序使得 Hyperledger Fabric 与其他区块链稍有不同,在其他区块链中,相同的交易可以被打包成多个不同的区块,从而形成一个链。在 Hyperledger Fabric 中,由排序服务生成的区块是最终的。一旦一笔交易被写进一个区块,它在账本中的地位就得到了保证。正如我们前面所说,Hyperledger Fabric 的最终性意味着没有账本分叉,也就是说,经过验证的交易永远不会被重写或删除。
我们还可以看到,虽然Peer节点执行智能合约并处理交易,而排序节点不会这样做。到达排序节点的每个授权交易都被机械地打包在一个区块中,排序节点不判断交易的内容(前面提到的通道配置交易除外)。
在第二阶段的最后,我们看到排序节点负责一些简单但重要的过程,包括收集已提案的交易更新、排序并将它们打包成区块、准备分发。
阶段三:交易验证和提交
交易工作流的第三个阶段主要任务包括:
(1)将经过排序和打包的区块从订购服务分发到通道的Peer节点;
(2)对这些包含交易信息的区块进行验证并提交到分类账本中。
第三阶段从排序服务开始,将区块分发给所有通道上的Peer 节点。值得注意的是,并不是每个Peer 节点都需要连接到一个排序节点——Peer 节点可以使用 gossip协议将块关联到其他节点——尽管建议直接从排序服务接收块。
每个Peer 节点将独立地验证分布式的区块,确保分类账保持一致。具体来说,通道中的每个Peer 节点将验证块中的每个交易,以确保它已得到所需组织的节点背书,也就是节点的背书和背书策略要相匹配,并且不会因最初认可该交易时可能正在运行的其他最近提交的交易而失效。无效的交易记录仍然保留在排序节点创建的不可变块中,但它们被Peer 节点标记为无效,并且不会更新分类帐的状态。
排序节点的第二个角色是将区块分发给 Peer 节点。在本例中,排序节点 O1 将区块 B2 分配给节点 P1 和 P2。节点 P1 处理区块 B2,在 P1 上的账本 L1 中添加一个新区块。同时,节点 P2 处理区块 B2,从而将一个新区块添加到 P2 上的账本 L1中。一旦这个过程完成,节点 P1 和 P2 上的账本 L1 就会保持一致的更新,并且每个节点都可以通知与之连接的应用程序交易已经被处理。
总之,第三阶段看到的是由排序服务生成的区块一致地应用于账本。将交易严格地按区块排序,允许每个节点验证交易更新是否在整个区块链网络上一致地应用。
排序服务实现方案
虽然当前可用的每个排序服务都以相同的方式处理交易和配置更新,但是仍然存在好几种不同的实现方案可以在排序服务节点之间就严格的交易排序达成共识。它们是:
在Hyperledger Fabric网络中,一个节点或一组节点一起形成了所谓的“排序服务”,它将交易排序成块,然后Peer节点将验证并提交到他们的账本中。这将Fabric与其他分布式区块链(如Ethereum和Bitcoin)区分开来——在这些区块链中,这种排序由任何和所有节点完成。
仅用于测试和开发目的的Fabric网络通常具有仅由一个节点组成的排序服务(这些节点通常被称为“订购者”或“排序节点”),而生产环境中的Fabric网络则需要至少三个节点的更稳健的部署。
- 拜占庭容错(BFT,3.0版起新增)排序服务,顾名思义,不仅可以承受崩溃故障,还可以承受恶意行为的节点子集。现在,可以使用SmartBFT库作为其基础共识协议来运行BFT排序服务。如果需要真正的去中心化,则官方强烈考虑使用BFT排序服务,因为有些情况下由于恶意或被泄露原因,可能存在不信任多达三分之一(不包括三分之一)的排序节点。
- Raft (推荐)作为 v1.4.1 的新特性,Raft 是一种基于 etcd 中 Raft 协议实现的崩溃容错(Crash Fault Tolerant,CFT)排序服务。Raft 遵循“领导者跟随者(leader and follower)”模型,这个模型中,在每个通道上选举领导者节点,其决策被跟随者复制。Raft 排序服务会比基于 Kafka 的排序服务更容易设置和管理,它的设计允许不同的组织为分布式排序服务贡献节点。
Kafka(在 v2.0 中被弃用)和基于 Raft 的排序类似,Apache Kafka 也是一个 CFT 的实现,它也是使用“领导者和跟随者”节点配置。Kafka 利用一个 ZooKeeper 进行管理。基于 Kafka 的排序服务从 Fabric v1.0 开始就可以使用,但许多用户可能会发现管理 Kafka 集群的额外管理开销令人生畏或不受欢迎。- Solo (在 v2.0 中被弃用)
排序服务的 Solo 实现仅仅是为了测试,并且只包含了一个单一的排序节点。它已经被弃用了,可能会在将来的版本中被完全移除。官方建议是:既存的 Solo 用户应该迁移到一个单一节点的 Raft 网络上以便获得同样的功能。
Raft算法
有关如何配置 Raft 排序服务的信息,请参阅有关配置 Raft 排序服务文档。
对于将用于生产网络的排序服务,Fabric 实现了使用“领导者跟随者”模型的 Raft 协议,领导者是在一个通道的排序节点中动态选择的(这个集合的节点称为“共识者集合(consenter set)”),领导者将信息复制到跟随者节点。Raft 被称为“崩溃容错”是因为系统可以承受节点的损失,包括领导者节点,前提是要剩余大量的排序节点(称为“法定人数(quorum)”)。换句话说,如果一个通道中有三个节点,它可以承受一个节点的丢失(剩下两个节点)。如果一个通道中有五个节点,则可以丢失两个节点(剩下三个节点)。
从它们提供给网络或通道的服务的角度来看,Raft 和现有的基于 Kafka 的排序服务是相似的。它们都是使用领导者跟随者模型设计的 CFT 排序服务。如果您是应用程序开发人员、智能合约开发人员或节点管理员,您不会注意到基于 Raft 和 Kafka 的排序服务之间的功能差异。然而,有几个主要的差异值得考虑,特别是如果你打算管理一个排序服务的话:
- Raft 更容易设置。虽然 Kafka 有很多崇拜者,但即使是那些崇拜者也(通常)会承认部署 Kafka 集群及其 ZooKeeper 集群会很棘手,需要在 Kafka 基础设施和设置方面拥有高水平的专业知识。此外,使用 Kafka 管理的组件比使用 Raft 管理的组件多,这意味着有更多的地方会出现问题。Kafka 有自己的版本,必须与排序节点协调。使用 Raft,所有内容都会嵌入到您的排序节点中。
- Kafka 和 Zookeeper 并不是为了在大型网络上运行。Kafka 是 CFT,它应该在一组紧密的主机中运行。这意味着实际上,您需要有一个组织运行 Kafka 集群。考虑到这一点,在使用 Kafka 时,让不同组织运行排序节点不会给您带来太多的分散性,因为这些节点都将进入同一个由单个组织控制的 Kafka 集群。使用 Raft,每个组织都可以有自己的排序节点参与排序服务,从而形成一个更加分散的系统。
- Raft 是原生支持的,这就意味着用户需要自己去获得所需的镜像并且学习应该如何使用 Kafka 和 Zookeeper。同样,对 Kafka 相关问题的支持是通过 Apache 来处理的,Apache 是 Kafka 的开源开发者,而不是 Hyperledge Fabric。另一方面,Fabric Raft 的实现已经开发出来了,并将在 Fabric 开发人员社区及其支持设备中得到支持。
- Kafka 使用一个服务器池(称为“Kafka 代理”),而且排序组织的管理员要指定在特定通道上使用多少个节点,但是 Raft 允许用户指定哪个排序节点要部署到哪个通道。通过这种方式,节点组织可以确保如果他们也拥有一个排序节点,那么这个节点将成为该通道的排序服务的一部分,而不是信任并依赖一个中心来管理 Kafka 节点。
- Raft 是向开发拜占庭容错(BFT)排序服务迈出的第一步。正如我们将看到的,Fabric 开发中的一些决策是由这个驱动的。如果你对 BFT 感兴趣,学习如何使用 Raft 应该可以慢慢过渡。
由于所有这些原因,在 Fabric v2.0 中,对于基于 Kafka 的排序服务正在被弃用。
注意:与 Solo 和 Kafka 类似,在向客户发送回执后 Raft 排序服务也可能会丢失交易!例如,如果领导者和跟随者提供回执时同时崩溃。因此,应用程序客户端应该监听节点上的交易提交事件,而不是检查交易的有效性。但是应该格外小心,要确保客户机也能优雅地容忍在配置的时间内没有交易提交超时。根据应用程序的不同,在这种超时情况下可能需要重新提交交易或收集一组新的背书。
Raft概念
虽然 Raft 提供了许多与 Kafka 相同的功能(尽管它是一个简单易用的软件包)但它与 Kafka 的功能却大不相同,它向 Fabric 引入了许多新的概念,或改变了现有的概念。
- 日志条目(Log entry)。 Raft 排序服务中的主要工作单元是一个“日志条目”,该项的完整序列称为“日志”。如果大多数成员(换句话说是一个法定人数)同意条目及其顺序,则我们认为条目是一致的,然后将日志复制到不同排序节点上。
- 共识者集合(Consenter set)。主动参与给定通道的共识机制并接收该通道的日志副本的排序节点。这可以是所有可用的节点(在单个集群中或在多个集群中为系统通道提供服务),也可以是这些节点的一个子集。
- 有限状态机(Finite-State Machine,FSM)。Raft 中的每个排序节点都有一个 FSM,它们共同用于确保各个排序节点中的日志序列是确定(以相同的顺序编写)。
- 法定人数(Quorum)。描述需要确认提案的最小同意人数。对于每个共识者集合,这是大多数节点。在具有五个节点的集群中,必须有三个节点可用,才能有一个法定人数。如果节点的法定人数因任何原因不可用,则排序服务集群对于通道上的读和写操作都不可用,并且不能提交任何新日志。
- 领导者(Leader)。这并不是一个新概念,正如我们所说,Kafka 也使用了领导者,但是在任何给定的时间,通道的共识者集合都选择一个节点作为领导者,这一点非常重要(我们稍后将在 Raft 中描述这是如何发生的)。领导者负责接收新的日志条目,将它们复制到跟随者的排序节点,并在认为提交了某个条目时进行管理。这不是一种特殊类型的排序节点。它只是排序节点在某些时候可能扮演的角色,而不是由客观环境决定的其他角色。
- 跟随者(Follower)。再次强调,这不是一个新概念,但是理解跟随者的关键是跟随者从领导者那里接收日志并复制它们,确保日志保持一致。我们将在关于领导者选举的部分中看到,跟随者也会收到来自领导者的“心跳”消息。如果领导者在一段可配置的时间内停止发送这些消息,跟随者将发起一次领导者选举,它们中的一个将当选为新的领导者。
交易流程中的Raft
每个通道都在 Raft 协议的单独实例上运行,该协议允许每个实例选择不同的领导者。这种配置还允许在集群由不同组织控制的排序节点组成的用例中进一步分散服务。虽然所有 Raft 节点都必须是系统通道的一部分,但它们不一定必须是所有应用程序通道的一部分。通道创建者(和通道管理员)能够选择可用排序节点的子集,并根据需要添加或删除排序节点(只要一次只添加或删除一个节点)。
虽然这种配置以冗余心跳消息和线程的形式产生了更多的开销,但它为 BFT 奠定了必要的基础。
在 Raft 中,交易(以提案或配置更新的形式)由接收交易的排序节点自动路由到该通道的当前领导者。这意味着,Peer 节点和应用程序在任何特定时间都不需要知道谁是领导者节点,只有排序节点需要知道。
当排序节点检查完成后,将按照我们交易流程的第二阶段的描述,对交易进行排序、打包成区块、协商并分发。
架构说明
(1)Raft 是如何选举领导者的
尽管选举领导者的过程发生在排序节点的内部过程中,但是值得注意一下这个过程是如何工作的。
归纳来看,任何一个节点总是处于以下三种状态之一:
- 跟随者
- 候选人
- 或领导者
所有节点最初都是作为跟随者开始的。在这种状态下,他们可以接受来自领导者的日志条目(如果其中一个已经当选),或者为领导者投票。如果在一段时间内没有接收到日志条目或心跳(例如,5秒),节点将自己提升到候选状态。在候选状态中,节点从其他节点请求选票。如果候选人获得法定人数的选票,那么他就被提升为领导者。领导者必须接受新的日志条目并将其复制到跟随者。
要了解领导者选举过程的可视化表示,请查看数据的秘密生活。
(2)快照
如果一个排序节点宕机,它如何在重新启动时获得它丢失的日志?
虽然可以无限期地保留所有日志,但是为了节省磁盘空间,Raft 使用了一个称为“快照”的过程,在这个过程中,用户可以定义日志中要保留多少字节的数据。这个数据量将决定区块的数量(这取决于区块中的数据量。注意,快照中只存储完整的区块)。
例如,假设滞后副本 R1
刚刚重新连接到网络,它最新的区块是100
。领导者 L
位于第 196
块,并被配置为快照20个区块。R1
因此将从 L
接收区块 180
,然后为区块 101
到 180
区块 分发
请求。然后180
到 196
的区块将通过正常 Raft 协议复制到 R1
。
Kafka:在 v2.0中已被弃用
1.X版本的Fabric 支持的另一个崩溃容错(Crash Fault Tolerant,CFT)排序服务是对 Kafka 分布式流平台的改写,将其用作排序节点集群。Kafka 使用与 Raft 相同概念上的“领导者跟随者”配置,其中的交易(Kafka 称之为“消息”)从领导者节点复制到跟随者节点。就像 Raft 一样,在领导者节点宕机的情况下,一个跟随者可转变成为一个领导者,排序可以继续,以此来确保容错。
Kafka 集群的管理,包括任务协调、集群成员、访问控制和控制器选择等,由 ZooKeeper 集合及其相关 API 来处理。
Kafka 集群和 ZooKeeper 集合的设置是出了名的棘手,这也可能是在新的2.X版本中弃用Kafka流平台的一个主要原因。
BFT算法¶
有关如何部署和管理BFT排序节点的信息,请务必查看部署指南。
Fabric的BFT排序服务实现所使用的协议是SmartBFT协议,该协议深受BFT-SMART协议的启发,其本身可以被认为是开创性PBFT协议的非流水线(*)版本。与Raft一样,该协议指定一个单独的领导者,将交易批处理到一个块中,并将其发送到其他节点,称为追随者。然而,与Raft不同的是,每次跟随节点怀疑前一个领导者有故障时,领导者不是动态选择的,而是以循环方式旋转。
与Raft进一步不同的是,在Raft中,排序服务需要一半以上的节点才能发挥作用,BFT协议可以承受多达(不包括)三分之一节点的故障。如果三分之一或更多的节点崩溃或无法访问,则无法就任何区块达成一致。
BFT排序节点相对于Raft排序节点的优势在于,它可以承受一些被破坏的节点。事实上,如果多达(但不包括)三分之一的排序节点被恶意方控制,系统仍然可以接受新的交易,对它们进行排序,最重要的是确保其余排序节点提交相同的块。这与Raft形成了鲜明对比,Raft不适合部署在如此苛刻的对手模型中。
BFT排序节点的操作与Raft排序节点的运行方式相同:在系统运行时,可以动态地从通道中添加和删除新节点,重新配置指南中对此进行了描述。
与Raft类似,BFT领导者定期向每个追随者发送心跳,如果后者在一段时间内没有收到领导者的消息,它就会开始游说其他追随者更换领导者。
与Raft的一个主要区别是,当客户端向BFT排序服务提交交易时,它应该将交易发送到所有节点,而不是单个节点。将交易发送到所有节点不仅可以确保它已经到达领导节点,还可以确保即使领导节点是恶意的,并且忽略了客户端的交易,它最终也将别无选择,只能将交易包括在未来的某个块中,或者面临被从客户端接收到交易并失去耐心等待其被包括在领导者发送的块中的跟随者节点推翻。
虽然将交易发送到所有节点似乎是一个缺点,但BFT交易语义实际上比Raft交易语义有一个隐含的优点:在Raft中,将交易发送给订购者并不能保证它包含在块中,也不能保证将它发送到所有节点,因为领导者可能会崩溃,然后交易可能会丢失。然而,在BFT中,即使领导者崩溃,交易仍将保留在跟随节点的内存中,并最终被发送到领导者,然后被包含在块中,或者领导者将被迫更改为最终包含交易的新领导者。
通过网关服务提交交易的应用程序不需要更改任何内容,因为网关服务知道它应该根据通道的配置提交给所有排序节点还是只提交给一个排序节点,并相应地采取行动。
(*)一个没有管道的共识协议只有在前一个区块达成一致后才能就区块达成一致。