Initial QSfera import
This commit is contained in:
+123
@@ -0,0 +1,123 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"go-micro.dev/v4/registry"
|
||||
"go-micro.dev/v4/registry/cache"
|
||||
)
|
||||
|
||||
type registrySelector struct {
|
||||
so Options
|
||||
rc cache.Cache
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *registrySelector) newCache() cache.Cache {
|
||||
opts := make([]cache.Option, 0, 1)
|
||||
|
||||
if c.so.Context != nil {
|
||||
if t, ok := c.so.Context.Value("selector_ttl").(time.Duration); ok {
|
||||
opts = append(opts, cache.WithTTL(t))
|
||||
}
|
||||
}
|
||||
|
||||
return cache.New(c.so.Registry, opts...)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *registrySelector) Options() Options {
|
||||
return c.so
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *registrySelector) Mark(service string, node *registry.Node, err error) {
|
||||
}
|
||||
|
||||
func (c *registrySelector) Reset(service string) {
|
||||
}
|
||||
|
||||
// Close stops the watcher and destroys the cache.
|
||||
func (c *registrySelector) Close() error {
|
||||
c.rc.Stop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *registrySelector) String() string {
|
||||
return "registry"
|
||||
}
|
||||
|
||||
// NewSelector creates a new default selector.
|
||||
func NewSelector(opts ...Option) Selector {
|
||||
sopts := Options{
|
||||
Strategy: Random,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&sopts)
|
||||
}
|
||||
|
||||
if sopts.Registry == nil {
|
||||
sopts.Registry = registry.DefaultRegistry
|
||||
}
|
||||
|
||||
s := ®istrySelector{
|
||||
so: sopts,
|
||||
}
|
||||
s.rc = s.newCache()
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"go-micro.dev/v4/registry"
|
||||
)
|
||||
|
||||
// FilterEndpoint is an endpoint based Select Filter which will
|
||||
// only return services with the endpoint specified.
|
||||
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 is a label based Select Filter which will
|
||||
// only return services with the label specified.
|
||||
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 is a version based Select Filter which will
|
||||
// only return services with the version specified.
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go-micro.dev/v4/logger"
|
||||
"go-micro.dev/v4/registry"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Registry registry.Registry
|
||||
Strategy Strategy
|
||||
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
Context context.Context
|
||||
// Logger is the underline logger
|
||||
Logger logger.Logger
|
||||
}
|
||||
|
||||
type SelectOptions struct {
|
||||
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
Context context.Context
|
||||
Strategy Strategy
|
||||
|
||||
Filters []Filter
|
||||
}
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
// SelectOption used when making a select call.
|
||||
type SelectOption func(*SelectOptions)
|
||||
|
||||
// Registry sets the registry used by the selector.
|
||||
func Registry(r registry.Registry) Option {
|
||||
return func(o *Options) {
|
||||
o.Registry = r
|
||||
}
|
||||
}
|
||||
|
||||
// SetStrategy sets the default strategy for the selector.
|
||||
func SetStrategy(fn Strategy) Option {
|
||||
return func(o *Options) {
|
||||
o.Strategy = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithFilter adds a filter function to the list of filters
|
||||
// used during the Select call.
|
||||
func WithFilter(fn ...Filter) SelectOption {
|
||||
return func(o *SelectOptions) {
|
||||
o.Filters = append(o.Filters, fn...)
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy sets the selector strategy.
|
||||
func WithStrategy(fn Strategy) SelectOption {
|
||||
return func(o *SelectOptions) {
|
||||
o.Strategy = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger sets the underline logger.
|
||||
func WithLogger(l logger.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = l
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Package selector is a way to pick a list of service nodes
|
||||
package selector
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"go-micro.dev/v4/registry"
|
||||
)
|
||||
|
||||
// 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")
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go-micro.dev/v4/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Random is a random strategy algorithm for node selection.
|
||||
func Random(services []*registry.Service) Next {
|
||||
nodes := make([]*registry.Node, 0, len(services))
|
||||
|
||||
for _, service := range services {
|
||||
nodes = append(nodes, service.Nodes...)
|
||||
}
|
||||
|
||||
return func() (*registry.Node, error) {
|
||||
if len(nodes) == 0 {
|
||||
return nil, ErrNoneAvailable
|
||||
}
|
||||
|
||||
i := rand.Int() % len(nodes)
|
||||
return nodes[i], nil
|
||||
}
|
||||
}
|
||||
|
||||
// RoundRobin is a roundrobin strategy algorithm for node selection.
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user