Micro microservices framework - modrpc/info GitHub Wiki

Table of Contents

Overview

  • Article
  • Components
    • micro: microservice toolkit (blog post)
      • go-micro: pluggable RPC framework for microservices
        • go-plugins: plugins for go-micro (registry, broker, codec, transport, ...)
    • os: microservice operating system
    • go-os: go library for Micro OS
    • go-grpc: go-micro based gRPC library

Go Micro

Pluggable RPC framework used to build microservices in Go. It delivers the essential features required to create, discover and communicate with services. The core of any good microservice architecture begins by addressing service discovery, synchronous and asynchronous communication.

Packages

Includes packages and features:

  • Registry: Client side service discovery
  • Transport: Synchronous communication
  • Broker: Asynchronous comunication
  • Selector: Node filtering and load balancing
  • Codec: Message encoding/decoding
  • Server: RPC server building on the above
  • Client: RPC client building on the above

Registry

The registry provides a service discovery mechanism to resolve names to addresses. It can be backed by consul, etcd, zookeeper, dns, gossip, etc. Services should register using the registry on startup and deregister on shutdown. Services can optionally provide an expiry TTL and reregister on an interval to ensure liveness and that the service is cleaned up if it dies.

Selector

The selector is a load balancing abstraction which builds on the registry. It allows services to be "filtered" using filter functions and "selected" using a choice of algorithms such as random, roundrobin, leastconn, etc. The selector is leveraged by the Client when making requests. The client will use the selector rather than the registry as it provides that built in mechanism of load balancing.

Transport

The transport is the interface for synchronous request/response communication between services. It's akin to the golang net package but provides a higher level abstraction which allows us to switch out communication mechanisms e.g http, rabbitmq, websockets, NATS. The transport also supports bidirectional streaming. This is powerful for client side push to the server.

Broker

The broker provides an interface to a message broker for asynchronous pub/sub communication. This is one of the fundamental requirements of an event driven architecture and microservices. By default we use an inbox style point to point HTTP system to minimise the number of dependencies required to get started. However there are many message broker implementations available in go-plugins e.g RabbitMQ, NATS, NSQ, Google Cloud Pub Sub.

Codec

The codec is used for encoding and decoding messages before transporting them across the wire. This could be json, protobuf, bson, msgpack, etc. Where this differs from most other codecs is that we actually support the RPC format here as well. So we have JSON-RPC, PROTO-RPC, BSON-RPC, etc. It separates encoding from the client/server and provides a powerful method for integrating other systems such as gRPC, Vanadium, etc.

Server

The server is the building block for writing a service. Here you can name your service, register request handlers, add middeware, etc. The service builds on the above packages to provide a unified interface for serving requests. The built in server is an RPC system. In the future there maybe other implementations. The server also allows you to define multiple codecs to serve different encoded messages.

Client

The client provides an interface to make requests to services. Again like the server, it builds on the other packages to provide a unified interface for finding services by name using the registry, load balancing using the selector, making synchronous requests with the transport and asynchronous messaging using the broker.

The above components are combined at the top-level of micro as a Service.

Plugins

By default go-micro only provides a few implementation of each interface at the core but it's completely pluggable. There's already dozens of plugins which are available at github.com/micro/go-plugins

Go Micro Example

package main

import (
	"fmt"
	"os"

	"github.com/micro/cli"
	"github.com/micro/go-micro"
	proto "github.com/micro/go-micro/examples/service/proto"
	"golang.org/x/net/context"
)

/*
Example usage of top level service initialisation
*/

type Greeter struct{}

func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
	rsp.Greeting = "Hello " + req.Name
	return nil
}

// Setup and the client
func runClient(service micro.Service) {
	// Create new greeter client
	greeter := proto.NewGreeterClient("greeter", service.Client())

	// Call the greeter
	rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "John"})
	if err != nil {
		fmt.Println(err)
		return
	}

	// Print response
	fmt.Println(rsp.Greeting)
}

func main() {
	// Create a new service. Optionally include some options here.
	service := micro.NewService(
		micro.Name("greeter"),
		micro.Version("latest"),
		micro.Metadata(map[string]string{
			"type": "helloworld",
		}),

		// Setup some flags. Specify --run_client to run the client

		// Add runtime flags
		// We could do this below too
		micro.Flags(cli.BoolFlag{
			Name:  "run_client",
			Usage: "Launch the client",
		}),
	)

	// Init will parse the command line flags. Any flags set will
	// override the above settings. Options defined here will
	// override anything set on the command line.
	service.Init(
		// Add runtime action
		// We could actually do this above
		micro.Action(func(c *cli.Context) {
			if c.Bool("run_client") {
				runClient(service)
				os.Exit(0)
			}
		}),
	)

	// By default we'll run the server unless the flags catch us

	// Setup the server

	// Register handler
	proto.RegisterGreeterHandler(service.Server(), new(Greeter))

	// Run the server
	if err := service.Run(); err != nil {
		fmt.Println(err)
	}
}

Code Generation Example

Define proto service

syntax = "proto3";

// package name is used as the service name for discovery
// if service name is not passed in when initialising the 
// client
package go.micro.srv.greeter;

service Say {
    rpc Hello(Request) returns (Response) {}
}

message Request {
    optional string name = 1;
}

message Response {
    optional string msg = 1;
}

Generate code

protoc --go_out=plugins=micro:. hello.proto

Generated code

// Client API for Say service

type SayClient interface {
        Hello(ctx context.Context, in *Request) (*Response, error)
}

type sayClient struct {
        c           client.Client
        serviceName string
}

func NewSayClient(serviceName string, c client.Client) SayClient {
        if c == nil {
                c = client.NewClient()
        }
        if len(serviceName) == 0 {
                serviceName = "go.micro.srv.greeter"
        }
        return &sayClient{
                c:           c,
                serviceName: serviceName,
        }
}

func (c *sayClient) Hello(ctx context.Context, in *Request) (*Response, error) {
        req := c.c.NewRequest(c.serviceName, "Say.Hello", in)
        out := new(Response)
        err := c.c.Call(ctx, req, out)
        if err != nil {
                return nil, err
        }
        return out, nil
}

// Server API for Say service

type SayHandler interface {
        Hello(context.Context, *Request, *Response) error
}

func RegisterSayHandler(s server.Server, hdlr SayHandler) {
        s.Handle(s.NewHandler(hdlr))
}

Use client

import (
    "fmt"

    "golang.org/x/net/context"
    "github.com/micro/go-micro/client"
    hello "path/to/hello/proto"
)

func main() {
    cl := hello.NewSayClient("go.micro.srv.greeter", client.DefaultClient)
    // alternative initialisation
    // cl := hello.NewSayClient("", nil)

    rsp, err := cl.Hello(contex.Background(), &hello.Request{"Name": "John"})
    if err != nil {
        fmt.Println(err)
    }
}

Micro

CLI

NAME:
   micro - A microservices toolkit

USAGE:
   micro [global options] command [command options] [arguments...]

VERSION:
   latest

COMMANDS:
   api      Run the micro API
   bot      Run the micro bot
   registry Query registry
   query    Query a service method using rpc
   stream   Query a service method using streaming rpc
   health   Query the health of a service
   list     List items in registry
   register Register an item in the registry
   deregister   Deregister an item in the registry
   get      Get item from registry
   sidecar  Run the micro sidecar
   web      Run the micro web app
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --server_name                                Name of the server. go.micro.srv.example [$MICRO_SERVER_NAME]
   --server_version                                 Version of the server. 1.1.0 [$MICRO_SERVER_VERSION]
   --server_id                                  Id of the server. Auto-generated if not specified [$MICRO_SERVER_ID]
   --server_address                                 Bind address for the server. 127.0.0.1:8080 [$MICRO_SERVER_ADDRESS]
   --server_advertise                               Used instead of the server_address when registering with discovery. 127.0.0.1:8080 [$MICRO_SERVER_ADVERTISE]
   --server_metadata [--server_metadata option --server_metadata option]    A list of key-value pairs defining metadata. version=1.0.0 [$MICRO_SERVER_METADATA]
   --broker                                     Broker for pub/sub. http, nats, rabbitmq [$MICRO_BROKER]
   --broker_address                                 Comma-separated list of broker addresses [$MICRO_BROKER_ADDRESS]
   --registry                                   Registry for discovery. memory, consul, etcd, kubernetes [$MICRO_REGISTRY]
   --registry_address                               Comma-separated list of registry addresses [$MICRO_REGISTRY_ADDRESS]
   --selector                                   Selector used to pick nodes for querying. random, roundrobin, blacklist [$MICRO_SELECTOR]
   --transport                                  Transport mechanism used; http, rabbitmq, nats [$MICRO_TRANSPORT]
   --transport_address                              Comma-separated list of transport addresses [$MICRO_TRANSPORT_ADDRESS]
   --enable_tls                                 Enable TLS [$MICRO_ENABLE_TLS]
   --tls_cert_file                              TLS Certificate file [$MICRO_TLS_CERT_File]
   --tls_key_file                               TLS Key file [$MICRO_TLS_KEY_File]
   --api_address                                Set the api address e.g 0.0.0.0:8080 [$MICRO_API_ADDRESS]
   --proxy_address                              Proxy requests via the HTTP address specified [$MICRO_PROXY_ADDRESS]
   --sidecar_address                                Set the sidecar address e.g 0.0.0.0:8081 [$MICRO_SIDECAR_ADDRESS]
   --web_address                                Set the web UI address e.g 0.0.0.0:8082 [$MICRO_WEB_ADDRESS]
   --register_ttl "0"                               Register TTL in seconds [$MICRO_REGISTER_TTL]
   --register_interval "0"                          Register interval in seconds [$MICRO_REGISTER_INTERVAL]
   --api_handler                                Specify the request handler to be used for mapping HTTP requests to services. e.g api, proxy [$MICRO_API_HANDLER]
   --api_namespace                              Set the namespace used by the API e.g. com.example.api [$MICRO_API_NAMESPACE]
   --web_namespace                              Set the namespace used by the Web proxy e.g. com.example.web [$MICRO_WEB_NAMESPACE]
   --api_cors                                   Comma separated whitelist of allowed origins for CORS [$MICRO_API_CORS]
   --web_cors                                   Comma separated whitelist of allowed origins for CORS [$MICRO_WEB_CORS]
   --sidecar_cors                               Comma separated whitelist of allowed origins for CORS [$MICRO_SIDECAR_CORS]
   --enable_stats                               Enable stats [$MICRO_ENABLE_STATS]
   --help, -h                                   show help

Micro OS

Auth

Auth addresses authentication and authorization of services and users. The default implementation is Oauth2 with an additional policy engine coming soon. This is the best way to authenticate users and service to service calls using a centralised authority. Security is a first class citizen in a microservice OS.

Config

Config implements an interface for dynamic configuration. The config can be hierarchically loaded and merged from multiple sources e.g file, url, config service. It can and should also be namespaced so that environment specific config is loaded when running in dev, staging or production. The config interface is useful for business level configuration required by your services. It can be reloaded without needing to restart a service.

Db

The DB interface is an experiment CRUD interface to simplify database access and management. The amount of CRUD boilerplate written and rewritten in a microservice world is immense. By offloading this to a backend service and using RPC, we eliminate much of that and speed up development. The Micro OS implementation includes pluggable backends such as mysql, cassandra, elasticsearch and utilises the registry to lookup which nodes databases are assigned to.

Discovery

Discovery provides a high level service discovery interface on top of the go-micro registry. It utilises the watcher to locally cache service records and also heartbeats to a discovery service. It's akin to the Netflix Eureka 2.0 architecture whereby we split the read and write layers of discovery into separate services.

Event

The event package provides a way to send events and essentially create an event stream and record of all that's happening in your microservice environment. On the backend an event service aggregates the records and allows you to subscribe to a specific set of events. An event driven architecture is a powerful concept in a microservice environment and must be addressed adequately. At scale it's essential for correlating events within a distributed system e.g provisioning of new services, change of dynamic config, logouts for customers, tracking notifications, alerts.

Key-Value Service

KV represents a simple distributed key-value interface. It's useful for sharing small fast access bits of data amonst instances of a service. We provide three implementations currently. Memcached, redis and a consistently hashed in distributed in memory system.

Log

Log provides a structured logging interface which allows log messages to be tagged with key-value pairs. The default output plugin is file which allows many centralised logging systems to be used such as the ELK stack.

Monitor

The monitor provides a way to publish Status, Stats and Healtchecks to a monitoring service. Healthchecks are user defined checks that may be critical to a service e.g can access database, can sync from s3, etc. Monitoring in a distributed system is fundamentally different from the classic LAMP stack. In the old ways pings and tcp checks were regarded as enough, in a distributed system we require much more fine grained metrics and a monitoring service which can make sense of what failure means in this world.

Router

The router builds on the registry and selector to provide rate limiting, circuit breaking and global service load balancing. It implements the selector interface. Stats are recorded for every request and periodically published. A centralised routing service aggregates these metrics from all services in the environment and makes decisions about how to route requests. The routing service is not a proxy. Proxies are a weak form of load balancing, we prefer smart clients which retrieve a list of nodes from the router and make direct connections, this means if the routing service dies or misbehaves, clients can continue to make request independently.

Sync

Sync is an interface for distributed synchronisation. This provides an easy way to do leadership election and locking to serialise access to a resource. We expect there to be multiple copies of a service running to provide fault tolerance and scalability but it makes it much harder to deal with transactions or serialising access. The sync package provides a way to regain some of these semantics.

Trace

Trace is a client side interface for distributed tracing e.g dapper, zipkin, appdash. In a microservice world, a single request may fan out to 20-30 services. Failure may be non deterministic and difficult to track. Distributed tracing is a way of tracking the lifetime of a request. The interface utilises client and server wrappers to simplify using tracing.

⚠️ **GitHub.com Fallback** ⚠️