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