You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go-libp2p节点通过DHT无法发现对等节点问题求助

问题描述

我基于libp2p GossipSub开发PubSub应用,在两台云服务器各部署3个测试节点,所有端口已开放,且节点间可直接连接(非PubSub场景已验证)。但使用DHT做节点发现时,始终得到FindPeers succeeded: found peers: 0的结果——所有节点已连接引导节点,却无法互相发现。

我的假设,请指正:

  1. 互联网环境下的对等节点发现必须使用DHT
  2. DHT需要引导节点(bootstrap nodes)
  3. 我的场景下一个引导节点足够

以下是适配Go库API更新后的代码:

func NewDHT(ctx context.Context, host host.Host, bootstrapPeers []multiaddr.Multiaddr) (*dht.IpfsDHT, error) {
    var options []dht.Option

    if len(bootstrapPeers) == 0 {
        options = append(options, dht.Mode(dht.ModeServer))
    }
    options = append(options, dht.ProtocolPrefix("/myapp"))

    kdht, err := dht.New(ctx, host, options...)
    if err != nil {
        return nil, err
    }

    if err = kdht.Bootstrap(ctx); err != nil {
        return nil, err
    }

    var wg sync.WaitGroup
    for _, peerAddr := range bootstrapPeers {
        peerinfo, _ := peer.AddrInfoFromP2pAddr(peerAddr)

        wg.Add(1)
        go func() {
            defer wg.Done()
            if err := host.Connect(ctx, *peerinfo); err != nil {
                fmt.Println("connecting failed")
                fmt.Println(err)
            } else {
                fmt.Println("connected to bootstrap")
            }
        }()
    }
    wg.Wait()

    return kdht, nil
}

func Discover(ctx context.Context, h host.Host, dht *dht.IpfsDHT, rendezvous string) {
    var rt = routing.NewRoutingDiscovery(dht)

    ticker1 := time.NewTicker(time.Second * 1)
LOOP:
    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker1.C:
            ttl, err := rt.Advertise(ctx, rendezvous)
            if err != nil {
                fmt.Println("Advertise failed")
                continue
            }
            fmt.Println("Advertise success")
            fmt.Println(ttl)
            break LOOP
        }
    }

    ticker := time.NewTicker(time.Second * 1)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            peers, err := rt.FindPeers(ctx, rendezvous)
            if err != nil {
                fmt.Println("failed find peers")
                fmt.Println(err)
                continue
            }

            fmt.Println(fmt.Sprintf("FindPeers succeeded: found peers: %d", len(peers)))
            for p := range peers {
                if p.ID == h.ID() {
                    continue
                }
                if h.Network().Connectedness(p.ID) != network.Connected {
                    _, err = h.Network().DialPeer(ctx, p.ID)
                    if err != nil {
                        fmt.Println("failed to connect peer")
                        continue
                    }
                    fmt.Println("connected to peer")
                }
            }
        }
    }
}

假设验证
  • 假设1:不完全正确。互联网环境下节点发现有多种方案(如静态节点列表、自定义发现协议),但DHT是去中心化场景下最常用的方案之一,适合公开节点部署。
  • 假设2:正确。公开网络中的DHT节点必须依赖引导节点初始化路由表,否则无法接入DHT网络。
  • 假设3:理论上可行,但生产环境建议部署2-3个引导节点提升可用性——单引导节点故障会导致整个DHT网络无法正常运作。

问题排查与修复建议

1. DHT模式配置错误

当前代码仅在无引导节点时设置dht.ModeServer,但:

  • 引导节点必须运行在Server模式,才能接收其他节点的DHT查询
  • 普通节点若要被发现,也需设置为Server或Auto模式(Auto会根据网络情况自动切换,更灵活)

修改代码:

// 替换原有的模式判断逻辑,强制设置为Auto或Server模式
options = append(options, dht.Mode(dht.ModeAuto))
// 或固定为Server模式:options = append(options, dht.Mode(dht.ModeServer))

2. Advertise逻辑失效

当前代码仅执行一次Advertise就退出循环,但rt.Advertise的TTL有时间限制(默认几分钟),过期后节点会从DHT中消失,其他节点无法发现。

修改建议:合并Advertise与FindPeers逻辑,定期重新发布节点信息:

func Discover(ctx context.Context, h host.Host, dht *dht.IpfsDHT, rendezvous string) {
    var rt = routing.NewRoutingDiscovery(dht)
    // 每30秒重新发布节点信息,同时执行Peer查询
    ticker := time.NewTicker(time.Second * 30)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            // 定期重新Advertise
            _, err := rt.Advertise(ctx, rendezvous)
            if err != nil {
                fmt.Println("Advertise failed:", err)
            }
            // 执行Peer查询
            peers, err := rt.FindPeers(ctx, rendezvous)
            if err != nil {
                fmt.Println("failed find peers:", err)
                continue
            }

            fmt.Printf("FindPeers succeeded: found peers: %d\n", len(peers))
            for p := range peers {
                if p.ID == h.ID() {
                    continue
                }
                if h.Network().Connectedness(p.ID) != network.Connected {
                    _, err = h.Network().DialPeer(ctx, p.ID)
                    if err != nil {
                        fmt.Println("failed to connect peer:", err)
                        continue
                    }
                    fmt.Println("connected to peer:", p.ID)
                }
            }
        }
    }
}

3. 引导节点配置验证

确保引导节点:

  • 运行在Server模式
  • 已完成Bootstrap,且路由表能正常接收其他节点
  • 地址完全公开可访问(IP、端口无防火墙拦截)

可在引导节点添加路由表检查代码:

peers, err := kdht.RoutingTable().ListPeers()
if err != nil {
    fmt.Println("Failed to list routing table peers:", err)
} else {
    fmt.Printf("Bootstrap node routing table has %d peers\n", len(peers))
}

4. 协议前缀一致性

所有节点(含引导节点)的dht.ProtocolPrefix必须完全一致(当前代码用/myapp),否则会加入不同的DHT网络,无法互相发现。

5. 额外调试建议

  • 开启DHT日志,查看详细交互过程:
    import "github.com/libp2p/go-libp2p/core/logger"
    logger.SetDebugLogLevel("dht")
    
  • 检查节点外部地址:通过host.Addrs()确认节点包含可公开访问的IP和端口
  • 验证引导节点连通性:确保普通节点能正常连接引导节点,且引导节点能处理DHT查询请求

内容的提问来源于stack exchange,提问作者phetherer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 15:48:10