我们定义好的服务,如何进行方便的使用,而不是直接指定特定的地址进行调用。这就需要使用额外的服务发现组件,在产线上,可能大家比较熟悉的有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
字段。
Field | Description | Length bits |
---|---|---|
QNAME | Name of the node to which the query pertains | Variable |
QTYPE | The type of the query, i.e. the type of RR which should be returned in responses. | 16 |
UNICAST-RESPONSE | Boolean flag indicating whether a unicast-response is desired | 1 |
QCLASS | Class code, 1 a.k.a. "IN" for the Internet and IP networks | 15 |
与单播DNS中一样,QNAME字段由一系列称为“标签”的长度/值子字段组成。 每个标签代表完全限定域名(FQDN)中点分隔的子字符串之一。该列表以单个空字节终止,表示DNS的“根”。UNICAST-RESPONSE
字段用于最小化网络上不必要的广播.如果设置了位,应答者应该直接向查询节点发送定向单播响应,而不是向整个网络广播响应。QCLASS
字段与单播dns中的相同。
答案,权威名称服务器和其他记录部分中的所有记录都具有相同的格式,统称为资源记录(RR)。与单播DNS相比,mDNS中的资源记录的一般格式也略有修改
Field | Description | Length bits |
---|---|---|
RRNAME | Name of the node to which the record pertains | Variable |
RRTYPE | The type of the Resource Record | 16 |
CACHE-FLUSH | Boolean flag indicating whether outdated cached records should be purged | 1 |
RRCLASS | Class code, 1 a.k.a. "IN" for the Internet and IP networks | 15 |
TTL | Time interval (in seconds) that the RR should be cached | 32 |
RDLENGTH | Integer representing the length (in octets) of the RDATA field | 16 |
RDATA | Resource data; internal structure varies by RRTYPE | Variable |
CACHE-FLUSH
位用于指示邻居节点记录应覆盖此RRNAME
和RRTYPE
的任何现有缓存条目,而不是附加到该条目。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
的所有内容,Deregister
和Register
是一个逆过程。
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 = ®istry.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, ®istry.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 := ®istry.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, ®istry.Node{
Id: strings.TrimSuffix(e.Name, suffix),
Address: fmt.Sprintf("%s:%d", addr, e.Port),
Metadata: txt.Metadata,
})
return ®istry.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()
}
}