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 ¶
const ( LatencyAlgorithm algorithm = "latency" // Pick the fastest most reliable server TraditionalAlgorithm = "traditional" // Pick until fails - just as res_send() does )
Variables ¶
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 ¶
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.