1
0
mirror of https://github.com/go-micro/go-micro.git synced 2025-08-10 21:52:01 +02:00

Move to a selector package

This commit is contained in:
Asim
2015-12-09 19:23:16 +00:00
parent 91405e5993
commit eefb9c53d4
18 changed files with 304 additions and 188 deletions

View File

@@ -1,164 +0,0 @@
package registry
import (
"math/rand"
"sync"
"time"
)
type blackListNode struct {
age time.Time
id string
service string
}
type blackListSelector struct {
so SelectorOptions
ttl int64
exit chan bool
once sync.Once
sync.RWMutex
bl map[string]blackListNode
}
func init() {
rand.Seed(time.Now().Unix())
}
func (r *blackListSelector) purge() {
now := time.Now()
r.Lock()
for k, v := range r.bl {
if d := v.age.Sub(now); d.Seconds() < 0 {
delete(r.bl, k)
}
}
r.Unlock()
}
func (r *blackListSelector) run() {
t := time.NewTicker(time.Duration(r.ttl) * time.Second)
for {
select {
case <-r.exit:
t.Stop()
return
case <-t.C:
r.purge()
}
}
}
func (r *blackListSelector) Select(service string, opts ...SelectOption) (SelectNext, error) {
var sopts SelectOptions
for _, opt := range opts {
opt(&sopts)
}
// get the service
services, err := r.so.Registry.GetService(service)
if err != nil {
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, ErrNotFound
}
var nodes []*Node
for _, service := range services {
for _, node := range service.Nodes {
nodes = append(nodes, node)
}
}
if len(nodes) == 0 {
return nil, ErrNotFound
}
return func() (*Node, error) {
var viable []*Node
r.RLock()
for _, node := range nodes {
if _, ok := r.bl[node.Id]; !ok {
viable = append(viable, node)
}
}
r.RUnlock()
if len(viable) == 0 {
return nil, ErrNoneAvailable
}
return viable[rand.Int()%len(viable)], nil
}, nil
}
func (r *blackListSelector) Mark(service string, node *Node, err error) {
r.Lock()
defer r.Unlock()
if err == nil {
delete(r.bl, node.Id)
return
}
r.bl[node.Id] = blackListNode{
age: time.Now().Add(time.Duration(r.ttl) * time.Second),
id: node.Id,
service: service,
}
return
}
func (r *blackListSelector) Reset(service string) {
r.Lock()
defer r.Unlock()
for k, v := range r.bl {
if v.service == service {
delete(r.bl, k)
}
}
return
}
func (r *blackListSelector) Close() error {
r.once.Do(func() {
close(r.exit)
})
return nil
}
func NewBlackListSelector(opts ...SelectorOption) Selector {
var sopts SelectorOptions
for _, opt := range opts {
opt(&sopts)
}
if sopts.Registry == nil {
sopts.Registry = DefaultRegistry
}
var once sync.Once
bl := &blackListSelector{
once: once,
so: sopts,
ttl: 60,
bl: make(map[string]blackListNode),
exit: make(chan bool),
}
go bl.run()
return bl
}

View File

@@ -1,70 +0,0 @@
package registry
import (
"errors"
"testing"
"time"
)
func TestBlackListSelector(t *testing.T) {
counts := map[string]int{}
bl := &blackListSelector{
so: SelectorOptions{
Registry: &mockRegistry{},
},
ttl: 2,
bl: make(map[string]blackListNode),
exit: make(chan bool),
}
go bl.run()
defer bl.Close()
next, err := bl.Select("foo")
if err != nil {
t.Errorf("Unexpected error calling bl select: %v", err)
}
for i := 0; i < 100; i++ {
node, err := next()
if err != nil {
t.Errorf("Expected node err, got err: %v", err)
}
counts[node.Id]++
}
t.Logf("BlackList Counts %v", counts)
// test blacklisting
for i := 0; i < 4; i++ {
node, err := next()
if err != nil {
t.Errorf("Expected node err, got err: %v", err)
}
bl.Mark("foo", node, errors.New("blacklist"))
}
if node, err := next(); err != ErrNoneAvailable {
t.Errorf("Expected none available err, got node %v err %v", node, err)
}
time.Sleep(time.Second * time.Duration(bl.ttl) * 2)
if _, err := next(); err != nil {
t.Errorf("Unexpected err %v", err)
}
// test resetting
for i := 0; i < 4; i++ {
node, err := next()
if err != nil {
t.Errorf("Unexpected err: %v", err)
}
bl.Mark("foo", node, errors.New("blacklist"))
}
if node, err := next(); err != ErrNoneAvailable {
t.Errorf("Expected none available err, got node %v err %v", node, err)
}
bl.Reset("foo")
if _, err := next(); err != nil {
t.Errorf("Unexpected err %v", err)
}
}

70
registry/mock/mock.go Normal file
View File

@@ -0,0 +1,70 @@
package mock
import (
"github.com/micro/go-micro/registry"
)
type MockRegistry struct{}
func (m *MockRegistry) GetService(service string) ([]*registry.Service, error) {
return []*registry.Service{
{
Name: "foo",
Version: "1.0.0",
Nodes: []*registry.Node{
{
Id: "foo-1.0.0-123",
Address: "localhost",
Port: 9999,
},
{
Id: "foo-1.0.0-321",
Address: "localhost",
Port: 9999,
},
},
},
{
Name: "foo",
Version: "1.0.1",
Nodes: []*registry.Node{
{
Id: "foo-1.0.1-321",
Address: "localhost",
Port: 6666,
},
},
},
{
Name: "foo",
Version: "1.0.3",
Nodes: []*registry.Node{
{
Id: "foo-1.0.3-345",
Address: "localhost",
Port: 8888,
},
},
},
}, nil
}
func (m *MockRegistry) ListServices() ([]*registry.Service, error) {
return []*registry.Service{}, nil
}
func (m *MockRegistry) Register(s *registry.Service) error {
return nil
}
func (m *MockRegistry) Deregister(s *registry.Service) error {
return nil
}
func (m *MockRegistry) Watch() (registry.Watcher, error) {
return nil, nil
}
func NewRegistry() *MockRegistry {
return &MockRegistry{}
}

View File

@@ -1,62 +0,0 @@
package registry
type mockRegistry struct{}
func (m *mockRegistry) GetService(service string) ([]*Service, error) {
return []*Service{
{
Name: "foo",
Version: "1.0.0",
Nodes: []*Node{
{
Id: "foo-1.0.0-123",
Address: "localhost",
Port: 9999,
},
{
Id: "foo-1.0.0-321",
Address: "localhost",
Port: 9999,
},
},
},
{
Name: "foo",
Version: "1.0.1",
Nodes: []*Node{
{
Id: "foo-1.0.1-321",
Address: "localhost",
Port: 6666,
},
},
},
{
Name: "foo",
Version: "1.0.3",
Nodes: []*Node{
{
Id: "foo-1.0.3-345",
Address: "localhost",
Port: 8888,
},
},
},
}, nil
}
func (m *mockRegistry) ListServices() ([]*Service, error) {
return []*Service{}, nil
}
func (m *mockRegistry) Register(s *Service) error {
return nil
}
func (m *mockRegistry) Deregister(s *Service) error {
return nil
}
func (m *mockRegistry) Watch() (Watcher, error) {
return nil, nil
}

View File

@@ -1,87 +0,0 @@
package registry
import (
"math/rand"
"time"
)
type randomSelector struct {
so SelectorOptions
}
func init() {
rand.Seed(time.Now().Unix())
}
func (r *randomSelector) Select(service string, opts ...SelectOption) (SelectNext, error) {
var sopts SelectOptions
for _, opt := range opts {
opt(&sopts)
}
// get the service
services, err := r.so.Registry.GetService(service)
if err != nil {
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, ErrNotFound
}
var nodes []*Node
for _, service := range services {
for _, node := range service.Nodes {
nodes = append(nodes, node)
}
}
if len(nodes) == 0 {
return nil, ErrNotFound
}
return func() (*Node, error) {
i := rand.Int()
j := i % len(services)
if len(services[j].Nodes) == 0 {
return nil, ErrNotFound
}
k := i % len(services[j].Nodes)
return services[j].Nodes[k], nil
}, nil
}
func (r *randomSelector) Mark(service string, node *Node, err error) {
return
}
func (r *randomSelector) Reset(service string) {
return
}
func (r *randomSelector) Close() error {
return nil
}
func NewRandomSelector(opts ...SelectorOption) Selector {
var sopts SelectorOptions
for _, opt := range opts {
opt(&sopts)
}
if sopts.Registry == nil {
sopts.Registry = DefaultRegistry
}
return &randomSelector{sopts}
}

View File

@@ -1,30 +0,0 @@
package registry
import (
"testing"
)
func TestRandomSelector(t *testing.T) {
counts := map[string]int{}
rr := &randomSelector{
so: SelectorOptions{
Registry: &mockRegistry{},
},
}
next, err := rr.Select("foo")
if err != nil {
t.Errorf("Unexpected error calling random select: %v", err)
}
for i := 0; i < 100; i++ {
node, err := next()
if err != nil {
t.Errorf("Expected node err, got err: %v", err)
}
counts[node.Id]++
}
t.Logf("Random Counts %v", counts)
}

View File

@@ -1,9 +1,5 @@
package registry
import (
"errors"
)
type Registry interface {
Register(*Service) error
Deregister(*Service) error
@@ -18,9 +14,6 @@ type Option func(*options)
var (
DefaultRegistry = newConsulRegistry([]string{})
ErrNotFound = errors.New("not found")
ErrNoneAvailable = errors.New("none available")
)
func NewRegistry(addrs []string, opt ...Option) Registry {

View File

@@ -1,80 +0,0 @@
package registry
import (
"sync"
)
type roundRobinSelector struct {
so SelectorOptions
}
func (r *roundRobinSelector) Select(service string, opts ...SelectOption) (SelectNext, error) {
var sopts SelectOptions
for _, opt := range opts {
opt(&sopts)
}
// get the service
services, err := r.so.Registry.GetService(service)
if err != nil {
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, ErrNotFound
}
var nodes []*Node
for _, service := range services {
for _, node := range service.Nodes {
nodes = append(nodes, node)
}
}
if len(nodes) == 0 {
return nil, ErrNotFound
}
var i int
var mtx sync.Mutex
return func() (*Node, error) {
mtx.Lock()
defer mtx.Unlock()
i++
return nodes[i%len(nodes)], nil
}, nil
}
func (r *roundRobinSelector) Mark(service string, node *Node, err error) {
return
}
func (r *roundRobinSelector) Reset(service string) {
return
}
func (r *roundRobinSelector) Close() error {
return nil
}
func NewRoundRobinSelector(opts ...SelectorOption) Selector {
var sopts SelectorOptions
for _, opt := range opts {
opt(&sopts)
}
if sopts.Registry == nil {
sopts.Registry = DefaultRegistry
}
return &roundRobinSelector{sopts}
}

View File

@@ -1,30 +0,0 @@
package registry
import (
"testing"
)
func TestRoundRobinSelector(t *testing.T) {
counts := map[string]int{}
rr := &roundRobinSelector{
so: SelectorOptions{
Registry: &mockRegistry{},
},
}
next, err := rr.Select("foo")
if err != nil {
t.Errorf("Unexpected error calling rr select: %v", err)
}
for i := 0; i < 100; i++ {
node, err := next()
if err != nil {
t.Errorf("Expected node err, got err: %v", err)
}
counts[node.Id]++
}
t.Logf("Round Robin Counts %v", counts)
}

View File

@@ -1,44 +0,0 @@
package 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 {
Select(service string, opts ...SelectOption) (SelectNext, error)
Mark(service string, node *Node, err error)
Reset(service string)
Close() error
}
// SelectNext is a function that returns the next node
// based on the selector's algorithm
type SelectNext func() (*Node, error)
type SelectorOptions struct {
Registry Registry
}
type SelectOptions struct {
Filters []func([]*Service) []*Service
}
// Option used to initialise the selector
type SelectorOption func(*SelectorOptions)
// Option used when making a select call
type SelectOption func(*SelectOptions)
// SelectorRegistry sets the registry used by the selector
func SelectorRegistry(r Registry) SelectorOption {
return func(o *SelectorOptions) {
o.Registry = r
}
}
// SelectFilter adds a filter function to the list of filters
// used during the Select call.
func SelectFilter(fn func([]*Service) []*Service) SelectOption {
return func(o *SelectOptions) {
o.Filters = append(o.Filters, fn)
}
}