Go 微服务框架:Go Micro、Go Kit、Gizmo 和 Kite(译文) | Go主题月

3,336 阅读16分钟

image.png 图片来自于作者 Simon Abrams 网站 Unsplash

我最近在墨尔本 Golang 会议上做了一个关于如何开发微服务和框架的演讲。在这篇文章中,我将与你分享我的知识(另外,这对我也是一个很好的提醒)。

以下是我将对比的框架:

框架简介

Go Micro

在我看来,这是最流行的框架之一。有很多博客文章和简单的例子。你可以在 Medium@MicroHQ 上关注 microhq,以获取 Go Micro 的最新更新。

好的,什么是 Go Micro ?它是一个可插入的 RPC 框架,用于在 Go 中编写微服务。开箱即用,您将收到:

  • 服务发现 - 自动向服务发现系统注册的应用程序。
  • 负载平衡 - 客户端负载平衡,用于平衡服务实例之间的请求。
  • 同步通信 - 提供请求/响应传输层。
  • 异步通信 - 内置发布/订阅功能。
  • 消息编码 - 基于消息的内容类型头的编码/解码。
  • RPC客户机/服务器包 - 利用上述特性和公开接口来构建微服务。

Go Micro 体系结构可以描述为三层堆栈。

image.png

图1. Go Micro 体系结构

顶层由客户机-服务器模型和服务抽象组成。服务器是编写服务的构建块。客户机提供了一个接口来向服务发出请求。

底层由以下类型的插件组成:

  • 代理 Broker - 为异步发布/订阅通信提供到消息代理的接口。
  • 编解码器 Codec - 用于编码/解码消息。支持的格式包括 json、bson、protobuf、msgpack 等。
  • 注册表 Registry - 提供服务发现机制(默认为 Consul )。
  • 选择器 Selector - 建立在注册表上的负载平衡抽象。它允许使用诸如 random、roundrobin、leastconn 等算法“选择”服务。
  • 传输 Transport - 服务间同步请求/响应通信的接口。

Go Micro 还提供了 Sidecar 等功能。这允许您使用以 Go 以外的语言编写的服务。Sidecar 提供服务注册、gRPC 编码/解码和 HTTP 处理程序。有多种语言可供选择。

Go Kit

Go Kit 是一个用于在 Go 中构建微服务的编程工具包。与 Go Micro 不同的是,它是一个库,可以导入到二进制软件包中。

Go Kit 遵循以下简单规则:

  • 无全局状态
  • 声明式组合
  • 显式依赖关系
  • 接口即约定
  • 领域驱动设计

在 Go Kit 中,您可以找到以下软件包:

  • 认证 Authentication - 基本认证和 JWT 认证。
  • 传输 Transport - HTTP、Nats、gRPC 和其他。
  • 日志记录 Logging - 结构化登录服务的通用接口。
  • 指标 Metrics - CloudWatch、Statsd、Graphite 和其他。
  • 追踪 Tracing - Zipkin 和 Opentracing。
  • 服务发现 Service discovery - Consult、Etcd、Eureka 和其他。
  • 断路器 Circuitbreaker - 在 Go 中实现 Hystrix。

您可以在 Peter Bourgon 的文章和演示幻灯片中找到关于 Go 工具包的最佳描述之一:

Go kit: 在现代企业中使用 Go

Go + 微服务

此外,在 Go + microservices 幻灯片中,您将看到一个使用Go Kit构建的服务体系结构的示例。为了快速参考,这里有一个服务架构图。

image.png

图2. 使用 Go Kit 构建的服务架构示例(原始图像在 Go + microservices 幻灯片中)

Gizmo

Gizmo 是《纽约时报》的一个微服务工具包。它提供了将服务器和 pubsub 守护进程组合在一起的包。它公开以下包:

  • server - 提供两个服务器实现:SimpleServer(通过 HTTP)、RPCServer(通过 gRPC)。
  • server/kit - 基于 Go kit 的包,目前在测试中。
  • config - 包含从 JSON 文件、cordon k/v 中的 JSON blob 或环境变量配置的函数。
  • pubsub - 提供发布和使用队列中数据的通用接口。
  • publisher/pubsetst - 包含发布者和订阅者的测试接口。
  • web - 公开用于从请求查询和有效负载解析类型的函数。

Pubsub 包提供了处理以下队列的接口:

所以,在我看来,Gizmo 在 Go Micro 和 Go Kit 之间。它不是像 Go Micro 那样完整的“黑匣子”。同时,它没有 Go Kit 那么原始。它提供了更高级别的构建组件,例如 configpubsub 包。

Kite

Kite 是一个开发 Go 微服务的框架。它公开 RPC 客户端和服务器包。创建的服务将自动注册到服务发现系统 Kontrol。Kontrol 是用 Kite 写的,它本身就是 Kite 服务。这意味着 Kite 微服务在自己的环境中工作良好。如果需要将 Kite 微服务连接到另一个服务发现系统,则需要定制。这就是我觉得 Kite 不行的主要原因,并决定不评审这个框架。

比较框架

我将使用四种类别比较框架:

  • 客观比较 - GitHub 统计
  • 文档和示例
  • 用户和社区
  • 代码质量

GitHub 统计信息

image.png

表1. Go微服务框架统计(2018年4月收集)

文档和示例

简单地说,没有一个框架提供可靠的文档。通常,唯一的正式文档是项目首页上的 readme。

对于 Go Micro,可以在 micro.mumicrohq@MicroHQ 了解。

对于 Go kit,最好的文档在 Peter Bourgon’s blog 。我发现的最好的例子之一是在 ru-rocker blog

对于 Gizmo,源代码提供了最好的文档和示例。

总而言之,如果您来自 NodeJS 世界,并且希望看到 ExpressJS 之类的教程,您将会失望。另一方面,这是一个很好的机会来创建自己的教程。

用户和社区

基于 GitHub 的统计数据,

Go Kit 是最流行的微服务框架 —— 在本篇文章发表时超过了 10k 颗星星。它有大量的贡献者(122人)和超过 1000 个 fork。最后,Go Kit 获得了 DigitalOcean 的支持。

Go Micro 排名第二,拥有超过 3600 颗星星,27 位贡献者和 385 个 fork。Go Micro 最大的赞助商之一是 Sixt

Gizmo 排名第三,超过 2200 颗星星,31 位贡献者和 137 个 fork。由《纽约时报》支持和创建。

代码质量

Go Kit 在代码质量类别中排名第一。它几乎有 80% 的代码覆盖率和优秀的 Go 报告评级。Gizmo 也有一个很好的 Go 报告评级。但它的代码覆盖率只有 46%。Go Micro 不提供报道信息,但它有一个很棒的 Go 报告评级

微服务示例

好啦,理论够了。为了更好地理解这些框架,我创建了三个简单的微服务。

image.png

图3. 实例体系结构

这些服务实现了一个业务功能:问候语。当用户向服务传递 name 参数时,服务将发送问候响应。此外,所有服务均满足以下要求:

  • 服务应在服务发现系统中自注册。
  • 服务应具有运行状况检查终结点。
  • 服务应至少支持 HTTP 和 gRPC 传输。

对于那些喜欢阅读源代码的人。您可以停在这里阅读存储库中的源代码。

Go Micro greeter

要使用 Go Micro 创建服务,首先需要定义 protobuf 描述。接下来,这三个服务都使用了相同的 protobuf 定义。我创建了以下服务描述:

go-micro-greeter.proto

syntax = "proto3";

package pb;

service Greeter {
  rpc Greeting(GreetingRequest) returns (GreetingResponse) {}
}

message GreetingRequest {
  string name = 1;
}

message GreetingResponse {
  string greeting = 2;
}

问候器服务协议定义

接口由一种方法 — Greeting 组成。请求中有一个参数 — name,响应中有一个参数 — greeting

然后,我使用修改后的 protoc 从 protobuf 生成服务接口。生成器由 Go Micro forked 并进行了修改,以支持框架的一些特性。我在 greeter 服务中把这一切都连接在一起。此时,服务正在启动并注册到服务发现系统。它只支持 gRPC 传输协议:

go-micro-greeter-grpc-main.go

package main

import (
	"log"

	pb "github.com/antklim/go-microservices/go-micro-greeter/pb"
	"github.com/micro/go-micro"
	"golang.org/x/net/context"
)

// Greeter implements greeter service.
type Greeter struct{}

// Greeting method implementation.
func (g *Greeter) Greeting(ctx context.Context, in *pb.GreetingRequest, out *pb.GreetingResponse) error {
	out.Greeting = "GO-MICRO Hello " + in.Name
	return nil
}

func main() {
	service := micro.NewService(
		micro.Name("go-micro-srv-greeter"),
		micro.Version("latest"),
	)

	service.Init()

	pb.RegisterGreeterHandler(service.Server(), new(Greeter))

	if err := service.Run(); err != nil {
		log.Fatal(err)
	}
}

Go Micro中 gRP C服务的实现

为了支持 HTTP 传输,我不得不添加另一个模块。它将 HTTP 请求映射到 protobuf 定义的请求。并呼叫 gRPC 服务。然后将服务响应映射到 HTTP 响应,并将其回复给用户。

go-micro-greeter-http-main.go

package main

import (
	"context"
	"encoding/json"
	"log"
	"net/http"

	proto "github.com/antklim/go-microservices/go-micro-greeter/pb"
	"github.com/micro/go-micro/client"
	web "github.com/micro/go-web"
)

func main() {
	service := web.NewService(
		web.Name("go-micro-web-greeter"),
	)

	service.HandleFunc("/greeting", func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "GET" {
			var name string
			vars := r.URL.Query()
			names, exists := vars["name"]
			if !exists || len(names) != 1 {
				name = ""
			} else {
				name = names[0]
			}

			cl := proto.NewGreeterClient("go-micro-srv-greeter", client.DefaultClient)
			rsp, err := cl.Greeting(context.Background(), &proto.GreetingRequest{Name: name})
			if err != nil {
				http.Error(w, err.Error(), 500)
				return
			}

			js, err := json.Marshal(rsp)
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			w.Header().Set("Content-Type", "application/json")
			w.Write(js)
			return
		}
	})

	if err := service.Init(); err != nil {
		log.Fatal(err)
	}

	if err := service.Run(); err != nil {
		log.Fatal(err)
	}
}

Go Micro 中 Web 服务的实现

非常简单和直接。许多事情都是由 Go Micro 在幕后处理的,比如在服务发现系统中注册。另一方面,很难创建纯 HTTP 服务。

Go Kit greeter

在我完成了 Go Micro 之后,我转到了 Go Kit 服务实现。我花了很多时间阅读 Go Kit 库中提供的代码示例。理解端点的概念花了我很多时间。下一个耗时的难题是服务发现注册器代码。在我找到一个好的例子之后,才把它实现了。

最后,我为以下内容创建了四个包:

  • 服务逻辑实现。
  • 传输不可知的服务端点。
  • 传输特定终结点(gRPC、HTTP)
  • 服务发现注册器。

go-kit-greeter-service.go

package greeterservice

// Service describe greetings service.
type Service interface {
	Health() bool
	Greeting(name string) string
}

// GreeterService implementation of the Service interface.
type GreeterService struct{}

// Health implementation of the Service.
func (GreeterService) Health() bool {
	return true
}

// Greeting implementation of the Service.
func (GreeterService) Greeting(name string) (greeting string) {
	greeting = "GO-KIT Hello " + name
	return
}

Go Kit 中的服务逻辑实现

如您所见,代码没有任何依赖关系。它只是实现了逻辑。下一个代码 服务端点定义:

go-kit-greeter-endpoints.go

package greeterendpoint

import (
	"context"

	"github.com/go-kit/kit/log"

	"github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterservice"
	"github.com/go-kit/kit/endpoint"
)

// Endpoints collects all of the endpoints that compose a greeter service. It's
// meant to be used as a helper struct, to collect all of the endpoints into a
// single parameter.
type Endpoints struct {
	HealthEndpoint   endpoint.Endpoint // used by Consul for the healthcheck
	GreetingEndpoint endpoint.Endpoint
}

// MakeServerEndpoints returns service Endoints, and wires in all the provided
// middlewares.
func MakeServerEndpoints(s greeterservice.Service, logger log.Logger) Endpoints {
	var healthEndpoint endpoint.Endpoint
	{
		healthEndpoint = MakeHealthEndpoint(s)
		healthEndpoint = LoggingMiddleware(log.With(logger, "method", "Health"))(healthEndpoint)
	}

	var greetingEndpoint endpoint.Endpoint
	{
		greetingEndpoint = MakeGreetingEndpoint(s)
		greetingEndpoint = LoggingMiddleware(log.With(logger, "method", "Greeting"))(greetingEndpoint)
	}

	return Endpoints{
		HealthEndpoint:   healthEndpoint,
		GreetingEndpoint: greetingEndpoint,
	}
}

// MakeHealthEndpoint constructs a Health endpoint wrapping the service.
func MakeHealthEndpoint(s greeterservice.Service) endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (response interface{}, err error) {
		healthy := s.Health()
		return HealthResponse{Healthy: healthy}, nil
	}
}

// MakeGreetingEndpoint constructs a Greeter endpoint wrapping the service.
func MakeGreetingEndpoint(s greeterservice.Service) endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (response interface{}, err error) {
		req := request.(GreetingRequest)
		greeting := s.Greeting(req.Name)
		return GreetingResponse{Greeting: greeting}, nil
	}
}

// Failer is an interface that should be implemented by response types.
// Response encoders can check if responses are Failer, and if so if they've
// failed, and if so encode them using a separate write path based on the error.
type Failer interface {
	Failed() error
}

// HealthRequest collects the request parameters for the Health method.
type HealthRequest struct{}

// HealthResponse collects the response values for the Health method.
type HealthResponse struct {
	Healthy bool  `json:"healthy,omitempty"`
	Err     error `json:"err,omitempty"`
}

// Failed implements Failer.
func (r HealthResponse) Failed() error { return r.Err }

// GreetingRequest collects the request parameters for the Greeting method.
type GreetingRequest struct {
	Name string `json:"name,omitempty"`
}

// GreetingResponse collects the response values for the Greeting method.
type GreetingResponse struct {
	Greeting string `json:"greeting,omitempty"`
	Err      error  `json:"err,omitempty"`
}

// Failed implements Failer.
func (r GreetingResponse) Failed() error { return r.Err }

Go Kit 服务端点定义(传输无关)

在定义了服务和端点之后,我开始通过不同的传输协议公开端点。我从 HTTP 传输开始:

go-kit-greeter-http.go

package greetertransport

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"

	"github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint"
	"github.com/go-kit/kit/log"
	httptransport "github.com/go-kit/kit/transport/http"
	"github.com/gorilla/mux"
)

var (
	// ErrBadRouting is returned when an expected path variable is missing.
	ErrBadRouting = errors.New("inconsistent mapping between route and handler")
)

// NewHTTPHandler returns an HTTP handler that makes a set of endpoints
// available on predefined paths.
func NewHTTPHandler(endpoints greeterendpoint.Endpoints, logger log.Logger) http.Handler {
	m := mux.NewRouter()
	options := []httptransport.ServerOption{
		httptransport.ServerErrorEncoder(encodeError),
		httptransport.ServerErrorLogger(logger),
	}

	// GET /health         retrieves service heath information
	// GET /greeting?name  retrieves greeting

	m.Methods("GET").Path("/health").Handler(httptransport.NewServer(
		endpoints.HealthEndpoint,
		DecodeHTTPHealthRequest,
		EncodeHTTPGenericResponse,
		options...,
	))
	m.Methods("GET").Path("/greeting").Handler(httptransport.NewServer(
		endpoints.GreetingEndpoint,
		DecodeHTTPGreetingRequest,
		EncodeHTTPGenericResponse,
		options...,
	))
	return m
}

// DecodeHTTPHealthRequest method.
func DecodeHTTPHealthRequest(_ context.Context, _ *http.Request) (interface{}, error) {
	return greeterendpoint.HealthRequest{}, nil
}

// DecodeHTTPGreetingRequest method.
func DecodeHTTPGreetingRequest(_ context.Context, r *http.Request) (interface{}, error) {
	vars := r.URL.Query()
	names, exists := vars["name"]
	if !exists || len(names) != 1 {
		return nil, ErrBadRouting
	}
	req := greeterendpoint.GreetingRequest{Name: names[0]}
	return req, nil
}

func encodeError(_ context.Context, err error, w http.ResponseWriter) {
	w.WriteHeader(err2code(err))
	json.NewEncoder(w).Encode(errorWrapper{Error: err.Error()})
}

func err2code(err error) int {
	switch err {
	default:
		return http.StatusInternalServerError
	}
}

type errorWrapper struct {
	Error string `json:"error"`
}

// EncodeHTTPGenericResponse is a transport/http.EncodeResponseFunc that encodes
// the response as JSON to the response writer
func EncodeHTTPGenericResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
	if f, ok := response.(greeterendpoint.Failer); ok && f.Failed() != nil {
		encodeError(ctx, f.Failed(), w)
		return nil
	}
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	return json.NewEncoder(w).Encode(response)
}

Go Kit 服务 HTTP 端点

在开始 gRPC 端点实现之前,我不需要 protobuf 定义。我复制了 Go Micro 服务协议。但是在 Go Kit 的例子中,我使用了默认的服务生成器来创建服务接口。

go-greeter-gen.sh

#!/usr/bin/env sh

protoc greeter.proto --go_out=plugins=grpc:.

来自 protobuf 定义的服务接口生成器

go-kit-grpc.go

package greetertransport

import (
	"context"

	"github.com/antklim/go-microservices/go-kit-greeter/pb"
	"github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint"
	"github.com/go-kit/kit/log"
	grpctransport "github.com/go-kit/kit/transport/grpc"
	oldcontext "golang.org/x/net/context"
)

type grpcServer struct {
	greeter grpctransport.Handler
}

// NewGRPCServer makes a set of endpoints available as a gRPC GreeterServer.
func NewGRPCServer(endpoints greeterendpoint.Endpoints, logger log.Logger) pb.GreeterServer {
	options := []grpctransport.ServerOption{
		grpctransport.ServerErrorLogger(logger),
	}

	return &grpcServer{
		greeter: grpctransport.NewServer(
			endpoints.GreetingEndpoint,
			decodeGRPCGreetingRequest,
			encodeGRPCGreetingResponse,
			options...,
		),
	}
}

// Greeting implementation of the method of the GreeterService interface.
func (s *grpcServer) Greeting(ctx oldcontext.Context, req *pb.GreetingRequest) (*pb.GreetingResponse, error) {
	_, res, err := s.greeter.ServeGRPC(ctx, req)
	if err != nil {
		return nil, err
	}
	return res.(*pb.GreetingResponse), nil
}

// decodeGRPCGreetingRequest is a transport/grpc.DecodeRequestFunc that converts
// a gRPC greeting request to a user-domain greeting request.
func decodeGRPCGreetingRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
	req := grpcReq.(*pb.GreetingRequest)
	return greeterendpoint.GreetingRequest{Name: req.Name}, nil
}

// encodeGRPCGreetingResponse is a transport/grpc.EncodeResponseFunc that converts
// a user-domain greeting response to a gRPC greeting response.
func encodeGRPCGreetingResponse(_ context.Context, response interface{}) (interface{}, error) {
	res := response.(greeterendpoint.GreetingResponse)
	return &pb.GreetingResponse{Greeting: res.Greeting}, nil
}

Go Kit 服务 gRPC 终结点

最后,我实现了服务发现注册器:

go-kit-sd.go

package greetersd

import (
	"math/rand"
	"os"
	"strconv"
	"time"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/sd"
	consulsd "github.com/go-kit/kit/sd/consul"
	"github.com/hashicorp/consul/api"
)

// ConsulRegister method.
func ConsulRegister(consulAddress string,
	consulPort string,
	advertiseAddress string,
	advertisePort string) (registar sd.Registrar) {

	// Logging domain.
	var logger log.Logger
	{
		logger = log.NewLogfmtLogger(os.Stderr)
		logger = log.With(logger, "ts", log.DefaultTimestampUTC)
		logger = log.With(logger, "caller", log.DefaultCaller)
	}

	rand.Seed(time.Now().UTC().UnixNano())

	// Service discovery domain. In this example we use Consul.
	var client consulsd.Client
	{
		consulConfig := api.DefaultConfig()
		consulConfig.Address = consulAddress + ":" + consulPort
		consulClient, err := api.NewClient(consulConfig)
		if err != nil {
			logger.Log("err", err)
			os.Exit(1)
		}
		client = consulsd.NewClient(consulClient)
	}

	check := api.AgentServiceCheck{
		HTTP:     "http://" + advertiseAddress + ":" + advertisePort + "/health",
		Interval: "10s",
		Timeout:  "1s",
		Notes:    "Basic health checks",
	}

	port, _ := strconv.Atoi(advertisePort)
	num := rand.Intn(100) // to make service ID unique
	asr := api.AgentServiceRegistration{
		ID:      "go-kit-srv-greeter-" + strconv.Itoa(num), //unique service ID
		Name:    "go-kit-srv-greeter",
		Address: advertiseAddress,
		Port:    port,
		Tags:    []string{"go-kit", "greeter"},
		Check:   &check,
	}
	registar = consulsd.NewRegistrar(client, &asr, logger)
	return
}

Go Kit 服务发现注册器

准备好所有构建块后,我将它们合并到启动服务中:

go-kit-service-starter.go

package main

import (
	"flag"
	"fmt"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"text/tabwriter"

	"github.com/antklim/go-microservices/go-kit-greeter/pb"
	"google.golang.org/grpc"

	"github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint"
	"github.com/antklim/go-microservices/go-kit-greeter/pkg/greetersd"
	"github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterservice"
	"github.com/antklim/go-microservices/go-kit-greeter/pkg/greetertransport"

	"github.com/go-kit/kit/log"
	"github.com/oklog/oklog/pkg/group"
)

func main() {
	fs := flag.NewFlagSet("greetersvc", flag.ExitOnError)
	var (
		debugAddr  = fs.String("debug.addr", ":9100", "Debug and metrics listen address")
		consulAddr = fs.String("consul.addr", "", "Consul Address")
		consulPort = fs.String("consul.port", "8500", "Consul Port")
		httpAddr   = fs.String("http.addr", "", "HTTP Listen Address")
		httpPort   = fs.String("http.port", "9110", "HTTP Listen Port")
		grpcAddr   = fs.String("grpc-addr", ":9120", "gRPC listen address")
	)
	fs.Usage = usageFor(fs, os.Args[0]+" [flags]")
	fs.Parse(os.Args[1:])

	var logger log.Logger
	{
		logger = log.NewLogfmtLogger(os.Stderr)
		logger = log.With(logger, "ts", log.DefaultTimestampUTC)
		logger = log.With(logger, "caller", log.DefaultCaller)
	}

	var service greeterservice.Service
	{
		service = greeterservice.GreeterService{}
		service = greeterservice.LoggingMiddleware(logger)(service)
	}

	var (
		endpoints   = greeterendpoint.MakeServerEndpoints(service, logger)
		httpHandler = greetertransport.NewHTTPHandler(endpoints, logger)
		registar    = greetersd.ConsulRegister(*consulAddr, *consulPort, *httpAddr, *httpPort)
		grpcServer  = greetertransport.NewGRPCServer(endpoints, logger)
	)

	var g group.Group
	{
		// The debug listener mounts the http.DefaultServeMux, and serves up
		// stuff like the Go debug and profiling routes, and so on.
		debugListener, err := net.Listen("tcp", *debugAddr)
		if err != nil {
			logger.Log("transport", "debug/HTTP", "during", "Listen", "err", err)
			os.Exit(1)
		}
		g.Add(func() error {
			logger.Log("transport", "debug/HTTP", "addr", *debugAddr)
			return http.Serve(debugListener, http.DefaultServeMux)
		}, func(error) {
			debugListener.Close()
		})
	}
	{
		// The service discovery registration.
		g.Add(func() error {
			logger.Log("transport", "HTTP", "addr", *httpAddr, "port", *httpPort)
			registar.Register()
			return http.ListenAndServe(":"+*httpPort, httpHandler)
		}, func(error) {
			registar.Deregister()
		})
	}
	{
		// The gRPC listener mounts the Go kit gRPC server we created.
		grpcListener, err := net.Listen("tcp", *grpcAddr)
		if err != nil {
			logger.Log("transport", "gRPC", "during", "Listen", "err", err)
			os.Exit(1)
		}
		g.Add(func() error {
			logger.Log("transport", "gRPC", "addr", *grpcAddr)
			baseServer := grpc.NewServer()
			pb.RegisterGreeterServer(baseServer, grpcServer)
			return baseServer.Serve(grpcListener)
		}, func(error) {
			grpcListener.Close()
		})
	}
	{
		// This function just sits and waits for ctrl-C.
		cancelInterrupt := make(chan struct{})
		g.Add(func() error {
			c := make(chan os.Signal, 1)
			signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
			select {
			case sig := <-c:
				return fmt.Errorf("received signal %s", sig)
			case <-cancelInterrupt:
				return nil
			}
		}, func(error) {
			close(cancelInterrupt)
		})
	}
	logger.Log("exit", g.Run())
}

func usageFor(fs *flag.FlagSet, short string) func() {
	return func() {
		fmt.Fprintf(os.Stderr, "USAGE\n")
		fmt.Fprintf(os.Stderr, "  %s\n", short)
		fmt.Fprintf(os.Stderr, "\n")
		fmt.Fprintf(os.Stderr, "FLAGS\n")
		w := tabwriter.NewWriter(os.Stderr, 0, 2, 2, ' ', 0)
		fs.VisitAll(func(f *flag.Flag) {
			fmt.Fprintf(w, "\t-%s %s\t%s\n", f.Name, f.DefValue, f.Usage)
		})
		w.Flush()
		fmt.Fprintf(os.Stderr, "\n")
	}
}

Go Kit 服务启动程序

go-kit-greeter-endpoints-middleware.go

package greeterendpoint

import (
	"context"
	"time"

	"github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/log"
)

// LoggingMiddleware returns an endpoint middleware that logs the
// duration of each invocation, and the resulting error, if any.
func LoggingMiddleware(logger log.Logger) endpoint.Middleware {
	return func(next endpoint.Endpoint) endpoint.Endpoint {
		return func(ctx context.Context, request interface{}) (response interface{}, err error) {
			defer func(begin time.Time) {
				logger.Log("transport_error", err, "took", time.Since(begin))
			}(time.Now())
			return next(ctx, request)
		}
	}
}

Endpoints 层日志记录中间件

Gizmo greeter

我以类似的方式创建了 Gizmo 服务。我为服务、端点、传输和服务发现注册器定义了四个包。

服务实现和服务发现系统注册器与 Go Kit 服务共享相同的代码。但是 endpoints 定义和传输实现必须根据 Gizmo 特性来完成。

gizmo-greeter-endpoints.go

package greeterendpoint

import (
	"net/http"

	ocontext "golang.org/x/net/context"

	"github.com/NYTimes/gizmo/server"
	"github.com/antklim/go-microservices/gizmo-greeter/pkg/greeterservice"
)

// Endpoints collects all of the endpoints that compose a greeter service.
type Endpoints struct {
	HealthEndpoint   server.JSONContextEndpoint
	GreetingEndpoint server.JSONContextEndpoint
}

// MakeServerEndpoints returns service Endoints
func MakeServerEndpoints(s greeterservice.Service) Endpoints {
	healthEndpoint := MakeHealthEndpoint(s)
	greetingEndpoint := MakeGreetingEndpoint(s)

	return Endpoints{
		HealthEndpoint:   healthEndpoint,
		GreetingEndpoint: greetingEndpoint,
	}
}

// MakeHealthEndpoint constructs a Health endpoint.
func MakeHealthEndpoint(s greeterservice.Service) server.JSONContextEndpoint {
	return func(ctx ocontext.Context, r *http.Request) (int, interface{}, error) {
		healthy := s.Health()
		return http.StatusOK, HealthResponse{Healthy: healthy}, nil
	}
}

// MakeGreetingEndpoint constructs a Greeting endpoint.
func MakeGreetingEndpoint(s greeterservice.Service) server.JSONContextEndpoint {
	return func(ctx ocontext.Context, r *http.Request) (int, interface{}, error) {
		vars := r.URL.Query()
		names, exists := vars["name"]
		if !exists || len(names) != 1 {
			return http.StatusBadRequest, errorResponse{Error: "query parameter 'name' required"}, nil
		}
		greeting := s.Greeting(names[0])
		return http.StatusOK, GreetingResponse{Greeting: greeting}, nil
	}
}

// HealthRequest collects the request parameters for the Health method.
type HealthRequest struct{}

// HealthResponse collects the response values for the Health method.
type HealthResponse struct {
	Healthy bool `json:"healthy,omitempty"`
}

// GreetingRequest collects the request parameters for the Greeting method.
type GreetingRequest struct {
	Name string `json:"name,omitempty"`
}

// GreetingResponse collects the response values for the Greeting method.
type GreetingResponse struct {
	Greeting string `json:"greeting,omitempty"`
}

type errorResponse struct {
	Error string `json:"error"`
}

Gizmo greeter endpoints

如您所见,代码片段类似于Go Kit。主要区别在于应返回的接口类型:

gizmo-greeter-http.go

package greetertransport

import (
	"context"

	"github.com/NYTimes/gizmo/server"
	"google.golang.org/grpc"

	"errors"
	"net/http"

	"github.com/NYTimes/gziphandler"
	pb "github.com/antklim/go-microservices/gizmo-greeter/pb"
	"github.com/antklim/go-microservices/gizmo-greeter/pkg/greeterendpoint"
	"github.com/sirupsen/logrus"
)

type (
	// TService will implement server.RPCService and handle all requests to the server.
	TService struct {
		Endpoints greeterendpoint.Endpoints
	}

	// Config is a struct to contain all the needed
	// configuration for our JSONService.
	Config struct {
		Server *server.Config
	}
)

// NewTService will instantiate a RPCService with the given configuration.
func NewTService(cfg *Config, endpoints greeterendpoint.Endpoints) *TService {
	return &TService{Endpoints: endpoints}
}

// Prefix returns the string prefix used for all endpoints within this service.
func (s *TService) Prefix() string {
	return ""
}

// Service provides the TService with a description of the service to serve and
// the implementation.
func (s *TService) Service() (*grpc.ServiceDesc, interface{}) {
	return &pb.Greeter_serviceDesc, s
}

// Middleware provides an http.Handler hook wrapped around all requests.
// In this implementation, we're using a GzipHandler middleware to
// compress our responses.
func (s *TService) Middleware(h http.Handler) http.Handler {
	return gziphandler.GzipHandler(h)
}

// ContextMiddleware provides a server.ContextHAndler hook wrapped around all
// requests. This could be handy if you need to decorate the request context.
func (s *TService) ContextMiddleware(h server.ContextHandler) server.ContextHandler {
	return h
}

// JSONMiddleware provides a JSONEndpoint hook wrapped around all requests.
// In this implementation, we're using it to provide application logging and to check errors
// and provide generic responses.
func (s *TService) JSONMiddleware(j server.JSONContextEndpoint) server.JSONContextEndpoint {
	return func(ctx context.Context, r *http.Request) (int, interface{}, error) {

		status, res, err := j(ctx, r)
		if err != nil {
			server.LogWithFields(r).WithFields(logrus.Fields{
				"error": err,
			}).Error("problems with serving request")
			return http.StatusServiceUnavailable, nil, errors.New("sorry, this service is unavailable")
		}

		server.LogWithFields(r).Info("success!")
		return status, res, nil
	}
}

// ContextEndpoints may be needed if your server has any non-RPC-able
// endpoints. In this case, we have none but still need this method to
// satisfy the server.RPCService interface.
func (s *TService) ContextEndpoints() map[string]map[string]server.ContextHandlerFunc {
	return map[string]map[string]server.ContextHandlerFunc{}
}

// JSONEndpoints is a listing of all endpoints available in the TService.
func (s *TService) JSONEndpoints() map[string]map[string]server.JSONContextEndpoint {
	return map[string]map[string]server.JSONContextEndpoint{
		"/health": map[string]server.JSONContextEndpoint{
			"GET": s.Endpoints.HealthEndpoint,
		},
		"/greeting": map[string]server.JSONContextEndpoint{
			"GET": s.Endpoints.GreetingEndpoint,
		},
	}
}

Gizmo greeter HTTP endpoints

gizmo-greeter-grpc.go


package greetertransport

import (
	pb "github.com/antklim/go-microservices/gizmo-greeter/pb"
	ocontext "golang.org/x/net/context"
)

// Greeting implementation of the gRPC service.
func (s *TService) Greeting(ctx ocontext.Context, r *pb.GreetingRequest) (*pb.GreetingResponse, error) {
	return &pb.GreetingResponse{Greeting: "Hola Gizmo RPC " + r.Name}, nil
}

GIzmo greeter gRPC

Go-Kit 和 Gizmo 之间的显著差异在于运输实现。Gizmo 提供了几种服务类型,您可以使用这些服务类型。我所要做的就是将 HTTP 路径映射到 endpoints 定义。Gizmo 处理了低级 HTTP 请求/响应处理。

结论

Go Micro 是启动微服务系统的最快方式。框架提供了许多特性。所以你不需要再改造轮子。但是这种舒适和速度带来了牺牲-灵活性。更改或更新系统的部件并不像 Go kit 那么简单。并将 gRPC 作为一级通信类型。

你可能需要一些时间熟悉 Go Kit。它需要一个良好的知识,GoLang 的特性和经验在软件架构。另一方面,没有框架限制。所有零件都可以独立更换和更新。

Gizmo 在 Go Micro 和 Go Kit 之间。它提供了一些更高级别的抽象,例如服务包。但是缺乏文档和示例意味着我必须阅读源代码来理解不同的服务类型是如何工作的。使用 Gizmo 比使用 Go Kit 更容易。但它并不像 Go Micro 那么光滑。

今天就这些。谢谢你的阅读。有关详细信息,请查看 microservices 代码存储库。如果您有 Go 和 microservice 框架的任何经验,请在下面的评论中分享。

原文连接:medium.com/seek-blog/m…