Blog catalog Games using Microservices refactoring Notes (2) : Micro Framework introduction: Micro Toolkit

Micro and go – micro

As mentioned above, Micro is a Toolkit, mainly used for development, debugging, deployment, operation and maintenance, API gateway, etc., while Go-Micro is the project frequently used in our code

We already used Go-Micro in the HelloWorld example

package main

import (
	"context"
	"log"

        # here
	"github.com/micro/go-micro"// Reference the proto file proto generated above"micro-blog/helloworld/proto"
)
Copy the code

Service discovery

Service discovery is used to resolve service names and addresses. Service discovery is the core of microservice development. When service A wants to collaborate with service B, it needs to know where B is. Micro 0.17.0 The default service discovery system is Consul, and 0.22.0 the default service discovery system is Mdns. Mdns is independent of other components and can be used as a locally developed service discovery mode

  • Changing service discovery

    Append the parameter –registry=consul –registry_address=localhost:8500 or configure the environment MICRO_REGISTRY=consul MICRO_REGISTRY_ADDRESS=localhost:8500. If the service discovery mode is changed, you need to restart the Micro API gateway. Otherwise, the service list cannot be read

  • Custom service discovery

    Service discovery in Micro is easy to expand, you can use plug-ins to implement their own way of service discovery, such as etCD, Kubernetes, ZooKeeper, Nats, Redis, etc., refer to the Micro/Go-plugins library

Protobuf

A key requirement in microservices is a strong definition of the interface. Micro uses Protobuf to fulfill this requirement. Let’s define a microservice Greeter that has a Hello method. It has HelloRequest input object and HelloResponse output parameter object, both of which take a string parameter.

Install the Protoc Micro plug-in

go get github.com/micro/protoc-gen-micro
Copy the code

Write the proto

syntax = "proto3";

service Greeter {
	rpc Hello(HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
	string name = 1;
}

message HelloResponse {
	string greeting = 2;
}
Copy the code

Compile Proto and don’t forget the Micro plugin

protoc -I . --go_out=. --micro_out=. proto/greeter.proto 
Copy the code

Greeter.micro. Go and greeter.pb.go, where greeter.pb.go is a protoc file originally generated. Greeter.micro. Go is extra generated for Go-Micro, which is equivalent to some extra packaging. Our microservice needs to implement the Handler interface

// Server API for Greeter service

type GreeterHandler interface {
	Hello(context.Context, *HelloRequest, *HelloResponse) error
}
Copy the code

Write the service code to implement the interface

package main

import (
	"context"
	"fmt"

	micro "github.com/micro/go-micro"
	proto "github.com/micro/examples/service/proto"
)

type Greeter struct{}

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

func main() {// Create a new service, where you can pass in other options. service := micro.NewService( micro.Name("greeter"), / / initialization method parses the command line identification service. The Init () / / registered processor proto RegisterGreeterHandler (service. The Server (), new (Greeter)) / / run the serviceiferr := service.Run(); err ! = nil { fmt.Println(err) } }Copy the code

perform

go run main.go
Copy the code

The output

2016/03/14 10:59:14 Listening on [::]:50137
2016/03/14 10:59:14 Broker Listening on [::]:50138
2016/03/14 10:59:14 Registering node: greeter-ca62b017-e9d3-11e5-9bbb-68a86d0d36b6
Copy the code

This is a simple microservice

The client

To access other microservices, use the microservice client, which is included in the proto prototype file we generated above, to reduce the amount of template code. When creating a client, there are many other options such as selectors, filters, transport, codec, Load Balancing, Wrappers, and more, which will be covered in a future blog, Let’s create the simplest client here

package main

import (
	"context"
	"fmt"

	micro "github.com/micro/go-micro"
	proto "github.com/micro/examples/service/proto"
)


func main() {// Define the service, and other optional arguments can be passed in service := micro-newService (micro-name ()"greeter.client"// Create a new client greeter := proto.newGreeterService ()"greeter", service.client ()) // Call greeter RSP, err := greeter.Hello(context.todo (), &proto.helloRequest {Name:"Pengju"})
	iferr ! = nil {fmt.println (err)} // Prints response requests fmt.println (rsp.greeting)}Copy the code

perform

go run client.go
Copy the code

The output

Hello Pengju
Copy the code

Publish/subscribe

Publish/subscribe is a very common design pattern, and using publish/subscribe in Micro is simple and extensible. Go-micro builds a message broker interface into the event-driven architecture. When sending messages, messages are automatically encoded/decoded like RPC and sent via proxies. The default proxy is HTTP. You can use Go-plugins to expand your favorite proxy method

  • Changing the Broker Broker

Append parameters at startup — Broker = Nats –broker_address= LOCALhost :4222 or the configuration environment MICRO_BROKER= Nats MICRO_BROKER_ADDRESS= LOCALhost :4222

  • Customize the Broker broker

See the Micro/Go-plugins library for examples: HTTP (default), GRPC, Kafka, MQTT, Nats, RabbitMQ, Redis, etc

  • release

Write and compile Proto

syntax = "proto3";

// Example message
message Event {
	// unique id
	string id = 1;
	// unix timestamp
	int64 timestamp = 2;
	// message
	string message = 3;
}

Copy the code

Create the publisher, pass in the topic topic name, and the client

p := micro.NewPublisher("events", service.Client())

Publish a Protobuf message

p.Publish(context.TODO(), &proto.Event{
	Id:        uuid.NewUUID().String(),
	Timestamp: time.Now().Unix(),
	Message:   fmt.Sprintf("Messaging you all day on %s", topic),
})
Copy the code
  • To subscribe to

Creating a message handler

func ProcessEvent(ctx context.Context, event *proto.Event) error {
	fmt.Printf("Got event %+v\n", event)
	return nil
}
Copy the code

Subscribe to news

micro.RegisterSubscriber("events", ProcessEvent)
Copy the code

Functional programming

A Function is a service that receives a request, executes it and exits. Writing a Function is basically the same as writing a service.

package main

import (
	"context"

	proto "github.com/micro/examples/function/proto"
	"github.com/micro/go-micro"
)

type Greeter struct{}

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

func main() {// create a NewFunction FNC := micro-newfunction (micro-name ()"greeter"),) // Initialize command line fnc.init () // register handler fnc.handle (new(Greeter)) // Run service fnc.run ()}Copy the code

run

go run main.go
Copy the code

The output

2019/02/25 16:01:16 Transport [http] Listening on [::]:53445
2019/02/25 16:01:16 Broker [http] Listening on [::]:53446
2019/02/25 16:01:16 Registering node: greeter-fbc3f506-d302-4df3-bb90-2ae8142ca9d6

Copy the code

Using client calls

Service := micro.NewService(micro.Name())"greeter.client"))
service.Init()

cli := proto.NewGreeterService("greeter", service.client ()) // Call greeter RSP, err := cli.hello (context.todo (), &proto.helloRequest {Name:"Pengju"})
iferr ! = nil {fmt.println (err)} // Prints response requests fmt.println (rsp.greeting)Copy the code

Or use micro Toolkit

micro call greeter Greeter.Hello '{"name": "Pengju"}'
Copy the code

Will be output

{
	"greeting": "Hello Pengju"
}
Copy the code

At the same time, Function will exit

2019/02/25 16:07:41 Deregistering node: greeter-fbc3f506-d302-4df3-bb90-2ae8142ca9d6
Copy the code

Wrappers

Go-micro has the concept of middleware as wrappers. The client or processor can be wrapped in decorator mode. The following is to implement the Log Wrapper on both the server side and the client side for the log printing requirement.

  • Server (Handler)
// Implement the Server.handlerWrapper interface funclogWrapper(fn server.HandlerFunc) server.HandlerFunc {
	return func(ctx context.Context, req server.Request, rsp interface{}) error {
		fmt.Printf("[%v] server request: %s", time.Now(), req.Endpoint())
		return fn(ctx, req, rsp)
	}
}
Copy the code

Can be initialized when the service is created

service := micro.NewService(
	micro.Name("greeter"), // wrap the handler around micro.wraphandler (logWrapper),
)
Copy the code
  • Client
type logWrapper struct {
	client.Client
}

func (l *logWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ... client.CallOption) error { fmt.Printf("[wrapper] client request to service: %s method: %s\n", req.Service(), req.Endpoint())
	returnCall(CTX, req, RSP)} // implement client.wrapper, which acts as the log Wrapper funclogWrap(c client.Client) client.Client {
	return &logWrapper{c}
}
Copy the code

This can be initialized when the client is created, as in the Function call above

Service := micro.NewService(micro.Name())"greeter.client"), micro.WrapClient(logWrap))
service.Init()

cli := proto.NewGreeterService("greeter", service.client ()) // Call greeter RSP, err := cli.hello (context.todo (), &proto.helloRequest {Name:"Pengju"})
iferr ! = nil {fmt.println (err)} // Prints response requests fmt.println (rsp.greeting)Copy the code

Call output again

[wrapper] client request to service: greeter method: Greeter.Hello
Copy the code

Selector

If the greeter microservice has three service instances started, a randomly processed hash load balancing mechanism will be used by default to access the three service instances when a client makes RPC calls. If we want to access one of the service instances that meets the customized conditions, the greeter microservice can access the three service instances. Using selector, the client will always call the first service instance fetched from service discovery, using firstNodeSelector as an example. Customizing a selector is as simple as implementing the Selector interface in the Selector package

typeFirstNodeSelector struct {opts selector.Options} func (n *firstNodeSelector) Init(opts... selector.Option) error {for _, o := range opts {
		o(&n.opts)
	}
	returnNil} // selector returns options func (n *firstNodeSelector) options () selector.Options {returnFunc (n *firstNodeSelector) Select(service string, opts... selector.SelectOption) (selector.Next, error) { services, err := n.opts.Registry.GetService(service)iferr ! = nil {return nil, err
	}

	if len(services) == 0 {
		return nil, selector.ErrNotFound
	}

	var sopts selector.SelectOptions
	for _, opt := range opts {
		opt(&sopts)
	}

	for _, filter := range sopts.Filters {
		services = filter(services)
	}

	if len(services) == 0 {
		return nil, selector.ErrNotFound
	}

	if len(services[0].Nodes) == 0 {
		return nil, selector.ErrNotFound
	}

	return func() (*registry.Node, error) {
		return services[0].Nodes[0], nil
	}, nil
}

func (n *firstNodeSelector) Mark(service string, node *registry.Node, err error) {
	return
}

func (n *firstNodeSelector) Reset(service string) {
	return
}

func (n *firstNodeSelector) Close() error {
	returnNil} // Return selector name func (n *firstNodeSelector) String() String {return "first"
}
Copy the code

When creating a client, add a selector

cli := client.NewClient(
	client.Selector(FirstNodeSelector()),
)
Copy the code

Filter (filter)

Similar to selector, a filter is configured to filter service instances that match the conditions. A filter is a simplified version of a selector. The following uses a version selection filter as an example to filter service instances of a specific version

func Filter(v string) client.CallOption {
	filter := func(services []*registry.Service) []*registry.Service {
		var filtered []*registry.Service

		for _, service := range services {
			if service.Version == v {
				filtered = append(filtered, service)
			}
		}

		return filtered
	}

	return client.WithSelectOption(selector.WithFilter(filter))
}
Copy the code

Add a filter when called

rsp, err := greeter.Hello(
	// provide a context
	context.TODO(),
	// provide the request
	&proto.HelloRequest{Name: "Pengju"},
	// set the filter
	version.Filter("latest"),Copy the code

I have learned golang, Micro, K8S, GRPC, Protobuf and other knowledge for a short time, if there is any misunderstanding, welcome criticism and correction, you can add my wechat to discuss learning