【go-libp2p源码剖析】DHT Kademlia 路由

接口定义

整个dht routing共定义了8个方法:Provide、FindProvidersAsync、FindPeer、PutValue、GetValue、SearchValue、Bootstrap

//间接的值提供层。它用于查找谁拥有相关内容。
//内容由CID(内容标识符)标识,该CID以经得起未来考验的方式对所标识内容的哈希进行编码。
type ContentRouting interface {
    
    
	// 将给定的cid添加到内容路由系统.如果传递了“ true”,它也会发布(广播)它,否则它仅保留在提供对象的本地帐户中。 
	Provide(context.Context, cid.Cid, bool) error

	// 搜索能够提供给定key的peers
	//
	// 当count为0时,此方法将返回无限数量的结果。
	FindProvidersAsync(context.Context, cid.Cid, int) <-chan peer.AddrInfo
}

// 一种查找有关某些peer的地址信息的方法。这可以通过简单的lookup table、tracking server甚至DHT来实现
type PeerRouting interface {
    
    
	// 搜索具有给定ID的peer,并返回相关地址信息(peer.AddrInfo)。
	FindPeer(context.Context, peer.ID) (peer.AddrInfo, error)
}

// 本的Put /Get接口
type ValueStore interface {
    
    

	// 添加与给定Key相对应的值。
	PutValue(context.Context, string, []byte, ...Option) error

	// 搜索与给定Key对应的值。
	GetValue(context.Context, string, ...Option) ([]byte, error)

	// SearchValue从该值存储中搜索与给定键相对应的更好的值。默认情况下,实现必须在找到合适的值后停止搜索。 “好”值是从GetValue返回的值。
	//
	// 当你想要一个结果*now*但仍然想得到better/newer的结果时非常有用。
	//
	// 此方法的实现不会返回ErrNotFound。当找不到值时,通道将关闭而不传递任何结果 
	SearchValue(context.Context, string, ...Option) (<-chan []byte, error)
}

// 组合上面的接口
type Routing interface {
    
    
	ContentRouting
	PeerRouting
	ValueStore

	// 允许调用者提示路由系统进入Boostrapped状态并保持在该状态。这不是同步调用。
	Bootstrap(context.Context) error
}

接口实现

IpfsDHT实现了routing接口

需了解runLookupWithFollowup的运行机制,请查看另一篇文章-DHT Kademlia 迭代查询

Provide

// 间接存储的抽象程序(ADD_PROVIDE操作)
// 一些DHTs直接存储value,而间接存储存储指向值位置的指针
func (dht *IpfsDHT) Provide(ctx context.Context, key cid.Cid, brdcst bool) (err error) {
    
    
	keyMH := key.Hash()

	// 本地先添加
	dht.ProviderManager.AddProvider(ctx, keyMH, dht.self)
	if !brdcst {
    
    
		return nil
	}
	...
	//获取这个cid最近的20个peer节点
	peers, err := dht.GetClosestPeers(closerCtx, string(keyMH))
	...
	mes, err := dht.makeProvRecord(keyMH)
	if err != nil {
    
    
		return err
	}

	wg := sync.WaitGroup{
    
    }
	for p := range peers {
    
    
		wg.Add(1)
		go func(p peer.ID) {
    
    
			defer wg.Done()
			//一次发送ADD_PROVIDE给最近的节点,服务端参考另外一篇文章
			err := dht.sendMessage(ctx, p, mes)
			if err != nil {
    
    
				logger.Debug(err)
			}
		}(p)
	}
	wg.Wait()
	...
	return ctx.Err()
}

func (dht *IpfsDHT) makeProvRecord(key []byte) (*pb.Message, error) {
    
    
	pi := peer.AddrInfo{
    
    
		ID:    dht.self,
		Addrs: dht.host.Addrs(),
	}
	...
	pmes := pb.NewMessage(pb.Message_ADD_PROVIDER, key, 0)
	//ProviderPeers中只有当前主机
	pmes.ProviderPeers = pb.RawPeerInfosToPBPeers([]peer.AddrInfo{
    
    pi})
	return pmes, nil
}

FindProvidersAsync

FindProvidersAsync与FindProviders相同,但是返回一个chan,FindProviders会收集chan的结果再返回

// peer在找到后立即返回通道,甚至可能在搜索查询完成之前。如果count为零,则查询将一直运行直到完成。
// 注意:不从返回的通道读取可能会阻止查询进行,如果count为0,则通道大小为1
func (dht *IpfsDHT) FindProvidersAsync(ctx context.Context, key cid.Cid, count int) <-chan peer.AddrInfo {
    
    
	...
	chSize := count
	if count == 0 {
    
    
		chSize = 1
	}
	peerOut := make(chan peer.AddrInfo, chSize)

	keyMH := key.Hash()
	//启动一个协程去查询
	go dht.findProvidersAsyncRoutine(ctx, keyMH, count, peerOut)
	return peerOut
}



func (dht *IpfsDHT) findProvidersAsyncRoutine(ctx context.Context, key multihash.Multihash, count int, peerOut chan peer.AddrInfo) {
    
    
	defer close(peerOut)

	findAll := count == 0
	//PeerSet的修改是线程安全的,内部有锁
	var ps *peer.Set
	if findAll {
    
    
		ps = peer.NewSet()
	} else {
    
    
		ps = peer.NewLimitedSet(count)
	}
	//本地先查找
	provs := dht.ProviderManager.GetProviders(ctx, key)
	for _, p := range provs {
    
    
		// 已经存在或超过count会添加失败
		if ps.TryAdd(p) {
    
    
			pi := dht.peerstore.PeerInfo(p)
			select {
    
    
			case peerOut <- pi:
			case <-ctx.Done():
				return
			}
		}
		// 如果我们在本地有足够的peer,不需要理会远程RPC请求了 
		if !findAll && ps.Size() >= count {
    
    
			return
		}
	}

	//根据key迭代查询
	lookupRes, err := dht.runLookupWithFollowup(ctx, string(key),
		func(ctx context.Context, p peer.ID) ([]*peer.AddrInfo, error) {
    
    
			...

			pmes, err := dht.findProvidersSingle(ctx, p, key)
			if err != nil {
    
    
				return nil, err
			}
			...

			// 和本地处理逻辑一样
			for _, prov := range provs {
    
    
				//多了一个maybeAddAddrs操作,将没连接的节点加入peerstore
				dht.maybeAddAddrs(prov.ID, prov.Addrs, peerstore.TempAddrTTL)
				if ps.TryAdd(prov.ID) {
    
    
					select {
    
    
					case peerOut <- *prov:
					case <-ctx.Done():
						return nil, ctx.Err()
					}
				}
				if !findAll && ps.Size() >= count {
    
    
					return nil, nil
				}
			}

			// 获取到最近的节点,并返回,准备在迭代查询里再次查询这些节点,直至找到count个peer
			closer := pmes.GetCloserPeers()
			peers := pb.PBPeersToPeerInfos(closer)
			...
			return peers, nil
		},
		func() bool {
    
    
			return !findAll && ps.Size() >= count
		},
	)

	if err == nil && ctx.Err() == nil {
    
    
		// 如果整个查询完成,还需要更新路由表里的cplRefreshedAt字段
		dht.refreshRTIfNoShortcut(kb.ConvertKey(string(key)), lookupRes)
	}
}

func (dht *IpfsDHT) refreshRTIfNoShortcut(key kb.ID, lookupRes *lookupWithFollowupResult) {
    
    
	if lookupRes.completed {
    
    
		// refresh the cpl for this key as the query was successful
		dht.routingTable.ResetCplRefreshedAtForID(key, time.Now())
	}
}

func (rt *RoutingTable) ResetCplRefreshedAtForID(id ID, newTime time.Time) {
    
    
	cpl := CommonPrefixLen(id, rt.local)
	if uint(cpl) > maxCplForRefresh {
    
    
		return
	}

	rt.cplRefreshLk.Lock()
	defer rt.cplRefreshLk.Unlock()

	rt.cplRefreshedAt[uint(cpl)] = newTime
}

FindPeer

根据指定的id查找peer

// FindPeer searches for a peer with given ID.
func (dht *IpfsDHT) FindPeer(ctx context.Context, id peer.ID) (_ peer.AddrInfo, err error) {
    
    
	
	// 检查当前节点是否已经连接到要查找的节点
	if pi := dht.FindLocal(id); pi.ID != "" {
    
    
		return pi, nil
	}
	//迭代查询
	//第三个参数queryFn调用的是findPeerSingle
	//第四个参数stopFn条件是只要这个peer连接到了本节点查询就可以结束
	lookupRes, err := dht.runLookupWithFollowup(ctx, string(id),
		func(ctx context.Context, p peer.ID) ([]*peer.AddrInfo, error) {
    
    
			pmes, err := dht.findPeerSingle(ctx, p, id)
			if err != nil {
    
    
				logger.Debugf("error getting closer peers: %s", err)
				return nil, err
			}
			peers := pb.PBPeersToPeerInfos(pmes.GetCloserPeers())

			return peers, err
		},
		func() bool {
    
    
			return dht.host.Network().Connectedness(id) == network.Connected
		},
	)

	if err != nil {
    
    
		return peer.AddrInfo{
    
    }, err
	}

	dialedPeerDuringQuery := false
	for i, p := range lookupRes.peers {
    
    
		if p == id {
    
    
			// PeerUnreachable为有效状态,因为对等方可能不支持DHT协议
			dialedPeerDuringQuery = (lookupRes.state[i] == qpeerset.PeerQueried || lookupRes.state[i] == qpeerset.PeerUnreachable || lookupRes.state[i] == qpeerset.PeerWaiting)
			break
		}
	}

	// 如果我们在查询期间尝试拨号peer,或者我们连接到peer,则返回peer信息。
	connectedness := dht.host.Network().Connectedness(id)
	if dialedPeerDuringQuery || connectedness == network.Connected || connectedness == network.CanConnect {
    
    
		return dht.peerstore.PeerInfo(id), nil
	}

	return peer.AddrInfo{
    
    }, routing.ErrNotFound
}

func (dht *IpfsDHT) FindLocal(id peer.ID) peer.AddrInfo {
    
    
	switch dht.host.Network().Connectedness(id) {
    
    
	case network.Connected, network.CanConnect:
		return dht.peerstore.PeerInfo(id)
	default:
		return peer.AddrInfo{
    
    }
	}
}

PutValue

// 添加给定Key对应的值
// DHT的Store操作
func (dht *IpfsDHT) PutValue(ctx context.Context, key string, value []byte, opts ...routing.Option) (err error) {
    
    
	...
	//先从本地datastore里获取
	old, err := dht.getLocal(key)
	if err != nil {
    
    
		return err
	}

	// 检查是否有旧值与新值不相同。
	if old != nil && !bytes.Equal(old.GetValue(), value) {
    
    
		// 检查新值是否更好,可能这个新值比旧值更老
		i, err := dht.Validator.Select(key, [][]byte{
    
    value, old.GetValue()})
		if err != nil {
    
    
			return err
		}
		if i != 0 {
    
    
			return fmt.Errorf("can't replace a newer value with an older value")
		}
	}

	rec := record.MakePutRecord(key, value)
	rec.TimeReceived = u.FormatRFC3339(time.Now())
	//本地先存一份
	err = dht.putLocal(key, rec)
	if err != nil {
    
    
		return err
	}
	//获取到最近的节点
	pchan, err := dht.GetClosestPeers(ctx, key)
	if err != nil {
    
    
		return err
	}
	// 遍历这些peer,调用putValueToPeer将值推往这些peer
	wg := sync.WaitGroup{
    
    }
	for p := range pchan {
    
    
		wg.Add(1)
		go func(p peer.ID) {
    
    
			ctx, cancel := context.WithCancel(ctx)
			defer cancel()
			defer wg.Done()
			err := dht.putValueToPeer(ctx, p, rec)
			if err != nil {
    
    
				logger.Debugf("failed putting value to peer: %s", err)
			}
		}(p)
	}
	wg.Wait()

	return nil
}

GetValue

获取Quorum参数,然后直接调用了SearchValue,将best


// 搜索与给定Key对应的值
func (dht *IpfsDHT) GetValue(ctx context.Context, key string, opts ...routing.Option) (_ []byte, err error) {
    
    
	...

	// defaultQuorum默认为0。Quorum是一个DHT选项,它告诉DHT在返回最佳值之前需要从多少个同级中获取值。0表示DHT查询应完成而不是提早返回。
	var cfg routing.Options
	if err := cfg.Apply(opts...); err != nil {
    
    
		return nil, err
	}
	opts = append(opts, Quorum(getQuorum(&cfg, defaultQuorum)))

	responses, err := dht.SearchValue(ctx, key, opts...)
	if err != nil {
    
    
		return nil, err
	}
	var best []byte

	for r := range responses {
    
    
		best = r
	}

	if ctx.Err() != nil {
    
    
		return best, ctx.Err()
	}

	if best == nil {
    
    
		return nil, routing.ErrNotFound
	}
	return best, nil
}

SearchValue

这个接口有点绕。大概意思就是根据某个key去获取值,值可能是旧的,需要拿到最新的value。而迭代查询本身就是异步的,因此需要逐个比较哪个值是最好的。获取到最好的值后还需要将dht网络的旧值更新。最后通过chan将value返回。

// 搜索与给定Key对应的值,并通过chan传输结果。
func (dht *IpfsDHT) SearchValue(ctx context.Context, key string, opts ...routing.Option) (<-chan []byte, error) {
    
    

	var cfg routing.Options
	if err := cfg.Apply(opts...); err != nil {
    
    
		return nil, err
	}

	responsesNeeded := 0
	if !cfg.Offline {
    
    
		responsesNeeded = getQuorum(&cfg, defaultQuorum)
	}

	stopCh := make(chan struct{
    
    })
	//首先调用getValues获取value chan
	valCh, lookupRes := dht.getValues(ctx, key, stopCh)

	out := make(chan []byte)
	go func() {
    
    
		defer close(out)
		//将从getValues获取value chan传入
		best, peersWithBest, aborted := dht.searchValueQuorum(ctx, key, valCh, stopCh, out, responsesNeeded)
		if best == nil || aborted {
    
    
			return
		}

		updatePeers := make([]peer.ID, 0, dht.bucketSize)
		select {
    
    
		case l := <-lookupRes:
			if l == nil {
    
    
				return
			}

			for _, p := range l.peers {
    
    
				if _, ok := peersWithBest[p]; !ok {
    
    
					updatePeers = append(updatePeers, p)
				}
			}
		case <-ctx.Done():
			return
		}

		dht.updatePeerValues(dht.Context(), key, best, updatePeers)
	}()

	return out, nil
}

//迭代查询,这里的终止条件是stopQuery
func (dht *IpfsDHT) getValues(ctx context.Context, key string, stopQuery chan struct{
    
    }) (<-chan RecvdVal, <-chan *lookupWithFollowupResult) {
    
    
	// 注意:这里chan大小为1
	valCh := make(chan RecvdVal, 1)
	lookupResCh := make(chan *lookupWithFollowupResult, 1)

	//先从本地datastore查找
	if rec, err := dht.getLocal(key); rec != nil && err == nil {
    
    
		select {
    
    
		// 将获取到的RecvdVal塞入返回值chan
		case valCh <- RecvdVal{
    
    
			Val:  rec.GetValue(),
			From: dht.self,
		}:
		case <-ctx.Done():
		}
	}

	go func() {
    
    
		defer close(valCh)
		defer close(lookupResCh)
		//启动迭代查询
		lookupRes, err := dht.runLookupWithFollowup(ctx, key,
			func(ctx context.Context, p peer.ID) ([]*peer.AddrInfo, error) {
    
    
				...
				//获取value和最近的peer
				rec, peers, err := dht.getValueOrPeers(ctx, p, key)
				switch err {
    
    
				case routing.ErrNotFound:
					...
					return nil, err
				default:
					return nil, err
				case nil, errInvalidRecord:
					// in either of these cases, we want to keep going
				}

				
				if rec != nil && rec.GetValue() != nil {
    
    
					rv := RecvdVal{
    
    
						Val:  rec.GetValue(),
						From: p,
					}

					select {
    
    
					// 将获取到的RecvdVal塞入返回值chan
					case valCh <- rv:
					case <-ctx.Done():
						return nil, ctx.Err()
					}
				}

				...
				return peers, err
			},
			func() bool {
    
    
				select {
    
    
				case <-stopQuery:
					return true
				default:
					return false
				}
			},
		)

		if err != nil {
    
    
			return
		}
		lookupResCh <- lookupRes

		if ctx.Err() == nil {
    
    
			dht.refreshRTIfNoShortcut(kb.ConvertKey(key), lookupRes)
		}
	}()

	return valCh, lookupResCh
}

//根据Quorum设置条件判断search是否应该结束。只是调用了processValues,每收到一个值numResponses计数就会加1,不过nvals默认为0(Quorum的值)。这里只是把收到的val返回出去
func (dht *IpfsDHT) searchValueQuorum(ctx context.Context, key string, valCh <-chan RecvdVal, stopCh chan struct{
    
    },
	out chan<- []byte, nvals int) ([]byte, map[peer.ID]struct{
    
    }, bool) {
    
    
	numResponses := 0
	return dht.processValues(ctx, key, valCh,
		func(ctx context.Context, v RecvdVal, better bool) bool {
    
    
			numResponses++
			// 只有processValues方法里调用newVal时最后一个参数为true,才会返回
			if better {
    
    
				select {
    
    
				case out <- v.Val:
				case <-ctx.Done():
					return false
				}
			}

			if nvals > 0 && numResponses > nvals {
    
    
				close(stopCh)
				return true
			}
			return false
		})
}


func (dht *IpfsDHT) processValues(ctx context.Context, key string, vals <-chan RecvdVal,
	newVal func(ctx context.Context, v RecvdVal, better bool) bool) (best []byte, peersWithBest map[peer.ID]struct{
    
    }, aborted bool) {
    
    
loop:
	for {
    
    
		//也就是searchValueQuorum的传入的闭包(默认不会终止)
		if aborted {
    
    
			return
		}

		select {
    
    
		case v, ok := <-vals:
			if !ok {
    
    
				break loop
			}

			// Select best value
			if best != nil {
    
    
				if bytes.Equal(best, v.Val) {
    
    
					peersWithBest[v.From] = struct{
    
    }{
    
    }
					aborted = newVal(ctx, v, false)
					continue
				}
				//
				sel, err := dht.Validator.Select(key, [][]byte{
    
    best, v.Val})
				if err != nil {
    
    
					continue
				}
				if sel != 1 {
    
    
					aborted = newVal(ctx, v, false)
					continue
				}
			}
			//peersWithBest用于updatePeerValues
			peersWithBest = make(map[peer.ID]struct{
    
    })
			peersWithBest[v.From] = struct{
    
    }{
    
    }
			best = v.Val
			//终于找到了最好的值
			aborted = newVal(ctx, v, true)
		case <-ctx.Done():
			return
		}
	}

	return
}

//更新值操作。循坏peers调用putValueToPeer更新
func (dht *IpfsDHT) updatePeerValues(ctx context.Context, key string, val []byte, peers []peer.ID) {
    
    
	fixupRec := record.MakePutRecord(key, val)
	for _, p := range peers {
    
    
		go func(p peer.ID) {
    
    
			//TODO: Is this possible?
			if p == dht.self {
    
    
				err := dht.putLocal(key, fixupRec)
				if err != nil {
    
    
					logger.Error("Error correcting local dht entry:", err)
				}
				return
			}
			ctx, cancel := context.WithTimeout(ctx, time.Second*30)
			defer cancel()
			err := dht.putValueToPeer(ctx, p, fixupRec)
			if err != nil {
    
    
				logger.Debug("Error correcting DHT entry: ", err)
			}
		}(p)
	}
}

Validator select

默认有两个validator: pk和ipns,pk的select没有实现默认返回0;pns的select会比较Sequence和Validity

PublicKeyValidator

func (pkv PublicKeyValidator) Select(k string, vals [][]byte) (int, error) {
    
    
	return 0, nil
}

ipns.Validator

type IpnsEntry struct {
    
    
	Value        []byte                  `protobuf:"bytes,1,req,name=value" json:"value,omitempty"`
	Signature    []byte                  `protobuf:"bytes,2,req,name=signature" json:"signature,omitempty"`
	ValidityType *IpnsEntry_ValidityType `protobuf:"varint,3,opt,name=validityType,enum=ipns.pb.IpnsEntry_ValidityType" json:"validityType,omitempty"`
	Validity     []byte                  `protobuf:"bytes,4,opt,name=validity" json:"validity,omitempty"`
	Sequence     *uint64                 `protobuf:"varint,5,opt,name=sequence" json:"sequence,omitempty"`
	Ttl          *uint64                 `protobuf:"varint,6,opt,name=ttl" json:"ttl,omitempty"`
	// in order for nodes to properly validate a record upon receipt, they need the public
	// key associated with it. For old RSA keys, its easiest if we just send this as part of
	// the record itself. For newer ed25519 keys, the public key can be embedded in the
	// peerID, making this field unnecessary.
	PubKey               []byte   `protobuf:"bytes,7,opt,name=pubKey" 
}

func (v Validator) Select(k string, vals [][]byte) (int, error) {
    
    
	var recs []*pb.IpnsEntry
	for _, v := range vals {
    
    
		e := new(pb.IpnsEntry)
		if err := proto.Unmarshal(v, e); err != nil {
    
    
			return -1, err
		}
		recs = append(recs, e)
	}

	return selectRecord(recs, vals)
}

func selectRecord(recs []*pb.IpnsEntry, vals [][]byte) (int, error) {
    
    
	switch len(recs) {
    
    
	case 0:
		return -1, errors.New("no usable records in given set")
	case 1:
		return 0, nil
	}

	var i int
	for j := 1; j < len(recs); j++ {
    
    
		cmp, err := Compare(recs[i], recs[j])
		if err != nil {
    
    
			return -1, err
		}
		if cmp == 0 {
    
    
			cmp = bytes.Compare(vals[i], vals[j])
		}
		if cmp < 0 {
    
    
			i = j
		}
	}

	return i, nil
}

func Compare(a, b *pb.IpnsEntry) (int, error) {
    
    
	as := a.GetSequence()
	bs := b.GetSequence()

	if as > bs {
    
    
		return 1, nil
	} else if as < bs {
    
    
		return -1, nil
	}

	at, err := u.ParseRFC3339(string(a.GetValidity()))
	if err != nil {
    
    
		return 0, err
	}

	bt, err := u.ParseRFC3339(string(b.GetValidity()))
	if err != nil {
    
    
		return 0, err
	}

	if at.After(bt) {
    
    
		return 1, nil
	} else if bt.After(at) {
    
    
		return -1, nil
	}

	return 0, nil
}

猜你喜欢

转载自blog.csdn.net/kk3909/article/details/111033604