排序服务

受众:架构师、排序服务管理员、通道创建者

本章节将概念性地介绍排序的概念、排序节点(Orderer)是如何与 Peer 节点交互的、它们在交易流程中扮演的角色以及当前可用的排序服务的实现方式,其中,重点介绍了推荐使用的 Raft 排序服务实现。

什么是排序?

许多分布式区块链,如以太坊(Ethereum)和比特币(Bitcoin),都是非许可的,这意味着任何节点都可以参与共识过程,在共识过程中,交易被排序并打包成区块。基于这个事实,这些系统依靠概率共识算法,最终保证账本的一致性可以达到很高的概率,但仍容易受到不同账本(亦称为一个账本“分叉”)的影响,因为在网络中不同的参与者对于交易顺序有不同的看法。

Hyperledger Fabric 的工作方式不同。它特有一种称为排序节点(Orderer)的节点用于交易排序,并与其它排序节点一起形成一个排序服务。因为 Fabric 的设计依赖于确定性共识算法,任何由 Peer 节点所验证的区块都保证是最终的和正确的,所以账本不会像在其它分布式的且无需许可的区块链网络那样产生分叉。

除了改进共识的确定性之外,将链码执行交易的背书(发生在 Peer 节点)与交易排序分离,这在性能和可伸缩性方面也给 Fabric 带来了优势,消除了由同一个节点执行交易和排序交易可能出现的瓶颈。

排序节点和通道配置

排序节点也加强了各个通道的基础访问控制,限制着谁能在通道中进行读、写,谁能进行通道配置。记住,在一个通道中,谁被授权可以修改通道配置项,是由策略约束的,而该策略又是由最初创建联盟或通道的相关管理员(Administrator)设定的。配置交易均会被排序节点处理,因为排序节点需要知道当前最新的策略集合,以执行其基础的访问控制功能。例如,当排序节点处理一笔配置更新交易时,需要确定交易的发起者(requestor)是否有合适的管理员权限,如果有,排序节点会通过当前已存在的配置,去验证更新配置的请求,并生成一个新的配置交易,将之打包成一个块(译者注: Fabric 中,一个配置交易会单独打包成一个块),之后该块将被转发给通道中所有的 Peer 节点。然后, Peer 节点也会处理这笔交易,目的是验证由排序节点批准的配置修改确实可以满足通道中定义的策略。

排序节点和身份

与区块链网络交互的所有参与者,包括 Peer 节点、应用程序、管理员和排序节点,都需要从它们的数字证书和成员服务提供者(MSP)定义中获取它们的组织身份。

有关身份和 MSP 的更多信息,请查看我们关于 身份成员 的文档。

与 Peer 节点一样,每个排序节点都各自归属于一个组织。也与 Peer 节点类似,每个组织使用单独的证书授权机构(CA)。是否将这个 CA 作为根 CA 发挥作用,或者您是否选择部署根 CA,然后再部署由该根 CA 签发的中间 CA,这些您都可以自由决定。

排序节点和交易流程

阶段一:交易的提案和背书

Peer 节点 的章节中,我们已经看到, Peer 节点构成了区块链网络的基础,并各自持有账本,应用程序可以通过智能合约查询和更新这些账本(译者注:一个通道内每个 Peer 节点持有的账本是最终一致的,也可以称为账本副本。智能合约一般运行于 Peer 节点,与 Peer 节点交互)。

具体来说,应用程序想去更新账本,会涉及到一个包含三个阶段的处理过程,该过程可以确保区块链网络中所有 Peer 节点的账本保持彼此的一致性。

第一阶段,客户端应用程序将交易提案发送给Fabric的网关服务(Gateway Service),网关服务会将提案交给一个可信的 Peer 节点。这个 Peer 节点会执行该交易或将之转发给同组织中的其它 Peer 节点去执行(译者注:执行交易后,一个 Peer 节点会代表其所属组织对交易应答进行签名,然后返回交易应答,就完成了背书的过程)。

出于背书策略的需要,网关服务也会将交易转发给其它组织的 Peer 节点,这些 Peer 节点也会运行交易并返回交易应答到网关服务。此时,提案中的更新还不会应用到这些 Peer 节点的账本副本中。已背书的交易最终会在第二阶段被排序并打包进区块(Block),之后在第三阶段被分发给所有的 Peer 节点,做最后的验证(Validation)和提交(Commitment)。

注意:v2.4 客户端应用中的 Fabric 网关服务的逻辑,已嵌入 Fabric v2.3 SDK 中——详情请参阅 v2.3 Applications and Peers

要深入了解第一个阶段,请参阅 Peer 节点 章节。

阶段二:交易的呈递和排序

在成功完成第一阶段后,客户端应用会从 Fabric 网关服务处收到一笔已背书的交易提案应答。对于这笔已背书的交易,网关服务会将之呈递给排序服务,排序服务会将这笔交易与其它的已背书交易进行排序,然后将它们全部打包进一个区块

排序服务生成这些包含交易的区块后,在第三阶段,这些区块最终会被分发给通道中所有的 Peer 节点进行验证,并被提交至账本。这些区块自身也是有序的,且是账本的基本组成部分。

排序服务节点同时从多个不同的应用客户端(通过网关服务)接收交易,这些排序服务节点共同构建起排序服务,并可以被多个通道共享(译者注:这里需要强调一下对排序的理解。这里的排序,与一般算法中的排序不太一样。算法中,排序过程会根据元素之间的关系,如大小关系,将元素挪动到其应处的位置,最终让所有元素形成一个顺序;而这里的排序是在处理交易的过程中所形成的顺序:排序节点不断地接收并缓存一笔笔来自不同应用的交易,当达到某个条件,排序节点会将缓存的一批交易作为一个区块,这个区块中的交易就形成了一个前后顺序,然后是下一个区块,再下一个区块,如此,所有的交易就形成了一个顺序)。

区块中的交易数量取决于通道配置中与期望大小、最大间隔时间相关的参数(确切地说,是 BatchSizeBatchTimeout 参数)。然后,这些区块将被保存到排序服务节点的账本中,并分发给通道中的所有 Peer 节点。如果有一个 Peer 节点此时恰好是宕机状态,或者稍后才加入通道,则它将通过 Gossip 服务与其它 Peer 节点通信并接收到这些区块。我们将在第三阶段看到 Peer 节点是如何处理这些区块的。

值得注意的是,一个区块中交易的顺序,无需与排序服务接收到这些交易时的顺序相同,因为可能会有多个排序服务节点几乎同时地接收到交易(译者注:即,接收的顺序不重要)。重要的是,排序服务会将这些交易置于一个严格的顺序中,并且 Peer 节点在验证和提交交易时将沿用这个顺序。

区块内交易的严格排序使得 Hyperledger Fabric 与其它区块链稍有不同,在其它区块链中,同一笔交易会被打包进多个不同的区块中,并相互竞争以形成一个链(译者注:如比特币,同一笔交易会被记入不同矿工生成的区块中,矿工间会竞争记账权,竞争获胜方的区块将成为最长合法链的一部分,而其余的区块则被视为无效的分叉并丢弃)。在 Hyperledger Fabric 中,由排序服务生成的区块是最终的。一旦一笔交易被写进一个区块,它在账本中的位置就得到了不可篡改的保证。正如我们前面所说,Hyperledger Fabric 的确定性(finality)意味着不存在账本分叉,也就是说,已验证、已提交的交易永远不会被重写或丢弃。

我们还可以看到,由于 Peer 节点负责运行智能合约(链码)并执行交易,排序节点自然不必再做这些事情。每笔已授权的交易到达排序节点后,只是被按部就班地打包进一个区块中,排序节点并不会研判交易的内容(除了前面提到的通道配置交易)。

到了这里的最后,我们明白了:排序节点负责着一些简单但至关重要的过程,包括收集已提案的交易更新、将它们排序并打包成区块、准备分发给通道中的 Peer 节点。

阶段三:交易的验证和提交

交易流程的第三个阶段,涉及从排序服务将已排序、已打包的区块分发给通道中的 Peer 节点,以执行验证(Validation)和提交(Commitment)至账本的工作。

第三阶段始于排序服务先将区块分发到通道中的所有 Peer 节点。值得注意的是,虽然我们建议从排序服务直接接收区块,但并不是每个 Peer 节点都需要连接到一个排序节点,Peer 节点也会使用 gossip 协议 将自身拥有的区块传播到其它节点。

每个 Peer 节点将独立地验证分发而来的区块,以确保账本保持一致。具体来说,通道中的每个 Peer 节点将验证区块中的每一笔交易,包括两点:一,确保每笔交易得到了所需组织的节点背书,即背书与背书策略相匹配;二,交易没有因最近已提交的其它交易而失效(译者注:例如,交易 T 从阶段一、二,到达此处的验证环节,是一个过程。这期间,其它交易的提交仍在持续进行,如提交了区块 B 。所以验证 T 时,可能 T 所使用的原始数据已经被 B 中的交易更改,则 T 使用的原始数据变为无效的旧数据,从而导致 T 无效)。无效的交易仍然会保留在由排序节点创建的区块中,但是 Peer 节点会将它们标记为无效,并且不会更新账本相应的状态。

Orderer2

排序节点的第二个角色是将区块分发给 Peer 节点。在此图例中,排序节点 O1 将区块 B2 分发给 Peer 节点 P1 、 P2。P1 处理区块 B2,结果是,会在 P1 的账本 L1 中添加一个新区块(译者注:即B2)。同时,P2 也会处理区块 B2,从而也将一个新区块添加到 P2 的账本 L1中。一旦这个过程完成,P1 和 P2 上的账本 L1 即会得到一致地更新,并且每个 Peer 节点都可以通知与之连接的应用程序:交易已经被处理。

总结一下:在第三阶段,我们看到的是含有多笔交易的、由排序服务生成的区块,这些区块通过 Peer 节点被一致地应用于账本。将交易严格地排序进区块中,使得整个通道内每个 Peer 节点在验证时,交易更新都可以被一致地应用。

要更深入地了解第三阶段,请参阅 Peer节点 章节。

排序服务的实现

虽然每个当前可用的排序服务都会以相同的方式去处理普通交易和配置更新,但在多个排序服务节点之间对“交易的严格排序”之事达成共识,仍然有几种不同的具体实现。

关于如何建立一个排序节点(无论该节点被用于何种实现)的信息,请查阅我们的文档 部署一个生产环境的排序服务

  • Raft (推荐)

    作为 v1.4.1 的新特性,Raft 是一种崩溃容错(Crash Fault Tolerant,CFT)排序服务,基于 etcd中对 Raft协议 的一种实现。Raft 遵循一种“领导者和跟随者”模型,在这个模型中,会选出一个领导节点,且该领导节点的任何决定(译者注:如提交一个区块)均会被跟随者复制。 Raft 排序服务会比基于 Kafka 的排序服务更容易设立和管理,且它的设计使得不同的组织都可以为分布式的排序服务提供节点。

  • Kafka (在 v2.x 中被弃用)

    与基于 Raft 的排序类似,Apache Kafka 也是一个使用“领导者和跟随者”节点配置的CFT实现。Kafka 利用一个 ZooKeeper 组实现管理目标。基于 Kafka 的排序服务从 Fabric v1.0 开始就可以使用,但许多用户可能会觉得,管理一个 Kafka 集群产生的额外管理负担,挺令人生畏或不受欢迎的。

  • Solo (在 v2.x 中被弃用)

    排序服务的 Solo 实现仅仅用于测试,并且只由一个单一的排序节点组成。它已经被弃用了,在将来发行版本中可能会被完全移除。现存的 Solo 用户应该迁移到一个单一节点的 Raft 网络去实现对等的功能。

Raft

一个排序节点的配置由orderer.yaml文件决定,关于如何自定义该配置文件,相关信息请查阅 生产环境排序节点的配置清单

作为生产网络环境首选的排序服务,Fabric 实现了著名的 Raft 协议,使用了一种“领导者和跟随者”的模型。在这个模型中,一个领导者会从通道的排序节点中被自动选举出来(这些节点的集合也被称为“共识者集群”),且该领导者会复制消息给一众跟随者节点。因为这个系统可以承受节点丢失,包括领导者节点——只要多数排序节点(也被称为”法定人数(quorum)”,译者注:半数以上)还在,所以 Raft 也被说成是“崩溃容错”(CFT)。换句话说,如果一个通道中有3个节点,整个系统可以忍受1个节点的丢失(剩下2个)。如果你有5个节点,则你可以丢失2个节点(剩下3个)。 Raft 排序服务的这种特性,是针对你的排序服务建立高可用策略的要素之一。此外,在生产环境下,你应该会想把排序节点分散到不同的数据中心,甚至是多个本地环境。例如,3个不同的数据中心各放1个节点。这样的话,如果一个数据中心或者整个本地环境失效,其它数据中心上的节点依然可以继续运行。

从它们给一个网络或通道提供服务的角度来看,Raft 和现有的基于 Kafka 的排序服务(我们将在稍后讨论)是相似的。它们都是使用“领导者跟随者”模型设计的 CFT 排序服务。如果你是应用程序开发人员、智能合约开发人员或 Peer 节点管理员,你不会感受到任何一个基于 Raft 和基于 Kafka 排序服务之间的功能性差异。然而,这里仍有几个主要差异值得考虑,特别是如果你打算管理一个排序服务。

  • Raft 更容易组建。虽然 Kafka 有很多簇拥者,但即使是那些簇拥者也(通常)会承认部署一个 Kafka 集群及其 ZooKeeper 组会很棘手,需要在 Kafka 基础设施和设置方面拥有高水平的专业知识。此外,使用 Kafka 管理的组件比 Raft 多,这就意味着有更多的地方会出现问题。Kafka 也有自己的版本,这些版本必须适用于您的排序节点。使用 Raft,则所有事情都全部嵌入了您的排序节点中
  • Kafka 和 Zookeeper 并不是为了在大型网络间运行而设计的。Kafka 是 CFT,它应该在一组紧密的主机中运行。这意味着实际上,您需要单一的组织运行 Kafka 集群。考虑到这一点,当使用 Kafka 时,让不同组织运行排序节点( Fabric 支持)不会给这些节点带来去中心化,因为这些节点最终都会进入一个由单个组织控制的 Kafka 集群。使用 Raft,则每个组织都可以有自己的排序节点,参与到排序服务,从而形成一个更加去中心化的系统。
  • Raft 是原生支持的,这就意味着用户需要自己去获得所需的镜像并且学习应该如何使用 Kafka 和 Zookeeper。同样,对 Kafka 相关问题的支持是通过 Apache 来处理的,Apache 是 Kafka 的开源开发者,而不是 Hyperledger Fabric。另一方面,Fabric Raft 的实现已经开发出来了,并将在 Fabric 开发人员社区及其支持设备中得到支持。
  • Kafka 使用一个服务器池(称为“ Kafka 代理群”),且排序节点的组织管理员指定其在特定通道上想使用多少个节点,而 Raft 则允许用户去指定哪个排序节点部署到哪个通道。通过这种方式, Peer 节点组织可以确定,如果他们也有一个排序节点,那么这个节点将成为该通道的排序服务的一部分,而不是让自己信任并依赖一个中心化的管理员来打理 Kafka 节点。
  • Raft 是 Fabric 向开发拜占庭容错(BFT)排序服务迈出的第一步。正如我们将看到的,Raft 开发中的一些决策是由这一点驱动的。如果你对 BFT 感兴趣,学习如何使用 Raft 应该可以更好地过渡。

出于以上所有的这些原因考虑,在 Fabric v2.x 中,基于 Kafka 的排序服务正在被弃用。

注意:与 Solo 和 Kafka 类似,在将已接收回执发送给客户端之后,Raft 排序服务仍可能丢失交易。例如,当一个跟随者节点提供已接收回执之时,几乎与此同时,领导节点宕机了(译者注:Raft属于“强势领导”协议,即一切决定均由领导节点做出。客户端发送交易 T 给跟随者节点,跟随者节点在返给客户端已接收应答后,是不会直接处理 T 的,而是要转交给领导者节点去处理,试想,如果此时领导节点宕机,T 将丢失)。因此,应用客户端应该监听 Peer 节点上的交易提交事件,且不要一味地只想着检查交易验证结果,也应额外的留意保证客户端能从容地忍受一个交易在理论时间框架内未被提交而产生的超时。

面对这样的一个超时,应用程序可以自行决定重新呈递原交易或收集一批新的背书。

Raft 相关概念

虽然 Raft 提供了许多与 Kafka 相同的特性——尽管它是一个简单易用的软件包——但它的功能基本上与 Kafka 是完全不同的,而且它向 Fabric 引入了许多新的概念,或转化了一些现有概念。

日志条目(Log entry)。 Raft 排序服务中主要的工作单元是一个“日志条目”,这些条目完整排序后被称为“日志”。如果大多数成员(换句话说是一个法定人数)认可条目及其顺序,则我们认为条目是一致的,然后会将这些日志复制到不同排序节点上。

共识者集合(Consenter set)。在指定的通道中,排序节点(译者注:作为共识者)会积极参与共识机制并接收该通道内的日志副本。

有限状态机(Finite-State Machine,FSM)。Raft 中每个排序节点都有一个 FSM,它们共同用于确保各个排序节点中的日志序列是确定的(以相同的顺序写入)。

法定人数(Quorum)。定义了需要确认提案的最小同意人数,如此,交易才能被排序。对于每个共识者集合而言,这是大多数节点。在拥有5个节点的集群中,必须有3个节点可用,才能达到法定人数。无论什么原因,一旦法定人数的节点不可用,则排序服务集群对于通道上的读、写操作都变为不可用,且不能提交任何新的日志。

领导者(Leader)。这并不是一个新概念,正如我们所说,Kafka 也使用了领导者,但理解这一点很关键:在任何给定的时间,通道的共识者集合都会选举一个单一节点作为领导者(我们稍后将描述在 Raft 中,该选举是如何发生的)。领导者负责接收新的日志条目,将它们复制到作为跟随者的排序节点,并在认为某个条目已提交后进行管理(译者注:如通知其它跟随者节点也进行相应的提交操作)。它不是一种特殊类型的排序节点。它只是随着客观环境决定,在某段确定的时间内,一个排序节点可以而其它节点不可以扮演的角色。

跟随者(Follower)。再一次的,这不是一个新概念,但是理解这一点很关键:跟随者从领导者处接收日志并如实地复制它们,以确保日志保持一致。正如我们将在领导者选举章节看到的,跟随者也会收到来自领导者的“心跳”消息。当领导者在一个可配置的时间范围内停止发送这些心跳消息,跟随者将发起一次领导者选举,它们中的一个将当选为新的领导者。

交易流程中的 Raft

每个通道都在一个单独的 Raft 协议实例上运行,该协议允许每个实例选举不同的领导者。在集群由不同组织控制的排序节点组成的用例中,这种配置还可以形成进一步去中心化的服务。排序节点可以随需求从通道中添加或删除,只要一次一个地进行添加或删除即可。虽然这种配置以冗余心跳消息和协程的形式产生了更多的开销,但它为 BFT 奠定了必要的基础。

在 Raft 中,交易(以提案或配置更新的形式)由接收交易的排序节点自动路由到该通道当前的领导者。这意味着 Peer 节点和应用程序在任何特定时间都不需要知道谁是领导者节点。只有排序节点需要知道。

当排序节点验证检查完成后,将按照我们交易流程第二阶段的描述,对交易进行排序、打包成区块、共识并分发。

架构相关的注意事项

Raft 是如何选举领导者的

尽管选举领导者的过程发生在排序节点的内部流程中,但还是值得注意一下这个过程是如何工作的。

Raft 节点总是处于以下三种状态之一:跟随者、候选人或领导者。所有节点最初都是作为跟随者启动的。在这种状态下,它们可以接收来自领导者的日志条目(如果其中一个已经当选),或者为领导者投票。如果在一段时间内(例如,5秒)没有接收到日志条目或心跳,节点将自发地变为候选人状态。在候选人状态中,节点会从其它节点处索要选票。如果一个候选人获得了法定人数的选票,那么它就被提升为领导者。领导者必须接收新的日志条目并将其复制给其余跟随者。

要了解领导者选举过程的可视化展示,请查看 The Secret Lives of Data

快照

如果一个排序节点宕机,当它重启后,如何获取这期间错过的日志呢?

虽然无限地保留所有日志是可能的,但是为了节省磁盘空间,Raft 使用了一个被称为“快照”的处理过程,在这个过程中,用户可以定义日志中能保留多少字节的数据。这个数据量将对应一定数量的区块(这取决于区块中的数据量。注意,快照中只存储完整的区块)。

例如,假设落后的副本 R1 刚刚重新连接到网络。它最新的区块是100。领导者 L 则位于第 196 块,并被配置了相当于20个区块数据量的快照。R1 因此将从 L 接收区块 180,然后生成一个区块 101180分发(Deliver) 请求。再之后,区块180196 将通过正常的 Raft 协议复制到 R1(译者注:副本 R1 即为因宕机而错过部分日志的节点。L 被配置了20个区块数据量的快照,相当于 L 最多只能完整地缓存20个区块,且每20个区块会产生一个快照,并重新开始缓存新的20个区块,也即从区块100之后,120、140、160、180,L 共产生了4次快照。R1 重连时,L 的快照是区块180,所以 R1 将从 L 接收区块180,此时 R1 发现自身的区块与最新快照的差距是101-180,所以会发起一个 Deliver 请求,要求 L 将101-180复制给自己)。

Kafka (在 v2.x 中被弃用)

Fabric 支持的另一个崩溃容错排序服务是对 Kafka 分布式流平台的改造,将其用作一个排序节点集群。您可以在 Apache Kafka 网站 上阅读更多关于 Kafka 的信息,从高层次上看,Kafka 使用了与 Raft 相同概念的“领导者跟随者”配置,其中交易(Kafka 称之为“消息”)从领导者节点复制到跟随者节点。就像 Raft 一样,在领导者节点宕机的情况下,一个跟随者会成为新的领导者,排序也可以继续,以此来保证容错。

Kafka 集群的管理,包括任务协调、集群成员、访问控制和控制器选择等,均由 ZooKeeper 组及其相关 API 来处理。

Kafka 集群和 ZooKeeper 组的设置是出了名的棘手,所以我们的文档假设您对 Kafka 和 ZooKeeper 是有一定了解的。如果您决定在不具备此专业知识的情况下使用 Kafka,那么在试验基于 Kafka 的排序服务之前,您至少应该完成 Kafka 快速入门指南 的前六个步骤。您还可以参考 这个配置文件示例 中对 Kafka 和 ZooKeeper 合理默认值的简要解释。

要了解如何启动一个基于 Kafka 的排序服务,请查看我们关于 Kafka 的文档