这部分代码从结构上来说,非常的简单
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的味道了,很有意思