Go - Micro微服务框架实践 - 使用指南(三)
一、安装指南
安装指南
依赖
我们需要服务发现,所以让我们启动Consul(默认); 可以使用其他 go-plugins将其换掉。
领事
brew install consul
consul agent -dev
要么
docker run consul
组播DNS
我们可以使用多播DNS进行零依赖性发现
传递--registry=mdns
给任何命令,例如micro --registry=mdns list services
Go微服务
Go Micro是Go中用于开发微服务的RPC框架
安装
go get github.com/micro/go-micro
的Protobuf
您还需要protoc-gen-micro来生成代码
工具包
微工具包提供了访问微服务的各种方法
安装
go get github.com/micro/micro
Docker
预制的 docker images可用
docker pull microhq/micro
Try CLI
运行greeter服务
go get github.com/micro/examples/greeter/srv && srv
List services 列出服务
$ micro list services
consul
go.micro.srv.greeter
Get Service 获得服务
$ micro get service go.micro.srv.greeter
service go.micro.srv.greeter
version 1.0.0
Id Address Port Metadata
go.micro.srv.greeter-34c55534-368b-11e6-b732-68a86d0d36b6 192.168.1.66 62525 server=rpc,registry=consul,transport=http,broker=http
Endpoint: Say.Hello
Metadata: stream=false
Request: {
name string
}
Response: {
msg string
}
Call service调用服务
$ micro call go.micro.srv.greeter Say.Hello '{"name": "John"}'
{
"msg": "Hello John"
}
二、GRPC网关
GRPC网关
本指南有助于将grpc网关与go-micro服务一起使用。
该GRPC网关是一个插件protoc。它读取gRPC服务定义,并生成反向代理服务器,将RESTful JSON API转换为gRPC。
我们使用go-grpc来编写后端服务。Go-GRPC是围绕go-micro和客户端和服务器的grpc插件的简单包装器。调用grpc.NewService时, 它返回一个micro.Service。
示例代码
在examples / grpc中找到示例代码。
Prereqs
这些是一些先决条件
安装protobuf
mkdir tmp
cd tmp
git clone https://github.com/google/protobuf
cd protobuf
./autogen.sh
./configure
make
make check
sudo make install
安装插件
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/micro/protobuf/protoc-gen-go
Greeter 微服务
在这个例子中,我们使用go-grpc创建了一个greeter微服务。服务很简单。
原型如下:
syntax = "proto3";
package go.micro.srv.greeter;
service Say {
rpc Hello(Request) returns (Response) {}
}
message Request {
string name = 1;
}
message Response {
string msg = 1;
}
服务如下:
package main
import (
"log"
"time"
hello "github.com/micro/examples/greeter/srv/proto/hello"
"github.com/micro/go-grpc"
"github.com/micro/go-micro"
"golang.org/x/net/context"
)
type Say struct{}
func (s *Say) Hello(ctx context.Context, req *hello.Request, rsp *hello.Response) error {
log.Print("Received Say.Hello request")
rsp.Msg = "Hello " + req.Name
return nil
}
func main() {
service := grpc.NewService(
micro.Name("go.micro.srv.greeter"),
micro.RegisterTTL(time.Second*30),
micro.RegisterInterval(time.Second*10),
)
// optionally setup command line usage
service.Init()
// Register Handlers
hello.RegisterSayHandler(service.Server(), new(Say))
// Run server
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
GRPC网关
grpc网关使用与服务相同的proto并添加了http选项
syntax = "proto3";
package greeter;
import "google/api/annotations.proto";
service Say {
rpc Hello(Request) returns (Response) {
option (google.api.http) = {
post: "/greeter/hello"
body: "*"
};
}
}
message Request {
string name = 1;
}
message Response {
string msg = 1;
}
proto使用以下命令生成grpc存根和反向代理
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
path/to/your_service.proto
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--grpc-gateway_out=logtostderr=true:. \
path/to/your_service.proto
我们使用以下代码为greeter服务创建了一个示例api。将编写类似的代码来注册其他端点。请注意,网关需要greeter服务的端点地址。
package main
import (
"flag"
"net/http"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
hello "github.com/micro/examples/grpc/gateway/proto/hello"
)
var (
// the go.micro.srv.greeter address
endpoint = flag.String("endpoint", "localhost:9090", "go.micro.srv.greeter address")
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := hello.RegisterSayHandlerFromEndpoint(ctx, mux, *endpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":8080", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
运行示例
运行greeter服务。指定mdns,因为我们不需要发现
go run examples/grpc/greeter/srv/main.go --registry=mdns --server_address=localhost:9090
运行网关。对于greeter服务,它将默认为端点localhost:9090
go run examples/grpc/gateway/main.go
模拟的请求(localhost:8080)
curl -d '{"name": "john"}' http://localhost:8080/greeter/hello
限制
示例grpc网关需要提供服务地址,而我们自己的micro api使用服务发现,动态路由和负载平衡。这使得grpc网关的集成不那么通用。
三、写一个Go服务
写一个Go服务
这是go-micro入门指南。
如果您首先想要更高级别的工具包概述,请查看介绍性博客文章https://micro.mu/blog/2016/03/20/micro.html。
服务端
顶级服务接口是构建服务的主要组件。它将Go Micro的所有底层软件包包装在一个方便的界面中。
type Service interface {
Init(...Option)
Options() Options
Client() client.Client
Server() server.Server
Run() error
String() string
}
1.初始化
使用这样创建服务micro.NewService
。
import "github.com/micro/go-micro"
service := micro.NewService()
选项可以在创建期间传递。
service := micro.NewService(
micro.Name("greeter"),
micro.Version("latest"),
)
所有可用的选项都可以在这里找到。
Go Micro还提供了一种使用命令行标志的方法micro.Flags
。
import (
"github.com/micro/cli"
"github.com/micro/go-micro"
)
service := micro.NewService(
micro.Flags(
cli.StringFlag{
Name: "environment",
Usage: "The environment",
},
)
)
解析标志使用service.Init
。此外,访问标志使用该micro.Action
选项。
service.Init(
micro.Action(func(c *cli.Context) {
env := c.StringFlag("environment")
if len(env) > 0 {
fmt.Println("Environment set to", env)
}
}),
)
Go Micro提供了预定义的标志,如果service.Init
被调用则会设置和解析。看到这里的所有标志 。
2.定义API
我们使用protobuf文件来定义服务API接口。这是一种非常方便的方法,可以严格定义API并为服务器和客户端提供具体类型。
这是一个示例定义。
greeter.proto
syntax = "proto3";
service Greeter {
rpc Hello(HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string greeting = 2;
}
这里我们使用方法Hello定义一个名为Greeter的服务处理程序,它接受参数HelloRequest类型并返回HelloResponse。
3.生成API接口
您需要以下内容来生成protobuf代码
我们使用protoc,protoc-gen-go和protoc-gen-micro为这个定义生成具体的go实现。
go get github.com/golang/protobuf/{proto,protoc-gen-go}
go get github.com/micro/protoc-gen-micro
protoc --proto_path=$GOPATH/src:. --micro_out=. --go_out=. greeter.proto
生成的类型现在可以在发出请求时导入并在服务器或客户端的处理程序中使用。
这是生成代码的一部分。
type HelloRequest struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
type HelloResponse struct {
Greeting string `protobuf:"bytes,2,opt,name=greeting" json:"greeting,omitempty"`
}
// Client API for Greeter service
type GreeterClient interface {
Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error)
}
type greeterClient struct {
c client.Client
serviceName string
}
func NewGreeterClient(serviceName string, c client.Client) GreeterClient {
if c == nil {
c = client.NewClient()
}
if len(serviceName) == 0 {
serviceName = "greeter"
}
return &greeterClient{
c: c,
serviceName: serviceName,
}
}
func (c *greeterClient) Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) {
req := c.c.NewRequest(c.serviceName, "Greeter.Hello", in)
out := new(HelloResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Greeter service
type GreeterHandler interface {
Hello(context.Context, *HelloRequest, *HelloResponse) error
}
func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler) {
s.Handle(s.NewHandler(&Greeter{hdlr}))
}
4.实现处理程序
服务器需要注册处理程序以提供请求。处理程序是一种公共类型,其公共方法符合签名func(ctx context.Context, req interface{}, rsp interface{}) error
。
如上所示,Greeter接口的处理程序签名如下所示。
type GreeterHandler interface {
Hello(context.Context, *HelloRequest, *HelloResponse) error
}
这是Greeter处理程序的一个实现。
import 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
}
处理程序在您的服务中注册,就像http.Handler一样。
service := micro.NewService(
micro.Name("greeter"),
)
proto.RegisterGreeterHandler(service.Server(), new(Greeter))
您还可以创建双向流处理程序,但我们会将其留给另一天。
5.运行服务
该服务可以通过调用运行server.Run
。这会导致服务绑定到配置中的地址(默认为找到的第一个RFC1918接口和一个随机端口)并侦听请求。
这将另外在启动时向注册表注册服务,并在发出终止信号时注销。
if err := service.Run(); err != nil {
log.Fatal(err)
}
6.完整的服务
greeter.go
package main
import (
"log"
"github.com/micro/go-micro"
proto "github.com/micro/examples/service/proto"
"golang.org/x/net/context"
)
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() {
service := micro.NewService(
micro.Name("greeter"),
micro.Version("latest"),
)
service.Init()
proto.RegisterGreeterHandler(service.Server(), new(Greeter))
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
注意。服务发现机制需要运行,以便服务可以注册以便客户端和其他服务发现。入门对于一个快速的是在这里。
客户端
该客户端软件包用于查询服务。创建服务时,会包含一个客户端,该客户端与服务器使用的初始化包匹配。
查询上述服务非常简单如下。
// create the greeter client using the service name and client
greeter := proto.NewGreeterClient("greeter", service.Client())
// request the Hello method on the Greeter handler
rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{
Name: "John",
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(rsp.Greeter)
proto.NewGreeterClient
获取服务名称和用于发出请求的客户端。
完整的示例可以在go-micro / examples / service中找到。
四、编写Go函数
编写Go函数
这是开始使用微观功能的指南。函数是一次执行服务。
如果您首先想要更高级别的工具包概述,请查看介绍性博客文章https://micro.mu/blog/2016/03/20/micro.html。
写一个函数
顶级函数接口是go-micro中函数编程模型的主要组件。它封装了Service接口,同时提供一次性执行。
// Function is a one time executing Service
type Function interface {
// Inherits Service interface
Service
// Done signals to complete execution
Done() error
// Handle registers an RPC handler
Handle(v interface{}) error
// Subscribe registers a subscriber
Subscribe(topic string, v interface{}) error
}
1.初始化
像这样使用创建函数micro.NewFunction
。
import "github.com/micro/go-micro"
function := micro.NewFunction()
选项可以在创建期间传递。
function := micro.NewFunction(
micro.Name("greeter"),
micro.Version("latest"),
)
所有可用的选项都可以在这里找到。
Go Micro还提供了一种使用命令行标志的方法micro.Flags
。
import (
"github.com/micro/cli"
"github.com/micro/go-micro"
)
function := micro.NewFunction(
micro.Flags(
cli.StringFlag{
Name: "environment",
Usage: "The environment",
},
)
)
解析标志使用function.Init
。此外,访问标志使用该micro.Action
选项。
function.Init(
micro.Action(func(c *cli.Context) {
env := c.StringFlag("environment")
if len(env) > 0 {
fmt.Println("Environment set to", env)
}
}),
)
Go Micro提供了预定义的标志,如果function.Init
被调用则会设置和解析。看到这里的所有标志 。
2.定义API
我们使用protobuf文件来定义API接口。这是一种非常方便的方法,可以严格定义API并为服务器和客户端提供具体类型。
这是一个示例定义。
greeter.proto
syntax = "proto3";
service Greeter {
rpc Hello(HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string greeting = 2;
}
这里我们使用方法Hello定义一个名为Greeter的函数处理程序,它接受参数HelloRequest类型并返回HelloResponse。
3.生成API接口
我们使用protoc和protoc-gen-go为这个定义生成具体的go实现。
Go-micro使用代码生成来提供客户端存根方法,以减少锅炉板代码,就像gRPC一样。它是通过一个protobuf插件完成的,需要一个golang / protobuf的分支,可以在这里找到 github.com/micro/protobuf。
go get github.com/micro/protobuf/{proto,protoc-gen-go}
protoc --go_out=plugins=micro:. greeter.proto
生成的类型现在可以在发出请求时导入并在服务器或客户端的处理程序中使用。
这是生成代码的一部分。
type HelloRequest struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
type HelloResponse struct {
Greeting string `protobuf:"bytes,2,opt,name=greeting" json:"greeting,omitempty"`
}
// Client API for Greeter service
type GreeterClient interface {
Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error)
}
type greeterClient struct {
c client.Client
serviceName string
}
func NewGreeterClient(serviceName string, c client.Client) GreeterClient {
if c == nil {
c = client.NewClient()
}
if len(serviceName) == 0 {
serviceName = "greeter"
}
return &greeterClient{
c: c,
serviceName: serviceName,
}
}
func (c *greeterClient) Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) {
req := c.c.NewRequest(c.serviceName, "Greeter.Hello", in)
out := new(HelloResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Greeter service
type GreeterHandler interface {
Hello(context.Context, *HelloRequest, *HelloResponse) error
}
func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler) {
s.Handle(s.NewHandler(&Greeter{hdlr}))
}
4.实现处理程序
服务器需要注册处理程序以提供请求。处理程序是一种公共类型,其公共方法符合签名func(ctx context.Context, req interface{}, rsp interface{}) error
。
如上所示,Greeter接口的处理程序签名如下所示。
type GreeterHandler interface {
Hello(context.Context, *HelloRequest, *HelloResponse) error
}
这是Greeter处理程序的一个实现。
import 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
}
处理程序的注册方式与http.Handler非常相似。
function := micro.NewFunction(
micro.Name("greeter"),
)
proto.RegisterGreeterHandler(service.Server(), new(Greeter))
或者,Function接口提供更简单的注册模式。
function := micro.NewFunction(
micro.Name("greeter"),
)
function.Handle(new(Greeter))
您还可以使用Subscribe方法注册异步订阅者。
5.运行该功能
该函数可以通过调用运行function.Run
。这会导致它绑定到配置中的地址(默认为找到的第一个RFC1918接口和一个随机端口)并侦听请求。
这将另外在启动时注册函数和注册表,并在发出kill信号时注销。
if err := function.Run(); err != nil {
log.Fatal(err)
}
在提出请求后,该功能将退出。您可以使用微运行来管理功能的生命周期。可以在examples / function中找到完整的示例。
6.完整的功能
greeter.go
package main
import (
"log"
"github.com/micro/go-micro"
proto "github.com/micro/examples/function/proto"
"golang.org/x/net/context"
)
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() {
function := micro.NewFunction(
micro.Name("greeter"),
micro.Version("latest"),
)
function.Init()
function.Handle(new(Greeter))
if err := function.Run(); err != nil {
log.Fatal(err)
}
}
注意。服务发现机制将需要运行,以便该函数可以注册以供那些希望查询它的人发现。入门对于一个快速的是在这里。
写客户
该客户端软件包用于查询功能和服务。创建函数时,会包含一个客户端,该客户端与服务器使用的初始化包匹配。
查询上述功能非常简单,如下所示。
// create the greeter client using the service name and client
greeter := proto.NewGreeterClient("greeter", function.Client())
// request the Hello method on the Greeter handler
rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{
Name: "John",
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(rsp.Greeter)
proto.NewGreeterClient
获取函数名称和用于发出请求的客户端。
完整的示例可以在go-micro / examples / function中找到。
五、容错
容错
在分布式系统中,世界始终存在故障。Micro试图通过一些容错的最佳实践来解决这个问题。本文档介绍了可以配置它们的一些方法。
心跳检查
心跳是一种在服务发现中刷新注册的机制。
基础理论
服务在启动时注册服务发现,在注销时注销。有时,这些服务可能意外死亡,被强行杀死或面临短暂的网络问题。在这些情况下,陈旧节点将留在服务发现中。如果自动删除服务,那将是理想的选择。
解决方案
Micro支持选择寄存器TTL和寄存器间隔,原因如下。TTL指定注册在发布之前应存在多长时间,然后将其删除。间隔是服务应重新注册以保留服务发现中的注册的时间。
这些是go-micro中可用的选项以及微工具箱中的标志
用法
对于微型工具包,只需使用内置标志来设置ttl和interval。
micro --register_ttl=30 --register_interval=15 api
此示例显示我们将ttl设置为30秒,重新注册间隔为15秒。
对于go-micro,在声明微服务时,您可以将选项作为time.Duration传递
service := micro.NewService(
micro.Name("com.example.srv.foo"),
micro.RegisterTTL(time.Second*30),
micro.RegisterInterval(time.Second*15),
)
负载均衡
负载平衡是一种传播请求负载或维持高可用性的方法
基础理论
任何单个流程应用程序的可用性和扩展都存在限制。如果应用程序因任何原因而死亡,则您无法再处理请求。如果向应用程序发送了足够的请求负载,它可能开始缓慢响应或根本不响应。跨应用程序的多个副本发送请求将解决这些问题。
解决方案
Micro通过选择器接口进行客户端负载平衡,以跨服务的任意数量的节点传播请求。当服务启动时,它将服务发现注册为具有唯一地址和id的服务节点。在发出请求时,微客户端使用选择器来决定向哪个节点发出请求。选择器使用服务注册表来查找服务的节点,然后使用负载平衡策略(如随机散列或循环法)来选择要发送请求的节点。
用法
客户端负载平衡内置于go-micro客户端。这是自动完成的。
重试
重试是一种在请求失败时重试请求的方法
基础理论
请求可能由于多种原因而失败; 网络错误,请求加载,应用程序死亡。在这些情况下,如果我们可以针对应用程序的不同副本重试请求以获得成功的响应,那将是理想的。
解决方案
微客户端包括用于重试请求的机制。选择器(如上所述)返回Next函数,该函数在执行时使用负载平衡策略从列表中返回节点。Next函数可以多次执行,根据负载平衡策略返回一个新节点。设置重试后,如果请求失败,将执行Next函数,并将针对新节点重试请求。
用法
可以将重试设置为客户端的标志或选项。它默认为1,表示1次尝试请求。
改变通过旗帜
micro --client_retries=3
设置为选项
client.Init(
client.Retries(3),
)
缓存发现
发现缓存是服务发现信息的客户端缓存
基础理论
服务发现是微服务的核心依赖,但如果没有正确构建,也可能成为单点故障。每个发现系统都有自己的扩展和高可用性属性。如果发现发生故障,它会使系统的其余部分无法使用,因为服务不知道如何将名称解析为地址。如果对系统中的每个请求进行查找,则发现也可能成为瓶颈。
解决方案
客户端缓存是一种消除服务发现作为瓶颈和单点故障的方法。Micro包括一个选择器(客户端负载均衡器),它维护与其相关的服务发现信息的内存缓存。如果发生高速缓存未命中,则选择器将使用服务注册表进行查找并缓存数据。缓存也会定期TTL输出,以确保过时的数据不会存在。
用法
可以通过标志或创建新服务时设置缓存选择器。
作为工具包的标志,执行以下操作
micro --selector=cache api
如果调用Init方法,Go-micro服务也可以使用相同的标志。或者,可以用代码设置选择器。
import (
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/selector/cache"
)
service := micro.NewService(
micro.Name("com.example.srv.foo"),
)
service.Client().Init(cache.NewSelector())
访问github.com/micro/micro了解更多信息