从go-micro默认的服务发现来学习go-micro

1,216 阅读4分钟

服务发现

服务发现是微服务开发的核心。当服务 A 需要与服务 B 通话时,它需要该服务的位置,go-micro中的registry组件定义了一套实现服务发现的接口,默认发现机制是多播 DNS (mdns),通常在实际项目中一般是使用etcd,consul,zookeeper等来实现服务发现的能力,当然,现在的云原生能力也已经有服务发现的能力的。

服务发现接口

// The registry provides an interface for service discovery
// and an abstraction over varying implementations
// {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
}

go-micro之所以说是可插拔的,就是定义了一套接口,只要实现接口,就可以插拔式的使用我们自定义的组件来完成相对应的功能。

默认的服务发现mdns

我们在micro中的mdns介绍过什么是mdns,你完全可以把它当成跟consul一样的工具,只是实现的方式不同罢了,下面我们就一起来通过Mdns来学习一下如何实现一套服务发现能力



// NewRegistry returns a new default registry which is mdns
func NewRegistry(opts ...Option) Registry {
	return newRegistry(opts...)
}
func newRegistry(opts ...Option) Registry {
	options := Options{
		Context: context.Background(),
		Timeout: time.Millisecond * 100,
	}

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

	// set the domain
	domain := mdnsDomain

	d, ok := options.Context.Value("mdns.domain").(string)
	if ok {
		domain = d
	}

	return &mdnsRegistry{
		opts:     options,
		domain:   domain,
		services: make(map[string][]*mdnsEntry),
		watchers: make(map[string]*mdnsWatcher),
	}
}

mdnsRegistry就是一个注册中心的实例,他的原型为:

type mdnsRegistry struct {
	opts Options
	// the mdns domain
	domain string

	sync.Mutex
	services map[string][]*mdnsEntry

	mtx sync.RWMutex

	// watchers
	watchers map[string]*mdnsWatcher

	// listener
	listener chan *mdns.ServiceEntry
}

针对Registry接口方法,我们简单一起过一遍

  • Init方法

将option选项加到mdnsRegisty实例的opts上。

  • Register方法

将服务注册到注册中心去,


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

	entries, ok := m.services[service.Name]
	// first entry, create wildcard used for list queries
	if !ok {
		s, err := mdns.NewMDNSService(
			service.Name,
			"_services",
			m.domain+".",
			"",
			9999,
			[]net.IP{net.ParseIP("0.0.0.0")},
			nil,
		)
		if err != nil {
			return err
		}

		srv, err := mdns.NewServer(&mdns.Config{Zone: &mdns.DNSSDService{MDNSService: s}})
		if err != nil {
			return err
		}

		// append the wildcard entry
		entries = append(entries, &mdnsEntry{id: "*", node: srv})
	}

	var gerr error

	for _, node := range service.Nodes {
		var seen bool
		var e *mdnsEntry

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

		// already registered, continue
		if seen {
			continue
			// doesn't exist
		} else {
			e = &mdnsEntry{}
		}

		txt, err := encode(&mdnsTxt{
			Service:   service.Name,
			Version:   service.Version,
			Endpoints: service.Endpoints,
			Metadata:  node.Metadata,
		})

		if err != nil {
			gerr = err
			continue
		}

		host, pt, err := net.SplitHostPort(node.Address)
		if err != nil {
			gerr = 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)
		}
		// we got here, new node
		s, err := mdns.NewMDNSService(
			node.Id,
			service.Name,
			m.domain+".",
			"",
			port,
			[]net.IP{net.ParseIP(host)},
			txt,
		)
		if err != nil {
			gerr = err
			continue
		}

		srv, err := mdns.NewServer(&mdns.Config{Zone: s, LocalhostChecking: true})
		if err != nil {
			gerr = err
			continue
		}

		e.id = node.Id
		e.node = srv
		entries = append(entries, e)
	}

	// save
	m.services[service.Name] = entries

	return gerr
}

首先根据参数服务的名称,在mdnsRegistry上的services字典中查看是否存在对应的服务,如果不存在就New一个,最终实现一个mdnsEntry实例,然后放到entries列表中。这个列表的用途是,在遍历服务的节点列表时,会比对entries列表中的某一个mdnsEntry实例,看看节点id和mdnsEntry的id是否一直,如果一致的话,说明该服务已经注册过了,不用重复注册了。否则就实现一个新的mdnsEntry实例,并将它放到注册中心的services字典中去。

  • GetService和ListServices

这两个方法就是通过mdns中的QueryParam,实时的查询entries管道里的ServiceEntry,拿到后就进行信息比对,把匹配到的services放到列表中返回给调用方。

  • Watch

实现监听者,负责注册中心的服务变更,具体实现是,实例化一个mdnsWatcher,然后把它放到mdnsRegistry实例的watchs里面。

如果mdnsRegistry的listener不存在,就启动一个listener

在watch方法了,for持续监听mdns.ServiceEntry的管道,如果有新的ServiceEntry就取出来广播给mdnsRegistry.watchers里面所有的watcher。

我们知道,Micro工具集使用的命令行工具是cli,这个工具的具体内容,我在之前的文章中有提到,你只要知道,当我们运行一个App的时候,调用app.Run的时候,针对用户不同的选项,会执行对应的action.不仅如此,在action执行前后,会根据是否有Before方法和After方法来进行对应的操作。

在Before方法里,会做各项初始化过程中,注册中心这块,会根据注册option来初始化默认的DefaultRegistry

if err := muregistry.DefaultRegistry.Init(registryOpts...); err != nil {
		logger.Fatalf("Error configuring registry: %v", err)
	}

在action里,首先会检查子命令是否存在,如果存在就直接运行子命令,如果不存在就会lookupService获取指定的服务列表。找到之后就可以调用相应的服务了callService。

lookupService里面调用serviceWithName来找到对应的服务,而serviceWithName就是

// find a service in a domain matching the name
func serviceWithName(name, domain string) (*goregistry.Service, error) {
	srvs, err := registry.DefaultRegistry.GetService(name, goregistry.GetDomain(domain))
	if err == goregistry.ErrNotFound {
		return nil, nil
	} else if err != nil {
		return nil, err
	}
	if len(srvs) == 0 {
		return nil, nil
	}
	return srvs[0], nil
}