A Go library for master-less peer-to-peer autodiscovery and RPC between HTTP services

Overview

sleuth

API documentation Coverage Status

sleuth is a Go library that provides master-less peer-to-peer autodiscovery and RPC between HTTP services that reside on the same network. It works with minimal configuration and provides a mechanism to join a local network both as a client that offers no services and as any service that speaks HTTP. Its primary use case is for microservices on the same network that make calls to one another.

For a full introduction and tutorial, check out: Service autodiscovery in Go with sleuth

Installation

sleuth is dependent on libzmq, which can be installed either from source or from binaries. For more information, please refer to ØMQ: "Get the Software" or the libzmq repository.

Another option is to use a Docker container that comes with Go and ZeroMQ.

Once libzmq is available on a system, sleuth can be installed like any other Go library:

go get -u github.com/ursiform/sleuth

API

The sleuth API documentation is available on GoDoc or you can simply run:

godoc github.com/ursiform/sleuth

Examples

Example (1): The echo-service is a toy service that merely echoes back anything in an HTTP request body. It has made itself available on a sleuth network:

package main

import (
  "io/ioutil"
  "net/http"

  "github.com/ursiform/sleuth"
)

type echoHandler struct{}

func (h *echoHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
  body, _ := ioutil.ReadAll(req.Body)
  res.Write(body)
}

func main() {
  handler := new(echoHandler)
  // In the real world, the Interface field of the sleuth.Config object
  // should be set so that all services are on the same subnet.
  config := &sleuth.Config{
    Handler: handler,
    LogLevel: "debug",
    Service: "echo-service",
  }
  server, err := sleuth.New(config)
  if err != nil {
    panic(err.Error())
  }
  defer server.Close()
  http.ListenAndServe(":9873", handler)
}

And here is a trivial client that waits until it has connected to the network and found the echo-service to make a request before it exits. Note that the *sleuth.Client works as a drop-in replacement for an *http.Client when making requests using the Do() method:

package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/ursiform/sleuth"
)

func main() {
	service := "echo-service"
	// In the real world, the Interface field of the sleuth.Config object
	// should be set so that all services are on the same subnet.
	config := &sleuth.Config{LogLevel: "debug"}
	client, err := sleuth.New(config)
	if err != nil {
		panic(err.Error())
	}
	defer client.Close()
	client.WaitFor(service)
	input := "This is the value I am inputting."
	body := bytes.NewBuffer([]byte(input))
	request, _ := http.NewRequest("POST", "sleuth://"+service+"/", body)
	response, err := client.Do(request)
	if err != nil {
		panic(err.Error())
	}
	output, _ := ioutil.ReadAll(response.Body)
	if string(output) == input {
		fmt.Println("It works.")
	} else {
		fmt.Println("It doesn't work.")
	}
}

Example (2): sleuth-example is a fuller example of two services on a sleuth network that need to communicate with each other.

A complete tutorial based on that example can be found here: Service autodiscovery in Go with sleuth.

Test

go test -cover github.com/ursiform/sleuth

Q & A

Q: How does it work? I understand what sleuth does, but I want to know how it does it.

A: Services that instantiate a sleuth.Client create an ad hoc Gyre network. Gyre is the Go port of the Zyre project, which is built on top of ØMQ (ZeroMQ). Nodes in the network discover each other using a UDP beacon on port 5670. The actual communication between nodes happens on ephemeral TCP connections. What sleuth does is to manage this life cycle:

  • A peer joins the Gyre network as a member of the group SLEUTH-v1. If the peer offers a service, i.e., if it has an http.Handler, it notifies the rest of the network when it announces itself. The peer might have no service to offer, thus operating in client-only mode, or it may offer one service.
  • The peer finds other peers on the network. If you have asked the sleuth client to WaitFor() one or more services to appear before continuing, that call will block until it has found those services.
  • If the peer is offering a service, sleuth automatically listens for incoming requests in a separate goroutine and responds to incoming requests by invoking the http.Handler that was passed in during instantiation.
  • When you make a request to an available service, sleuth marshals the request, sends it to one of the available peers that offers that service, and waits for a response. If the response succeeds, it returns an http.Response; if it times out, it returns an error. The sleuth client Do() method has the same signature as the http client Do() method in order to operate as a drop-in replacement.
  • When you want to leave the network, e.g., when the application is quitting, the sleuth client Close() method immediately notifies the rest of the network that the peer is leaving. This is not strictly necessary because peers regularly check in to make sure the network knows they are alive, so the network automatically knows if a service has disappeared; but it is a good idea.

Q: What is the messaging protocol sleuth uses?

A: Under the hood, sleuth marshals HTTP requests and responses into plain JSON objects and then compresses them via gzip. Instead of adding another dependency on something like Protocol Buffers, sleuth depends on the fact that most API responses between microservices will be fairly small and it leaves the door open to ports in a wide variety of languages and environments. One hard dependency seemed quite enough.


Q: What if I have multiple instances of the same service?

A: Great! sleuth will automatically round-robin the requests each client makes to all services that share the same name.


Q: What happens if a service goes offline?

A: Whenever possible, a service should call its client's Close() method before exiting to notify the network of its departure. But even if a service fails to do that, the sleuth network's underlying Gyre network will detect within about one second that a peer has disappeared. All requests to that service will be routed to other peers offering the same service. If no peers exist for that service, then requests (which are made by calling the sleuth client Do() method) will return an unknown service error (code 919), which means that if you're already handling errors when making requests, you're covered.


Q: It doesn't work.

A: That's not a question. But have you checked to make sure your firewall allows UDP traffic on port 5670?


Q: It still doesn't work.

A: That's still not a question. But have you set the Interface field of your sleuth.Config object? The services you want to connect need to be on the same network and if you leave that field blank, the underlying Gyre network may not reside where you think it does. If you run ifconfig you'll get a list of available interfaces on your system.


Q: Why is it called sleuth?

A: Because "sleuth" is the collective noun for a group of bears: the original reason for writing this library was to connect a group of bear/forest services. Also because a sleuth searches for things and discovers them. Hence the logo:

License

sleuth is licensed under the MIT License.

The underlying libraries that sleuth relies on, Gyre and libzmq, are licensed under the LGPL. In effect, users who do not plan on modifying Gyre or libzmq can release their own applications under any license they see fit.

Resources

You might also like...
Distributed-Services - Distributed Systems with Golang to consequently build a fully-fletched distributed service

Distributed-Services This project is essentially a result of my attempt to under

Lockgate is a cross-platform locking library for Go with distributed locks using Kubernetes or lockgate HTTP lock server as well as the OS file locks support.

Lockgate Lockgate is a locking library for Go. Classical interface: 2 types of locks: shared and exclusive; 2 modes of locking: blocking and non-block

Take control of your data, connect with anything, and expose it anywhere through protocols such as HTTP, GraphQL, and gRPC.
Take control of your data, connect with anything, and expose it anywhere through protocols such as HTTP, GraphQL, and gRPC.

Semaphore Chat: Discord Documentation: Github pages Go package documentation: GoDev Take control of your data, connect with anything, and expose it an

Golang client library for adding support for interacting and monitoring Celery workers, tasks and events.

Celeriac Golang client library for adding support for interacting and monitoring Celery workers and tasks. It provides functionality to place tasks on

dht is used by anacrolix/torrent, and is intended for use as a library in other projects both torrent related and otherwise

dht Installation Install the library package with go get github.com/anacrolix/dht, or the provided cmds with go get github.com/anacrolix/dht/cmd/....

A feature complete and high performance multi-group Raft library in Go.
A feature complete and high performance multi-group Raft library in Go.

Dragonboat - A Multi-Group Raft library in Go / 中文版 News 2021-01-20 Dragonboat v3.3 has been released, please check CHANGELOG for all changes. 2020-03

🌧 BitTorrent client and library in Go
🌧 BitTorrent client and library in Go

rain BitTorrent client and library in Go. Running in production at put.io. Features Core protocol Fast extension Magnet links Multiple trackers UDP tr

A distributed systems library for Kubernetes deployments built on top of spindle and Cloud Spanner.

hedge A library built on top of spindle and Cloud Spanner that provides rudimentary distributed computing facilities to Kubernetes deployments. Featur

A distributed locking library built on top of Cloud Spanner and TrueTime.

A distributed locking library built on top of Cloud Spanner and TrueTime.

Comments
  • Can't find/use services which are already running on the network

    Can't find/use services which are already running on the network

    Hi ! I have this code running on multiple computers on the same network

    package main
    
    
    import (
    	"bytes"
    	"fmt"
    	"github.com/gorilla/mux"
    	"github.com/ursiform/sleuth"
    	"net/http"
    	"time"
    )
    
    var (
    	client *sleuth.Client
    )
    
    func handler(res http.ResponseWriter, req *http.Request) {
    	fmt.Println("test :D")
    }
    
    func main() {
    	router := mux.NewRouter()
    	router.HandleFunc("/", handler).Methods("POST")
    	client, _ = sleuth.New(&sleuth.Config{Service: "comment-service", Handler: router})
    
    	go surveilCurrentAvailableServices()
    
    	fmt.Println("ready...")
    	http.ListenAndServe(":9872", router)
    }
    
    func surveilCurrentAvailableServices() {
    	for {
    		fmt.Println(client)
    		client.WaitFor("comment-service")
    
    		//try to make a post to the another services
    		input := "This is the value I am inputting."
    		body := bytes.NewBuffer([]byte(input))
    		request, _ := http.NewRequest("POST", "sleuth://comment-service/", body)
    		client.Do(request)
    		time.Sleep(500 * time.Millisecond)
    	}
    }
    

    I run the code first on C1 (computer 1) then C2 and then C3

    With the fmt.Println(client) I get this client object:

    • On C1: &{500ms 0xc8201046e0 false SLEUTH-v1 145 0xc8200145a0 0xc820102470 0xc8200e2128 0xc820014640 map[B18125:comment-service F5C8BB:comment-service] 0xc820102480}

    • On C2 &{500ms 0x107ce3d0 false SLEUTH-v1 0 0x10712540 0x107cc120 0x107be7bc 0x10712630 map[B18125:comment-service] 0x107cc128}

    • On C3: &{500ms 0x107ce3d0 false SLEUTH-v1 0 0x10712540 0x107cc120 0x107be7cc 0x10712630 map[] 0x107cc128}

    So C1 detect C2 and C3 but C3 doesn't find the service of C1. So when you run the code sleuth doesn't find services which are already running on another devices on the network.

    Is that an issue or is there a solution for that?

    Moreover the POST request made on sleuth://comment-service/ doesn't work and I doesn't understand why.

    Thanks in advance!

    Best regards

    opened by Majonsi 13
  • Issue with UDP communication on finding a service

    Issue with UDP communication on finding a service

    I am having an issue with service discovery using Sleuth. I have followed your example without any changes except specifying the interface on the config. I've also made sure all UDP/TCP ports are open but still my client is not able to discover the service, it just keeps hanging on WaitFor(). If I run the service and client on same machine it works obviously but not when I run them on different machines on the same network.

    I have been using Sleuth with latest Gyre version (go get ) and libzmq 4.2.1/4.2.3.

    Is there any specific version of Gyre and libzmq that Sleuth requires as it seems Sleuth hasn't been updated for quite some time.

    opened by hasancana 2
Releases(v1.0.2)
  • v1.0.2(Dec 28, 2016)

    This is the official 1.0.2 release of sleuth, a Go library for peer-to-peer communication between web services on a distributed system.

    Change log

    v1.0.2 (2016-12-28)

    • WaitFor function now warns about being asked to wait for duplicate services.

    v1.0.1 (2016-10-02)

    • Race conditions that existed in v1.0.0 have been resolved.
    • The default log level is now set to silent.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.1(Oct 2, 2016)

    This is the official 1.0.1 release of sleuth, a Go library for peer-to-peer communication between web services on a distributed system.

    Change log

    v1.0.1 (2016-10-02)

    • Race conditions that existed in v1.0.0 have been resolved.
    • The default log level is now set to silent.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Sep 26, 2016)

  • v1.0.0-rc.3(Sep 26, 2016)

    This is a release candidate for the 1.0 release of sleuth, a Go library for peer-to-peer communication between web services on a distributed system.

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc.2(Sep 26, 2016)

    This is a release candidate for the 1.0 release of sleuth, a Go library for peer-to-peer communication between web services on a distributed system.

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc.1(Sep 26, 2016)

    This is a release candidate for the 1.0 release of sleuth, a Go library for peer-to-peer communication between web services on a distributed system.

    Source code(tar.gz)
    Source code(zip)
A simple go implementation of json rpc 2.0 client over http

JSON-RPC 2.0 Client for golang A go implementation of an rpc client using json as data format over http. The implementation is based on the JSON-RPC 2

Alexander Gehres 252 Nov 21, 2022
Simple, fast and scalable golang rpc library for high load

gorpc Simple, fast and scalable golang RPC library for high load and microservices. Gorpc provides the following features useful for highly loaded pro

Aliaksandr Valialkin 665 Nov 29, 2022
AppsFlyer 500 Dec 1, 2022
An experimental library for building clustered services in Go

Donut is a library for building clustered applications in Go. Example package main import ( "context" "log" "os" // Wait for etcd client v3.4, t

David Forsythe 97 Nov 17, 2022
This library contains utilities that are useful for building distributed services.

Grafana Dskit This library contains utilities that are useful for building distributed services. Current state This library is still in development. D

Grafana Labs 275 Dec 1, 2022
Hprose is a cross-language RPC. This project is Hprose for Golang.

Hprose 3.0 for Golang Introduction Hprose is a High Performance Remote Object Service Engine. It is a modern, lightweight, cross-language, cross-platf

Hprose 1.2k Dec 2, 2022
The jsonrpc package helps implement of JSON-RPC 2.0

jsonrpc About Simple, Poetic, Pithy. No reflect package. But reflect package is used only when invoke the debug handler. Support GAE/Go Standard Envir

Osamu TONOMORI 172 Nov 20, 2022
Distributed Lab 2: RPC in Go

Distributed Lab 2: RPC in Go Using the lab sheet There are two ways to use the lab sheet, you can either: create a new repo from this template - this

null 0 Oct 18, 2021
Skynet is a framework for distributed services in Go.

##Introduction Skynet is a communication protocol for building massively distributed apps in Go. It is not constrained to Go, so it will lend itself n

null 2k Nov 18, 2022
A distributed, proof of stake blockchain designed for the financial services industry.

Provenance Blockchain Provenance is a distributed, proof of stake blockchain designed for the financial services industry.

Provenance Blockchain, Inc. 64 Nov 6, 2022