排序服务是可插拔的,代码里提供了几种实现:
·基于单进程(Solo)的排序服务;
·基于Kafka的排序服务。
目前的版本没有基于*BFT的排序服务。
6.3.1 排序服务接口
排序服务的业务需求可以实现不同的逻辑,Hyperledger Fabric 1.0已经预留了一些接口,需要修改地方如下所示:
·创建链的接口;
·链消息处理的接口;
·增加新的排序服务支持。
1.创建链的接口
创建链的接口定义如下:
// Consenter定义了后台的排序机制
type Consenter interface {
// 创建并返回一个对Chain的引用,用于提供资源
// 每个进程会被指定的chain调用一次。通常情况,发生错误不可恢复,并会导致系统关闭,有关的
// 详细信息,请参考Chain的描述
// 第二个参数是一个指针,指向该Chain中账本最后一个提交块ORDERER的存储元数据。由于
// genesis块中没有存储元数据字段定义,所以在新的Chain中值是nil。
HandleChain(support ConsenterSupport, metadata *cb.Metadata) (Chain, error)
}
当排序服务节点接收到创建通道的请求时,会根据链创世区块里配置的通道类型创建新的通道,调用的是不同通道类型的 HandleChain函数,返回能够处理链上交易的Chain对象。传入的参数support ConsenterSupport提供交易过滤、交易切割、区块签名等功能。参数metadata*cb.Metadata可以保存一些跟排序服务相关的 元数据(BlockMetadataIndex_ORDERER),比如保存Kafka最新的偏移(Offset)。元数据的写入和读取是由不同的排序类 型服务完成的,不同类型的排序服务有不同的处理逻辑,比如solo就不需要这个元数据信息。需要注意的是,元数据会写入到区块数据中,不同的排序服务区块 元数据是不一样的,所以排序服务是不能动态切换的。
2.链消息处理的接口
排序服务接收到某个通道上的交易后会提交给Chain处理,需要实现的接口如下所示:
type Chain interface {
// 成功接收消息返回true,否则返回false
Enqueue(env *cb.Envelope) bool
// 发生错误时,会返回报错的通道,这对Deliver客户端来说很重要,在没有达成最新共识时可以
// 终止客户端的等待
Errored() <-chan struct{}
// 用于分配Chain的各种资源,并保持相关的最新状态。通常情况,包括从排序服务中读取资源,
// 将消息传递给区块进行拆分,以及将区块结果写入账本Start()
// 用于释放给Chain分配的资源
Halt()
}
接口Chain接收交易请求并进行排序,生成最终的区块。接口Chain提供了可以提交消息进行排序的方法。在实现这个接口的时候,需要把排好序的交易通过blockcutter.Receiver进行交易分割,最后写到账本中。交易的分割有两种模式。
1)交易先进入消息流中,它在消息流中是有序的,消息流中的交易分割到不同的区块里,最后写入到账本中。solo和Kafka都是这种模式。
2)交易分割到不同的区块中,区块是有序的,最后写入账本中。sbft是这种模式。
3.增加新的排序服务支持
排序服务的支持需要配置文件的支持和配置文件参数的识别。在生成创世区块的配置文件configtx.yaml的参数Orderer.OrdererType中,添加新的排序服务类型,比如newconsenter。
添加了配置文件以后,排序服务节点还需要能够识别新增加的参数,在initializeMulti ChainManager的consenters里增加一个类似newconsenter的映射:
consenters["newconsenter"] = newconsenter.New()
在newconsenter里实现新的排序和共识算法。注意:配置参数里配置的名称要和新增的名称保持一致。
如果新的排序服务需要更多的参数支持,可以参考Kafka的参数设置方法,修改相关的代码。
6.3.2 基于单进程的排序服务
1.创建链的实现
单进程(Solo)的实现方式比较简单,利用Golang的并发机制内部构建一个接收消息的通道sendChan,然后返回处理交易信息的链multichain.Chain:
func newChain(support multichain.ConsenterSupport) *chain {
return &chain{
batchTimeout: support.SharedConfig().BatchTimeout(),
support: support,
sendChan: make(chan *cb.Envelope),
exitChan: make(chan struct{}),
}
}
其中,batchTimeout是最长的区块生成间隔时间,support辅助提供交易切割和生成区块的功能,exitChan是服务异常的终止信号。
2.接收交易请求的实现
创建链以后,还需要通过Start()启动链的处理过程。在Solo的实现中,就是启动一个协程循环的接收发送到sendChan通道的数据,然后进行交易的切割和区块的写入。
排序服务接收到交易请求以后,会根据不同的排序服务类型提交给不同的链进行处理,入口函数就是Enqueue。Solo类型接收到消息以后发送给sendChan就结束了。
3.错误处理的实现
排序服务内部如果出现异常,会给exitChan发送消息,外部程序可以读取Errored返回的通道,进行异常处理。
6.3.3 基于Kafka的排序服务
基于Kafka的排序服务利用Kafka作为交易的消息队列,实现高吞吐量的数据分发。每个通道都对应Kafka的一个主题(topic),排序服务节点在不同阶段充当不同的角色。
1)接收交易阶段:排序服务节点充当的是Kafka的生产者(producer),接收到交易后通过权限检查转发给对应通道的主题。
2)消息处理阶段:排序服务节点充当的是Kafka的消费者(consumer),实时监听消息进行后续的处理,生成区块或者交易分割消息等。
1.创建链的实现
在基于Kafka的排序服务创建链的时候,同样返回一个能处理交易的multichain.Chain对象,实际返回的是实现了Chain接口的chainImpl,实现如下所示:
type chainImpl struct {
consenter commonConsenter
support multichain.ConsenterSupport
channel channel
lastOffsetPersisted int64
lastCutBlockNumber uint64
producer sarama.SyncProducer
parentConsumer sarama.Consumer
channelConsumer sarama.PartitionConsumer
// 当发生分区消耗错误时关闭通道,否则,它是一个开放无缓冲的通道
errorChan chan struct{}
// 当收到Halt()请求时关闭channel,与errorChan不同,channel在关闭时不会重新启动
// 打开,伴随着关闭还将触发processMessagesToBlock退出循环
haltChan chan struct{}
// 在Start重试步骤完成后关闭
startChan chan struct{}
}
特别注意的参数是lastOffsetPersisted和lastCutBlockNumber。参数 lastOffsetPersisted记录的是排序服务节点最近读取Kafka集群消息的偏移量(offset)。Kafka的每个分区 (partition)都是有序的消息队列,偏移量用来表示队列中每个消息的序列号。通过这个偏移量,排序服务节点就能确定哪些消息是已经处理过的,哪些 是还需要后续继续处理的。这个偏移量是持久化保存在区块的Metadata中,类型是BlockMetadataIndex_ORDERER。这样,不同 的排序服务节点读取最新的区块就能确定最新读取过的消息偏移量了。那么这个偏移量是如何更新的呢?偏移量记录的是当前区块对应的Kafka分区中最后一个 交易的序列号,有两种情况会产生新的区块,在区块中记录当前区块交易在Kafka中的偏移量。
(1)正常的交易分割
当交易数量达到设定的最大交易数Orderer.BatchSize.MaxMessageCount、未打包交 易的大小超过设定的区块大小Orderer.BatchSize.PreferredMaxBytes或者新提交的交易加入后未打包交易大小超过设定的区 块大小时,会进行区块分割。由于交易是顺序处理的,满足分割条件就会分割出一个区块,所以一次最多会分割出两个区块,分割出的第二个区块最多只有一个交 易。同时生成多个区块,并更新每个区块偏移量的时候可以用最后一个交易在Kafka中的偏移量作为最后一个区块的偏移量,前一个交易的偏移量为上一个区块 的偏移量,用算法来描述就是:
offset := receivedOffset - int64(len(batches)-i-1)
其中,receivedOffset为从Kafka集群中接收到交易。
(2)超时的交易分割
当距离产生上一个区块的时间间隔超过设定的最大区块间隔时间Orderer.BatchTimeout时,会检查 超时打包消息记录的区块号是否正好是下一个分割区块的序号,如果ttcNumber==*lastCutBlockNumber+1就分割未打包的交易形 成新的区块。详细的区块分割逻辑参考后面的内容。
参数lastCutBlockNumber记录的是排序服务节点这个链上最近分割区块的序号。由于不同的节点是独 立打包的,所以各个节点的时间并不会完全一致。目前的实现方案是每个节点都有一个交易打包超时的定时器,时间到了就会产生一个超时打包消息 KafkaMessage TimeToCut并提交到链对应的分区上。多个排序服务节点可能产生多个相同的超时打包消息,每个节点都以第一个超时打包消息为准,自动忽略后面相同区 块号的超时打包消息,这样各个节点的区块打包过程就一致了。而且当各个节点打包速度不一致时,这也能根据Kafka里的消息独立完成。这是一种把时间同步 转换成消息同步的机制。
2.接收交易请求的实现
基于Kafka的排序服务节点提供两种服务角色:
1)对记账节点和应用程序,排序服务节点是服务端,则提供原子的广播服务,包括Broadcast和Deliver服务。
2)对Kafka集群,排序服务节点是客户端,则接收记账节点和应用程序的交易请求并转发给Kafka集群,再从集群获取交易,进行打包生成区块再广播给记账节点。
排序服务节点启动的时候会读取配置文件orderer.yaml的地址 General.ListenAddress和端口General.ListenPort(如果设置了环境变量 ORDERER_GENERAL_LISTENADDRESS和ORDERER_GENERAL_LISTENPORT,以环境变量为准),在指定的地址 和端口上启动服务进行监听。接收到Broadcast的gRPC请求以后,会检查类型是否是HeaderType_CONFIG_UPDATE来判断是否 是配置交易,配置交易有两种情况。
1)配置更新请求:它会转换成类型为HeaderType_CONFIG的交易请求,转换的时候会对比配置更新的内容和当前最新的配置信息,生成包含最新更新内容的全量配置交易请求。
2)新链创建请求:它会转换成类型为HeaderType_ORDERER_TRANSACTION的交易请求,转换的时候会读取系统链上的配置信息,生成包含组织信息等全量配置交易请求。
排序服务节点作为Kafka集群的生产者提交请求到通道对应的Kafka集群主题和分区上,提交成功就给发送请求 方返回状态为Status_SUCCESS的BroadcastResponse。需要注意的是,新链创建请求是提交到系统链通道上进行处理的,这个时候 还没有处理新链消息的链包装对象,需要系统链在处理的时候创建出来,交易请求也会记录到系统链的区块里,只是系统链的区块只在排序服务节点上能看到。
(1)通道的启动
启动通道的时候会进行一些初始化操作,比如在Kafka上创建主题和默认分区,创建消息消费者协程等。每个通道会 在Kafka上以链编号为名称创建主题,由于每个主题的消息只在一个分区内有序,所以在创建主题的时候默认只会创建一个编号为0的分区,这样从排序服务节 点提交到Kafka的消息都是有序的。创建主题和分区的方法是给Kafka发送一个CONNECT的消息,这个消息是在系统链接收到创建链交易请求以后提 交区块的过程中发送的。所有的排序服务节点都能作为消费者接收到创建链的交易请求,独立进行消息处理,也都会发送CONNECT消息,所以Kafka的同 一个主题的同一个分区会有多个相同的CONNECT消息,各个排序服务节点接收到以后自动忽略即可。由于所有的排序服务节点都同时从Kakfa上读取消 息,所以第一个接收到创建链消息的节点并不一定是提交创建链消息给Kafka的排序服务节点,也不能确定第一个发送CONNECT消息的节点是哪个节点。 创建消息消费者协程的时候需要指定分区的偏移量,新建分区的偏移量是从0开始的,在通道配置更新的时候,偏移量是从排序服务节点本地账本的最新区块元数据 里读取的。如果消费者协程在连接Kafka集群的时候,无法连接指定的主题、分区和偏移量时,就会关闭和Kafka的连接,那么排序服务节点接收到这个通 道的交易请求时就会直接丢弃,无法进行排序生成新的区块。
(2)交易消息的排序和分割
交易的排序和分割是由blockcutter.Receiver实现的,这和具体的排序服务类型没有关系。Receiver内部维护的数据结构如下:
type receiver struct {
sharedConfigManager config.Orderer
filters *filter.RuleSet
pendingBatch []*cb.Envelope
pendingBatchSizeBytes uint32
pendingCommitters []filter.Committer
}
其中,sharedConfigManager维护的是区块大小、区块包含的最大交易数、区块生成的间隔时间等。 filters是消息过滤器规则,每个消息都需要经过消息过滤器的检查,检查不通过就直接丢弃,检查通过的消息会返回提交器。pendingBatch是 内部维护的未打包的交易列表,Receiver按照接收到消息的顺序追加到列表后面,由于从Kafka上获取的消息已经是按照某种顺序排序了,这里的操作 其实是按照顺序进行的,所以并不需要加锁。pendingBatchSizeBytes记录的是未打包交易列表中所有的交易大小之和,以避免重复计算。 pendingCommitters是未打包交易的提交器列表,也是为了缓存,避免重复计算。
receiver的接口Ordered接收交易的请求,经过过滤器检查以后,默认会追加到未打包的交易列表中等待分割打包。区块的分割有如下几种策略。
1)按照区块中包含的交易数量:这是通过通道的配置 Orderer.BatchSize.MaxMessage Count设置的。如果追加到未打包交易列表以后所有的交易数达到设定的交易数量,那么就把所有未打包交易列表pendingBatch中的交易全部打包 成区块,并返回消息提交器pendingCommitters,以便对每个交易进行不同的提交处理操作。
2)按照区块的大小:这是通过通道的配置 Orderer.BatchSize.PreferredMaxBytes设置的。如果追加到未打包交易列表以后所有的交易大小总和达到设定的区块大小, 就需要先进行交易分割,把原来未打包的交易列表分割以后,再把新的交易请求追加到pendingBatch中。如果新交易请求的大小已经超过了区块大小的 限制,则会单独打包成一个区块。
3)按照区块的间隔时间:这是通过通道的配置 Orderer.BatchTimeout设置的。在接收到新区块的第一个交易以后会启动一个定时器,若在超时时间之内还没有足够的交易生成新的区块,就 发送一个KafkaMessageTimeToCut消息主动触发交易的分割,每个排序服务节点接收到超时交易分割消息以后,把所有未打包的交易列表 pendingBatch中的交易全部打包生成新的区块。
4)按照区块中包含的交易类型:这是内置规则,配置交易 和普通交易存放在不同的区块中。配置交易记录了组织、排序服务配置等内容,是单独打包成区块并添加到账本数据中的。创世区块就是一个配置区块,如果对配置 信息进行了修改,还会创建新的配置区块,每个配置区块都包含了全量的配置信息,并把配置区块的区块号保存在后续生成的普通区块中。这样,从普通区块就能快 速地找到最新的配置区块,不用遍历所有的区块。配置交易的判断是通过配置消息过滤器(configFilter)来实现的,交易能通过Isolated函 数的返回值识别出来是否配置交易。
交易分割是对排序服务节点Receiver维护的未打包交易列表pendingBatch进行操作,如果在此过程 中排序服务节点出现异常,则重新启动服务也能从异常中恢复。排序服务节点启动的时候会从本地的账本数据中读取最新的区块,读取元数据里保存的 LastOffset Persisted,初始化读取Kafka的消费者对象,重构pendingBatch列表进行交易分割。
(3)系统链和普通链的处理
系统链和普通链的处理过程基本类似,系统链是为了维护多链而存在的,记录了联盟链的组织信息等内容,创建新链的请 求也是提交到系统链中处理的。系统链创建并启动新链以后的配置更新就由普通链自行处理,所以系统链只是记录普通链的初始配置信息,最新普通链的配置信息还 由普通链自行维护。
(4)重复消息的处理
有两种类型的重复消息,一种是创建主题和分区的CONNECT消息,另外一种是超时设置的交易分割消息 KafkaMessageTimeToCut。这两种消息都由每个排序服务节点独立生成的,重复的消息会自动过滤。发送完CONNECT消息就完成了它的 作用,所以接收到实际的CONNECT消息都不会有业务逻辑的处理。超时分割消息的过滤方法是通过内部记录的指针lastCutBlockNumber来 识别的,具体的过程前面已经介绍过。
3.基于Kafka排序服务的最佳实践
本节提供一些基于Kafka排序服务的参考部署。
(1)Kafka和Zookeeper节点数的选择
假设Kafka和Zookeeper的节点数分别用K和Z来表示。
1)K最少需要4个节点,才可以在1个节点宕机以后还能继续提交交易和排序,并且创建新的通道,后面的步骤会说明为什么最少是4个节点。
2)Z选择3、5或7个节点都可以。选择奇数个节点可以避免脑裂,1个节点会存在单点问题,7个以上的节点就太多了。
(2)创建创世区块
编辑configtx.yaml文件,主要修改项如下所示:
·Orderer.OrdererType设置为Kafka
·Orderer.Kafka.Brokers设置至少2个Kafka的节点地址
·Orderer.AbsoluteMaxBytes设置区块最大的字节数(不包括请求头)
(3)配置Kafka集群
(4)连接Kafka节点出现异常时的重试设置
排序服务节点和Kafka节点连接设置了一些重试机制,主要分为如下几类。
·快速重试的设置:Kafka.Retry.ShortInterval和Kafka.Retry.ShortTotal,用来控制快速重试的时间间隔和总时间。
·升级重试的设置:Kafka.Retry.LongInterval和Kafka.Retry.LongTotal,用来控制升级重试的时间间隔和总时间。
·网络重试的设置:Kafka.NetworkTimeouts.DialTimeout、 Kafka.NetworkTimeouts.ReadTimeout和Kafka.NetworkTimeouts.WriteTimeout,用来控 制网络状况的设置,包括连接超时时间、读取数据超时时间、写入数据超时时间。
·元数据重试的设置:Kafka.Metadata.RetryBackoff和Kafka.Metadata.RetryMax,元数据记录了当前可用的Kafka节点地址及其分区等信息。
·生产者重试的设置:Kafka.Producer.RetryBackoff和Kafka.Producer.RetryMax,生产者写入数据时等待节点稳定的退避间隔时间和重试次数。
·消费者重试的设置:Kafka.Consumer.RetryBackoff,消费者读取数据时等待节点稳定的退避间隔时间。
快速重试和升级重试的关系和策略如下所示。
快速重试是在出现连接异常的情况下的重试策略,间隔时间和重试的总时间较短。升级重试是在快速重试失败的情况下,更长时间间隔的重试。
(5)排序服务节点和Kafka节点之间的安全传输
在生产环境中,对于排序服务节点和Kafka节点之间的加密传输,需要同时在传输的两端进行设置。
但目前Hyperledger Fabric 1.0对于Kafka TLS的实现存在漏洞,所以若想使用Kafka TLS必须修改源码并重新编译生成orderer镜像。
1)修改源码,重新生成orderer镜像。
源码fabric/orderer/kafka/config.go第39行的X509KeyPair参数错误地将证书路径当成了证书内容来使用,45行同样如此。
所以将这部分源码修改为:
if brokerConfig.Net.TLS.Enable {
// 使用方法LoadX509KeyPair创建公私钥对
keyPair, err := tls.LoadX509KeyPair(tlsConfig.Certificate, tlsConfig.PrivateKey)
if err != nil {
logger.Panic("Unable to decode public/private key pair====:", err)
}
// 创建根CA池
rootCAs := x509.NewCertPool()
for _, certificate := range tlsConfig.RootCAs {
// 现根据路径获取证书
caCert, err := ioutil.ReadFile(certificate)
if err != nil {
logger.Panic("Unable to load CA cert file.")
}
if !rootCAs.AppendCertsFromPEM(caCert) {
logger.Panic("Unable to parse the root certificate authority
certificates (Kafka.Tls.RootCAs)===")
}
}
brokerConfig.Net.TLS.Config = &tls.Config{
Certificates: []tls.Certificate{keyPair},
RootCAs: rootCAs,
MinVersion: tls.VersionTLS12,
MaxVersion: 0, // 最新支持的TLS版本
}
}
2)生成Kafka TLS证书。
假设有3个Kafka节点,分别是kafka0、kafka1、kafka2,以及1个orderer节点。生成证书脚本如下:
# 利用OpenSSL工具生成CA的私钥和证书
openssl genrsa -out ca.key 2048
printf "CN\nBJ\nBJ\nPS\nPS\nps.com\n\n" |openssl req -x509 -new -nodes -key
ca.key -days 3650 -out ca.crt
# 利用OpenSSL工具生成Kafka节点的私钥
openssl genrsa -out server.key 2048
# 利用OpenSSL工具生成Kafka节点的证书
printf "CN\nBJ\nBJ\nPS\nPS\nkafka0\n\n\n\n" |openssl req -new -key server.key
-out kafka0.csr
openssl x509 -req -in kafka0.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out
kafka0.crt -days 3650
printf "CN\nBJ\nBJ\nPS\nPS\nkafka1\n\n\n\n" |openssl req -new -key server.key
-out kafka1.csr
openssl x509 -req -in kafka1.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out
kafka1.crt -days 3650
printf "CN\nBJ\nBJ\nPS\nPS\nkafka2\n\n\n\n" |openssl req -new -key server.key
-out kafka2.csr
openssl x509 -req -in kafka2.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out
kafka2.crt -days 3650
# 利用OpenSSL工具生成客户端证书
openssl genrsa -out client.key 2048
printf "CN\nBJ\nBJ\nPS\nPS\nclient\n\n\n\n" |openssl req -new -key client.key
-out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out
client.crt -days 3650
# 利用OpenSSL工具转换证书格式
openssl pkcs12 -export -in kafka0.crt -inkey server.key -out server.pk12 -name
kafka0 -passout pass:test1234
printf "test1234\ntest1234\nY\n\n" | keytool -importkeystore -deststorepass
test1234 -destkeypass test1234 -destkeystore kafka0.keystore.jks -srckeystore
server.pk12 -srcstoretype PKCS12 -srcstorepass test1234 -alias kafka0
openssl pkcs12 -export -in Kkafka1.crt -inkey server.key -out server.pk12 -name
kafka1 -passout pass:test1234
printf "test1234\ntest1234\nY\n\n" | keytool -importkeystore -deststorepass
test1234 -destkeypass test1234 -destkeystore kafka1.keystore.jks -srckeystore
server.pk12 -srcstoretype PKCS12 -srcstorepass test1234 -alias kafka1
openssl pkcs12 -export -in kafka2.crt -inkey server.key -out server.pk12 -name
kafka2 -passout pass:test1234
printf "test1234\ntest1234\nY\n\n" | keytool -importkeystore -deststorepass
test1234 -destkeypass test1234 -destkeystore kafka2.keystore.jks -srckeystore
server.pk12 -srcstoretype PKCS12 -srcstorepass test1234 -alias kafka2
# 导入签名证书到JKS中
printf "test1234\ntest1234\nY\n\n" | keytool -keystore server.truststore.jks
-alias CARoot -import -file ca.crt
printf "test1234\n\n" | keytool -keystore server.truststore.jks -alias kafka0
-import -file kafka0.crt
printf "test1234\n\n" | keytool -keystore server.truststore.jks -alias kafka1
-import -file kafka1.crt
printf "test1234\n\n" | keytool -keystore server.truststore.jks -alias kafka2
-import -file kafka2.crt
printf "test1234\n\n" | keytool -keystore server.truststore.jks -alias client
-import -file client.crt
其中:
①printf为后边OpenSSL或keytool命令的输入参数,在执行命令时去掉这部分,可按照提示手动输入相关参数。
②test1234为密码,可换成自己的正式密码,此处用于测试。
3)服务配置。生成证书后,需配置Kafka和orderer服务。
①orderer服务配置。
orderer作为Kafka服务的客户端,需要配置私钥、证书及CA。具体参数配置如下所示:
- ORDERER_KAFKA_TLS_ENABLED=true
- ORDERER_KAFKA_TLS_PRIVATEKEY=xxx/client.key
- ORDERER_KAFKA_TLS_CERTIFICATE=xxx/client.crt
- ORDERER_KAFKA_TLS_ROOTCAS=[xxx/ca.crt]
其中client.key、client.crt、ca.crt为上一步生成的文件。
②Kafka服务配置。对于每一个kafka都有如下配置:
- KAFKA_LISTENERS=PLAINTEXT://:8092,SSL://:9092
- KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://:8092,SSL://:9092
- KAFKA_SSL_CLIENT_AUTH=required
- KAFKA_SSL_KEYSTORE_LOCATION=xxx/kafka0.keystore.jks
- KAFKA_SSL_TRUSTSTORE_LOCATION=xxx/server.truststore.jks
- KAFKA_SSL_KEY_PASSWORD=test1234
- KAFKA_SSL_KEYSTORE_PASSWORD=test1234
- KAFKA_SSL_TRUSTSTORE_PASSWORD=test1234
- KAFKA_SSL_KEYSTORE_TYPE=JKS
- KAFKA_SSL_TRUSTSTORE_TYPE=JKS
- KAFKA_SSL_ENABLED_PROTOCOLS=TLSv1.2,TLSv1.1,TLSv1
- KAFKA_SSL_INTER_BROKER_PROTOCOL=SSL
其中kafka0.keystore.jks、server.truststore.jks为上一步生成的文件,test1234为上一步使用的密码。对于不同的Kafka服务要将KAFKA_SSL_KEYSTORE_LOCATION设置为对应的文件。
4)启动服务并执行交易。
(6)Kafka节点的异常处理
Kafka提供的是可回溯的消息队列,消费者读取消息以后还可以重复读取数据,在排序服务中存放的是原始的交易请 求。排序服务节点读取交易请求并生成区块以后,就不再需要这些交易信息了,所以Kafka里的数据是可以定期清理的。如果Kafka集群出现异常,并不会 丢失所有的数据,只会影响还没有打包的交易请求。极端情况下,可以重构Kafka集群,只要初始化通道对应的主题,填充任意数据使其超过原有的偏移量即 可。不用担心Kafka里插入数据带来的安全问题,最终记账还需要经过多重检查,比如消息类型的检查和签名验证、记账节点对背书策略的验证和交易内容的校 验。
Kafka只提供崩溃故障容错,并不提供拜占庭容错。就是说,目前版本并不能防止恶意节点攻击。
6.3.4 链消息过滤器
排序服务节点定义了一些规则来对消息进行过滤,每个过滤器处理后的状态分为3种。
·转发(Forward):不确定消息是否合法,转发给下一个过滤器进行处理;
·接收(Accept):确认消息合法,调用本过滤器的规则进行处理;
·拒绝(Reject):该消息是非法消息,不进行下一步的处理。
各个过滤器的解释如下。
1)空消息过滤器(emptyRejectRule):空消息过滤器会检查信封的有效载荷是否为空,如果为空就返回拒绝,不进行后续处理,否则转发给下一个过滤器处理。
2)消息最大字节过滤器(maxBytesRule):消息最大字节过滤器会检查信封的大小(包括有效载荷和签名)是否超过消息的最大字节数,如果超过就返回拒绝,不进行后续处理,否则转发给下一个过滤器处理。
3)消息签名验证过滤器(sigFilter):消息签名验证过滤器会检查信封的签名是否满足策略,如果不满足就返回拒绝,不进行后续处理,否则转发给下一个过滤器处理。
4)系统链消息过滤器(systemChainFilter):系统链消息过滤器会检查信封是否是系统链的消息,即检查类型是否是HeaderType_ORDERER_TRANSACTION,如果是就会创建一个新的通道以处理这个链上的消息,否则转发给下一个过滤器处理。
5)配置消息过滤器(configFilter):配置消息过滤器会检查信封是否是通道配置信息,即检查类型是否是HeaderType_CONFIG,如果是就提交给configManager处理,否则转发给下一个过滤器处理。
6)接收消息过滤器(acceptRule):接收消息过滤器通常是最后一个过滤器,默认的操作是返回接收,不对消息进行任何处理。
本章首先介绍了数据一致性是区块链系统必须满足的特性,以及在分布式系统环境下,可能会遇到的一些问题。不同的共识机制解决的问题不一样,并对不同的算法 类型进行对比。然后介绍了Hyperledger Fabric 1.0的共识机制,它是由3个阶段组成的,每个节点都是可插拔的架构设计。最后详细介绍了目前版本的两种排序服务:Solo和Kafka,以及它们的实现 和最佳实践。
来源:我是码农,转载请保留出处和链接!
本文链接:http://www.54manong.com/?id=1062