micro中的默认服务发现mdns

2,933 阅读16分钟

我们定义好的服务,如何进行方便的使用,而不是直接指定特定的地址进行调用。这就需要使用额外的服务发现组件,在产线上,可能大家比较熟悉的有etcd,consul,zookeeper,这些都是功能比较完备的服务发现,各有各的优势和特点,但是在micro默认的服务发现中,通常使用的是mdns.大家可以从在上一篇文章中讲的micro server命令的输出中,看到它

2020-09-27 02:52:28  file=grpc/grpc.go:732 level=info Registry [mdns] Registering node: server-bf8d0391-bd46-4aec-8455-3ea686463aee

那么什么是mdns?以及他是如何被作为服务发现的呢?

在计算机网络中,多播DNS (mDNS)协议在不包括本地名称服务器的小网络中将主机名解析为IP地址。它是一个零配置服务,使用与单播域名系统(DNS)基本相同的编程接口、包格式和操作语义。虽然将mDNS设计成一个独立的协议,但它可以与标准的DNS服务器协同工作。

mDNS协议发布为RFC 6762,使用IP多播用户数据报协议(UDP)包,由Apple Bonjour和开源的Avahi软件包实现,大多数Linux发行版都包含这些软件包。在Windows 10中也实现了mDNS,最初仅限于发现联网打印机,后来也能够解析主机名。

  • 协议简介:当mDNS客户机需要解析主机名时,它发送一个IP多播查询消息,要求具有该名称的主机标识自己。目标机器然后多播包含其IP地址的消息。然后,该子网中的所有机器都可以使用这些信息来更新它们的mDNS缓存。任何主机都可以通过发送一个活时间(TTL)等于零的响应包来放弃对某个名称的声明。默认情况下,mDNS只解析以.local顶级域名结尾的主机名。 如果.local包含不实现mDNS但可以通过传统的单播DNS服务器找到的主机,则会导致问题。解决此类冲突需要更改网络配置,而mDNS旨在避免这种更改。

  • 包结构:mDNS消息是使用以下地址发送的多播UDP包

    • IPv4 address 224.0.0.251 or IPv6 address ff02::fb
    • UDP port 5353
    • 当使用以太网帧时,标准的IP多播MAC地址01:00:5E:00:00:FB (IPv4)或33:33:00:00:FB (IPv6)

有效负载结构基于单播DNS包格式,由头和数据两部分组成。

header与单播DNS中的相同,数据部分中的子部分也是如此:查询、答案,权威名称服务器和其他记录。每个子节中的记录数与标题中对应的*COUNT字段的值相匹配。

  • queries: 查询部分中记录的有线格式比单播DNS中的略微修改,增加了UNICAST-RESPONSE字段。
FieldDescriptionLength bits
QNAMEName of the node to which the query pertainsVariable
QTYPEThe type of the query, i.e. the type of RR which should be returned in responses.16
UNICAST-RESPONSEBoolean flag indicating whether a unicast-response is desired1
QCLASSClass code, 1 a.k.a. "IN" for the Internet and IP networks15

与单播DNS中一样,QNAME字段由一系列称为“标签”的长度/值子字段组成。 每个标签代表完全限定域名(FQDN)中点分隔的子字符串之一。该列表以单个空字节终止,表示DNS的“根”。UNICAST-RESPONSE字段用于最小化网络上不必要的广播.如果设置了位,应答者应该直接向查询节点发送定向单播响应,而不是向整个网络广播响应。QCLASS字段与单播dns中的相同。

答案,权威名称服务器和其他记录部分中的所有记录都具有相同的格式,统称为资源记录(RR)。与单播DNS相比,mDNS中的资源记录的一般格式也略有修改

FieldDescriptionLength bits
RRNAMEName of the node to which the record pertainsVariable
RRTYPEThe type of the Resource Record16
CACHE-FLUSHBoolean flag indicating whether outdated cached records should be purged1
RRCLASSClass code, 1 a.k.a. "IN" for the Internet and IP networks15
TTLTime interval (in seconds) that the RR should be cached32
RDLENGTHInteger representing the length (in octets) of the RDATA field16
RDATAResource data; internal structure varies by RRTYPEVariable

CACHE-FLUSH位用于指示邻居节点记录应覆盖此RRNAMERRTYPE的任何现有缓存条目,而不是附加到该条目。RDATA字段的格式与单播DNS中的格式相同。然而,DNS服务发现(dn-sd), mDNS最常见的用例,指定了对其某些格式(特别是TXT记录)的轻微修改。

go-micro包中,提供了一个套默认的mDNS实现,

// Zone是一个接口,用于与服务器集成并动态提供记录的接口
type Zone interface {
    // Records 在一个DNS请求中返回DNS记录
	Records(q dns.Question) []dns.RR
}

// MDNSService通过实现Zone接口用于导出命名服务
type MDNSService struct {
	Instance     string   // 实例名称 (e.g. "hostService name")
	Service      string   // 服务名称 (e.g. "_http._tcp.")
	Domain       string   // 如果为空,假设为 "local"
	HostName     string   // 主机的DNS名 (e.g. "mymachine.net.")
	Port         int      // 服务端口
	IPs          []net.IP // 服务的主机IP地址列表
	TXT          []string // Service TXT records
	TTL          uint32
	serviceAddr  string // 完全限定的服务地址
	instanceAddr string // 完全限定的实例地址
	enumAddr     string // _services._dns-sd._udp.<domain>
}

接着看看DNSSDService,DNSSDService是一项符合DNS-SD(RFC 6762)和MDNS(RFC 6762)规范的服务,用于基于本地、多播DNS的发现。DNSSDService实现了Zone接口并包裹了一个MDNSService实例。

type DNSSDService struct {
	MDNSService *MDNSService
}
// Records返回一个DNS记录用于响应DNS请求。这个函数返回底层MDNSService实例的DNS响应。
// 在请求`_services._dns-sd._udp.<Domain>`时他还返回一个PTR记录,用于浏览底层的MDNSService实例
func (s *DNSSDService) Records(q dns.Question) []dns.RR {
	var recs []dns.RR
	if q.Name == "_services._dns-sd._udp."+s.MDNSService.Domain+"." {
		recs = s.dnssdMetaQueryRecords(q)
	}
	return append(recs, s.MDNSService.Records(q)...)
}

// dnssdMetaQueryRecords 返回DNS记录在一次meta-query查询中,一个meta-query查询形如`_services._dns-sd._udp.<Domain>`
func (s *DNSSDService) dnssdMetaQueryRecords(q dns.Question) []dns.RR {
	// 预期行为, 就像RFC文档中描述的那样:
	//     ...对于网络管理员来说,在网络上查找已播发服务类型的列表可能会很有用,
    // 即使这些服务名称只是不透明的标识符,并且孤立地提供的信息也不多。
	//
    // 为了这个目的,一个指定的元查询据定义了,一个DNS查询PTR记录通常有一个名字
	//     "_services._dns-sd._udp.<Domain>" 将返回一系列PTR记录。
	//    where the rdata of each PTR record is the two-abel
    //    每个PTR记录的rdata都是一个双标签<Service>名称,加上相同的domain.例如`_http._tcp.<Domain>`
    //    在PTR rdata中包含域可以在单播DNS响应中更好地进行名称压缩,
    //    但是对于服务类型枚举而言,只有前两个标签是相关的。 
    //    然后,可以使用这两个标签的服务类型在此<Domain>或其他域中构造后续的服务实例枚举PTR查询,以发现该服务类型的实例。
	return []dns.RR{
		&dns.PTR{
			Hdr: dns.RR_Header{
				Name:   q.Name,
				Rrtype: dns.TypePTR,
				Class:  dns.ClassINET,
				Ttl:    defaultTTL,
			},
			Ptr: s.MDNSService.serviceAddr,
		},
	}
}

为了部署一个符合DNS-SD的mDNS服务,建议只注册被包裹的实例,例如

      service := &mdns.DNSSDService{
       MDNSService: &mdns.MDNSService{
 	       Instance: "My Foobar Service",
 	       Service: "_foobar._tcp",
 	       Port:    8000,
        }
      }
      server, err := mdns.NewServer(&mdns.Config{Zone: service})
      if err != nil {
        log.Fatalf("Error creating server: %v", err)
      }
      defer server.Shutdown()

再来看看如何实现一个mDNS服务器的

// Config用于配置mDNS服务器。
type Config struct {
    // Zone 必须提供支持查询响应
	Zone Zone

	
    // Iface 如果提供了,绑定多播监听。如果没提供,使用系统默认的多播接口
	Iface *net.Interface

    // Port 如果不是0,使用它代替5353
	Port int

    // GetMachineIP是一个函数,用于返回本地机器的IP地址
	GetMachineIP GetMachineIP

    // LocalhostChecking 如果设置了,请求服务,并发送响应到0.0.0.0,如果目标IP是这个主机
    // 如果机器位于VPN上,该VPN阻止非标准端口上的通信,则很有用
	LocalhostChecking bool
}


// Server 是一个mDNS服务器,用于监听mDNS查询并响应如果有一个存在的本地记录。
type Server struct {
	config *Config

	ipv4List *net.UDPConn
	ipv6List *net.UDPConn

	shutdown     bool
	shutdownCh   chan struct{}
	shutdownLock sync.Mutex
	wg           sync.WaitGroup

	outboundIP net.IP
}


// NewServer 用于根据一个config创建一个mDNS服务器,然后再goroutine中开启监听
func NewServer(config *Config) (*Server, error) {
	setCustomPort(config.Port)

	// Create the listeners
	// Create wildcard connections (because :5353 can be already taken by other apps)
	ipv4List, _ := net.ListenUDP("udp4", mdnsWildcardAddrIPv4)
	ipv6List, _ := net.ListenUDP("udp6", mdnsWildcardAddrIPv6)
	if ipv4List == nil && ipv6List == nil {
		return nil, fmt.Errorf("[ERR] mdns: Failed to bind to any udp port!")
	}

	if ipv4List == nil {
		ipv4List = &net.UDPConn{}
	}
	if ipv6List == nil {
		ipv6List = &net.UDPConn{}
	}

	// Join multicast groups to receive announcements
	p1 := ipv4.NewPacketConn(ipv4List)
	p2 := ipv6.NewPacketConn(ipv6List)
	p1.SetMulticastLoopback(true)
	p2.SetMulticastLoopback(true)

	if config.Iface != nil {
		if err := p1.JoinGroup(config.Iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil {
			return nil, err
		}
		if err := p2.JoinGroup(config.Iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil {
			return nil, err
		}
	} else {
		ifaces, err := net.Interfaces()
		if err != nil {
			return nil, err
		}
		errCount1, errCount2 := 0, 0
		for _, iface := range ifaces {
			if err := p1.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil {
				errCount1++
			}
			if err := p2.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil {
				errCount2++
			}
		}
		if len(ifaces) == errCount1 && len(ifaces) == errCount2 {
			return nil, fmt.Errorf("Failed to join multicast group on all interfaces!")
		}
	}

	ipFunc := getOutboundIP
	if config.GetMachineIP != nil {
		ipFunc = config.GetMachineIP
	}

	s := &Server{
		config:     config,
		ipv4List:   ipv4List,
		ipv6List:   ipv6List,
		shutdownCh: make(chan struct{}),
		outboundIP: ipFunc(),
	}

	go s.recv(s.ipv4List)
	go s.recv(s.ipv6List)

	s.wg.Add(1)
    // probe广播或者单播返回响应
	go s.probe()

	return s, nil
}

实现了一个mDNS服务器之后,就可以进行服务查询了。那么mdns又是如何作为一个注册中心来作为服务发现使用的呢?

package registry

import (
	"errors"
)

const (
	// WildcardDomain indicates any domain
	WildcardDomain = "*"
	// DefaultDomain to use if none was provided in options
	DefaultDomain = "micro"
)

var (
	// Not found error when GetService is called
	ErrNotFound = errors.New("service not found")
	// Watcher stopped error when watcher is stopped
	ErrWatcherStopped = errors.New("watcher stopped")
)

// 注册中心提供了一个接口用于服务发现,以及给广泛实现({consul, etcd, zookeeper, ...})提供了一个抽象
type Registry interface {
	Init(...Option) error
	Options() Options
	Register(*Service, ...RegisterOption) error
	Deregister(*Service, ...DeregisterOption) error
	GetService(string, ...GetOption) ([]*Service, error)
	ListServices(...ListOption) ([]*Service, error)
	Watch(...WatchOption) (Watcher, error)
	String() string
}

type Service struct {
	Name      string            `json:"name"`
	Version   string            `json:"version"`
	Metadata  map[string]string `json:"metadata"`
	Endpoints []*Endpoint       `json:"endpoints"`
	Nodes     []*Node           `json:"nodes"`
}

type Node struct {
	Id       string            `json:"id"`
	Address  string            `json:"address"`
	Metadata map[string]string `json:"metadata"`
}

type Endpoint struct {
	Name     string            `json:"name"`
	Request  *Value            `json:"request"`
	Response *Value            `json:"response"`
	Metadata map[string]string `json:"metadata"`
}

type Value struct {
	Name   string   `json:"name"`
	Type   string   `json:"type"`
	Values []*Value `json:"values"`
}

type Option func(*Options)

type RegisterOption func(*RegisterOptions)

type WatchOption func(*WatchOptions)

type DeregisterOption func(*DeregisterOptions)

type GetOption func(*GetOptions)

type ListOption func(*ListOptions)

监控功能接口

package registry

import "time"

// Watcher 是一个接口类型,可以返回注册中心中服务的变更
type Watcher interface {
    // Next 是一个阻塞调用
	Next() (*Result, error)
	Stop()
}

// Result是调用watcher上Next方法的返回值。action可以是创建,更新,删除
type Result struct {
	Action  string
	Service *Service
}

// EventType 定义了注册中心的事件类型
type EventType int

const (
	// Create 事件是在注册新服务时发生的
	Create EventType = iota
    // Delete 事件是在取消注册时发生的
	Delete
    // Update 事件是在服务发生变更时发生的
	Update
)

// String 返回事件类型的字符串形式
func (t EventType) String() string {
	switch t {
	case Create:
		return "create"
	case Delete:
		return "delete"
	case Update:
		return "update"
	default:
		return "unknown"
	}
}

// Event 是注册中心事件
type Event struct {
	// Id is registry id
	Id string
	// Type defines type of event
	Type EventType
	// Timestamp is event timestamp
	Timestamp time.Time
	// Service is registry service
	Service *Service
}

我们看看,micro server是如何一步步的使用mdns的作为注册中心的。我们知道,micro server默认使用的服务器是grpc Server.

func newGRPCServer(opts ...server.Option) server.Server {
	options := newOptions(opts...)

	// create a grpc server
	srv := &grpcServer{
		opts: options,
		rpc: &rServer{
			serviceMap: make(map[string]*service),
		},
		handlers:    make(map[string]server.Handler),
		subscribers: make(map[*subscriber][]broker.Subscriber),
		exit:        make(chan chan error),
		wg:          wait(options.Context),
	}

	// configure the grpc server
	srv.configure()

	return srv
}

grpc server在创建Server时,创建的选项就默认使用了我们的mdns.


func newOptions(opt ...server.Option) server.Options {
	opts := server.Options{
		Codecs:           make(map[string]codec.NewCodec),
		Metadata:         map[string]string{},
		Broker:           http.NewBroker(),
		Registry:         mdns.NewRegistry(),
		Address:          server.DefaultAddress,
		Name:             server.DefaultName,
		Id:               server.DefaultId,
		Version:          server.DefaultVersion,
		RegisterInterval: server.DefaultRegisterInterval,
		RegisterTTL:      server.DefaultRegisterTTL,
	}

	for _, o := range opt {
		o(&opts)
	}

	return opts
}

mdns.NewRegistry方法返回的就是默认的注册中心。

func NewRegistry(opts ...registry.Option) registry.Registry {
	return newRegistry(opts...)
}

func newRegistry(opts ...registry.Option) registry.Registry {
	options := registry.Options{
		Context: context.Background(),
		Timeout: time.Millisecond * 100,
	}

	for _, o := range opts {
		o(&options)
	}

	// set the domain
	defaultDomain := registry.DefaultDomain
	if d, ok := options.Context.Value("mdns.domain").(string); ok {
		defaultDomain = d
	}

	return &mdnsRegistry{
		defaultDomain: defaultDomain, // default value is micro
		globalDomain:  globalDomain,  // default value is global
		opts:          options,
		domains:       make(map[string]services),
		watchers:      make(map[string]*mdnsWatcher),
	}
}

// mdns 注册中心结构体
type mdnsRegistry struct {
	opts registry.Options

	// the top level domains, these can be overriden using options
	defaultDomain string
	globalDomain  string

	sync.Mutex
	domains map[string]services

	mtx sync.RWMutex

	// watchers
	watchers map[string]*mdnsWatcher

	// listener
	listener chan *mdns.ServiceEntry
}

// services 是一个键值对字典,服务名作为key,value是一个mdns条目列表,代表拥有服务条目的单个节点
type services map[string][]*mdnsEntry

// 该结构体代表用于对应服务的节点
type mdnsEntry struct {
	id   string
	node *mdns.Server
}

默认的注册中心的Init方法主要用途是进行选项的赋值。将参数附加到注册中心的选项集合上。

func (m *mdnsRegistry) Init(opts ...registry.Option) error {
	for _, o := range opts {
		o(&m.opts)
	}
	return nil
}

func (m *mdnsRegistry) Options() registry.Options {
	return m.opts
}

而真正的注册动作为Register,该方法不仅将服务注册到指定的域名,而且还会将其注册到全局的域中,用于查询使用。如果在注册选项中没有给出Domain,那么默认的domian就是micro.

func (m *mdnsRegistry) Register(service *registry.Service, opts ...registry.RegisterOption) error {
	m.Lock()

	// 解析options
	var options registry.RegisterOptions
	for _, o := range opts {
		o(&options)
	}
    
    // 如果要在其中注册的域为空,那么将使用默认的域,也就是micro。
	if len(options.Domain) == 0 {
		options.Domain = m.defaultDomain
	}

    // 在内存存储中创建域,如果该域还未存在的话
    // m.domains是一个字典,key是域名,value是一个也是一个字典,value字典用于存储服务名以及对应的节点条目
	if _, ok := m.domains[options.Domain]; !ok {
		m.domains[options.Domain] = make(services)
	}

	entries, err := m.createMDNSEntries(options.Domain, service.Name)
	if err != nil {
		m.Unlock()
		return err
	}

	entries, gerr := registerService(service, entries, options)

	// 通过上面几步操作,就可以将给定域名下的服务的value更新为最新的服务条目列表
	m.domains[options.Domain][service.Name] = entries
	m.Unlock()

    // 此时,还要把服务同时注册到全局的域中,这样以便可以查询
	if options.Domain != m.globalDomain {
    	// 创建全局的服务,具体看下面的介绍
		srv := createGlobalDomainService(service, options)
        // 注册到全局的域中
		if err := m.Register(srv, append(opts, registry.RegisterDomain(m.globalDomain))...); err != nil {
			gerr = err
		}
	}

	return gerr
}

这个方法的内容比较多,为了更详细的理解其中的内容,我们分拆其中的功能,一步一步的看,首先

createMDNSEntries通过提供的域名,以及服务名,创建运行服务的节点条目。如果在m.domains中已经存在对应的域名和服务名,则直接返回。

// createMDNSEntries 创建给定域下的服务名对应的节点列表
func (m *mdnsRegistry) createMDNSEntries(domain, serviceName string) ([]*mdnsEntry, error) {
	// if it already exists don't reegister it again
	entries, ok := m.domains[domain][serviceName]
	if ok {
		return entries, nil
	}

	// create the wildcard entry used for list queries in this domain
	entry, err := createServiceMDNSEntry(serviceName, domain)
	if err != nil {
		return nil, err
	}

	return []*mdnsEntry{entry}, nil
}


// createServiceMDNSEntry 会创建一个新通配符的mdns 条目在给定的域中服务,该通配符mdns条目用于列出所有服务的时候。
func createServiceMDNSEntry(name, domain string) (*mdnsEntry, error) {
	ip := net.ParseIP("0.0.0.0")
    // NewMDNSService 返回一个新的MDNSService实例
	s, err := mdns.NewMDNSService(name, "_services", domain+".", "", 9999, []net.IP{ip}, nil)
	if err != nil {
		return nil, err
	}
    // NewServer 被用于根据给定的一个config创建一个新的mDNS服务器
	srv, err := mdns.NewServer(&mdns.Config{Zone: &mdns.DNSSDService{MDNSService: s}, LocalhostChecking: true})
	if err != nil {
		return nil, err
	}
    
    // 用新创建的mDNS服务器作为一个节点条目然后返回
	return &mdnsEntry{id: "*", node: srv}, nil
}

创建完了服务名针对的节点条目之后,就可以进行注册了。如下方法是服务注册功能的方法。第一个参数为要注册的服务,第二个参数为该服务针对的服务节点条目列表,第三个为注册选项。

该方法首先进行service.Nodes遍历来判断服务条目是否已经注册到每个node里。如果在某个节点中并没有注册。那么就在没创建服务的节点上创建服务并追加到服务条目列表中

这一通操作就是为了将所有的节点上都创建了服务节点条目。

func registerService(service *registry.Service, entries []*mdnsEntry, options registry.RegisterOptions) ([]*mdnsEntry, error) {
	var lastError error
	for _, node := range service.Nodes {
		var seen bool

		for _, entry := range entries {
			if node.Id == entry.id {
				seen = true
				break
			}
		}

		// this node has already been registered, continue
		if seen {
			continue
		}
        
        // 将mdns信息先json编码,在压缩编码,然后分割为255个字节的字符串列表
		txt, err := encode(&mdnsTxt{
			Service:   service.Name,
			Version:   service.Version,
			Endpoints: service.Endpoints,
			Metadata:  node.Metadata,
		})

		if err != nil {
			lastError = err
			continue
		}

		host, pt, err := net.SplitHostPort(node.Address)
		if err != nil {
			lastError = err
			continue
		}
		port, _ := strconv.Atoi(pt)

		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
			logger.Debugf("[mdns] registry create new service with ip: %s for: %s", net.ParseIP(host).String(), host)
		}
		// NewMDNSService 返回一个新的MDNSService实例
		s, err := mdns.NewMDNSService(
			node.Id,
			service.Name,
			options.Domain+".",
			"",
			port,
			[]net.IP{net.ParseIP(host)},
			txt,
		)
		if err != nil {
			lastError = err
			continue
		}
        // NewServer 被用于根据给定的一个config创建一个新的mDNS服务器
		srv, err := mdns.NewServer(&mdns.Config{Zone: s, LocalhostChecking: true})
		if err != nil {
			lastError = err
			continue
		}

		entries = append(entries, &mdnsEntry{id: node.Id, node: srv})
	}

	return entries, lastError
}

在将服务注册到给定的域中后,还要将服务注册到全局的域中,以便可以查询,而 createGlobalDomainService就是用来创建全局的服务的。参数为创建的服务,以及注册选项。

// createGlobalDomainService 的逻辑很简单,就是将上面的服务赋值一份到全局域,同时将node中的元数据中的domain改为源domain 也就是传入的上面的option.Domain。在默认的情况下就是micro
func createGlobalDomainService (service *registry.Service, options registry.RegisterOptions) *registry.Service {
	srv := *service
	srv.Nodes = nil

	for _, n := range service.Nodes {
		node := n

		// set the original domain in node metadata
		if node.Metadata == nil {
			node.Metadata = map[string]string{"domain": options.Domain}
		} else {
			node.Metadata["domain"] = options.Domain
		}

		srv.Nodes = append(srv.Nodes, node)
	}

	return &srv
}

至此,我们看完了Register的所有内容,DeregisterRegister是一个逆过程。

func (m *mdnsRegistry) Deregister(service *registry.Service, opts ...registry.DeregisterOption) error {
	// parse the options
	var options registry.DeregisterOptions
	for _, o := range opts {
		o(&options)
	}
	if len(options.Domain) == 0 {
		options.Domain = m.defaultDomain
	}

	// 将服务从全局域中注销掉,注意这里的注销是在defer中调用的,也就是说会在该方法执行结束之际执行
	var err error
	if options.Domain != m.globalDomain {
		defer func() {
			err = m.Deregister(service, append(opts, registry.DeregisterDomain(m.globalDomain))...)
		}()
	}


    // 这个解锁操作是在从全局域中调用deregister方法之前进行。
	m.Lock()
	defer m.Unlock()

    // 域不存在,服务不能注销,退出
	if _, ok := m.domains[options.Domain]; !ok {
		return err
	}

    // 循环退出条目,检查所有的匹配项,关闭它们。
	var newEntries []*mdnsEntry
	for _, entry := range m.domains[options.Domain][service.Name] {
		var remove bool

		for _, node := range service.Nodes {
			if node.Id == entry.id {
				entry.node.Shutdown()
				remove = true
				break
			}
		}

		// keep it?
		if !remove {
			newEntries = append(newEntries, entry)
		}
	}

    // 如果newEntries没有了条目,意思就是,这个服务的所有服务节点都没有了,直接退出
	if len(newEntries) == 0 {
		return nil
	}

	// we have more than one entry remaining, we can exit
    // 如果还存在服务节点条目,退出
	if len(newEntries) > 1 {
		m.domains[options.Domain][service.Name] = newEntries
		return err
	}

	
    // 如果遗留的服务条目不是一个通配符,退出
	if len(newEntries) == 1 && newEntries[0].id != "*" {
		m.domains[options.Domain][service.Name] = newEntries
		return err
	}

    // 如果最后省的是一个通配符条目,关闭节点,并将服务从域中删除
	newEntries[0].node.Shutdown()
	delete(m.domains[options.Domain], service.Name)

    // 检测域的长度是否为空,如果为空,直接将域从m.doamins中删除。
	if len(m.domains[options.Domain]) == 0 {
		delete(m.domains, options.Domain)
	}

	return err
}

通过给定字符串参数,以及选项,获取对应的服务和选项。

func (m *mdnsRegistry) GetService(service string, opts ...registry.GetOption) ([]*registry.Service, error) {
	// parse the options
	var options registry.GetOptions
	for _, o := range opts {
		o(&options)
	}
	if len(options.Domain) == 0 {
		options.Domain = m.defaultDomain
	}
	if options.Domain == registry.WildcardDomain {
		options.Domain = m.globalDomain
	}

	serviceMap := make(map[string]*registry.Service)
	entries := make(chan *mdns.ServiceEntry, 10)
	done := make(chan bool)
    
    // DefaultParams 用于返回一个默认的查询参数集合
	p := mdns.DefaultParams(service)
	// 设置超时上下文
	var cancel context.CancelFunc
	p.Context, cancel = context.WithTimeout(context.Background(), m.opts.Timeout)
	defer cancel()
	// set entries channel
	p.Entries = entries
	// set the domain
	p.Domain = options.Domain

	go func() {
		for {
			select {
			case e := <-entries:
				// list record so skip
				if e.Name == "_services" {
					continue
				}
				if e.TTL == 0 {
					continue
				}

				txt, err := decode(e.InfoFields)
				if err != nil {
					continue
				}

				if txt.Service != service {
					continue
				}

				s, ok := serviceMap[txt.Version]
				if !ok {
					s = &registry.Service{
						Name:      txt.Service,
						Version:   txt.Version,
						Endpoints: txt.Endpoints,
					}
				}
				addr := ""
				// prefer ipv4 addrs
				if len(e.AddrV4) > 0 {
					addr = e.AddrV4.String()
					// else use ipv6
				} else if len(e.AddrV6) > 0 {
					addr = "[" + e.AddrV6.String() + "]"
				} else {
					if logger.V(logger.InfoLevel, logger.DefaultLogger) {
						logger.Infof("[mdns]: invalid endpoint received: %v", e)
					}
					continue
				}
				s.Nodes = append(s.Nodes, &registry.Node{
					Id:       strings.TrimSuffix(e.Name, "."+p.Service+"."+p.Domain+"."),
					Address:  fmt.Sprintf("%s:%d", addr, e.Port),
					Metadata: txt.Metadata,
				})

				serviceMap[txt.Version] = s
			case <-p.Context.Done():
				close(done)
				return
			}
		}
	}()

	// execute the query
	if err := mdns.Query(p); err != nil {
		return nil, err
	}

	// wait for completion
	<-done

	// create list and return
	services := make([]*registry.Service, 0, len(serviceMap))

	for _, service := range serviceMap {
		services = append(services, service)
	}

	return services, nil
}

ListServices用于查询所有的服务列表

最后一部分内容关于监控的部分。其中的Watch方法返回的是一个实现了Watcher接口的监控器mdnsWatcher

func (m *mdnsRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) {
	var wo registry.WatchOptions
	for _, o := range opts {
		o(&wo)
	}
	if len(wo.Domain) == 0 {
		wo.Domain = m.defaultDomain
	}
	if wo.Domain == registry.WildcardDomain {
		wo.Domain = m.globalDomain
	}

	md := &mdnsWatcher{
		id:       uuid.New().String(),
		wo:       wo,
		ch:       make(chan *mdns.ServiceEntry, 32),
		exit:     make(chan struct{}),
		domain:   wo.Domain,
		registry: m,
	}

	m.mtx.Lock()
	defer m.mtx.Unlock()

	// 将监控器保存到注册中心的监控器map中
	m.watchers[md.id] = md

	// 检查注册中心的监听是否存在
	if m.listener != nil {
		return md, nil
	}

	// 开启监听
	go func() {
		// go to infinity
		for {
			m.mtx.Lock()

			// just return if there are no watchers
			if len(m.watchers) == 0 {
				m.listener = nil
				m.mtx.Unlock()
				return
			}

			// check existing listener
			if m.listener != nil {
				m.mtx.Unlock()
				return
			}

			// reset the listener
			exit := make(chan struct{})
			ch := make(chan *mdns.ServiceEntry, 32)
			m.listener = ch

			m.mtx.Unlock()

			// 把消息发送到监听器
			go func() {
				send := func(w *mdnsWatcher, e *mdns.ServiceEntry) {
					select {
					case w.ch <- e:
					default:
					}
				}

				for {
					select {
					case <-exit:
						return
					case e, ok := <-ch:
						if !ok {
							return
						}
						m.mtx.RLock()
						// send service entry to all watchers
						for _, w := range m.watchers {
							send(w, e)
						}
						m.mtx.RUnlock()
					}
				}

			}()

			// start listening, blocking call
			mdns.Listen(ch, exit)

			// mdns.Listen has unblocked
			// kill the saved listener
			m.mtx.Lock()
			m.listener = nil
			close(ch)
			m.mtx.Unlock()
		}
	}()

	return md, nil
}

mdnsWatcher监控器的Stop方法很简单,就是将监听器从注册中心的监听器记录中移除。Next方法从注册中心中接收事件并解析。

func (m *mdnsWatcher) Next() (*registry.Result, error) {
	for {
		select {
		case e := <-m.ch:
			txt, err := decode(e.InfoFields)
			if err != nil {
				continue
			}

			if len(txt.Service) == 0 || len(txt.Version) == 0 {
				continue
			}

			// Filter watch options
			// wo.Service: Only keep services we care about
			if len(m.wo.Service) > 0 && txt.Service != m.wo.Service {
				continue
			}
			var action string
			if e.TTL == 0 {
				action = "delete"
			} else {
				action = "create"
			}

			service := &registry.Service{
				Name:      txt.Service,
				Version:   txt.Version,
				Endpoints: txt.Endpoints,
				Metadata:  txt.Metadata,
			}

			// 过滤掉所有我们不关心的域
			suffix := fmt.Sprintf(".%s.%s.", service.Name, m.domain)
			if !strings.HasSuffix(e.Name, suffix) {
				continue
			}

			var addr string
			if len(e.AddrV4) > 0 {
				addr = e.AddrV4.String()
			} else if len(e.AddrV6) > 0 {
				addr = "[" + e.AddrV6.String() + "]"
			} else {
				addr = e.Addr.String()
			}

			service.Nodes = append(service.Nodes, &registry.Node{
				Id:       strings.TrimSuffix(e.Name, suffix),
				Address:  fmt.Sprintf("%s:%d", addr, e.Port),
				Metadata: txt.Metadata,
			})

			return &registry.Result{
				Action:  action,
				Service: service,
			}, nil
		case <-m.exit:
			return nil, registry.ErrWatcherStopped
		}
	}
}

func (m *mdnsWatcher) Stop() {
	select {
	case <-m.exit:
		return
	default:
		close(m.exit)
		// remove self from the registry
		m.registry.mtx.Lock()
		delete(m.registry.watchers, m.id)
		m.registry.mtx.Unlock()
	}
}