locker

package module
v0.0.0-...-dabdcbe Latest Latest
Warning

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

Go to latest
Published: Feb 2, 2014 License: MIT Imports: 4 Imported by: 0

README

locker

GoDoc

A distributed lock service client for etcd.

What? Why?

A distributed lock service is somewhat self-explanatory. Locking (mutexes) as a service that's distributed across a cluster.

What a lock service gives you is a key-value store with locking semantics, and a mechanism for watching state changes. The distributed part of a distributed lock service is thanks to the etcd underpinnings, and it simply means your locks are persisted across a cluster.

A couple of examples of what you can build with one of these services:

Name server

You could use a lock service just like a DNS, except a DNS that's populated by the hosts it fronts rather than being an independent store.

A service would lock its hostname record and set the value to its IP address:

client.Lock("www.example.com", "10.1.1.1", nil, nil)

Then clients wanting to resolve the address could client.Get("www.example.com") and recieve 10.1.1.1.

This gets a bit more interesting when clients start watching the records and are notified of changes when they occur.

valueChanges := make(chan string)
go client.Watch("www.example.com", valueChanges, nil)

select {
case newIp := <-valueChanges:
	redirectRequestsToNewIp(newIp)
}
Mesh/grid health monitoring

Imagine a grid or mesh of services which are all polling each-other for updates. When one service goes down, we don't want the others continuing to hammer it until it's in a healthy state again. You can do this quite easily with a lock service.

Each service locks a well-known record of its own when it's healthy.

// in service-a
client.Lock("service-a", "ok", nil, quit)

// in service-b
client.Lock("service-b", "ok", nil, quit)

When it becomes unhealthy, it kills its own lock.

// in service-b
quit <- true

Meanwhile, other services are watching the records of the services they depend on, and are notified when the lock dies. They can then respond to that service becoming unavailable.

// in service-a
client.Watch("service-b", changes)

select {
case change := <-changes:
	if change == "" {
		handleServiceBGoingOffline()
	}
}

You can take this further by making service-a then kill its own lock, leading to a cascading blackout of sorts (if that's what you want). An example of this could be when someone cuts your internet pipe, all your services gracefully shut down until the pipe comes back and then they restart.

Usage

Creating a lock
import "github.com/jagregory/locker"

// Create the locker client.
// Etcd is needed as a backend, create your etcd client elsewhere and pass it in.
client := locker.New(etcdclient)

go client.Lock("key", "value", nil, nil)

client.Get("key") // "value"
Listening for state changes

The third argument to Lock is a owned channel. When a transition of ownership occurs a bool will be dumped into this channel indicating whether you own the lock.

owned := make(chan bool)
go client.Lock("key", "value", owned, nil)

select {
case change := <-owned:
	fmt.Printf("Do we own the lock? %b", change)
}

Pass nil for this channel if you don't intend to read from it. Not doing so will block the lock from refreshing.

Releasing the lock

The fourth argument to Lock is a quit channel. Push anything into this channel to kill the locking, which will let the lock expire.

quit := make(chan bool)
go client.Lock("key", "value", nil, quit)

quit <- true
Watching a lock

An interesting aspect of lock services is the ability to watch a lock that isn't owned by you. A service can alter its behaviour depending on the value of a lock. You can use the Watch function to watch the value of a lock.

valueChanges := make(chan string)
go client.Watch("key", valueChanges, nil)

select {
case change := <-valueChanges
	fmt.Printf("Lock value changed: %s", change)
}

Quitting works the same way as Lock.

Gotchas

The Lock call is blocking, use a goroutine.

The Lock method takes a owned channel. Pass nil for this if you don't intend to read from it. Passing a channel and not reading from it will block the refreshing of the lock and you'll lose it.

Documentation

Overview

Package locker is a distributed lock service built on top of etcd. https://github.com/coreos/etcd

Locker gives you a way of creating and maintaining locks across a network, useful for synchronising access to resources when multiple machines are involved.

A simple example is a name server. A service would lock its hostname record and set the value to its IP address:

client.Lock("www.example.com", "10.1.1.1", nil, nil)

Other clients wanting to resolve the address could call

client.Get("www.example.com")

and would recieve the IP address: 10.1.1.1.

This gets a bit more interesting when clients start watching the records and are notified of changes when they occur.

valueChanges := make(chan string)
go client.Watch("www.example.com", valueChanges, nil)

select {
case newIp := <-valueChanges:
    redirectRequestsToNewIp(newIp)
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// Store is what locker uses to persist locks.
	Store Store
}

Client is the main locker type. Use it to manage your locks. A locker Client has a Store which it uses to persist the locks.

func New

func New(etcdclient *etcd.Client) Client

New creates a default locker client using Etcd as a store. It requires you provide an etcd.Client, this is so locker doesn't make any dumb assumptions about how your Etcd cluster is configured.

client := locker.New(etcdclient)

func (Client) Get

func (c Client) Get(name string) (string, error)

Get returns the value of a lock. LockNotFound will be returned if a lock with the name isn't held.

func (Client) Lock

func (c Client) Lock(name, value string, owned chan<- bool, quit <-chan bool) error

Lock will create a lock for a key and set its value. If the owned channel is provided a bool will be pushed whenever our ownership of the lock changes. Pushing true into the quit channel will stop the locker from refreshing the lock and let it expire if we own it.

owned := make(chan bool)

go client.Lock("my-service", "http://10.0.0.1:9292", owned, nil)

for {
  select {
  case v := <-owned:
    fmt.Printf("Lock ownership changed: %t\n", v)
  }
}

Lock is a blocking call, so it's recommended to run it in a goroutine.

func (Client) Watch

func (c Client) Watch(name string, valueChanges chan<- string, quit <-chan bool) error

Watch will create a watch on a lock and push value changes into the valueChanges channel. An empty string indicates the lack of a lock. Pushing true into quit will end the watch.

valueChanges := make(chan string)

go client.Watch("my-service", valueChanges, nil)

for {
  select {
  case v := <-valueChanges:
    fmt.Printf("Lock value changed: %s\n", v)
  }
}

Watch is a blocking call, so it's recommended to run it in a goroutine.

type EtcdStore

type EtcdStore struct {
	// Etcd client used for storing locks.
	Etcd *etcd.Client

	// Directory in Etcd to store locks. Default: locker.
	Directory string

	// TTL is the time-to-live for the lock. Default: 5s.
	TTL int

	Log log.Logger
}

EtcdStore is a backing store for Locker which uses Etcd for storage.

func (EtcdStore) AcquireOrFreshenLock

func (s EtcdStore) AcquireOrFreshenLock(name, value string) error

AcquireOrFreshenLock will aquires a named lock if it isn't already held, or updates its TTL if it is.

func (EtcdStore) Get

func (s EtcdStore) Get(name string) (string, error)

Get returns the value of a lock. LockNotFound will be returned if a lock with the name isn't held.

type LockDenied

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

LockDenied is returned when a lock is attempted on a key which has been acquired by another client.

func (LockDenied) Error

func (e LockDenied) Error() string

type LockNotFound

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

LockNotFound is returned when a Get is made against a key which isn't locked.

func (LockNotFound) Error

func (e LockNotFound) Error() string

type Store

type Store interface {
	// Get returns the value of a lock. LockNotFound will be returned if a
	// lock with the name isn't held.
	Get(name string) (string, error)

	// AcquireOrFreshenLock will aquires a named lock if it isn't already
	// held, or updates its TTL if it is.
	AcquireOrFreshenLock(name, value string) error
}

Store is a persistance mechaism for locker to store locks. Needs to be able to support querying and an atomic compare-and-swap. Currently, the only implementation of a Store is EtcdStore.

Jump to

Keyboard shortcuts

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