selector

55 阅读3分钟

这部分代码从结构上来说,非常的简单

strategy.go

提供了两个默认的策略,随机算法没什么好说的,针对用的比较少的RoundRobin有几点疑问

func RoundRobin(services []*registry.Service) Next {
	nodes := make([]*registry.Node, 0, len(services))

	for _, service := range services {
		nodes = append(nodes, service.Nodes...)
	}

	var i = rand.Int()
	var mtx sync.Mutex

	return func() (*registry.Node, error) {
		if len(nodes) == 0 {
			return nil, ErrNoneAvailable
		}

		mtx.Lock()
		node := nodes[i%len(nodes)]
		i++
		mtx.Unlock()

		return node, nil
	}
}

锁的存在看起来只是为了锁住i,那么使用atomic完全也是可以的,看了一下提交记录是9年前,一直没人用的算法可能缺少维护吧

这个模块看起来,最值得学习的是每次调用都会生成相应的node快照,对比我一直以来的写法,值得学习

selector.go

定义了selector的interface,基础定义

// Selector builds on the registry as a mechanism to pick nodes
// and mark their status. This allows host pools and other things
// to be built using various algorithms.
type Selector interface {
	Init(opts ...Option) error
	Options() Options
	// Select returns a function which should return the next node
	Select(service string, opts ...SelectOption) (Next, error)
	// Mark sets the success/error against a node
	Mark(service string, node *registry.Node, err error)
	// Reset returns state back to zero for a service
	Reset(service string)
	// Close renders the selector unusable
	Close() error
	// Name of the selector
	String() string
}

// Next is a function that returns the next node
// based on the selector's strategy.
type Next func() (*registry.Node, error)

// Filter is used to filter a service during the selection process.
type Filter func([]*registry.Service) []*registry.Service

// Strategy is a selection strategy e.g random, round robin.
type Strategy func([]*registry.Service) Next

var (
	DefaultSelector = NewSelector()

	ErrNotFound      = errors.New("not found")
	ErrNoneAvailable = errors.New("none available")
)

options.go

go-micro每个包都有的参数定义模块

filter.go

这个函数提供了主要的对外能力

FilterEndpoint

具体到grpc接口来说,这个函数提供了根据当前endpoint获取对应服务的能力

func FilterEndpoint(name string) Filter {
	return func(old []*registry.Service) []*registry.Service {
		var services []*registry.Service

		for _, service := range old {
			for _, ep := range service.Endpoints {
				if ep.Name == name {
					services = append(services, service)
					break
				}
			}
		}

		return services
	}
}

FilterLabel

支持根据label来筛选,从实际工作场景上来看,这个功能基本没怎么用到过,和上面的函数差不多,唯一的区别是他的数据是存储在metadata中的

func FilterLabel(key, val string) Filter {
	return func(old []*registry.Service) []*registry.Service {
		var services []*registry.Service

		for _, service := range old {
			serv := new(registry.Service)
			var nodes []*registry.Node

			for _, node := range service.Nodes {
				if node.Metadata == nil {
					continue
				}

				if node.Metadata[key] == val {
					nodes = append(nodes, node)
				}
			}

			// only add service if there's some nodes
			if len(nodes) > 0 {
				// copy
				*serv = *service
				serv.Nodes = nodes
				services = append(services, serv)
			}
		}

		return services
	}
}

FilterVersion

本意是提供版本控制的能力,但是在实际经验中,我们对V1 V2 V3之类的版本迭代,老接口一般是保留,直接新增V4


func FilterVersion(version string) Filter {
	return func(old []*registry.Service) []*registry.Service {
		var services []*registry.Service

		for _, service := range old {
			if service.Version == version {
				services = append(services, service)
			}
		}

		return services
	}
}

default.go

func (c *registrySelector) Init(opts ...Option) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	for _, o := range opts {
		o(&c.so)
	}

	c.rc.Stop()
	c.rc = c.newCache()

	return nil
}

这段代码很有意思,核心业务封装在rc中,通过不断的stop与newCache支持重新初始化,一般这样的逻辑在实际场景中我更偏向于写Reinit之类的,后续看看其他模块怎么写的。

func (c *registrySelector) Select(service string, opts ...SelectOption) (Next, error) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	sopts := SelectOptions{
		Strategy: c.so.Strategy,
	}

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

	// get the service
	// try the cache first
	// if that fails go directly to the registry
	services, err := c.rc.GetService(service)
	if err != nil {
		if errors.Is(err, registry.ErrNotFound) {
			return nil, ErrNotFound
		}

		return nil, err
	}

	// apply the filters
	for _, filter := range sopts.Filters {
		services = filter(services)
	}

	// if there's nothing left, return
	if len(services) == 0 {
		return nil, ErrNoneAvailable
	}

	return sopts.Strategy(services), nil
}

返回的sopts.Strategy(services)也仅仅是一个函数,实际取值由运行时调用返回的Next决定。如果失败,则重新init。 通过一系列的中间函数,生成构造一个最终的的结果函数,如果结果函数输出错误或者需要重新更新则init,否则一直保持,有点战锤mcv的味道了,很有意思