gone

package module
v0.0.0-...-83d4d26 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 3, 2015 License: MIT Imports: 11 Imported by: 1

README

What is it?

Gone (go-one) ensures there is one (and only one) instance of a Go routine running on a cluster of nodes.

How does it Work?

It uses a distributed lock to ensure the specified Go routine is only running within one process on the cluster. The lock has a relatively short expiry time and if the Go routine is healthy it is required to continuously extend the lock. If the lock expires due to a badly behaved or unhealthy Go routine (or the process itself has died) then another node will spawn the Go routine and take over. Currently the distributed lock is implemented in Redis, but an interface is provided so that the lock could be implemented in other services such as Etcd or Postgres.

Additionally, ZeroMQ is used so that any node can send data to the Go routine, regardless of which node it is actually running on. The distributed locking interface doubles as a primitive service-discovery mechanism so each node can find and talk to other nodes when needed.

Use cases

Gone was written specifically to solve the problem where a stateful connection is needed to talk to an external service or API (e.g. a persistent socket connection) in a horizontally scaled system. Take these simple use cases:

1) load_balancer -> N x web_server -> irc_chat_room
2) message_queue -> N x background_worker -> irc_chat_room

A message needs to be sent to an IRC chat room. IRC is stateful, that is you are persistently "connected". In a naive implementation one might open and close a connection to the IRC chat room for each message, but that would result in every one else in the room seeing your service constantly connect and disconnect. Additionally, the IRC server might begin to refuse connections or rate limit your service.

go-one solves this by enabling automatic negotiation, routing and failover for a Go routine across a cluster of nodes.

Usage

See the examples directory for a full working example.

Instantiate a new Gone and specify the IP address of the current machine (that should be routable by other instances), the port the process should listen on for messages from other instances, and finally the locking mechanism to use (currently only Redis is supported).

Next, call the RunOne function, passing in a unique identifier for the routine that will be run, as well as a RunHandler that go-one will ensure is always running on one node in the cluster.

The RunHandler is the most interesting item, it can read the runner.Data channel to receive messages from any instance for processing. It is also responsible for letting Gone know it is healthy by calling runner.Extend() within the lock expiry period (30 seconds by default). If the lock expires because the run handler has not extended it, the handler will quit and a new one will spawn on an arbitrary node.

See below for a full example:

func redisLock(id string, value string) (gone.GoneLock, error) {
  return gone.NewRedisLock(&net.TCPAddr{Port: 6379}, id, value)
}

func runner(r *gone.GoneRunner) {
  // Do some application-specific setup e.g. connect to stateful service
  ticker := time.NewTicker(25 * time.Second).C
  for {
    select {
    case <- r.Exit:
      glog.Debugf("Exit signalled; exiting runner.")
      return
    case data := <- r.Data:
      glog.Debugf("Received data: %v", string(data))
      // Do something application-specific with the data
    case <-ticker:
      err := r.Extend()
      if err != nil {
        glog.Debugf("Failed to extend lock; exiting runner: %v", err)
        return
      }
    }
  }
}

g, err := gone.NewGone("tcp://127.0.0.1:6381", redisLock)
if err != nil {
  panic(err.Error())
}

r, err := g.RunOne("unique_identifier", runner)
if err != nil {
  panic(err.Error())
}

r.SendData([]byte("hello world"))

To send data to the handler function, regardless of which instance it is running on, use the gone.SendData function. Gone will route the data to the handler function on the node it is currently executing on.

Documentation

Index

Constants

View Source
const (
	DefaultExpiry = 30 * time.Second
)

Variables

View Source
var (
	LockError = errors.New("Failed to acquire Redis lock.")
)

Functions

func NewGoneServer

func NewGoneServer(ip string, context *zmq.Context) (*goneServer, error)

Types

type Gone

type Gone struct {
	// contains filtered or unexported fields
}

func NewGone

func NewGone(ip string, lockFactory LockFactory) (*Gone, error)

func (*Gone) Close

func (g *Gone) Close()

func (*Gone) RunOne

func (g *Gone) RunOne(id string, runHandler RunHandler) (*GoneRunner, error)

func (*Gone) SendData

func (g *Gone) SendData(id string, data []byte) error

type GoneLock

type GoneLock interface {
	Lock() error
	Extend() error
	Unlock()
	GetValue() (string, error)
}

type GoneRunner

type GoneRunner struct {
	Lock GoneLock

	Data chan []byte
	Exit chan bool
	// contains filtered or unexported fields
}

func (*GoneRunner) Close

func (r *GoneRunner) Close()

func (*GoneRunner) Extend

func (r *GoneRunner) Extend() error

func (*GoneRunner) SendData

func (r *GoneRunner) SendData(data []byte) error

type LockFactory

type LockFactory func(string, string) (GoneLock, error)

type RedisLock

type RedisLock struct {
	Expiry time.Duration
	// contains filtered or unexported fields
}

func NewRedisLock

func NewRedisLock(redisIp net.Addr, lockId string, lockVal string) (*RedisLock, error)

func (*RedisLock) Extend

func (r *RedisLock) Extend() error

func (*RedisLock) GetValue

func (r *RedisLock) GetValue() (string, error)

func (*RedisLock) Lock

func (r *RedisLock) Lock() error

func (*RedisLock) Unlock

func (r *RedisLock) Unlock()

type RunHandler

type RunHandler func(*GoneRunner)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL