ringman

package module
v0.0.0-...-2684f2a Latest Latest
Warning

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

Go to latest
Published: May 10, 2019 License: MIT Imports: 13 Imported by: 3

README

Ringman

This is a consistent hash ring implementation backed by either our fork of Hashicorp's Memberlist library, or Sidecar service discovery platform, and the hashring library.

It sets up an automatic consistent hash ring across multiple nodes. The nodes are discovered and health validated either over Memberlist's implementation of the SWIM gossip protocol or via Sidecar. It manages adding and removing nodes from the ring based on events from Memberlist or Sidecar.

In addition, the package provides some HTTP handlers and a Mux that can be mounted anywhere in your application if you want to expose the hashring.

The Problem This Solves

If you're building a cluster of nodes and need to distribute data amongst them, you have a bunch of choices. One good choice is to build a consistent hash and back it with a list of cluster nodes. When distributing work/data/etc you can then do a lookup against the hash to identify which node(s) should handle the work. But now you need to maintain the list of nodes, and identify when they come and go from the cluster, and other details of clustering that don't really have much to do with the application you're trying to write. Sometimes a specialized load balancer can solve this problem for you. Sometimes you need to write your own mechanism.

This is where ringman comes in. It maintains a consistent hash and the clustering mechanism underneath it via one of two different provided backends. It also offers an optional queryable web API so you or other services can inspect the state of the cluster.

It takes one line of code to set up the cluster, and one line of code to query it! The cluster currently expects equal weighting for all nodes.

Memberlist Ring

If you just want to get information from the ring you can query it in your code directly like:

ring, err := ringman.NewDefaultMemberlistRing([]string{"127.0.0.1"}, "8000")
if err != nil {
    log.Fatalf("Unable to establish memberlist ring: %s", err)
}

println(ring.Manager().GetNode("mykey"))

The following would set up a Memberlist-backed consistent hash ring and serve the node information over HTTP:

ring, err := ringman.NewDefaultMemberlistRing([]string{"127.0.0.1"}, "8000")
if err != nil {
    log.Fatalf("Unable to establish memberlist ring: %s", err)
}

http.HandleFunc("/your_stuff", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("OK")) })
http.Handle("/hashring/", http.StripPrefix("/hashring", ring.HttpMux()))
err = http.ListenAndServe(":8080", http.DefaultServeMux)
if err != nil {
	log.Fatalf("Unable to start HTTP server: %s", err)
}

We can then query it with:

$ curl http://localhost:8000/hashring/nodes

Which will return output like:

[
  {
    "Name": "ubuntu",
    "Addr": "127.0.0.1",
    "Port": 7946,
    "Meta": null,
    "PMin": 1,
    "PMax": 4,
    "PCur": 2,
    "DMin": 0,
    "DMax": 0,
    "DCur": 0
  }
]

Or we can find the node for a specific key like:

$ curl http://docker1:8000/hashring/nodes/get?key=somekey

Which returns output like:

{
  "Node": "ubuntu",
  "Key": "somekey"
}
More About Memberlist

If you are going to set up the Memberlist ring, it may be helpful to read up on Memberlist and the SWIM paper that explains roughly how the underlying algorithm works.

Sidecar Ring

An alternate implementation backed by New Relic/Nitro's Sidecar is available as well. Underneath Sidecar also lies Memberlist, but Sidecar provides a lot of features on top of it. This implementation of Ringman assumes it will be subscribed to incoming Sidecar events on the listener port. See the Sidecar README for how to set that up. Once that is established, you can do the following:

ring, err := ringman.NewSidecarRing("http://localhost:7777/api/state.json")
if err != nil {
    log.Fatalf("Unable to establish sidecar ring: %s", err)
}

println(ring.Manager().GetNode("mykey"))

Documentation

Overview

Ringman implements a consistent hash ring for service sharding, backed either by Hashicorp's Memberlist directly, or by Sidecar service discovery platform. It maintains state about which nodes are available in a cluster and can be queried for a node to match a hash key.

Index

Constants

View Source
const (
	CmdAddNode    = iota
	CmdRemoveNode = iota
	CmdGetNode    = iota
	CmdPing       = iota
)
View Source
const (
	CommandChannelLength = 10                   // How big a buffer on our mailbox channel?
	PingTimeout          = 5 * time.Millisecond // This should be PLENTY of spare time
)
View Source
const (
	DefaultReceiverCapacity = 50
)

Variables

View Source
var (
	ErrNilManager error = errors.New("HashRingManager has not been initialized!")
)

Functions

This section is empty.

Types

type Delegate

type Delegate struct {
	RingMan *HashRingManager
	// contains filtered or unexported fields
}

Delegate is a Memberlist delegate that is responsible for handling integration between the hash ring and Memberlist messages. See the Memberlist documentation for detailed explanations of the callback methods.

func NewDelegate

func NewDelegate(ringMan *HashRingManager, meta *NodeMetadata) *Delegate

func (*Delegate) GetBroadcasts

func (d *Delegate) GetBroadcasts(overhead, limit int) [][]byte

func (*Delegate) LocalState

func (d *Delegate) LocalState(join bool) []byte

func (*Delegate) MergeRemoteState

func (d *Delegate) MergeRemoteState(buf []byte, join bool)

func (*Delegate) NodeMeta

func (d *Delegate) NodeMeta(limit int) []byte

func (*Delegate) NotifyJoin

func (d *Delegate) NotifyJoin(node *memberlist.Node)

func (*Delegate) NotifyLeave

func (d *Delegate) NotifyLeave(node *memberlist.Node)

func (*Delegate) NotifyMsg

func (d *Delegate) NotifyMsg(message []byte)

func (*Delegate) NotifyUpdate

func (d *Delegate) NotifyUpdate(node *memberlist.Node)

type HashRingManager

type HashRingManager struct {
	HashRing *hashring.HashRing
	// contains filtered or unexported fields
}

func NewHashRingManager

func NewHashRingManager(nodeList []string) *HashRingManager

NewHashRingManager returns a properly configured HashRingManager. It accepts zero or mode nodes to initialize the ring with.

func (*HashRingManager) AddNode

func (r *HashRingManager) AddNode(nodeName string) error

AddNode is a blocking call that will send an add message on the message channel for the HashManager.

func (*HashRingManager) GetNode

func (r *HashRingManager) GetNode(key string) (string, error)

GetNode requests a node from the ring to serve the provided key

func (*HashRingManager) Pending

func (r *HashRingManager) Pending() int

Pending returns the number of pending commands in the command channel

func (*HashRingManager) Ping

func (r *HashRingManager) Ping() bool

Ping is a simple ping through the main processing loop with a timeout to make sure this thing is running the background goroutine.

func (*HashRingManager) RemoveNode

func (r *HashRingManager) RemoveNode(nodeName string) error

RemoveNode is a blocking call that will send an add message on the message channel for the HashManager.

func (*HashRingManager) Run

func (r *HashRingManager) Run(looper director.Looper) error

Run runs in a loop over the contents of cmdChan and processes the incoming work. This acts as the synchronization around the HashRing itself which is not mutable and has to be replaced on each command.

func (*HashRingManager) Stop

func (r *HashRingManager) Stop()

Stop the HashRingManager from running. This is currently permanent since the internal cmdChan it closes can't be re-opened.

type LoggingBridge

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

func (*LoggingBridge) Write

func (l *LoggingBridge) Write(data []byte) (int, error)

Processes one line at a time from the input. If we somehow get less than one line in the input, then weird things will happen. Experience shows this doesn't currently happen.

type MemberlistRing

type MemberlistRing struct {
	Memberlist *memberlist.Memberlist
	// contains filtered or unexported fields
}

A MemberlistRing is a ring backed by Hashicorp's Memberlist directly. It exchanges gossip messages directly between instances of this service and requires some open ports for them to communicate with each other. The nodes will need to have some seeds provided that allow them to find each other.

func NewDefaultMemberlistRing

func NewDefaultMemberlistRing(clusterSeeds []string, port string) (*MemberlistRing, error)

NewDefaultMemberlistRing returns a MemberlistRing configured using the DefaultLANConfig from the memberlist documentation. clusterSeeds must be 0 or more hosts to seed the cluster with. Note that the ring will be _running_ when returned from this method.

func NewMemberlistRing

func NewMemberlistRing(mlConfig *memberlist.Config, clusterSeeds []string, port string,
	clusterName string) (*MemberlistRing, error)

NewMemberlistRing configures a MemberlistRing according to the Memberlist configuration specified. clusterSeeds must be 0 or more hosts to seed the cluster with. Note that the ring will be _running_ when returned from this method.

* mlConfig is a memberlist config struct * clusterSeeds are the hostnames of the machines we'll bootstrap from * port is our own service port that the service (not memberist) will use

func (*MemberlistRing) HttpGetNodeHandler

func (r *MemberlistRing) HttpGetNodeHandler(w http.ResponseWriter, req *http.Request)

HttpGetNodeHandler is an http.Handler that will return an object containing the node that currently owns a specific key.

func (*MemberlistRing) HttpListNodesHandler

func (r *MemberlistRing) HttpListNodesHandler(w http.ResponseWriter, req *http.Request)

HttpListNodesHandler is an http.Handler that will return a JSON-encoded list of the Memberlist nodes in the current ring.

func (*MemberlistRing) HttpMux

func (r *MemberlistRing) HttpMux() *http.ServeMux

HttpMux returns an http.ServeMux configured to run the HTTP handlers on the MemberlistRing. You can either use this one, or mount the handlers on a mux of your own choosing (e.g. Gorilla mux or httprouter)

func (*MemberlistRing) Manager

func (r *MemberlistRing) Manager() *HashRingManager

func (*MemberlistRing) Shutdown

func (r *MemberlistRing) Shutdown()

Shutdown shuts down the memberlist node and stops the HashRingManager

type NodeMetadata

type NodeMetadata struct {
	ServicePort string
}

func DecodeNodeMetadata

func DecodeNodeMetadata(data []byte) (*NodeMetadata, error)

DecodeNodeMetadata takes a byte slice and deserializes it

type Ring

type Ring interface {
	HttpMux() *http.ServeMux
	Shutdown()
	Manager() *HashRingManager
}

type RingCommand

type RingCommand struct {
	Command   int
	NodeName  string
	Key       string
	ReplyChan chan *RingReply
}

type RingReply

type RingReply struct {
	Error error
	Nodes []string
}

type SidecarRing

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

A SidecarRing is a ring backed by service discovery from Sidecar https://github.com/Nitro/sidecar . Sidecar itself uses Memberlist under the covers, but layers a lot more on top. Sidecar takes care of all the work of managing and bootstrapping the cluster so we don't need to know anything about cluster seeds. This service is expected to subscribe to Sidecar events, however, and uses a Sidecar Receiver to process them.

func NewSidecarRing

func NewSidecarRing(sidecarUrl string, svcName string, svcPort int64) (*SidecarRing, error)

NewSidecarRing returns a properly configured SidecarRing that will filter incoming changes by the service name provided and will only watch the ServicePort number passed in. If the SidecarUrl is not empty string, then we will call that address to get initial state on bootstrap.

func (*SidecarRing) HttpGetNodeHandler

func (r *SidecarRing) HttpGetNodeHandler(w http.ResponseWriter, req *http.Request)

HttpGetNodeHandler is an http.Handler that will return an object containing the node that currently owns a specific key.

func (*SidecarRing) HttpListNodesHandler

func (r *SidecarRing) HttpListNodesHandler(w http.ResponseWriter, req *http.Request)

HttpListNodesHandler is an http.Handler that will return a JSON-encoded list of the Sidecar nodes in the current ring.

func (*SidecarRing) HttpMux

func (r *SidecarRing) HttpMux() *http.ServeMux

HttpMux returns an http.ServeMux configured to run the HTTP handlers on the SidecarRing. You can either use this one, or mount the handlers on a mux of your own choosing (e.g. Gorilla mux or httprouter)

func (*SidecarRing) Manager

func (r *SidecarRing) Manager() *HashRingManager

func (*SidecarRing) Shutdown

func (r *SidecarRing) Shutdown()

Shutdown stops the Receiver and the HashringManager

Jump to

Keyboard shortcuts

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