Fabric v2.0 源码解析——排序节点(Orderer)运行机制

本文的内容还需进一步丰富,有时间会继续完善。

文章目录

  • 1. Orderer在Fabric网络中的作用
    • Handle函数
  • 2. Orderer接收的交易类型
    • ProcessMessage函数
  • 3. 共识模块(可插拔)
    • Submit函数
    • run函数
    • ordered函数
  • 4. Orderer的配置
    • 常量配置(SetOrdererEnv函数)
    • 变量配置

1. Orderer在Fabric网络中的作用

Orderer在Fabric网络中的作用主要是原子广播(Atomic Broadcast)和全排序(Total Order )。

  • orderer通过broadcast接口,接受client发送的交易,然后将这些Tx进行排序;排序的的原则为FIFS(First In First Served),但是区块内交易的顺序不一定与实际顺序一样,而是由到达Orderer的时间来决定的。
  • 排序后的交易根据一定的规则打包成区块,通过deliver接口将区块发送给Peer或client;保证所有Orderer节点分发出来的block是一致的。

Handle函数

Handle函数负责orderer原子广播服务端的处理(orderer/common/broadcast/broadcast.go):

func (bh *Handler) Handle(srv ab.AtomicBroadcast_BroadcastServer) error {
	addr := util.ExtractRemoteAddress(srv.Context())
	logger.Debugf("Starting new broadcast loop for %s", addr)
	for {
		msg, err := srv.Recv()
		if err == io.EOF {
			logger.Debugf("Received EOF from %s, hangup", addr)
			return nil
		}
		if err != nil {
			logger.Warningf("Error reading from %s: %s", addr, err)
			return err
		}

		resp := bh.ProcessMessage(msg, addr)
		err = srv.Send(resp)
		if resp.Status != cb.Status_SUCCESS {
			return err
		}

		if err != nil {
			logger.Warningf("Error sending to %s: %s", addr, err)
			return err
		}
	}
}

Handle从Broadcast流里面读取请求,然后调用ProcessMessage函数进行处理,最后还会将处理结果返回给数据流。

2. Orderer接收的交易类型

排序节点接收的交易类型分为Normal交易和Config交易。

  • Normal交易的内容包含ProposalResponse以及其他内容;
  • Config交易主要是channel的创建或配置修改,因为需要让后面的Normal交易尽快在最新配置的channel上运行,Config交易都是单独成块的。

ProcessMessage函数

orderer/common/broadcast/broadcast.go:

func (bh *Handler) ProcessMessage(msg *cb.Envelope, addr string) (resp *ab.BroadcastResponse) {
	chdr, isConfig, processor, err := bh.SupportRegistrar.BroadcastChannelSupport(msg)

	if !isConfig {
		configSeq, err := processor.ProcessNormalMsg(msg)
		err = processor.WaitReady()
		err = processor.Order(msg, configSeq)
	} else { // isConfig
		config, configSeq, err := processor.ProcessConfigUpdateMsg(msg)
		err = processor.WaitReady()
		err = processor.Configure(config, configSeq)
	}
	return &ab.BroadcastResponse{Status: cb.Status_SUCCESS}
}

上面的ProcessMessage函数只保留了主要逻辑的代码部分,如果是普通的交易,调用ProcessNormalMsg函数基于当前配置验证交易,然后调用Order接口提交一个待排序的交易;如果是config交易,调用ProcessConfigUpdateMsg函数进行处理,生成config快照,并且返回configSeq,然后调用Configure接口提交一个待排序的交易。

值得注意的是,排序节点仅仅是Fabric共识机制的一部分,还包括收集背书、签名、验证等,整个流程才是共识过程。

3. 共识模块(可插拔)

排序节点将收到的交易序列化,并按一定的规则打包成区块。多个Orderer节点通过可插拔的共识模块对交易进行共识。
共识模块目前有solo、kafka、etcdraft。etcdraft共识的核心代码为orderer/consensus/etcdraft/chain.go,下面就etcdraft共识进行简单的解析。

Submit函数

上面提到的Orderer接口和Configure接口都调用的是Submit函数:

func (c *Chain) Submit(req *orderer.SubmitRequest, sender uint64) error {
	if err := c.isRunning(); err != nil {
		c.Metrics.ProposalFailures.Add(1)
		return err
	}
	leadC := make(chan uint64, 1)
	select {
	case c.submitC <- &submit{req, leadC}:
		lead := <-leadC
		if lead == raft.None {
			c.Metrics.ProposalFailures.Add(1)
			return errors.Errorf("no Raft leader")
		}
		if lead != c.raftID {
			if err := c.rpc.SendSubmit(lead, req); err != nil {
				c.Metrics.ProposalFailures.Add(1)
				return err
			}
		}
	case <-c.doneC:
		c.Metrics.ProposalFailures.Add(1)
		return errors.Errorf("chain is stopped")
	}
	return nil
}

将待提交的请求通过chan发送另一个goroutine处理;如果当前节点不是leader,还需要将请求发送给leader节点进行处理。

run函数

run函数的部分代码如下:

			s.leader <- soft.Lead
			if soft.Lead != c.raftID {
				continue
			}
			batches, pending, err := c.ordered(s.req)
			if err != nil {
				c.logger.Errorf("Failed to order message: %s", err)
				continue
			}
			if pending {
				startTimer() // no-op if timer is already started
			} else {
				stopTimer()
			}
			c.propose(propC, bc, batches...)

首先将leader节点的值给到s.leader,这样Submit函数的goroutine即可获取到本节点是否为leader节点。如果本节点不是leader节点,run函数会等待下一个数据的进入。

run函数还设置了超时机制:

		case <-timer.C():
			ticking = false

			batch := c.support.BlockCutter().Cut()
			if len(batch) == 0 {
				c.logger.Warningf("Batch timer expired with no pending requests, this might indicate a bug")
				continue
			}
			c.logger.Debugf("Batch timer expired, creating block")
			c.propose(propC, bc, batch) // we are certain this is normal block, no need to block

到了一定时间也会切割区块,防止长时间没有交易进入而无法切割。

ordered函数

如果是leader节点,那么将会执行ordered函数,进行batch的增加或切割。

  • 如果是config消息,则直接切割区块;
  • 如果是normal消息,根据当前现有交易量决定是增加交易或者切割交易。

4. Orderer的配置

使用spf13/viper库进行命令配置的解析,orderer的环境变量全部是以“ORDERER_”开头的,前缀使用viper.SetEnvPrefix指定。

常量配置(SetOrdererEnv函数)

internal/peer/common/ordererenv.go:

    viper.Set("orderer.tls.rootcert.file", caFile)
    viper.Set("orderer.tls.clientKey.file", keyFile)
    viper.Set("orderer.tls.clientCert.file", certFile)
    viper.Set("orderer.address", OrderingEndpoint)
    viper.Set("orderer.tls.serverhostoverride", ordererTLSHostnameOverride)
    viper.Set("orderer.tls.enabled", tlsEnabled)
    viper.Set("orderer.tls.clientAuthRequired", clientAuth)
    viper.Set("orderer.client.connTimeout", connTimeout)

使用viper.Set设置配置项。

变量配置

orderer的变量配置通过在configtx.yaml保存在区块中,可以通过config交易进行修改。

你可能感兴趣的:(Hyperledger,Fabric,#,Fabric,v2.x)