services

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

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

Go to latest
Published: Feb 16, 2018 License: MIT Imports: 14 Imported by: 2

README

services CircleCI Go Report Card GoDoc

Go package providing building blocks to work with service discovery patterns.

Motivations

When building large and dynamically scaling infrastructures made of complex pieces of software, classic ways of mapping service names to addresses at which they are available don't fit anymore. We need ways to indentify services that may be coming and going, binding random ports, registering to service discovery backends. As systems get more and more complex, they also end up needed more control over service name resolution to implement sharding solutions or custom load balancing algorithms.

This is what the services package attempts to help with. It provides building blocks for abstracting and integrating programs into more advanced service discovery models, in a way that integrates with the Go standard library.

Resolver

One of the core concepts of the services package is the Resolver. This interface abstracts the concept of translating a service name into an address at which the service can be reached.

The package offers a default implementation of this interface which makes DNS queries of the SRV type to obtain both the target host where the service is running, and the port at which it can be reached.

This example acts like a mini-version of dig <service> SRV:

package main

import (
    "fmt"
    "os"

    "github.com/segmentio/services"
)

func main() {
    r := services.NewResolver(nil)

    a, err := r.Resolve(ctx, os.Args[1])
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    fmt.Println(a)
}

Dialer

While service name resolution is at the core of every service discovery system, it is mostly useful to establish connections to running services.

The services package exposes a Dialer type which mirrors exactly the API exposed by the standard net.Dialer, but uses a Resolver to lookup addresses in order to establish connections.

Matching the standard library's design allows the Dialer to be injected in places where dial functions are expected. For example, this code modifies the default HTTP transport to use a service dialer for establishing all new TCP connections:

package main

import (
    "net/http"

    "github.com/segmentio/services"
)

func init() {
    if t, ok := http.DefaultTransport.(*http.Transport); ok {
        t.DialContext = (&services.Dialer{
                Timeout:   30 * time.Second,
                KeepAlive: 30 * time.Second,
                DualStack: true,
        }).DialContext
    }
}

...

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cache

type Cache struct {
	// Base registry to cache services for. This field must not be nil.
	Registry Registry

	// Minimum and maximum TTLs applied to cache entries.
	MinTTL time.Duration
	MaxTTL time.Duration

	// Maximum size of the cache (in bytes). Defaults to 1 MB.
	MaxBytes int64
	// contains filtered or unexported fields
}

Cache provides the implementation of an in-memory caching layer for a service registry.

When used as a resolver, the cache uses a load balancing strategy to return a different address on every call to Resolve.

Cache implements both the Registry and Resolver interfaces, which means they are safe to use concurrently from multiple goroutines.

Cache values must not be copied after being used.

func (*Cache) Lookup

func (c *Cache) Lookup(ctx context.Context, name string, tags ...string) ([]string, time.Duration, error)

Lookup satisfies the Registry interface.

func (*Cache) Resolve

func (c *Cache) Resolve(ctx context.Context, name string) (string, error)

Resolve satisfies the Resolver interface.

func (*Cache) Stats

func (c *Cache) Stats() CacheStats

Stats takes a snapshot of the current utilization statistics of the cache.

Note that because cache is safe to use concurrently from multiple goroutines, cache statistics are eventually consistent and a snapshot may not reflect the effect of concurrent utilization of the cache.

type CacheStats

type CacheStats struct {
	Bytes     int64 `metric:"services.cache.bytes"     type:"gauge"`
	Size      int64 `metric:"services.cache.size"      type:"gauge"`
	Hits      int64 `metric:"services.cache.hits"      type:"counter"`
	Misses    int64 `metric:"services.cache.misses"    type:"counter"`
	Evictions int64 `metric:"services.cache.evictions" type:"counter"`
}

CacheStats exposes internal statistics on service cache utilization.

type Dialer

type Dialer struct {
	// Timeout is the maximum amount of time a dial will wait for
	// a connect to complete. If Deadline is also set, it may fail
	// earlier.
	//
	// The default is no timeout.
	//
	// When using TCP and dialing a host name with multiple IP
	// addresses, the timeout may be divided between them.
	//
	// With or without a timeout, the operating system may impose
	// its own earlier timeout. For instance, TCP timeouts are
	// often around 3 minutes.
	Timeout time.Duration

	// Deadline is the absolute point in time after which dials
	// will fail. If Timeout is set, it may fail earlier.
	// Zero means no deadline, or dependent on the operating system
	// as with the Timeout option.
	Deadline time.Time

	// LocalAddr is the local address to use when dialing an
	// address. The address must be of a compatible type for the
	// network being dialed.
	// If nil, a local address is automatically chosen.
	LocalAddr net.Addr

	// DualStack enables RFC 6555-compliant "Happy Eyeballs"
	// dialing when the network is "tcp" and the host in the
	// address parameter resolves to both IPv4 and IPv6 addresses.
	// This allows a client to tolerate networks where one address
	// family is silently broken.
	DualStack bool

	// FallbackDelay specifies the length of time to wait before
	// spawning a fallback connection, when DualStack is enabled.
	// If zero, a default delay of 300ms is used.
	FallbackDelay time.Duration

	// KeepAlive specifies the keep-alive period for an active
	// network connection.
	// If zero, keep-alives are not enabled. Network protocols
	// that do not support keep-alives ignore this field.
	KeepAlive time.Duration

	// Resolver optionally specifies an alternate resolver to use.
	Resolver Resolver
}

A Dialer contains options for connecting to an address.

Dialer differs from the standard library's dialer by using a custom resolver to translate service names into network addresses.

The zero value for each field is equivalent to dialing without that option. Dialing with the zero value of Dialer is therefore equivalent to just calling the Dial function.

func (*Dialer) Dial

func (d *Dialer) Dial(network, address string) (net.Conn, error)

Dial connects to the address on the named network.

See https://golang.org/pkg/net/#Dialer.Dial for more details,

func (*Dialer) DialContext

func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error)

DialContext connects to the address on the named network using the provided context.

See https://golang.org/pkg/net/#Dialer.DialContext for more details.

type Registry

type Registry interface {
	// Lookup returns a set of addresses at which services with the given name
	// can be reached.
	//
	// An arbitrary list of tags can be passed to the method to narrow down the
	// result set to services matching this set of tags. No tags means to do no
	// filtering.
	//
	// The method also returns a TTL representing how long the result is valid
	// for. A zero TTL means that the caller should not reuse the result.
	//
	// The returned list of addresses must not be retained by implementations of
	// the Registry interface. The caller becomes the owner of the value after
	// the method returned.
	//
	// A non-nil error is returned when the lookup cannot be completed.
	//
	// The context can be used to asynchronously cancel the query when it
	// involves blocking operations.
	Lookup(ctx context.Context, name string, tags ...string) (addrs []string, ttl time.Duration, err error)
}

Registry is an interface implemented by types exposing a set of services.

The Registry interface provides a richer query interface, it allows programs to resolve service names to a list of address, potentially filtering down the result by applying tag filtering.

The interface design does come at a performance penality cost, because both the list of tags passed to the Lookup method and the returned list of addresses must be allocated on the heap. However, it is designed to only use standard types so code that wants to satisify the interface does not need to take a dependency on the package, offering strong decoupling abilities.

Registry implementations must be safe to use concurrently from multiple goroutines.

func Prefer

func Prefer(base Registry, tags ...string) Registry

Prefer decorates the registry to prefer exposing services with tags matching those passed as arguments.

The perference is achieved by adding the first tag to the lookup operations, if it returns no results the lookup is retried with the second tag, etc...

If none of the lookup operation returned any results the registry falls back to trying without any of the preferred tags.

type Resolver

type Resolver interface {
	// Resolve takes a service name as argument and returns an address at which
	// the service can be reached.
	//
	// The returned address must be a pair of an address and a port.
	// The address may be a v4 or v6 address, or a host name that can be
	// resolved by the default resolver.
	//
	// If service name resolution fails, an non-nil error is returned (but no
	// guarantee is made on the value of the address).
	//
	// The context can be used to asynchronously cancel the service name
	// resolution when it involves blocking operations.
	Resolve(ctx context.Context, name string) (addr string, err error)
}

The Resolver interface abstracts the concept of translating a service name into an address at which it can be reached.

The interface is voluntarily designed to solve the simplest use case, it does not provide support for advanced features found in popular service discovery backends, which maximizes compotiblity, composability, and performance.

The single method of the interface also uses basic Go types, so types that implement it don't even need to take a dependency on this package in order to satisfy the interface, making code built around this interface very flexible and easily decoupled of the service discovery backend being used.

Resolver implementations must be safe to use concurrently from multiple goroutines.

var DefaultResolver Resolver = NewResolver(nil)

DefaultResolver is the default service name resolver exposed by the services package.

func NewResolver

func NewResolver(r *net.Resolver) Resolver

NewResolver returns a value implementing the Resolver interface using the given standard resolver.

Service name resolution uses LookupSRV method to translate names to addresses made of the host name where they run and the port number at which they are available.

If r is nil, net.DefaultResolver is used.

Jump to

Keyboard shortcuts

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