commit
78da1fde94
@ -2,8 +2,6 @@
|
|||||||
package broker
|
package broker
|
||||||
|
|
||||||
// Broker is an interface used for asynchronous messaging.
|
// Broker is an interface used for asynchronous messaging.
|
||||||
// Its an abstraction over various message brokers
|
|
||||||
// {NATS, RabbitMQ, Kafka, ...}
|
|
||||||
type Broker interface {
|
type Broker interface {
|
||||||
Options() Options
|
Options() Options
|
||||||
Address() string
|
Address() string
|
||||||
|
@ -3,6 +3,7 @@ package broker
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -18,8 +19,9 @@ import (
|
|||||||
|
|
||||||
"github.com/micro/go-log"
|
"github.com/micro/go-log"
|
||||||
"github.com/micro/go-micro/broker/codec/json"
|
"github.com/micro/go-micro/broker/codec/json"
|
||||||
"github.com/micro/go-micro/errors"
|
merr "github.com/micro/go-micro/errors"
|
||||||
"github.com/micro/go-micro/registry"
|
"github.com/micro/go-micro/registry"
|
||||||
|
"github.com/micro/go-rcache"
|
||||||
maddr "github.com/micro/misc/lib/addr"
|
maddr "github.com/micro/misc/lib/addr"
|
||||||
mnet "github.com/micro/misc/lib/net"
|
mnet "github.com/micro/misc/lib/net"
|
||||||
mls "github.com/micro/misc/lib/tls"
|
mls "github.com/micro/misc/lib/tls"
|
||||||
@ -28,14 +30,10 @@ import (
|
|||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP Broker is a placeholder for actual message brokers.
|
// HTTP Broker is a point to point async broker
|
||||||
// This should not really be used in production but useful
|
|
||||||
// in developer where you want zero dependencies.
|
|
||||||
|
|
||||||
type httpBroker struct {
|
type httpBroker struct {
|
||||||
id string
|
id string
|
||||||
address string
|
address string
|
||||||
unsubscribe chan *httpSubscriber
|
|
||||||
opts Options
|
opts Options
|
||||||
|
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
@ -53,9 +51,9 @@ type httpSubscriber struct {
|
|||||||
opts SubscribeOptions
|
opts SubscribeOptions
|
||||||
id string
|
id string
|
||||||
topic string
|
topic string
|
||||||
ch chan *httpSubscriber
|
|
||||||
fn Handler
|
fn Handler
|
||||||
svc *registry.Service
|
svc *registry.Service
|
||||||
|
hb *httpBroker
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpPublication struct {
|
type httpPublication struct {
|
||||||
@ -106,11 +104,13 @@ func newHttpBroker(opts ...Option) Broker {
|
|||||||
o(&options)
|
o(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set address
|
||||||
addr := ":0"
|
addr := ":0"
|
||||||
if len(options.Addrs) > 0 && len(options.Addrs[0]) > 0 {
|
if len(options.Addrs) > 0 && len(options.Addrs[0]) > 0 {
|
||||||
addr = options.Addrs[0]
|
addr = options.Addrs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get registry
|
||||||
reg, ok := options.Context.Value(registryKey).(registry.Registry)
|
reg, ok := options.Context.Value(registryKey).(registry.Registry)
|
||||||
if !ok {
|
if !ok {
|
||||||
reg = registry.DefaultRegistry
|
reg = registry.DefaultRegistry
|
||||||
@ -123,7 +123,6 @@ func newHttpBroker(opts ...Option) Broker {
|
|||||||
r: reg,
|
r: reg,
|
||||||
c: &http.Client{Transport: newTransport(options.TLSConfig)},
|
c: &http.Client{Transport: newTransport(options.TLSConfig)},
|
||||||
subscribers: make(map[string][]*httpSubscriber),
|
subscribers: make(map[string][]*httpSubscriber),
|
||||||
unsubscribe: make(chan *httpSubscriber),
|
|
||||||
exit: make(chan chan error),
|
exit: make(chan chan error),
|
||||||
mux: http.NewServeMux(),
|
mux: http.NewServeMux(),
|
||||||
}
|
}
|
||||||
@ -153,9 +152,41 @@ func (h *httpSubscriber) Topic() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpSubscriber) Unsubscribe() error {
|
func (h *httpSubscriber) Unsubscribe() error {
|
||||||
h.ch <- h
|
return h.hb.unsubscribe(h)
|
||||||
// artificial delay
|
}
|
||||||
time.Sleep(time.Millisecond * 10)
|
|
||||||
|
func (h *httpBroker) subscribe(s *httpSubscriber) error {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
if err := h.r.Register(s.svc, registry.RegisterTTL(registerTTL)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.subscribers[s.topic] = append(h.subscribers[s.topic], s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpBroker) unsubscribe(s *httpSubscriber) error {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
var subscribers []*httpSubscriber
|
||||||
|
|
||||||
|
// look for subscriber
|
||||||
|
for _, sub := range h.subscribers[s.topic] {
|
||||||
|
// deregister and skip forward
|
||||||
|
if sub.id == s.id {
|
||||||
|
h.r.Deregister(sub.svc)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// keep subscriber
|
||||||
|
subscribers = append(subscribers, sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set subscribers
|
||||||
|
h.subscribers[s.topic] = subscribers
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,29 +208,75 @@ func (h *httpBroker) run(l net.Listener) {
|
|||||||
// received exit signal
|
// received exit signal
|
||||||
case ch := <-h.exit:
|
case ch := <-h.exit:
|
||||||
ch <- l.Close()
|
ch <- l.Close()
|
||||||
h.Lock()
|
h.RLock()
|
||||||
h.running = false
|
for _, subs := range h.subscribers {
|
||||||
h.Unlock()
|
for _, sub := range subs {
|
||||||
return
|
|
||||||
// unsubscribe subscriber
|
|
||||||
case subscriber := <-h.unsubscribe:
|
|
||||||
h.Lock()
|
|
||||||
var subscribers []*httpSubscriber
|
|
||||||
for _, sub := range h.subscribers[subscriber.topic] {
|
|
||||||
// deregister and skip forward
|
|
||||||
if sub.id == subscriber.id {
|
|
||||||
h.r.Deregister(sub.svc)
|
h.r.Deregister(sub.svc)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
subscribers = append(subscribers, sub)
|
|
||||||
}
|
}
|
||||||
h.subscribers[subscriber.topic] = subscribers
|
h.RUnlock()
|
||||||
h.Unlock()
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpBroker) start() error {
|
func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if req.Method != "POST" {
|
||||||
|
err := merr.BadRequest("go.micro.broker", "Method not allowed")
|
||||||
|
http.Error(w, err.Error(), http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer req.Body.Close()
|
||||||
|
|
||||||
|
req.ParseForm()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
errr := merr.InternalServerError("go.micro.broker", "Error reading request body: %v", err)
|
||||||
|
w.WriteHeader(500)
|
||||||
|
w.Write([]byte(errr.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var m *Message
|
||||||
|
if err = h.opts.Codec.Unmarshal(b, &m); err != nil {
|
||||||
|
errr := merr.InternalServerError("go.micro.broker", "Error parsing request body: %v", err)
|
||||||
|
w.WriteHeader(500)
|
||||||
|
w.Write([]byte(errr.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
topic := m.Header[":topic"]
|
||||||
|
delete(m.Header, ":topic")
|
||||||
|
|
||||||
|
if len(topic) == 0 {
|
||||||
|
errr := merr.InternalServerError("go.micro.broker", "Topic not found")
|
||||||
|
w.WriteHeader(500)
|
||||||
|
w.Write([]byte(errr.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &httpPublication{m: m, t: topic}
|
||||||
|
id := req.Form.Get("id")
|
||||||
|
|
||||||
|
h.RLock()
|
||||||
|
for _, subscriber := range h.subscribers[topic] {
|
||||||
|
if id == subscriber.id {
|
||||||
|
// sub is sync; crufty rate limiting
|
||||||
|
// so we don't hose the cpu
|
||||||
|
subscriber.fn(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpBroker) Address() string {
|
||||||
|
h.RLock()
|
||||||
|
defer h.RUnlock()
|
||||||
|
return h.address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpBroker) Connect() error {
|
||||||
h.Lock()
|
h.Lock()
|
||||||
defer h.Unlock()
|
defer h.Unlock()
|
||||||
|
|
||||||
@ -255,11 +332,20 @@ func (h *httpBroker) start() error {
|
|||||||
go http.Serve(l, h.mux)
|
go http.Serve(l, h.mux)
|
||||||
go h.run(l)
|
go h.run(l)
|
||||||
|
|
||||||
|
// get registry
|
||||||
|
reg, ok := h.opts.Context.Value(registryKey).(registry.Registry)
|
||||||
|
if !ok {
|
||||||
|
reg = registry.DefaultRegistry
|
||||||
|
}
|
||||||
|
// set rcache
|
||||||
|
h.r = rcache.New(reg)
|
||||||
|
|
||||||
|
// set running
|
||||||
h.running = true
|
h.running = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpBroker) stop() error {
|
func (h *httpBroker) Disconnect() error {
|
||||||
h.Lock()
|
h.Lock()
|
||||||
defer h.Unlock()
|
defer h.Unlock()
|
||||||
|
|
||||||
@ -267,76 +353,30 @@ func (h *httpBroker) stop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stop rcache
|
||||||
|
rc, ok := h.r.(rcache.Cache)
|
||||||
|
if ok {
|
||||||
|
rc.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit and return err
|
||||||
ch := make(chan error)
|
ch := make(chan error)
|
||||||
h.exit <- ch
|
h.exit <- ch
|
||||||
err := <-ch
|
err := <-ch
|
||||||
|
|
||||||
|
// set not running
|
||||||
h.running = false
|
h.running = false
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
if req.Method != "POST" {
|
|
||||||
err := errors.BadRequest("go.micro.broker", "Method not allowed")
|
|
||||||
http.Error(w, err.Error(), http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer req.Body.Close()
|
|
||||||
|
|
||||||
req.ParseForm()
|
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(req.Body)
|
|
||||||
if err != nil {
|
|
||||||
errr := errors.InternalServerError("go.micro.broker", "Error reading request body: %v", err)
|
|
||||||
w.WriteHeader(500)
|
|
||||||
w.Write([]byte(errr.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var m *Message
|
|
||||||
if err = h.opts.Codec.Unmarshal(b, &m); err != nil {
|
|
||||||
errr := errors.InternalServerError("go.micro.broker", "Error parsing request body: %v", err)
|
|
||||||
w.WriteHeader(500)
|
|
||||||
w.Write([]byte(errr.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
topic := m.Header[":topic"]
|
|
||||||
delete(m.Header, ":topic")
|
|
||||||
|
|
||||||
if len(topic) == 0 {
|
|
||||||
errr := errors.InternalServerError("go.micro.broker", "Topic not found")
|
|
||||||
w.WriteHeader(500)
|
|
||||||
w.Write([]byte(errr.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &httpPublication{m: m, t: topic}
|
|
||||||
id := req.Form.Get("id")
|
|
||||||
|
|
||||||
h.RLock()
|
|
||||||
for _, subscriber := range h.subscribers[topic] {
|
|
||||||
if id == subscriber.id {
|
|
||||||
// sub is sync; crufty rate limiting
|
|
||||||
// so we don't hose the cpu
|
|
||||||
subscriber.fn(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpBroker) Address() string {
|
|
||||||
return h.address
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpBroker) Connect() error {
|
|
||||||
return h.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpBroker) Disconnect() error {
|
|
||||||
return h.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpBroker) Init(opts ...Option) error {
|
func (h *httpBroker) Init(opts ...Option) error {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
if h.running {
|
||||||
|
return errors.New("cannot init while connected")
|
||||||
|
}
|
||||||
|
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(&h.opts)
|
o(&h.opts)
|
||||||
}
|
}
|
||||||
@ -345,12 +385,19 @@ func (h *httpBroker) Init(opts ...Option) error {
|
|||||||
h.id = "broker-" + uuid.NewUUID().String()
|
h.id = "broker-" + uuid.NewUUID().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get registry
|
||||||
reg, ok := h.opts.Context.Value(registryKey).(registry.Registry)
|
reg, ok := h.opts.Context.Value(registryKey).(registry.Registry)
|
||||||
if !ok {
|
if !ok {
|
||||||
reg = registry.DefaultRegistry
|
reg = registry.DefaultRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
h.r = reg
|
// get rcache
|
||||||
|
if rc, ok := h.r.(rcache.Cache); ok {
|
||||||
|
rc.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// set registry
|
||||||
|
h.r = rcache.New(reg)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -360,10 +407,13 @@ func (h *httpBroker) Options() Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption) error {
|
func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption) error {
|
||||||
|
h.RLock()
|
||||||
s, err := h.r.GetService("topic:" + topic)
|
s, err := h.r.GetService("topic:" + topic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
h.RUnlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
h.RUnlock()
|
||||||
|
|
||||||
m := &Message{
|
m := &Message{
|
||||||
Header: make(map[string]string),
|
Header: make(map[string]string),
|
||||||
@ -381,7 +431,7 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := func(node *registry.Node, b []byte) {
|
pub := func(node *registry.Node, b []byte) {
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
|
|
||||||
// check if secure is added in metadata
|
// check if secure is added in metadata
|
||||||
@ -411,15 +461,14 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption)
|
|||||||
case broadcastVersion:
|
case broadcastVersion:
|
||||||
for _, node := range service.Nodes {
|
for _, node := range service.Nodes {
|
||||||
// publish async
|
// publish async
|
||||||
go fn(node, b)
|
go pub(node, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// select node to publish to
|
// select node to publish to
|
||||||
node := service.Nodes[rand.Int()%len(service.Nodes)]
|
node := service.Nodes[rand.Int()%len(service.Nodes)]
|
||||||
|
|
||||||
// publish async
|
// publish async
|
||||||
go fn(node, b)
|
go pub(node, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,7 +476,7 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
|
func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
|
||||||
opt := newSubscribeOptions(opts...)
|
options := newSubscribeOptions(opts...)
|
||||||
|
|
||||||
// parse address for host, port
|
// parse address for host, port
|
||||||
parts := strings.Split(h.Address(), ":")
|
parts := strings.Split(h.Address(), ":")
|
||||||
@ -439,7 +488,8 @@ func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeO
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
id := uuid.NewUUID().String()
|
// create unique id
|
||||||
|
id := h.id + "." + uuid.NewUUID().String()
|
||||||
|
|
||||||
var secure bool
|
var secure bool
|
||||||
|
|
||||||
@ -449,7 +499,7 @@ func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeO
|
|||||||
|
|
||||||
// register service
|
// register service
|
||||||
node := ®istry.Node{
|
node := ®istry.Node{
|
||||||
Id: h.id + "." + id,
|
Id: id,
|
||||||
Address: addr,
|
Address: addr,
|
||||||
Port: port,
|
Port: port,
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
@ -457,7 +507,8 @@ func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeO
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
version := opt.Queue
|
// check for queue group or broadcast queue
|
||||||
|
version := options.Queue
|
||||||
if len(version) == 0 {
|
if len(version) == 0 {
|
||||||
version = broadcastVersion
|
version = broadcastVersion
|
||||||
}
|
}
|
||||||
@ -468,22 +519,22 @@ func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeO
|
|||||||
Nodes: []*registry.Node{node},
|
Nodes: []*registry.Node{node},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate subscriber
|
||||||
subscriber := &httpSubscriber{
|
subscriber := &httpSubscriber{
|
||||||
opts: opt,
|
opts: options,
|
||||||
id: h.id + "." + id,
|
hb: h,
|
||||||
|
id: id,
|
||||||
topic: topic,
|
topic: topic,
|
||||||
ch: h.unsubscribe,
|
|
||||||
fn: handler,
|
fn: handler,
|
||||||
svc: service,
|
svc: service,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.r.Register(service, registry.RegisterTTL(registerTTL)); err != nil {
|
// subscribe now
|
||||||
|
if err := h.subscribe(subscriber); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Lock()
|
// return the subscriber
|
||||||
h.subscribers[topic] = append(h.subscribers[topic], subscriber)
|
|
||||||
h.Unlock()
|
|
||||||
return subscriber, nil
|
return subscriber, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,8 @@ var (
|
|||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "client_pool_size",
|
Name: "client_pool_size",
|
||||||
EnvVar: "MICRO_CLIENT_POOL_SIZE",
|
EnvVar: "MICRO_CLIENT_POOL_SIZE",
|
||||||
Usage: "Sets the client connection pool size. Default: 0",
|
Usage: "Sets the client connection pool size. Default: 1",
|
||||||
|
Value: 1,
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "client_pool_ttl",
|
Name: "client_pool_ttl",
|
||||||
@ -132,6 +133,7 @@ var (
|
|||||||
Name: "selector",
|
Name: "selector",
|
||||||
EnvVar: "MICRO_SELECTOR",
|
EnvVar: "MICRO_SELECTOR",
|
||||||
Usage: "Selector used to pick nodes for querying",
|
Usage: "Selector used to pick nodes for querying",
|
||||||
|
Value: "cache",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "server",
|
Name: "server",
|
||||||
@ -181,7 +183,7 @@ var (
|
|||||||
defaultServer = "rpc"
|
defaultServer = "rpc"
|
||||||
defaultBroker = "http"
|
defaultBroker = "http"
|
||||||
defaultRegistry = "consul"
|
defaultRegistry = "consul"
|
||||||
defaultSelector = "default"
|
defaultSelector = "cache"
|
||||||
defaultTransport = "http"
|
defaultTransport = "http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
99
selector/cache/cache.go
vendored
99
selector/cache/cache.go
vendored
@ -23,6 +23,8 @@ type cacheSelector struct {
|
|||||||
cache map[string][]*registry.Service
|
cache map[string][]*registry.Service
|
||||||
ttls map[string]time.Time
|
ttls map[string]time.Time
|
||||||
|
|
||||||
|
once sync.Once
|
||||||
|
|
||||||
// used to close or reload watcher
|
// used to close or reload watcher
|
||||||
reload chan bool
|
reload chan bool
|
||||||
exit chan bool
|
exit chan bool
|
||||||
@ -86,30 +88,53 @@ func (c *cacheSelector) get(service string) ([]*registry.Service, error) {
|
|||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
|
|
||||||
// check the cache first
|
// get does the actual request for a service
|
||||||
services, ok := c.cache[service]
|
// it also caches it
|
||||||
ttl, kk := c.ttls[service]
|
get := func(service string) ([]*registry.Service, error) {
|
||||||
|
// ask the registry
|
||||||
// got results, copy and return
|
|
||||||
if ok && len(services) > 0 {
|
|
||||||
// only return if its less than the ttl
|
|
||||||
if kk && time.Since(ttl) < c.ttl {
|
|
||||||
return c.cp(services), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cache miss or ttl expired
|
|
||||||
|
|
||||||
// now ask the registry
|
|
||||||
services, err := c.so.Registry.GetService(service)
|
services, err := c.so.Registry.GetService(service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// we didn't have any results so cache
|
// cache results
|
||||||
c.cache[service] = c.cp(services)
|
c.set(service, c.cp(services))
|
||||||
c.ttls[service] = time.Now().Add(c.ttl)
|
|
||||||
return services, nil
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the cache first
|
||||||
|
services, ok := c.cache[service]
|
||||||
|
|
||||||
|
// cache miss or no services
|
||||||
|
if !ok || len(services) == 0 {
|
||||||
|
return get(service)
|
||||||
|
}
|
||||||
|
|
||||||
|
// got cache but lets check ttl
|
||||||
|
ttl, kk := c.ttls[service]
|
||||||
|
|
||||||
|
// within ttl so return cache
|
||||||
|
if kk && time.Since(ttl) < c.ttl {
|
||||||
|
return c.cp(services), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// expired entry so get service
|
||||||
|
services, err := get(service)
|
||||||
|
|
||||||
|
// no error then return error
|
||||||
|
if err == nil {
|
||||||
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// not found error then return
|
||||||
|
if err == registry.ErrNotFound {
|
||||||
|
return nil, selector.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// other error
|
||||||
|
|
||||||
|
// return expired cache as last resort
|
||||||
|
return c.cp(services), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cacheSelector) set(service string, services []*registry.Service) {
|
func (c *cacheSelector) set(service string, services []*registry.Service) {
|
||||||
@ -230,8 +255,6 @@ func (c *cacheSelector) update(res *registry.Result) {
|
|||||||
// reloads the watcher if Init is called
|
// reloads the watcher if Init is called
|
||||||
// and returns when Close is called
|
// and returns when Close is called
|
||||||
func (c *cacheSelector) run() {
|
func (c *cacheSelector) run() {
|
||||||
go c.tick()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// exit early if already dead
|
// exit early if already dead
|
||||||
if c.quit() {
|
if c.quit() {
|
||||||
@ -241,6 +264,9 @@ func (c *cacheSelector) run() {
|
|||||||
// create new watcher
|
// create new watcher
|
||||||
w, err := c.so.Registry.Watch()
|
w, err := c.so.Registry.Watch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if c.quit() {
|
||||||
|
return
|
||||||
|
}
|
||||||
log.Log(err)
|
log.Log(err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
continue
|
continue
|
||||||
@ -248,33 +274,15 @@ func (c *cacheSelector) run() {
|
|||||||
|
|
||||||
// watch for events
|
// watch for events
|
||||||
if err := c.watch(w); err != nil {
|
if err := c.watch(w); err != nil {
|
||||||
|
if c.quit() {
|
||||||
|
return
|
||||||
|
}
|
||||||
log.Log(err)
|
log.Log(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check cache and expire on each tick
|
|
||||||
func (c *cacheSelector) tick() {
|
|
||||||
t := time.NewTicker(time.Minute)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
c.Lock()
|
|
||||||
for service, expiry := range c.ttls {
|
|
||||||
if d := time.Since(expiry); d > c.ttl {
|
|
||||||
// TODO: maybe refresh the cache rather than blowing it away
|
|
||||||
c.del(service)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.Unlock()
|
|
||||||
case <-c.exit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// watch loops the next event and calls update
|
// watch loops the next event and calls update
|
||||||
// it returns if there's an error
|
// it returns if there's an error
|
||||||
func (c *cacheSelector) watch(w registry.Watcher) error {
|
func (c *cacheSelector) watch(w registry.Watcher) error {
|
||||||
@ -324,6 +332,10 @@ func (c *cacheSelector) Options() selector.Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cacheSelector) Select(service string, opts ...selector.SelectOption) (selector.Next, error) {
|
func (c *cacheSelector) Select(service string, opts ...selector.SelectOption) (selector.Next, error) {
|
||||||
|
c.once.Do(func() {
|
||||||
|
go c.run()
|
||||||
|
})
|
||||||
|
|
||||||
sopts := selector.SelectOptions{
|
sopts := selector.SelectOptions{
|
||||||
Strategy: c.so.Strategy,
|
Strategy: c.so.Strategy,
|
||||||
}
|
}
|
||||||
@ -401,7 +413,7 @@ func NewSelector(opts ...selector.Option) selector.Selector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &cacheSelector{
|
return &cacheSelector{
|
||||||
so: sopts,
|
so: sopts,
|
||||||
ttl: ttl,
|
ttl: ttl,
|
||||||
cache: make(map[string][]*registry.Service),
|
cache: make(map[string][]*registry.Service),
|
||||||
@ -409,7 +421,4 @@ func NewSelector(opts ...selector.Option) selector.Selector {
|
|||||||
reload: make(chan bool, 1),
|
reload: make(chan bool, 1),
|
||||||
exit: make(chan bool),
|
exit: make(chan bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
go c.run()
|
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user