带你读《GO语言公链开发实战》之三:守护进程的初始化与运行

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 本书的目标是引导读者全面了解区块链技术实现原理,笔者也一直坚信,了解某一系统最直接的方式就是研读它的源码,所以本书并不是只介绍区块链技术,而是深入分析其背后的实现原理。通过阅读本书,读者可以全面地了解一条公链的技术实现。本书基于比原链的源代码进行分析,比原链是一个开源的有智能合约功能的公共区块链平台,是国内优秀的公链,目前比原链的代码量不多,而且源码结构清晰,特别适合初学者学习。

点击查看第一章
点击查看第二章

第3章

守护进程的初始化与运行

3.1 概述

节点初始化是节点首次使用时,根据用户传入的参数进行设置,并根据参数进行网络、数据库、本地区块链以及P2P分布式网络等模块的初始化,使得节点能够正常运行。节点初始化由bytomd守护进程执行,在初次运行时一次性完成。
本章主要内容:

  • bytomd守护进程初始化流程。
  • 守护进程初始化的具体实现,包括网络、数据库、本地区块链初始化等。
  • 守护进程启动流程和停止流程。

3.2 bytomd守护进程初始化流程及命令参数

守护进程是一种特殊进程,启动后一直在后台运行,只有当触发特定的信号时,才会执行退出操作。比原链的守护进程是bytomd,初始化流程如图3-1所示。
在编写命令行程序时,通常需要对命令参数进行解析。不同语言一般都会提供解析命令行参数的方法或库,以方便程序员使用。在GO语言标准库中提供了flag包,方便进行命令行解析。

image.png

bytomd进程支持的传参如下:
image.png
image.png

3.3 bytomd守护进程的初始化实现

bytomd守护进程的Cobra流程与bytomcli过程非常相似,所以在此略去,后续主要对bytomd守护进程重要内容进行深入分析。在这里我们看一下bytomd预处理过程中使用到的代码文件结构,命令如下:
image.png

bytomd守护进程启动时,会根据不同的命令行flag参数,初始化不同的模块,最终以守护进程的方式运行。有关bytomd守护进程所有的运行工作都在node.NewNode(config)的具体实现中。下面介绍具体的实现过程。

3.3.1 Node对象

image.png

Node对象说明如下。

  • cmn.BaseService:服务管理。
  • config:当前节点的全局配置。
  • syncManager:区块和交易同步管理。
  • wallet:本地钱包管理。
  • accessTokens:token管理,用户访问凭证。
  • api:API Server接口服务。
  • chain:本地区块链管理对象。
  • txfeed:当前版本中该功能未使用。
  • cpuMiner:CPU挖矿管理对象。
  • miningPool:矿池管理对象。
  • miningEnable:是否启用挖矿模式。

node.NewNode(config)整个过程是为了创建Node对象,Node对象是整个bytomd所有模块运行的基础。
cmn.BaseService是tendermint框架的一个服务管理模块,在这里我们可以把Node作为一个服务,对该服务进行OnStart/OnStop/IsRunning等操作管理。tendermint框架可以保证这些操作不会被重复执行多次。

3.3.2 配置初始化

在执行node.NewNode(config)之前,config的默认配置就已经定义好了。在深入node.NewNode(config)分析之前,我们需要先了解默认配置都有哪些。
image.png

首先,bytomd守护进程声明一个config全局变量,表示整个bytomd守护进程的配置信息。进程启动时config对象被赋予一个默认的配置参数。
image.png

默认的配置参数分有6个,每个针对不同的模块。下面对配置进行说明。我们将默认参数归纳为三块:Base基础配置、P2P网络配置、其他配置。
1. Base基础配置
BaseConfig用于配置bytomd节点所需的基础参数,包括数据目录、日志、监听地址等相关参数。
image.png
image.png

部分参数从配置文件中获取默认值,比如ApiAddress参数,它的tag是api_addr。我们可以从config/toml.go中获取默认值:
image.png

2. P2P网络配置

P2PConfig用于配置bytomd P2P通信协议中使用的参数,包括本机监听端口、通信节点超时、地址簿等相关参数。
image.png
image.png

注意,在比特币中,节点会采用DNS的方式来询问种子节点,进而查询到其他节点的IP地址。而在比原链中,种子节点是IP地址,一般会硬编码到代码里。技术细节我们会在后面的第10章详细讲解。
3. 其他配置
WalletConfig用于配置bytomd本地钱包使用的参数,包括是否启用本地钱包和更新等相关参数。
image.png

在bytomd守护进程声明config = DefaultConfig()之后,init()函数实现了config对象中各属性的赋值。具体实现代码如下:
image.png
image.png

在init()函数中定义了很多不同类型的flag参数,并将flag的参数值绑定到config对象上,比如:
image.png

这条语句的含义为:

  • 定义一个Bool类型的flag参数。
  • 该flag的名称为mining。
  • 该flag的赋值对象为config.Mining。
  • 该flag的描述信息为Enable mining。

至此,bytomd守护进程所需要的配置信息初始化完毕,程序运行真正进入初始阶段。下面对此进行深入分析。

3.3.3 创建文件锁

在比原链中,一份数据目录(--root参数指定)只能同时由一个bytomd守护进程读写,因为LevelDB高性能键值数据库是单进程模式,如果多个进程同时读写一份数据,会造成数据不一致的情况。因此,需要使用文件锁可以保证同一时间一个进程读写一份数据目录,代码如下:
image.png

bytomd启动时,lockDataDirectory函数使用flock在RootDir目录下创建一个LOCK文件。如果bytomd进程在一个文件的inode上加了锁,那么再次启动bytomd进程则会对errors.New中的内容报错并退出进程。flock的作用是检测进程是否已经存在。
flock主要有3种操作类型。

  • LOCK_SH:共享锁,多个进程使用同一把锁用于读锁。
  • LOCK_EX:排他锁,同时只允许一个进程使用,一般用于写锁。
  • LOCK_UN:释放锁。

如果深入研究flock包的函数,我们可以看到,这里使用了LOCK_EX锁,即同时只允许一个进程使用,代码示例如下:
image.png

3.3.4 初始化网络类型

比原链的三种网络模式,分别是mainnet主网、testnet测试网和solonet单机模式。
image.png

其中initActiveNetParams函数根据用户传入的chain_id,初始化网络类型。consensus.ActiveNetParams对象保存了当前使用的网络模式。在比原链代码中会经常引用consensus.ActiveNetParams对象,用来识别当前节点连接的网络类型。
image.png

ActiveNetParams默认使用主网。MainNetParams中的参数说明如下。

  • Bech32HRPSegwit:隔离见证,是一种协议升级,我们会在后面第5章讲解。
  • Checkpoints:检查点,指定一个高度,以及与这个高度相匹配的hash值,用于快速同步时验证区块的正确性。通常在主网升级时,会将历史的块信息硬编码在Checkpoints中。

Checkpoints检查点有两种作用:第一是防止分叉,如果有人试图从检查点之前的区块进行分叉,当前节点不会接受这个分叉;也用于保护网络不受全网51%的算力攻击,因为攻击者不可能逆转检查点之前的交易。第二是用于节点间的快速同步,我们将在第10章中详细讲解。

3.3.5 初始化数据库(持久化存储)

创建一条公链,需要将链上的所有数据(包含块信息、交易信息等)存储在本地键值数据库中。在比原链中使用LevelDB来存储链上数据,代码如下:
image.png

dbm使用tendermint框架的db管理库。dbm.NewDB返回一个DB对象,DB对象提供了数据库接口和许多方法实现,包括使用内存映射、文件系统目录结构、GO中LevelDB等的实现。
dbm.NewDB返回一个DB对象,需要传入三个参数:db的名称,db使用的键值数据库(默认为LevelDB),db数据存储的路径。leveldb.NewStore函数返回一个Store对象,即比原链对LevelDB进行了封装,在LevelDB的基础上增加了区块缓存(cache)、区块验证、区块状态、区块查询等功能。

3.3.6 初始化交易池

当交易被广播到网络中并且被矿工接收到时,矿工会将接收到的交易加入到本地的TxPool交易池中,TxPool对象的作用是管理本地交易池。交易池相当于一个缓冲区,它并不是无限大。默认情况下比原链中交易池最大可以存储10 000笔交易。如果超出这个阈值,则会返回"transaction pool reach the max number"提示。
image.png

protocol.NewTxPool()返回一个TxPool实例对象。此处我们只介绍交易池初始化部分,交易池实现原理的代码将在6.10节中深入剖析。

3.5.7 创建一条本地区块链

当节点第一次启动时,判断本地持久化存储的状态,当状态为初始化时会初始化本地的区块链。区块链的第一个区块(创世区块)会被加入到区块高度为0的地方。代码如下:
image.png

protocol.NewChain返回一个Chain对象,NewChain需要接收两个参数:Store区块链的存储对象,TxPool交易池。Chain对象管理着比原链的整个区块链条。代码如下:
image.png

NewChain函数的执行可分为下面几个步骤:
1)实例化Chain对象。
2)store.GetStoreStatus获取本地区块链的存储状态,如果状态为nil则说明区块链未被初始化。执行initChainStatus初始化本地区块链,该函数初始化创世区块(第一个区块)并添加到本地链上。
3)store.LoadBlockIndex加载块索引,从数据库中读取所有Block Header信息并缓存在内存中,目的是加速访问区块头信息。
4)c.index.SetMainChain,设置当前节点已同步的最新区块。
5)go c.blockProcesser(),启动一个go rutine,用于更新本地区块链上的区块信息。

3.3.8 初始化本地钱包

默认情况下比原链节点会启用本地钱包功能。代码实例如下:
image.png

在比原链的节点启动时,上述代码流程主要逻辑为:
1)创建加密机hsm对象,hsm对象管理keystore文件,该文件是存储私钥的一种格式(JSON)。keystore是一串代码,本质上是加密后的私钥,需配合钱包的密码来使用。
2)创建钱包数据库。
3)创建账户管理对象。
4)创建资产管理对象。
5)实例化Wallet对象。
6)RescanBlocks扫描本地所有区块,触发钱包更新操作。

3.3.9 初始化网络同步管理

P2P通信模块主要由SyncManager管理,SyncManager负责节点业务层信息的同步工作,即区块和交易信息的同步。代码如下:
image.png
image.png

主要参数说明如下:

  • newBlockCh:通道用于新挖掘出的区块进行快速广播给其他节点。通道大小为1024。
  • netsync.NewSyncManager:实例化syncManager同步管理对象,它管理节点与节点之间的区块、交易信息同步。
  • newPoolTxListenner:启动一个goroutine,监听交易池中的交易,将交易发送给syncManager同步管理对象或本地钱包。

详细实现机制将在第10章进行讲解。

3.3.10 初始化Pprof性能分析工具

Pprof是GO语言标准库中自带的性能分析工具。用于内存分析、CPU分析、代码追踪等,还可以生成性能分析图表。(详细参考https://golang.org/pkg/net/http/pprof/ )。在比原链中默认不启用该功能,可以使用--prof_laddr参数启动代码性能分析功能,代码示例如下:
image.png

3.3.11 初始化CPU挖矿功能

在比原链节点源码中,只提供了CPU设备的挖矿功能,以目前全网的算力来看,CPU设备挖矿几乎挖不到BTM币了。目前主流的挖矿设备,有比特大陆定制的挖矿芯片或各大矿池使用GPU设备挖矿。挖矿和矿池细节将在第13章中详细解读。代码实例如下:
image.png

其中,simd参数用于Tenaority CPU指令的优化。

3.4 bytomd守护进程的启动方式和停止方式

我们在GO语言下实现守护进程的方式一般是,监听标准的SIGTERM信号。在监听到SIGTERM信号后,进程处于阻塞状态,以实现守护进程。只有当进程收到来自外部的SIGTERM信号时,进程则处于非阻塞状态,实现进程退出。Linux信号参考http://man7.org/linux/man-pages/man7/signal.7.html 。代码实例如下:
image.png

signal.Notify监听中断和Term信号。启用goroutine取c对象,select进入阻塞状态。当进程接收到Term信号则通知c对象,执行os.Exit退出守护进程。
发送Term信号有两种方式:一种是执行命令kill -15 pid;另一种是进程运行在前台。
当守护进程接收到Term信号后就停止运行,在其退出之前需要做扫尾工作,如退出挖矿模式,退出P2P同步功能等。代码示例如下:
image.png
image.png

3.5 本章小结

本章从源码的角度分析了bytomd启动过程中的Node对象创建和初始化,以及总结bytomd实现的逻辑。

相关文章
|
9天前
|
存储 Go 索引
go语言使用for循环遍历
go语言使用for循环遍历
23 7
|
12天前
|
存储 Go
go语言 遍历映射(map)
go语言 遍历映射(map)
25 2
|
13天前
|
Go 调度 开发者
Go语言中的并发编程:深入理解goroutines和channels####
本文旨在探讨Go语言中并发编程的核心概念——goroutines和channels。通过分析它们的工作原理、使用场景以及最佳实践,帮助开发者更好地理解和运用这两种强大的工具来构建高效、可扩展的应用程序。文章还将涵盖一些常见的陷阱和解决方案,以确保在实际应用中能够避免潜在的问题。 ####
|
13天前
|
测试技术 Go 索引
go语言使用 range 关键字遍历
go语言使用 range 关键字遍历
17 3
|
13天前
|
测试技术 Go 索引
go语言通过 for 循环遍历
go语言通过 for 循环遍历
23 3
|
15天前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
14天前
|
Go 索引
go语言按字符(Rune)遍历
go语言按字符(Rune)遍历
24 3
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
5月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
184 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
4月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。