微服务框架对比

Medium原文点这里。

对以下四个框架进行比较:

框架介绍

Go Micro

最受欢迎的框架之一,网上有很多的博客、文章,还有很多使用示例。可以在Medium上关注microhq,来获得Go Micro中的最新更新。

Go Micro是使用Go语言编写微服务的可插拔RPC框架,它开箱即用,有如下功能:

  1. 服务发现:应用程序自动在服务发现系统中注册
  2. 负载均衡:客户端侧负载均衡,用于平衡服务实例之间的请求
  3. 同步通信:传输层提供请求/响应
  4. 异步通信:内置发布订阅功能
  5. 消息编码:基于消息的Content-Type标头的编码/解码
  6. RPC客户端和服务端包:利用上述功能并暴露接口以构建微服务

Go Micro架构可以描述为如下所示的三层堆栈:

image

顶层由客户端-服务器模型和服务抽象组成。服务器(Server)是用于编写服务的构建块,客户端(Client)提供了向服务请求的接口。

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

  • 代理:为异步发布/订阅通信提供消息代理的接口
  • 编解码器:用于编码/解码消息,支持的格式包括jsonbsonprotobufmsgpack
  • 注册中心:提供服务发现机制(默认为Consul
  • 选择器:建立在注册中心上的负载均衡抽象,允许使用诸如随机,轮循,最少连接等算法来“选择”服务
  • 传输:服务之间同步请求/响应通信的接口

Go Micro还提供了Sidecar等功能。这就可以使用以Go以外的语言编写的服务。Sidecar提供服务注册,gRPC编码/解码和HTTP处理程序。它支持多种语言。

Go Kit

Go Kit是用于在Go中构建微服务的编程工具包。与Go Micro不同,它是一个旨在导入二进制软件包的库。Go Kit遵循简单原则,例如:

  • 没有全局状态
  • 声明式组成
  • 显式依赖
  • 合约式接口
  • 领域驱动设计

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

  1. 身份验证:basicJWT
  2. 数据传输:HTTP、Nats(开源消息传递系统)、gRPC等
  3. 日志记录:通用接口用于结构化服务的日志记录
  4. 指标监控:CloudWatchStatsdGraphite
  5. 链路追踪: ZipkinOpentracing
  6. 服务发现:ConsulEtcdEureka
  7. 断路器:使用Go语言实现的Hystrix

Peter Bourgon的文章和演示幻灯片是对Go Kit最好的描述之一:

在“ Go + microservices”幻灯片中,可以找到使用Go Kit构建的服务体系结构的示例。为了快速参考,服务架构图如下所示:

image

Gizmo

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

  • server:提供两种服务实现方式:
    • 简单服务(基于HTTP)
    • RPC服务(基于gRPC)
  • server/kit:基于Go Kit的实验性软件包
  • config:从以下位置获取配置参数的配置函数:
    • JSON文件,
    • Consul k/v中的JSON blob
    • 环境变量
  • pubsub:提供用于发布和订阅队列中数据的通用接口
  • pubsub/pubsubtest:包含发布者和订阅者接口的测试实现
  • web:暴露用于从请求查询和有效负载中解析类型的函数

Pubsub软件包提供了与以下队列一起使用的接口:

因此,可以认为Gizmo位于Go Micro和Go Kit之间。它不是像Go Micro这样的完整“黑箱”。同时,它不像Go Kit那样原始。它提供了更高级别的构建组件,例如config和pubsub软件包。

Kite

Kite是用于在Go中开发微服务的框架。它公开了RPC客户端和服务器程序包。创建的服务会自动在服务发现系统Kontrol中注册。 Kontrol是用Kite编写的,它本身就是Kite服务。这意味着Kite微服务在其自己的环境中运行良好。如果需要将Kite微服务连接到另一个服务发现系统,则需要自定义。这是从列表中删除Kite并决定不审查此框架的主要原因。

框架对比

使用四个类别来比较框架:

  • 目标比较:基于GitHub统计
  • 文档和示例
  • 用户和社区
  • 代码质量

Github统计

时间:2019/10/28

Go MicroGo KitGizmo
LicenseApache License 2.0MITApache License 2.0
CreatedJan 2015Feb 2015Dec 2015
Release60948
AuthorAsim AslamPeter BourgonJP Robinson
Stars9484151902937
Open/Closed Issues35/40056/37012/52
Last UpdateOct 26, 2019Oct 18, 2019Oct 14, 2019

文档和示例

Go Micro和Go kit 提供了一个主页,其中包含正式的文档和示例。Gizmo框架没有提供可靠的文档,唯一的正式文档是仓库中的README文件。

有关Go Micro的大量信息和公告,请访问micro.mumicrohq和社交媒体(https://twitter.com/MicroHQ)。

如果使用Go Kit,最好的文档可在Peter Bourgon博客中找到。我发现的最好的例子之一是在ru-rocker博客中。

使用Gizmo,源代码提供了最佳的文档和示例。

综上所述,如果您来自NodeJS领域,并希望看到类似ExpressJS的教程,您将感到失望。另一方面,这是创建自己的教程的绝佳机会。

用户和社区

Go Kit是最流行的微服务框架,基于GitHub统计数据,它已超过1万五千颗星。它有大量的贡献者(161)和超过1600个分叉。最后,Go Kit受DigitalOcean支持。

拥有9484颗星,54个贡献者和993个分叉-Go Micro位居第二。 Sixt是Go Micro的最大赞助商之一。

Gizmo第三名。超过2937星,41个贡献者和188个分叉。由《纽约时报》支持和创建。

代码质量

Go Kit在代码质量类别中排名第一。它具有近80%的代码覆盖率,并且具有出色的Go报告评级。Gizmo也具有出色的Go报告评级。但是它的代码覆盖率只有46%。Go Micro不提供覆盖范围信息,但确实具有很高的Go报告评级

代码质量评级网站

编写微服务

为了更好地了解框架,创建了三个简单的微服务,如下图所示:

image

这些服务都实现了一个业务逻辑:打招呼(greeting),当用户向服务中输入“名称”参数时,服务会发送问候语作为响应。所有服务都满足以下需求:

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

对于喜欢阅读源代码的人,可以在这里访问仓库源代码。

GomMicro greeter

使用Go Micro创建服务所需要做的第一件事是定义protobuf描述。所有这三种服务都使用了相同的protobuf定义。创建了以下服务描述:

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中生成服务接口,该proto生成器是Go Micro的一个分叉,并经过修改以支持该框架的某些功能。在打招呼服务中将所有这些连接在一起,此时,服务正在启动并在服务发现系统中注册,它仅支持gRPC传输协议。

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)
    }
}

为了支持HTTP传输,必须添加其他模块。它将HTTP请求映射到protobuf定义的请求,并调用gRPC服务。然后,它将服务响应映射到HTTP响应,并将其响应返回给用户。

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在后台处理了许多事情,例如在服务发现系统中进行注册。另一方面,很难创建纯HTTP服务。

Go Kit greeter

在完成Go Micro之后,继续进行Go Kit服务的实现。我花了很多时间阅读Go Kit存储库中提供的代码示例。了解端点的概念花了很多时间。下一个耗时的难题是服务发现注册中心代码。在找到一个很好的例子之前,我无法实现它。

最后,创建了四个包用于:

  1. 服务逻辑实现
  2. 传输不可知服务端点
  3. 传输特定端点(gRPC、HTTP)
  4. 服务发现注册中心

服务逻辑实现如下所示,代码没有任何依赖关系,它只是实现逻辑。

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
}

下一段代码显示了端点定义:

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"
)

//  端点集合收集打招呼服务的所有端点,它旨在用作辅助结构将所有端点收集到单个参数
type Endpoints struct {
    HealthEndpoint   endpoint.Endpoint // used by Consul for the healthcheck
    GreetingEndpoint endpoint.Endpoint
}

// MakeServerEndpoints返回服务端点,并在所有提供的中间件中进行连线
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构造Health端点来包装服务
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
    }
}

//  MakeHealthEndpoint构造Greeter端点来包装服务
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是应该由响应类型实现的接口
// 响应编码器可以检查响应是否为Failer,如果是Failer,
// 判断是否失败,或者是否基于错误使用单独的写入路径对响应进行编码
type Failer interface {
    Failed() error
}

// HealthRequest收集Health方法的请求参数
type HealthRequest struct{}

// HealthResponse收集Health方法的响应值
type HealthResponse struct {
    Healthy bool  `json:"healthy,omitempty"`
    Err     error `json:"err,omitempty"`
}

// Failed实现Failer接口
func (r HealthResponse) Failed() error { return r.Err }

// GreetingRequest收集Greeting方法的请求参数
type GreetingRequest struct {
    Name string `json:"name,omitempty"`
}

// GreetingResponse收集Greeting方法的响应值
type GreetingResponse struct {
    Greeting string `json:"greeting,omitempty"`
    Err      error  `json:"err,omitempty"`
}

// Failed实现Failer接口
func (r GreetingResponse) Failed() error { return r.Err }

定义服务和端点后,通过不同的传输协议暴露端点。从HTTP传输开始:

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
    ErrBadRouting = errors.New("inconsistent mapping between route and handler")
)

// NewHTTPHandler返回一个HTTP处理程序,该处理程序使一组端点在预定义路径上可用
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是`transport/http.EncodeResponseFunc`,它将响应编码为JSON编码到响应编写器
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)
}

在开始gRPC端点实现之前,不需要protobuf定义。直接复制上面Go Micro服务的protobuf定义。但是对于Go Kit,使用默认的服务生成器来创建服务接口。

protoc greeter.proto --go_out=plugins=grpc:.
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使一组端点可用作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实现GreeterService接口的方法
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
}

// encodeGRPCGreetingRequest是一个`transport/grpc.DecodeRequestFunc`,它将gRPC问候请求转换为用户域问候请求
func decodeGRPCGreetingRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
    req := grpcReq.(*pb.GreetingRequest)
    return greeterendpoint.GreetingRequest{Name: req.Name}, nil
}


// encodeGRPCGreetingResponse是`transport/grpc.EncodeResponseFunc`,它将用户域问候响应转换为gRPC问候响应
func encodeGRPCGreetingResponse(_ context.Context, response interface{}) (interface{}, error) {
    res := response.(greeterendpoint.GreetingResponse)
    return &pb.GreetingResponse{Greeting: res.Greeting}, nil
}

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

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
}

准备好所有构建块之后,我将它们连接到服务启动器中:

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")
    }
}

在几个地方使用了日志记录中间件。它使我们能够将日志记录逻辑与主要服务/端点工作流分离。

package greeterservice

import (
    "time"

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

// ServiceMiddleware描述了服务中间件
type ServiceMiddleware func(Service) Service

// LoggingMiddleware将记录器作为依赖项,并返回ServiceMiddleware。
func LoggingMiddleware(logger log.Logger) ServiceMiddleware {
    return func(next Service) Service {
        return loggingMiddleware{next, logger}
    }
}

type loggingMiddleware struct {
    Service
    logger log.Logger
}

func (m loggingMiddleware) Health() (healthy bool) {
    defer func(begin time.Time) {
        m.logger.Log(
            "method", "Health",
            "healthy", healthy,
            "took", time.Since(begin),
        )
    }(time.Now())
    healthy = m.Service.Health()
    return
}

func (m loggingMiddleware) Greeting(name string) (greeting string) {
    defer func(begin time.Time) {
        m.logger.Log(
            "method", "Greeting",
            "name", name,
            "greeting", greeting,
            "took", time.Since(begin),
        )
    }(time.Now())
    greeting = m.Service.Greeting(name)
    return
}
package greeterendpoint

import (
    "context"
    "time"

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

// LoggingMiddleware返回一个端点中间件,该中间件记录每次调用的持续时间以及所产生的错误(如果有)
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)
        }
    }
}

Gizmo greeter

以与Go Kit类似的方式创建了Gizmo服务。为服务,端点,传输和服务发现注册器定义了四个包。服务实现和服务发现系统注册中心与Go Kit服务共享相同的代码。但是端点定义和传输实现必须根据Gizmo的功能来完成。

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"`
}

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

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,
        },
    }
}
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
}

Go Kit和Gizmo之间的明显区别在于传输方式。 Gizmo提供了几种可以利用的服务类型。要做的就是将HTTP路径映射到端点定义。低级HTTP请求/响应处理由Gizmo处理。

总结

Go Micro是启动微服务系统的最快方法。框架提供了许多功能。因此,无需重新发明轮子。但是,这种舒适性和速度会牺牲灵活性。更改或更新系统部件并不像Go Kit那样容易。并且将gRPC强制为一等的通信类型。

可能需要一些时间来熟悉Go Kit。它需要具备Go语言特性的丰富知识和软件架构方面的经验。另一方面,没有框架限制。所有部件均可独立更改和更新。

Gizmo位于Go Micro和Go Kit之间。它提供了一些更高级别的抽象,例如Service包。但是缺少文档和示例,这意味着不得不通读源代码以了解不同服务类型的工作方式。使用Gizmo比使用Go Kit容易。但是它不像Go Micro那样流畅。

上次修改: 14 April 2020