概览

当排序节点通过RPC广播(Broadcast)接收到交易时 ,它会检查广播交易的客户端是否有权限去修改通道(channel)数据,然后将这些交易分发到Kafka适当的分区(partition)中。

每一个通道(channel)在Kafka中被映射到一个单独的单分区(partition)主题(topic)。 在这里 topic 与 partition 二者是一对一的关系

该分区也被排序节点所消费(consume),排序节点将接收到的交易分组写入到本地区块,将其保留在本地账本中,并通过Deliver RPC提供给需要接收的客户端。

在这个过程中orderer节点充当了 "生产者" 和 "消费者" 两个角色

生产者:对发送者的权限进行检查,之后发送给Kafka中对应通道(channel)的主题(Topic)分区(partition)

消费者: 实时监听消息,并对消息进行后续处理,生成区块或者分割交易。

Orderer主要包含两个接口 :

Broadcast: 客户端发送交易请求到排序服务进行排序处理。

Deliver:客户端或者Peer从排序服务获取排序后的区块。

Kafka相关的配置信息解析

大写的字母表示的是yaml文件中environment部分的内容。

xxx.yyy.zzz这种表示方式是按照yaml格式的层级关系

Kafka集群的节点数量最少为4个,这样就可以容忍一个节点宕机。所有的通道能够继续读写,也可以创建新的通道。

ZooKeeper集群的节点数量可以是3、5或者7。它必须是一个奇数来避免分裂(split-brain)情景,大于1以避免单点故障。 超过7个ZooKeeper服务器则被认为是多余的。

Orderer相关配置

Kafka 相关信息被写在网络的初始区块中. 如果你使用 configtxgen 工具, 编辑 configtx.yaml 文件– 或者挑一个现成的系统通道的初始区块配置文件。

Orderer.OrdererType:指定排序服务的类型Kafka

Orderer.BatchTimeout: 设置区块最长的时间间隔

Orderer.BatchSize. MaxMessageCount: 设定每个区块可以包含的最大交易数量

Orderer.BatchSize. PreferredMaxBytes:首选的区块大小

通过参数Orderer.Batchsize.PreferredMaxBytes设置首选的区块大小. Kafka 处理相对较小的信息有更高的吞吐量; 针对小于 1 MiB 大小的值

Orderer.BatchSize.AbsoluteMaxBytes: 设定区块的最大容量,不包含区块头。(会影响Kafka. Brokers的配置)

Orderer. Addresses:IP:Port的格式

Orderer.Kafka.Brokers:这是一个 Kafka的seed Brokers列表。里面至少要包含两个seed。

General.GenesisFile或者ORDERER_GENERAL_GENESISFILE:指定初始区块(genesis.block)的位置

例如如下配置内容:

Orderer: &OrdererDefaults
OrdererType: kafka
Addresses:
- orderer.example.com:7050
BatchTimeout: 2s
BatchSize:
MaxMessageCount: 10
AbsoluteMaxBytes: 98 MB
PreferredMaxBytes: 512 KB
Kafka:
Brokers:
- kafka0:9092
- kafka1:9092
- kafka2:9092
- kafka3:9092

使用 configtxgen 工具 创建初始区块. 设置是全局的设置, 也就是说这些设置的生效范围是网络中所有的排序节点. 记录下初始区块的位置.

channel创建时orderer与Kafka的交互过程

ORDERER_KAFKA_RETRY_SHORTINTERVAL=1s:轮询间隔

ORDERER_KAFKA_RETRY_SHORTTOTAL=30s:超时时间

Kafka.Retry 区域让你能够调整metadata/producer/consumer请求的频率以及socket的超时时间. (这些应该就是所有在 kafka 的生产者和消费者 中你需要的设置)

当一个 channel 被创建, 或当一个现有的 channel 被重新读取(刚启动 orderer 的情况), orderer 通过以下方式和 Kafka 集群进行交互

为 channel 对应的 Kafka 分区 创建一个 Kafka 生产者

通过生产者向这个分区发一个空的连接信息

为这个分区创建一个 Kafka 消费者.

如果任意步骤出错, 你可以调整其重复的频率.

这些步骤会在每一个Kafka.Retry.ShortInterval指定的时间间隔后进行重试 Kafka.Retry.ShortTotal次.

再以Kafka.Retry.LongInterval规定的时间间隔重试 Kafka.Retry.LongTotal次直到成功.

需要注意的是 orderer 不能读写该 channel 的数据直到所有上述步骤都成功执行.

Kafka相关配置

适当配置你的Kafka集群. 确保每一个Kafka节点都配置了以下的值

KAFKA_UNCLEAN_LEADER_ELECTION_ENABLE=false

数据一致性是区块链环境的关键. 我们不能选择不在同步副本集中的channel leader, 也不能冒风险去覆盖前一leader所产生的偏移量, 那样的结果就是重写orderers所产生的区块链数据. 所以设置为false

KAFKA_DEFAULT_REPLICATION_FACTOR = N (N是一个待设定的数字)。

N 的值应该小于Kafka集群的数量。

参数 N 表示每个channel的数据会复制到 N 个 broker 中. 这些是 channel同步副本集的候选. 不是所有 broker 都需要是随时可用的. N 值需要设置为绝对小于 Kafka集群的数量 , 因为channel的创建需要不少于 N 个broker是启动的. 所以如果设置 N = Kafka集群的节点数量 , 一个 broker 宕机就意味着区块链网络不能再创建channel. 那么故障容错的排序服务也就不存在了.

KAFKA_MIN_INSYNC_REPLICAS = M (M是一个待设定的数字)。

M 的值要大于 1 小于 N 。

数据至少复制到 M 个副本中,才被认为是有效同步。

如果 N - M 个设备宕机。还是可以正常工作。

如果可访问的数量小于M个, 则它会停止接受写入操作. 读操作可以正常运行. 当M个副本重新同步后,通道就可以再次变为可写入状态.

注意这个 M 和 N 的最小值是 2 和 3 。因为 Kafka集群的节点最小数量为 4 个。 1 < M < N < 4。

KAFKA_MESSAGE_MAX_BYTES : 消息的最大字节数
如果Orderer.BatchSize.AbsoluteMaxBytes 的值设置为98。
则可将KAFKA_MESSAGE_MAX_BYTES的值设置为99 * 1024 * 1024
KAFKA_REPLICA_FETCH_MAX_BYTES的值也设置为99 * 1024 * 1024
KAFKA_REPLICA_FETCH_MAX_BYTES :
KAFKA_MESSAGE_MAX_BYTES 和 KAFKA_REPLICA_FETCH_MAX_BYTES 的值需要大于 Orderer.BatchSize.AbsoluteMaxBytes 的值. 再为区块头增加一些余量 -- 1 MiB 就足够了. 需要满足以下条件:
KAFKA_MESSAGE_MAX_BYTES 需要严格小于 socket.request.max.bytes , 这个值默认是100Mib。(如果你希望区块大于100MiB, 你需要去修改硬代码中的变量 brokerConfig.Producer.MaxMessageBytes , 代码位置是 fabric/orderer/kafka/config.go , 再重新编译代码, 不建议这么做)
services:
zookeeper:
image: hyperledger/fabric-zookeeper
restart: always
ports:
- '2181'
- '2888'
- '3888'
kafka:
image: hyperledger/fabric-kafka
restart: always
environment:
- KAFKA_MESSAGE_MAX_BYTES=103809024 # 99 * 1024 * 1024 B
- KAFKA_REPLICA_FETCH_MAX_BYTES=103809024 # 99 * 1024 * 1024 B
- KAFKA_UNCLEAN_LEADER_ELECTION_ENABLE=false
ports:
- '9092'
orderer.example.com:
container_name: orderer.example.com
image: hyperledger/fabric-orderer
environment:
- ORDERER_KAFKA_RETRY_SHORTINTERVAL=1s
- ORDERER_KAFKA_RETRY_SHORTTOTAL=30s
- ORDERER_KAFKA_VERBOSE=true
ports:
- 7050:7050
kafka0:
container_name: kafka0
extends:
file: base/docker-compose-base.yaml
service: kafka
environment:
- KAFKA_BROKER_ID=0
- KAFKA_MIN_INSYNC_REPLICAS=2
- KAFKA_DEFAULT_REPLICATION_FACTOR=3
- KAFKA_ZOOKEEPER_CONNECT=zookeeper0:2181,zookeeper1:2181,zookeeper2:2181
depends_on:
- zookeeper0
- zookeeper1
- zookeeper2
kafka1:
container_name: kafka1
extends:
file: base/docker-compose-base.yaml
service: kafka
environment:
- KAFKA_BROKER_ID=1
- KAFKA_MIN_INSYNC_REPLICAS=2
- KAFKA_DEFAULT_REPLICATION_FACTOR=3
- KAFKA_ZOOKEEPER_CONNECT=zookeeper0:2181,zookeeper1:2181,zookeeper2:2181
depends_on:
- zookeeper0
- zookeeper1
- zookeeper2
kafka2:
container_name: kafka2
extends:
file: base/docker-compose-base.yaml
service: kafka
environment:
- KAFKA_BROKER_ID=2
- KAFKA_MIN_INSYNC_REPLICAS=2
- KAFKA_DEFAULT_REPLICATION_FACTOR=3
- KAFKA_ZOOKEEPER_CONNECT=zookeeper0:2181,zookeeper1:2181,zookeeper2:2181
depends_on:
- zookeeper0
- zookeeper1
- zookeeper2
kafka3:
container_name: kafka3
extends:
file: base/docker-compose-base.yaml
service: kafka
environment:
- KAFKA_BROKER_ID=3
- KAFKA_MIN_INSYNC_REPLICAS=2
- KAFKA_DEFAULT_REPLICATION_FACTOR=3
- KAFKA_ZOOKEEPER_CONNECT=zookeeper0:2181,zookeeper1:2181,zookeeper2:2181
depends_on:
- zookeeper0
- zookeeper1
- zookeeper2
orderer.example.com:
extends:
file: base/docker-compose-base.yaml
service: orderer.example.com
container_name: orderer.example.com
depends_on:
- zookeeper0
- zookeeper1
- zookeeper2
- kafka0
- kafka1
- kafka2
- kafka3
调试

设置 orderer.yaml文件中 General.LogLevel为 DEBUG和Kafka.Verbose为true

建议以及注意事项

你可以使用环境变量重写设置.

你能够通过设置环境变量来重写 Kafka节点和Zookeeper服务器的设置.

替换配置参数中的 点 为 下划线 – 例如:KAFKA_UNCLEAN_LEADER_ELECTION_ENABLE=false 环境变量重写配置参数 unclean.leader.election.enable.

环境变量重写同样适用于排序节点的本地配置, 即 orderer.yaml中所能设置的. 例如 ORDERER_KAFKA_RETRY_SHORTINTERVAL=1s 环境变量可以重写本地配置文件中的Orderer.Kafka.Retry.ShortInterval.

启动环境时,建议按照以下顺序启动:ZooKeeper 集群, Kafka 集群, 排序节点