翻译进度
20
分块数量
5
参与人数

Go 微服务框架对比:Go Micro, Go Kit, Gizmo, Kite

这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。


Golang

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

下面是我要比较的框架:

kuibatian 翻译于 2周前

框架简介

Go Micro

我认为最流行的框架之一。有很多博客文章和简单的例子。您可以在medium上关注microhq 或 @MicroHQ以获取Go-Micro中的最新更新。

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

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

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

图1. Go Micro 架构

kuibatian 翻译于 2周前

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

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

  • 代理-为异步发布/订阅通信提供消息代理的接口。
  • 编解码器-用于编码/解码消息。支持的格式包括json,bson,protobuf,msgpack等。
  • 注册表-提供服务发现机制(默认为Consul)。
  • 选择器-建立在注册表上的负载平衡抽象。它允许使用诸如随机,轮循,最小康等算法来“选择”服务。
  • 传输-服务之间同步请求/响应通信的接口。
  • Go Micro还提供了Sidecar等功能。这使您可以使用以Go以外的语言编写的服务。 Sidecar提供服务注册,gRPC编码/解码和HTTP处理程序。它支持多种语言。
cheche 翻译于 1周前

Go Kit

Go Kit是一个用于在 Go 中构建微服务的编程工具包。与Go Micro不同,它被设计为一个用于导入二进制包的库。

Go Kit遵循简单的规则,例如:

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

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

  • 认证 - Basic 认证和JWT认证
  • 传输 - HTTP、Nats、gRPC等等。
  • 日志记录 - 用于结构化服务日志记录的通用接口。
  • 指标 - CloudWatch、Statsd、Graphite 等。
  • 追踪 - Zipkin 和 Opentracing。
  • 服务发现 - Consul、Etcd、Eureka 等等。
  • 断路器 - Hystrix 的 Go 实现。
agantihuman 翻译于 1周前

在 Peter Bourgon 的文章和 幻灯片中,你可以找到关于 Go 工具包最好的描述:

此外,在「Go +微服务」幻灯片中,您将看到一个使用 Go Kit 构建的服务架构示例。为了快速入门,这里有一个服务架构图。

图 2。使用 Go Kit 构建的服务架构示例(原始图片在 「Go + 微服务」 幻灯片)

SmauelL 翻译于 1周前

Gizmo

Gizmo is a microservice toolkit from the New York Times. It provides packages to put together server and pubsub daemons. It exposes the following packages:

  • server - offers two server implementations: SimpleServer (over HTTP), RPCServer (over gRPC).
  • server/kit - experimental package based on Go Kit.
  • config - contains functions to configuration from JSON files, JSON blobs in Consul k/v, or environment variables.
  • pubsub - provides generic interfaces for publishing and consuming data from the queues.
  • pubsub/pubsubtest - contains test implementations of the publisher and subscriber interfaces.
  • web - exposes functions for parsing types from request queries and payloads.

Pubsub package provides interfaces to work with the following queues:

So, in my opinion, Gizmo sits somewhere between Go Micro and Go Kit. It’s not a complete “blackbox” like Go Micro. At the same time, it’s not as raw as Go Kit. It provides higher-level building components such as config and pubsub packages.

Kite

Kite is a framework for developing microservices in Go. It exposes RPC Client and Server packages. Created services are automatically registered with a service discovery system Kontrol. Kontrol is written in Kite and it’s a Kite service itself. It means that the Kite microservices work well within its own environment. If you need to connect Kite microservice to another service discovery system it will require customisation. That was the main reason I deleted Kite from the list and decided not to review this framework.

Comparing the frameworks

I’ll compare the frameworks using four categories:

  • Objective comparison - GitHub statistics
  • Documentation and examples
  • Users and community
  • Code quality.

GitHub statistics

Table 1. Go microservices frameworks statistics (collected in April 2018)

Documentation and examples

Well, simply speaking, none of the frameworks provide solid documentation. Usually, the only formal documentation is the readme on the front page of the repo.

For Go Micro lots of information and announcements are available at micro.mumicrohq, and social media @MicroHQ.

In case of Go Kit the best documentation you will find is in Peter Bourgon’s blog. And one of the best examples I found was in the ru-rocker blog.

With Gizmo, the source code provides the best documentation and examples.

To sum up, if you come from the NodeJS world and expect to see ExpressJS-like tutorials you will be disappointed. On the other hand, it’s a great opportunity to create your own tutorial.

用户和社区

根据GitHub统计数据,Go Kit是最受欢迎的微服务框架——在这篇文章发布前超过10k颗star。它有很多贡献者(122人)和1000多个 fork。最后,Go Kit得到了DigitalOcean 的支持。

拥有3600多颗star、27个贡献者和385个fork的 Go Micro 位居第二。Go Micro的最大赞助商之一是Sixt

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

代码质量

Go Kit在代码质量类别中排名第一。它拥有几乎80%的代码覆盖率和出色的Go评级报告 报告评级。Gizmo 的Go评级报告 也很高。但是它的代码覆盖率只有46%。Go Micro不提供覆盖信息,但它的Go评级报告很高。

agantihuman 翻译于 1周前

Coding microservices

Alright, enough theory. To better understand the frameworks, I’ve created three simple microservices.

Figure 3. Practical example architecture

These are the services which implement one business function - greeting. When a user passes a ‘name’ parameter to a service, then the service sends a greeting response. Also, all services meet the following requirements:

  • Service should self register with the Service Discovery system.
  • Service should have health check endpoint.
  • Service should support at least HTTP and gRPC transports.

For those of you who prefer to read the source code. You can stop here and read the source code in the repository.

Go Micro greeter

The first thing you need to do to create a service with Go Micro is to define the protobuf description. Going forward, the same protobuf definition was used in all three services. I created the following service description:

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

The interface consists of one method - ‘Greeting’. There’s one parameter in a request - ‘name’, and one parameter in a response - ‘greeting’.

Then I used modified protoc to generate service interfaces from the protobuf. The generator was forked by Go Micro and modified to support some of the framework’s features. I wired this all together in the ‘greeter’ service. At that point, the service was starting and registering with the service discovery system. It only supported gRPC transport protocol:

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

To support HTTP transport I had to add the other module. It mapped HTTP request to protobuf defined request. And called gRPC service. Then it mapped service response to HTTP response and replied it to a user.

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

Quite simple and straightforward. Many things were handled by Go Micro behind the scenes - such as registration in the service discovery system. On the other hand, it’s hard to create a pure HTTP service.

Go Kit greeter

完成Go Micro后,我接着写Go Kit服务的实现。我花了很多时间通读Go Kit 仓库中提供的示例代码。理解端点 Endpoint 的概念花了我很多时间。下一个花了我很多时间的难题是怎么去写服务发现注册器。我找到一个好的例子才把它实现出来了。 example.

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

  • 服务逻辑实现。
  • 与传输无关的服务端点。
  • 与传输特定的端点(gRPC, HTTP)
  • 服务发现注册器

go-kit-greeter-service.go

package greeterservice

// Service 描述了 greetings 这个服务
type Service interface {
    Health() bool
    Greeting(name string) string
}

// GreeterService  是 Service 接口的实现
type GreeterService struct{}

// Service 的 Health 接口实现
func (GreeterService) Health() bool {
    return true
}

// Service 的 Greeting 接口实现
func (GreeterService) Greeting(name string) (greeting string) {
    greeting = "GO-KIT Hello " + name
    return
}
agantihuman 翻译于 1周前

如您所见,代码没有任何依赖关系。它只是实现逻辑。下面的代码片段是端点Endpoint的定义:

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

type (
    // Endpoints 收集构成 Greeting Service 的所有端点。
    // 它应该被用作助手 struct ,将所有端点收集到一个参数中。
    Endpoints struct {
        // Consul 用这个端点做健康检查(Health Check)
        HealthEndpoint   endpoint.Endpoint
        GreetingEndpoint endpoint.Endpoint
    }
    HealthRequest struct {
    }
    HealthResponse struct {
        Healthy bool  `json:"health,omitempty"`
        Err     error `json:"err,omitempty"`
    }
    GreetingRequest struct {
        Name string `json:"name,omitempty"`
    }
    GreetingResponse struct {
        Greeting string `json:"greeting,omitempty"`
        Err      error  `json:"err,omitempty"`
    }
    // Failer是一个应该由响应类型实现的接口。
    // 响应编码器可以检查响应是否是一个 Failer。
    // 如果响应是一个 Failer, 那么就说明响应已经失败了,根据错误使用单独的写入路径对响应进行编码。
    Failer interface {
        Failed() error
    }
)

// MakeServiceEndpoints 返回服务端点, 将所有已提供的中间件连接起来
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,
    }
}

// MakeHealthEndpoints 构造一个 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
    }
}

// MakeGreetingEndpoints 构造一个 Greeter 端点,将 Greeting 服务包装为一个端点
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.Failed 接口
func (r HealthResponse) Failed() error {
    return r.Err
}

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

定义服务和端点后,我们接下来通过不同的传输协议暴露端点。我从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。
    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         获取服务健康信息
    // GET /greeting?name  获取 greeting

    // NewServer 方法需要端点,解码器,编码器作为参数
    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
}

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

// 解码 Health HTTP 请求的方法
func DecodeHTTPHealthRequest(_ context.Context, _ *http.Request) (interface{}, error) {
    return greeterendpoint.HealthRequest{}, nil
}

// 解码 Greeting HTTP 请求的方法
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
}

// errorWrapper 将 error 封装为一个 json 结构体方便转换为 json
type errorWrapper struct {
    Error string `json:"error"`
}

// 编码错误的方法
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
    w.WriteHeader(err2code(err))
    json.NewEncoder(w).Encode(errorWrapper{Error: err.Error()})
}

// err2code 函数将 error 转换为对应的 http 状态码
func err2code(err error) int {
    switch err {
    default:
        return http.StatusInternalServerError
    }
}

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

go-greeter-gen.sh

#!/usr/bin/env sh

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

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 使一组端点可用作 gRPC Greeting 服务器。
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...)}
}

// encodeGRPCGreetingResponse 是一个 transport/grpc.EncodeResponseFunc 将用户域
// 问Greeting响应转换为 gRPC Greeting 响应。
func encodeGRPCGreetingResponse(i context.Context, i2 interface{}) (response interface{}, err error) {
    res := response.(greeterendpoint.GreetingResponse)
    return &pb.GreetingResponse{Greeting: res.Greeting}, nil
}

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

// 实现 GreeterService.Greeting 接口
func (s *grpcServer) Greeting(ctx context.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
}
agantihuman 翻译于 1周前

Finally, I implemented service discovery registrar:

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
}

After all building blocks were prepared, I wired them together in service starter:

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

As you may have noticed, I used logging middleware in a couple of places. It allowed me to decouple logging logic from the main service/endpoints workflow.

go-kit-greeter-service-middleware.go

package greeterservice

import (
    "time"

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

// ServiceMiddleware describes a service middleware.
type ServiceMiddleware func(Service) Service

// LoggingMiddleware takes a logger as a dependency and returns a 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
}

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

Gizmo greeter

I created the Gizmo service in a similar way to Go Kit. I defined four packages for service, endpoints, transport, and service discovery registrar.

Service implementation and service discovery system registrar shared the same code with the Go Kit service. But endpoints definition and transport implementation had to be done according to Gizmo features.

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

As you can see, the code snippet is similar to Go Kit. The main difference is in the interface type which should be returned:

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

The noticeable difference between Go Kit and Gizmo was in transport implementation. Gizmo provides several Service types which you can utilise. All I had to do was to map HTTP paths to endpoints definition. The low-level HTTP request/response processing was handled by Gizmo.

结论

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

您可能需要一些时间来熟悉Go Kit。 它需要你具备 Go 功能的丰富知识和软件架构方面的经验。 灵活是他的另一个优势,没有框架限制, 所有部件均可独立更改和更新。

cheche 翻译于 1周前

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


今天就这些了。 谢谢阅读。 请查看微服务 code repository如果您对Go和微服务框架有任何经验,请在下面的评论中分享。

cheche 翻译于 1周前

本文章首发在 LearnKu.com 网站上。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://medium.com/seek-blog/microservic...

译文地址:https://learnku.com/golang/t/36973

参与译者:5
讨论数量: 1
yourself

go kit 严格上其实算不上框架,更多是提供了一个思想。

3天前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!