# Lotus daemon 集群配置

提示:

本文档仅适用于原语云 lotus-v1.15.1-20220418 以及以上的版本。

# 1. 使用场景

# 哪些应用场景可能需要 daemon 集群呢?

  1. 同时管理多个 Miner 节点,并且都在一个机房。通常 miner 和 daemon 是一一对应的关系(多个 Miner 共享一个 daemon 做法我们强烈不推荐,容易相互影响),那么你不妨把这多个 daemon 组成一个集群,然后所有的 Miner 都连接这个 daemon 集群,大家彼此守望相助,互为对方的冗余节点。
  2. 大型 Miner 节点,爆块和时空证明提交的频率都相对较高,daemon 故障那么一会,可能就损失了几个块或者掉了几个 Partition 的算力,那么部署一个 daemon 集群来实现高可用就变得非常有必要了。

# 2. 产品优势

之前实践过通过 nginx 代理或者网络层的 IP 路由实现动态转发请求的方案,导致了很多方案无法解决的问题。这次我们直接使用 golang 完全实现了一个 daemon 集群,对很多 api 请求进行了针对性的优化,尤其是对api转发调用的错误进行了拦截分析和必要的重试。产品核心优势如下:

  1. 心跳检测:定时检测全部 daemon 的状态及时禁用网络有问题或者区块同步不正常的节点。

  2. 智能检测:基于 sync status 和 node status 的同步检测,不仅检测同步状态还检测节点的 peers 连接情况,并且会集群节点之间也会进行对比一些检测数据让判断更准确。

  3. 热配置支持:几乎内部全部的配置选项都可以热设置,不用重启。

  4. 自动 primary 绑定:对于消息推送类的 api 请求,会自动绑定一个 primary (主要) 的节点,最大限度的分散消息推送的同时确保 nonce 的有序。

  5. 自动 channel 关闭:当某个节点被禁用后,会自动关闭全部的通知通道,让客户端重新选择同步正常的节点,例如时空证明监听的 HeadChange 通道,关闭后时空证明会重新选择同步正常的节点来及时触发时空证明挑战,不然会导致时空证明挑战无法触发或者重复触发。

  6. 自动错误重试:api请求转发后,会对如下的调用错误进行拦截和自动的重试:

    • 网络错误:无论是硬件问题或者是临时的数据丢包引起的 RPC client 或者 websocket connection 错误都会自动拦截转发到下一个可用节点直到成功或者其他错误。
    • Block not found 错误:对于带 TipsetKey/Epoch/Cid 的api请求,因为同步延迟导致的 block not found 错误会自动拦截并且转发到下一个可用节点直到成功或者其他错误。
    • Key/Actor not found 错误:对于带 Address 的 api 请求,因为address同步或者缺失导致的 key/actor not found 错误会自动拦截并且转发到下一个节点直到成功或者其他错误。

# 3. 快速上手 {#full-pool-quick-start}

你只需要在原语云控制台 lotus-miner 应用配置中把 _env_FULLNODE_API_USE_POOL 的值改成 true 即可:

daemon机器内网ip地址集合 这个配置用来指定你需要把哪些 daemon 节点加入当前 daemon 集群,不同的节点 IP 之间用英文逗号隔开。

如果选择手动启动 Miner 的话,只需在启动 Miner 的时候导出下面的环境变量:

export FULLNODE_API_USE_POOL=true

Miner 兼容旧的 FullNode api/token 的查找方式,如果查找成功会自动将该节点加入到集群, 通过导出 FULLNODE_API_POOL_INFO 环境变量可以将更多的节点添加到集群:

export FULLNODE_API_POOL_INFO=${token}:${api}:${hostname}
# 例如
export FULLNODE_API_POOL_INFO=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJyZWFkIiwid3JpdGUiLCJzaWduIiwiYWRtaW4iXX0.Xov5mHGev5g67tp0KWWjmiYwJeb-0Qv5goY8OXn8shg:/ip4/192.168.2.205/tcp/1234/http:daemon_01
# 多个节点的配置之间用英文逗号(,)隔开, 例如:
export FULLNODE_API_POOL_INFO=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJyZWFkIiwid3JpdGUiLCJzaWduIiwiYWRtaW4iXX0.Xov5mHGev5g67tp0KWWjmiYwJeb-0Qv5goY8OXn8shg:/ip4/192.168.2.205/tcp/1234/http:daemon_01,eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJyZWFkIiwid3JpdGUiLCJzaWduIiwiYWRtaW4iXX0.Xov5mHGev5g67tp0KWWjmiYwJeb-0Qv5goY8OXn8shg:/ip4/192.168.2.206/tcp/1234/http:daemon_02

至此,你的 daemon 集群已经配置完毕,Miner 启动之后就会创建一个 Daemon 集群(FullNode Pool)。

# 4. 集群运维 {#fullpool-opt-cmds}

对于普通用户来说,使用默认的配置,会一些基础运维,基本就能满足需求了。如果你有一些自定义的需求,需要根据自身集群的特点调节一些集群运行参数,那么你可以尝试去 进阶运维 中去寻找解决方案。

@Note: 当前第一个版本我们在程序中输出了大量的调试日志,方便你了解整个 daemon 集群的工作流程和排除错误,你可以等 daemon 集群运行稳定之后把日志等级设置成为 warn 屏蔽调试日志:

yy_lotus-miner log set-level --system=fullpool warn

# 4.1 基础运维 {#fullpool-basic-opt}

  1. 查看 daemon 节点信息列表:

    yy_lotus-miner fpool node list
    # or use
    yy_lotus-miner fpool nodes
    # 输出
    Node 3241a41f-46ce-4a6d-ab1e-35d39162c59f, host daemon_3[192.168.2.3:1234]
    Status: {Primary:(true, 2), Calls:(20929), Epochs:(Head:11106, Behind:0), Peers:(Message:1, Block:1)}
    Node 17ff4cc8-b51d-4dce-a0f7-1cbaf0f3923d, host yycloud-1700x[192.168.2.205:1234]
    Status: {Primary:(false, 1), Calls:(6067), Epochs:(Head:11106, Behind:0), Peers:(Message:1, Block:1)}
    

    字段介绍:

    • Primary(true,2): 第一个参数(true)表示是否是当前 Miner 的 Primary daemon,第二参数(2)表示有多少个 Miner 用这个 daemon 作为 Primary deamon。
    • Calls : 当前节点被调用的次数。
    • Epochs:(Head:11106, Behind:0) : 分别表示当前 daemon head 高度,以及落后多少个高度,0 表示已经同步到最新的 tipset 了。
    • Peers:(Message:1, Block:1) : 表示当前 daemon 的 PublishMessage 节点数量 和 PublishBlock 节点数量。
  2. 添加 daemon 节点到集群:

    yy_lotus-miner fpool node add --api=/ip4/192.168.1.6/tcp/1234/http --token=xxxxxx
    

    注意:

    此命令只是临时将节点添加到集群,Miner 重启之后会失效,如果想一直有效,建议使用上面导出 FULLNODE_API_POOL_INFO 环境变量的方式。

  3. 查看 FullNode API 分类列表,分为同步,钱包,消息池等十几个分类:

    yy_lotus-miner fpool api types
    chain
    beacon
    gas
    sync
    mpool
    ...
    
  4. 查看 FullNode 所有的 API 列表

    yy_lotus-miner fpool api list
    Cate    Api                                 CallId  Filter  Policy          NoHang  Timeout
    chain   ChainHead                           93      all     FirstOk         false   0s       
    chain   ChainTipSetWeight                   138     all     FirstOk         false   0s       
    beacon  BeaconGetEntry                      42      all     RoundRobin      false   0s       
    gas     GasEstimateGasPremium               0       all     RoundRobin      false   0s       
    sync    SyncState                           0       all     RoundRobin      false   0s 
    ...      
    
    • 第一列: Cat, API 所属分类。
    • 第二列: API 名称。
    • 第三列: CallId, 当前 API 已经被调用的次数,只读。
    • 第四列: Filter, 消息过滤配置,如果为 sealing 则说明该 API 只能在那些标记为 sealing 的 daemon 节点上去调用,all 表示可以到所有的 daemon 节点去调用。
    • 第五列: Policy, API 调用策略,具体介绍请参考 API 调度策略
    • 第六列: NoHang, 是否挂起,如果设置为 true,则表示如果没有可用 daemon 时,当前调用立即返回失败。
    • 第七列: Timeout, API 调用超时时间, 0 表示不设置超时,建议保持默认值。
  5. 获取 daemon 集群的 API_INFO 信息

    yy_lotus-miner fpool auth api-info
    # 输出
    FULLNODE_API_INFO=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJyZWFkIiwid3JpdGUiLCJzaWduIiwiYWRtaW4iXX0.zqmSIOTmXsciNMiQte85orvILMEfL6hroN54HlmMB1E:/ip4/192.168.2.100/tcp/2245
    

    你也可以导出 daemon 集群里面各个 daemon 的连接信息:

    yy_lotus-miner fpool auth api-pool-info
    # 输出
    FULLNODE_API_USE_POOL=true
    FULLNODE_API_POOL_INFO=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJyZWFkIiwid3JpdGUiLCJzaWduIiwiYWRtaW4iXX0.SQ7V8yRk2DZGaxb4XKgTRDzixH2kcW1NDCe7btUcTRY:/ip4/192.168.2.100/tcp/1234/http:daemon_100;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJyZWFkIiwid3JpdGUiLCJzaWduIiwiYWRtaW4iXX0.Xov5mHGev5g67tp0KWWjmiYwJeb-0Qv5goY8OXn8shg:/ip4/192.168.2.205/tcp/1234/http:yyclould-1700k
    

    该命令会自动用英文逗号(,)将所有的 daemon API 连接信息串联起来,如果你其他 Miner 节点也想使用这几个 daemon 节点创建 daemon 集群的话,只需要把上面命令输出的内容赋值给环境 Miner 的 FULLNODE_API_POOL_INFO 环境变量即可,相当于复制上一个 Miner 的 daemon 集群。

# 4.2 进阶运维 {#fullpool-advance-opt}

  1. 证明 Worker 使用 daemon 集群

    Root Miner 会启动一个 daemon 集群的 API 服务,证明 Worker 默认连接的就是这个 API 服务, 如果你不想用 Root Miner 的集群,想自己全新创建一个,那么只需在运行的时候导出下面的环境变量:

    export FULLNODE_API_USE_POOL=true
    

    如果你不想使用 daemon 集群,还是想使用原来的单节点连接方式,导出下面的环境变量即可:

    export FULLNODE_API_USE_POOL=false
    export FULLNODE_MINER_API_PRIORITY=false
    
  2. 设置钱包账号和节点的绑定关系,绑定之后所有该钱包发送的消息都会通过绑定的 daemon 提交。

     # 1. 查看 primary seed 绑定列表
     yy_lotus-miner fpool primary list
     # 输出
     Seed    Node                                                                             Status   
     t01000  daemon_3(id=3241a41f-46ce-4a6d-ab1e-35d39162c59f, addr=192.168.2.3:1234)         running
     t01002  daemon_3(id=3241a41f-46ce-4a6d-ab1e-35d39162c59f, addr=192.168.2.3:1234)         running 
     t01003  yycloud-1700x(id=17ff4cc8-b51d-4dce-a0f7-1cbaf0f3923d, addr=192.168.2.205:1234)  running
     # 2. 设置某个钱包地址的 primary-seed
     yy_lotus-miner fpool primary set --seed=<actor-id> --nid=<node-id> 
     # e.g 将 t01003 钱包账号的 primary 节点设置为 3241a41f-46ce-4a6d-ab1e-35d39162c59f 节点
     yy_lotus-miner fpool primary set --seed=t01003 --nid=3241a41f-46ce-4a6d-ab1e-35d39162c59f
    
     yy_lotus-miner fpool primary list
     # 输出
     Seed    Node                                                                             Status   
     t01000  daemon_3(id=3241a41f-46ce-4a6d-ab1e-35d39162c59f, addr=192.168.2.3:1234)         running
     t01002  daemon_3(id=3241a41f-46ce-4a6d-ab1e-35d39162c59f, addr=192.168.2.3:1234)         running 
     t01003  daemon_3(id=3241a41f-46ce-4a6d-ab1e-35d39162c59f, addr=192.168.2.3:1234)         running
    
  3. 设置 daemon 节点状态:

    # 暂停某个节点
    yy_lotus-miner fpool node set --nid=<node-uid> --key=status --val=pause
    # 将节点移除集群
    yy_lotus-miner fpool node set --nid=<node-uid> --key=status --val=exit
    
  4. 设置 API 调度策略:

    注意:

    设置 API 调度策略设置为专家运维选项,新手请不要随意更改配置,除非确实已经了解整个 daemon 集群的工作机制,否则会发生一些未知错误。

    集群内部默认为不同类型的 api 配置了默认的调度策略(OneBySeedRound),可以通过在启动 Miner 时导出下面的环境变量来自定义配置各个 API 接口的调度策略:

    # 设置 ChainNotify api 的调度策略为 OneByIdRound
    export ChainNotify_APIRT_CONFIG=OneByIdRound,false,0s
    # 设置 ChainGetBlock api 的调度策略为 OneByIndexAsc
    export ChainGetBlock_APIRT_CONFIG=OneByIndexAsc,false,0s
    

    上述环境变量传入了三个参数,第一个是调度策略,第二个是 NoHung 开关,表示如果暂时没有找到可用的 daemon 节点,是否立即返回失败。设置为 false 表示继续等待可用 daemon 节点。

    如果 Miner 已经启动,你还可以通过下面的命令来动态实时调整 API 的调度策略:

    yy_lotus-miner fpool api set --api=ChainHead --key=policy --val=OneBySeedRound
    
    yy_lotus-miner fpool api set --api=<api-name> --key=policy --val=<policy-name>
    # e.g
    yy_lotus-miner fpool api set --api=ChainHead --key=policy --val=OneByIndexAsc
    

    设置全局默认的 API 调度策略:

    # usage
    lotus-miner fpool config set --key=DefaultPolicy --val=<value>
    # e.g
    lotus-miner fpool config set --key=DefaultPolicy --val=OneByIdRound
    
  5. 设置 daemon 节点属性:

    # 1. 设置 PrimarySeed(用于获取 Primary daemon 节点)
    yy_lotus-miner fpool config set --key=PrimarySeed --val=1
    # 启动时导出下面的环境变量可以达到同样的效果
    export FPOOL_PRIMARY_SEED=1
    # 2. 设置 MaxSyncBehind
    # 最多容忍 daemon 落后多少个区块,否则将其标记为 disable 状态 (默认值:2)
    yy_lotus-miner fpool config set --key=MaxSyncBehind --val=1
    # 对应环境变量
    export FPOOL_MAX_SYNC_BEHIND=1
    # 3. 设置 MinMessagePeers(默认值:1)
    yy_lotus-miner fpool config set --key=MinMessagePeers --val=1
    # 对应环境变量
    export FPOOL_MIN_MESSAGE_PEERS=1
    # 4. 设置 MinBlockPeers(默认值:1)
    yy_lotus-miner fpool config set --key=MinBlockPeers --val=1
    # 对应环境变量
    export FPOOL_MIN_BLOCK_PEERS=1
    # 5. 设置 MaxBehindDiff(默认值:2)
    yy_lotus-miner fpool config set --key=MaxBehindDiff --val=1
    # 对应环境变量
    export FPOOL_MAX_BEHIND_DIFF=1
    

# 5. API 调度策略 {#full-pool-policy}

主干策略分为 2 类,一种是选取一个合适的 daemon 节点调用(One),一种是同时调用所有可用的 daemon 节点,只要有一个返回成功就返回。

如果是多个 Miner 账号共享一个 daemon 集群的话,为了更好地均衡负载,我们会强制为每个 Miner 账号(MinerID)绑定一个 Primary daemon(可以通过 FPOOL_PRIMARY_SEED 环境变量设置), 通常这个 Miner 会优先调用这个 Daemon(少数我们单独优化过的 API 除外),如果 Primary daemon 不可用(离线或者 RPC 错误), 才会调用下一个可用的 daemon。

目前我们实现了下面 8 种策略(one 表示只推送给一个节点运行,index 指节点索引序号):

  1. OneByIndexAsc: 选取第一个可用的 daemon 节点。
  2. OneByIndexDesc: 选取最后一个可用的 daemon 节点。
  3. OneByIdRound: 使用 API 调用次数对节点数量取模,获得节点序号, 优先调用该节点,如果失败,再轮询调用下一个节点。
  4. OneByChainHeadDesc: 选取 ChainHead 最大的节点。
  5. OneByMessagePeersDesc: 选取 MessagePeers 数量最多的 daemon 节点。
  6. OneByBlockPeersDesc: 选取 BlockPeers 数量最多的 daemon 节点。
  7. OneBySeedRound: 使用 PrimarySeed(可自定义设置) 对节点数量取模,获得节点序号,优先调用该节点,如果失败,再轮询调用下一个节点。PrimarySeed 也是所有 API(少数我们单独优化过的 API 除外) 的默认策略。
  8. AllWithFirstSucceed: 同时向所有可用的 daemon 节点推送消息,只要有一个节点返回成功即返回成功。
上次更新: 2022/5/7 12:58:14