bestserver

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Mar 25, 2021 License: BSD-2-Clause Imports: 4 Imported by: 0

Documentation

Overview

Package bestserver tracks the performance and reliability of each server for the purpose of identifying which server is the most reliable and has the lowest latency. This package *should* work for any sort of latency-based set of servers (or performance which can be expressed as a time.Duration) regardless of what they actually do.

The bestserver structure contains a list of all available servers, what a server represents, is unknown to this package. It could be a URL, an IP address, the name of a racing pigeon... whatever.

After a server is used by the application, the application calls this package to record success/failure and latency. That data is used internally to influence which server is chosen next.

Typical usage looks like this:

bs := bestServer.NewLatency(Config, ServerList...) // Construct a specific bestserver container
for {
     server, _ := bs.Best()                                                 // Get current best server
     doStuffWithServer(server.Name())                                       // Use it
     bs.Result(server, success bool, when time.Time, latency time.Duration) // Say how it went
}

A call to Result() with the current best server causes a reassessment of the best server. Calls to Best() will always return the same server details if no intervening calls to Result() have been made.

Calls to Result() with a server other than the current best result in accumulation of statistics but no reassessment of the current best.

Callers must not cache returns from Best() as that distorts the reassessment algorithm.

There are currently two types of "best servers" to choose from: 'latency' and 'traditional' which are created with the obviously named NewLatency() and NewTraditional() functions respectively. They each implement different algorithms when choosing a new best server. This package is structured to make it easy to add additional algorithms if the need arises.

The 'latency' algorithm generally tries to gravitate towards the lowest latency server by opportunistically sampling all servers to collect statistics on their performance. The selection algorithm is:

  • the first server on the list starts as the 'best' server

  • a reassessment occurs if any of the following conditions are true: o the current 'best' server is given an unsuccessful result o the configured reassessment timer has expired o the configured number of Result() calls have been reached

Reassessment chooses the server with the lowest weighted average latency to become the new 'best' server.

To ensure there is latency data for all server, after a Result() call, Best() will periodically return a non-'best' server to gather performance information for that server. The default sample rate at which non-'best' servers are returned is approximately 5% of the time.

Servers which are unsuccessful as indicated by Result() calls are excluded from this sampling process for a configured time period.

The expectation is that there are a relatively small number of servers as much of the selection algorithm is a simple linear search of all entries and thus O(n). A server list of 10-20 is reasonable, 1,000-10,000 is probably not.

The 'traditional' implementation created with NewTraditional() is intended to mimic nameserver selection by res_send(3) as described in RESOLVER(3). That is, the first server is used until it fails then the next server is used until it fails and so on. Once the end of the server list is reached, then the algorithm wraps around to the first server and the process repeats.

Multiple goroutines can safely invoke all the Manager interface methods concurrently.

Index

Constants

View Source
const (
	LatencyAlgorithm     algorithm = "latency"     // Pick the fastest most reliable server
	TraditionalAlgorithm           = "traditional" // Pick until fails - just as res_send() does
)

Variables

View Source
var (
	DefaultLatencyConfig = LatencyConfig{
		ReassessCount:     1061,
		ReassessAfter:     time.Second * 61,
		WeightForLatest:   67,
		ResetFailedAfter:  time.Minute * 3,
		SampleOthersEvery: 20,
	}
)

Functions

func NewLatency

func NewLatency(config LatencyConfig, servers []Server) (*latency, error)

func NewTraditional

func NewTraditional(config TraditionalConfig, servers []Server) (*traditional, error)

Types

type LatencyConfig

type LatencyConfig struct {
	ReassessAfter     time.Duration // Reassess 'best' server after this duration or
	ReassessCount     int           // this many Result() calls
	ResetFailedAfter  time.Duration // Reset server stats to zero if failed this long ago
	SampleOthersEvery int           // Result() samples another server once every SampleOthersEvery calls
	WeightForLatest   int           // Percent weight for latest Result() latency (range: 0-100)
}

LatencyConfig defines all the public parameters that the calling application can set. They control reassessment rate, the frequency at which sampling of servers occurs and how much influence the latest latency has on the overall "weight" of the server.

type Manager

type Manager interface {
	// Algorithm returns the name of the implementation
	Algorithm() string

	// Best returns the current best server (and its index into the Server
	// List) as determined by the underlying algorithm in use. It always
	// returns valid values. The returned index is an index to the server
	// list as originally supplied when this collection was created.
	Best() (Server, int)

	// Result updates internal statistics and *may* assess whether there is a
	// better choice for the current 'best' server.
	//
	// The Server passed into Result() must be exactly the value returned by
	// Best() as it is used as an index into a map. Result() requires the
	// Server parameter to be supplied rather than rely on the existing
	// "best" server as the "best" Server may have change between the two
	// calls by the action of another go-routine.
	//
	// Return false if Server is not part of this collection
	Result(server Server, success bool, now time.Time, latency time.Duration) bool

	// Servers returns a slice of all Servers in the order originally created.
	Servers() []Server

	// Len returns the count of servers
	Len() int
}

Manager is the public interface for bestserver.

type Server

type Server interface {
	Name() string
}

Server is the interface used to create a bestserver collection. It is returned by Best() and passed in to Result(). The underlying struct is supplied by the caller when they created a bestserver collection with one of the New* functions. This struct can be either one created by the caller or the default struct used by our NewFromNames() helper method. The application will normally supply its own if it wants to track other things related to the server, such as stats or server IP address or similar.

func ServersFromNames

func ServersFromNames(names []string) []Server

ServersFromNames is a helper function to construct a Server list for a string list. The order of the returned list is the same as that of the supplied names.

type TraditionalConfig

type TraditionalConfig struct {
}

TraditionalConfig defines all the public parameters that the calling application can set. Currently this is just a place-holder for the API. But easier to add a field to an empty struct than to add an additional parameter to an API in widespread use.

Jump to

Keyboard shortcuts

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