Documentation ¶
Overview ¶
Package dnsdisco is a DNS service discovery library with health check and load balancer features.
The library is very flexible and uses interfaces everywhere to make it possible for the library user to replace any part with a custom algorithm. Check the examples for more details.
Example (LoadBalancer) ¶
Example_loadBalancer shows how it is possible to replace the default load balancer algorithm with a new one following the round robin strategy (https://en.wikipedia.org/wiki/Round-robin_scheduling).
package main import ( "container/ring" "fmt" "net" "github.com/rafaeljusto/dnsdisco" ) // roundRobinLoadBalancer is a load balancer that selects the server using a // round robin algorithm. type roundRobinLoadBalancer struct { // servers is a circular linked list to allow a fast round robin algorithm. servers *ring.Ring } // ChangeServers will be called anytime that a new set of servers is retrieved. func (r *roundRobinLoadBalancer) ChangeServers(servers []*net.SRV) { r.servers = ring.New(len(servers)) i, n := 0, r.servers.Len() for p := r.servers; i < n; p = p.Next() { p.Value = servers[i] i++ } } // LoadBalance will choose the best target based on a round robin strategy. If // no server is selected an empty target and a zero port is returned. func (d roundRobinLoadBalancer) LoadBalance() (target string, port uint16) { if d.servers.Len() == 0 { return "", 0 } server, _ := d.servers.Value.(*net.SRV) d.servers = d.servers.Next() return server.Target, server.Port } // Example_loadBalancer shows how it is possible to replace the default load // balancer algorithm with a new one following the round robin strategy // (https://en.wikipedia.org/wiki/Round-robin_scheduling). func main() { discovery := dnsdisco.NewDiscovery("jabber", "tcp", "registro.br") discovery.SetLoadBalancer(new(roundRobinLoadBalancer)) // Retrieve the servers if err := discovery.Refresh(); err != nil { fmt.Println(err) return } target, port := discovery.Choose() fmt.Printf("Target: %s\nPort: %d\n", target, port) }
Output: Target: jabber.registro.br. Port: 5269
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Discover ¶
Discover is the fastest way to find a target using all the default parameters. It will send a SRV query in _service._proto.name format and return the target (address and port) selected by the RFC 2782 algorithm and that passed on the health check (simple connection check).
proto must be "udp" or "tcp", otherwise an UnknownNetworkError error will be returned. The library will use the local resolver to send the DNS package.
Example ¶
ExampleDiscover is the fastest way to select a server using all default algorithms.
package main import ( "fmt" "github.com/rafaeljusto/dnsdisco" ) func main() { target, port, err := dnsdisco.Discover("jabber", "tcp", "registro.br") if err != nil { fmt.Println(err) return } fmt.Printf("Target: %s\nPort: %d\n", target, port) }
Output: Target: jabber.registro.br. Port: 5269
Example (RefreshAsync) ¶
ExampleDiscover_refreshAsync updates the servers list asynchronously every 100 milliseconds.
package main import ( "fmt" "net" "time" "github.com/rafaeljusto/dnsdisco" ) func main() { discovery := dnsdisco.NewDiscovery("jabber", "tcp", "registro.br") // depending on where this examples run the retrieving time differs (DNS RTT), // so as we cannot sleep a deterministic period, to make this test more useful // we are creating a channel to alert the main go routine that we got an // answer from the network retrieved := make(chan bool) discovery.SetRetriever(dnsdisco.RetrieverFunc(func(service, proto, name string) (servers []*net.SRV, err error) { _, servers, err = net.LookupSRV(service, proto, name) retrieved <- true return })) // refresh the SRV records every 100 milliseconds stopRefresh := discovery.RefreshAsync(100 * time.Millisecond) <-retrieved // sleep for a short period only to allow the library to process the SRV // records retrieved from the network time.Sleep(100 * time.Millisecond) target, port := discovery.Choose() fmt.Printf("Target: %s\nPort: %d\n", target, port) close(stopRefresh) }
Output: Target: jabber.registro.br. Port: 5269
Types ¶
type Discovery ¶
type Discovery interface { // Refresh retrieves the servers using the DNS SRV solution. It is possible to // change the default behaviour (local resolver with default timeouts) using // the SetRetriever method from the Discovery interface. Refresh() error // RefreshAsync works exactly as Refresh, but is non-blocking and will repeat // the action on every interval. To stop the refresh the returned channel must // be closed. RefreshAsync(time.Duration) chan<- bool // Choose will return the best target to use based on a defined load balancer. // By default the library choose the server based on the RFC 2782 considering // only the online servers. It is possible to change the load balancer // behaviour using the SetLoadBalancer method from the Discovery interface. If // no good match is found it should return a empty target and a zero port. Choose() (target string, port uint16) // Errors return all errors found during asynchronous executions. Once this // method is called the internal errors buffer is cleared. Errors() []error // SetRetriever changes how the library retrieves the DNS SRV records. SetRetriever(Retriever) // SetHealthChecker changes the way the library health check each server. SetHealthChecker(HealthChecker) // SetLoadBalancer changes how the library selects the best server. SetLoadBalancer(LoadBalancer) }
Discovery contains all the methods to discover the services and select the best one at the moment. The use of interface allows the users to mock this library easily for unit tests.
func NewDiscovery ¶
NewDiscovery builds the default implementation of the Discovery interface. To retrieve the servers it will use the net.LookupSRV (local resolver), for health check will only perform a simple connection, and the chosen target will be selected using the RFC 2782 considering only online servers.
The returned type can be used globally as it is go routine safe. It is recommended to keep a global Discovery for each service to minimize the number of DNS requests.
type HealthChecker ¶
type HealthChecker interface { // HealthCheck will analyze the target port/proto to check if it is still // capable of receiving requests. HealthCheck(target string, port uint16, proto string) (ok bool, err error) }
HealthChecker allows the library user to define a custom health check algorithm.
func NewDefaultHealthChecker ¶
func NewDefaultHealthChecker() HealthChecker
NewDefaultHealthChecker returns an instance of the default health checker algorithm. The default health checker tries to do a simple connection to the server. If the connection is successful the health check pass, otherwise it fails with an error. Possible proto values are tcp or udp.
type HealthCheckerFunc ¶
HealthCheckerFunc is an easy-to-use implementation of the interface that is responsible for checking if a target is still alive.
Example ¶
ExampleHealthCheckerFunc tests HTTP fetching the homepage and checking the HTTP status code.
package main import ( "fmt" "net/http" "github.com/rafaeljusto/dnsdisco" ) func main() { discovery := dnsdisco.NewDiscovery("http", "tcp", "pantz.org") discovery.SetHealthChecker(dnsdisco.HealthCheckerFunc(func(target string, port uint16, proto string) (ok bool, err error) { response, err := http.Get("http://www.pantz.org") if err != nil { return false, err } return response.StatusCode == http.StatusOK, nil })) // Retrieve the servers if err := discovery.Refresh(); err != nil { fmt.Println(err) return } target, port := discovery.Choose() fmt.Printf("Target: %s\nPort: %d\n", target, port) }
Output: Target: www.pantz.org. Port: 80
func (HealthCheckerFunc) HealthCheck ¶
func (h HealthCheckerFunc) HealthCheck(target string, port uint16, proto string) (ok bool, err error)
HealthCheck will analyze the target port/proto to check if it is still capable of receiving requests.
type LoadBalancer ¶
type LoadBalancer interface { // ChangeServers will be called anytime that a new set of servers is // retrieved. ChangeServers(servers []*net.SRV) // LoadBalance will choose the best target. LoadBalance() (target string, port uint16) }
LoadBalancer allows the library user to define a custom balance algorithm.
func NewDefaultLoadBalancer ¶
func NewDefaultLoadBalancer() LoadBalancer
NewDefaultLoadBalancer returns an instance of the default load balancer algorithm, that selects the best server based on the RFC 2782 algorithm. If no server is selected an empty target and a zero port is returned.
type Retriever ¶
type Retriever interface { // Retrieve will send the DNS request and return all SRV records retrieved // from the response. Retrieve(service, proto, name string) ([]*net.SRV, error) }
Retriever allows the library user to define a custom DNS retrieve algorithm.
func NewDefaultRetriever ¶
func NewDefaultRetriever() Retriever
NewDefaultRetriever returns an instance of the default retriever algorithm, that uses the local resolver to retrieve the SRV records.
type RetrieverFunc ¶
RetrieverFunc is an easy-to-use implementation of the interface that is responsible for sending the DNS SRV requests.
Example ¶
ExampleRetrieverFunc uses a specific resolver with custom timeouts.
package main import ( "fmt" "net" "strings" "time" "github.com/miekg/dns" "github.com/rafaeljusto/dnsdisco" ) func main() { discovery := dnsdisco.NewDiscovery("jabber", "tcp", "registro.br") discovery.SetRetriever(dnsdisco.RetrieverFunc(func(service, proto, name string) (servers []*net.SRV, err error) { client := dns.Client{ ReadTimeout: 2 * time.Second, WriteTimeout: 2 * time.Second, } name = strings.TrimRight(name, ".") z := fmt.Sprintf("_%s._%s.%s.", service, proto, name) var request dns.Msg request.SetQuestion(z, dns.TypeSRV) request.RecursionDesired = true response, _, err := client.Exchange(&request, "8.8.8.8:53") if err != nil { return nil, err } for _, rr := range response.Answer { if srv, ok := rr.(*dns.SRV); ok { servers = append(servers, &net.SRV{ Target: srv.Target, Port: srv.Port, Priority: srv.Priority, Weight: srv.Weight, }) } } return })) // Retrieve the servers if err := discovery.Refresh(); err != nil { fmt.Println(err) return } target, port := discovery.Choose() fmt.Printf("Target: %s\nPort: %d\n", target, port) }
Output: Target: jabber.registro.br. Port: 5269