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

下面是我要比较的框架:

  • Go Micro[1]
  • Go Kit[2]
  • Gizmo[3]
  • Kite[4]

框架简介

Go Micro

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

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

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

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

图1. Go Micro 架构

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

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

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

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 实现。

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

  • Go kit: 在现代企业中使用 Go[7]
  • Go + 微服务[8]

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

img

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

Gizmo

Gizmo 是《纽约时报》开源的一个微服务工具包。它提供了将服务器和 pubsub 组合在一起的包。

功能如下:

  • server[10] - 提供两种服务器实现:SimpleServer(over HTTP),RPCServer(在 gRPC 上)。
  • server/kit[11] - 基于 Go-kit 的包,目前试验阶段。
  • config[12] - 包含配置 JSON 文件的函数,Consul k/v 中的 JSON blob,或环境变量。
  • pubsub[13] - 提供用于发布和使用队列中数据的通用接口。
  • pubsub/pubsubtest[14] - 包含发布者和订阅者接口的测试实现。
  • web[15] - 公开了解析请求查询和有效负载类型的函数。

Pubsub 包提供了与以下驱动:

  • pubsub/aws[16] - Amazon SNS/SQS.
  • pubsub/gcp[17] - Google Pubsub.
  • pubsub/kafka[18] - Kafka .
  • pubsub/http[19] - HTTP 队列

在我看来,Gizmo 的使用场景在于 Go Micro 和 Go Kit 中间。它不像 Go Micro 那样是一个完整的『黑匣子』。同时,它也不像 Go Kit 那样原始。它提供更高级的构建组件,如 configpubsub 包。

Kite

Kite 是一个在 Go 微服务框架。它公开了 RPC 客户端和服务器包。创建的服务将自动注册到服务发现系统 Kontrol 中。Kontrol 使用 Kite 构建的,它本身就是一种 Kite 服务。这意味着 Kite 微服务可以在自己的环境中正常工作。如果你需要连接 Kite 微服务到另一个服务发现系统,它将需要定制。这是我不看好此框架的主要原因。

框架对比

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

  • GitHub 统计
  • 文档和示例
  • 用户和社区
  • 代码品质

GitHub statistics

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

文档和代码示例

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

对 Go Micro 来说很多信息和公告可以在 micro.mu[20] ,microhq[21] 看到,还有 @MicroHQ[22] 作为他们的公共媒体。

对 Go Kit 来说最好的文档可以在 Peter Bourgon’s blog[23] 找到。最好的示例代码之一可以在 ru-rocker blog[24] 找到。

如果是 Gizmo 的话,它的源码提供了最好的文档和代码示例。

综上所述,如果你是 NodeJS 的忠实用户,期望看到和 ExpressJS 类似的教程,那你可能要失望了,但是从另一方面来说,这是一个你编写自己教程的好机会。

用户和社区

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

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

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

代码质量

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

微框架示例

好啦,理论的东西讲的差不多了,接下来开始编码。为了更好地理解框架,我创建了三个简单的微服务:

img

图 3. 实际示例架构

这些是实现一个问候语的服务。当用户将 name 参数传递给服务时,该服务将发送一个问候回应。此外,所有服务都满足以下要求:

  • 服务应在服务发现系统中自动发现
  • 服务应带有运行状况检查的 endpoint
  • 服务应至少支持 HTTP 和 gRPC 传输

对于喜欢阅读源代码的用户。您可以在这里停下来阅读 托管在 GitHub 上的源码[30]

Go 微框架问候器

使用 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[31] 从 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 实现问候服务。type Greeter struct{}

// 问候方法的实现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 响应并回复给用户。

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 在幕后处理的,例如在服务发现系统中注册。如果你自己创建一个纯净的 HTTP 服务器,这些开发起来还是挺费劲的 。

Go Kit greeter

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

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

  • 服务逻辑实现。
  • 与传输无关的服务端点。
  • 与传输特定的端点(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}

如您所见,代码没有任何依赖关系。它只是实现逻辑。下面的代码片段是端点 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 结构体方便转换为 jsontype 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}

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

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 方法
func ConsulRegister(consulAddress string,
    consulPort string,
    advertiseAddress string,
    advertisePort string) (registar sd.Registrar) {

// 日志相关
    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())

// 服务发现域。在本例中,我们使用 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-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')    }}

如你所见,我在几个地方使用了日志中间件。它允许我将日志逻辑与 service/endpoints 工作流区分开。

go-kit-greeter-service-middleware.go

package greeterservice

import (
    'time'

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

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

// LoggingMiddleware 将 logger 作为依赖项并返回 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')

// 日志中间件返回 endpoint 中间件,记录每次调用的持续时间,// 以及产生的错误(如果有)。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 服务共享相同的代码。但是 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'
)

// 收集组成 greeter 的所有 endpoints
type Endpoints struct {
    HealthEndpoint   server.JSONContextEndpoint
    GreetingEndpoint server.JSONContextEndpoint
}

// MakeServerEndpoints 返回服务 Endoints
func MakeServerEndpoints(s greeterservice.Service) Endpoints {
    healthEndpoint := MakeHealthEndpoint(s)
    greetingEndpoint := MakeGreetingEndpoint(s)

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

// MakeHealthEndpoint 构建一个健康监控 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 构建一个问候的 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 收集 Health 方法的请求参数
type HealthRequest struct{}

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

// GreetingRequest 收集问候语方法的请求参数
type GreetingRequest struct {
    Name string `json:'name,omitempty'`
}

// GreetingResponse 收集问候语方法的响应值
type GreetingResponse struct {
    Greeting string `json:'greeting,omitempty'`
}

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

如您所见,代码类似于 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 将实现 server.RPCService 并处理对服务器的所有请求。    TService struct {        Endpoints greeterendpoint.Endpoints    }

    // Config 包含 JSONService 所需的所有配置的结构。    Config struct {        Server *server.Config    })

// NewTService 将用给定配置实例化 RPCServicefunc NewTService(cfg *Config, endpoints greeterendpoint.Endpoints) *TService {    return &TService{Endpoints: endpoints}}

// Prefix 返回此服务中所有端点使用的字符串前缀func (s *TService) Prefix() string {    return ''}

// Service 向 TService 提供要服务的描述和实现func (s *TService) Service() (*grpc.ServiceDesc, interface{}) {    return &pb.Greeter_serviceDesc, s}

// 中间件提供了一个 http.Handler 钩子,它包装了所有请求。// 在这个实现中,我们使用 GzipHandler 中间件来压缩我们的响应。func (s *TService) Middleware(h http.Handler) http.Handler {    return gziphandler.GzipHandler(h)}

// ContextMiddleware 为所有请求提供一个 server.ContextHAndler 钩子。// 如果需要修饰请求上下文,这可能很方便。func (s *TService) ContextMiddleware(h server.ContextHandler) server.ContextHandler {    return h}

// JSONMiddleware 为所有请求提供一个 JSONEndpoint 钩子。//  我们使用它来提供日志记录和检查错误,并提供一般响应。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
}

Go-Kit 与 Gizmo 只要区别在于传输实现。Gizmo 提供了几种您可以使用的服务类型。我所要做的就是将 HTT P 路径映射到端点定义。底层 HTTP 请求 / 响应处理由 Gizmo 处理。

结论

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

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

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


今天就这些了。谢谢阅读。请查看微服务 code repository[33] 如果您对 Go 和微服务框架有任何经验,请留言分享。

原文地址:https://medium.com/seek-blog/microservices-in-go-2fc1570f6800

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

参考

[1]

Go Micro: https://micro.mu/

[2]

Go Kit: https://gokit.io/

[3]

Gizmo: https://github.com/NYTimes/gizmo

[4]

Kite: https://github.com/koding/kite

[5]

microhq: https://medium.com/microhq

[6]

@MicroHQ: https://twitter.com/MicroHQ

[7]

Go kit: 在现代企业中使用 Go: https://peter.bourgon.org/go-kit/

[8]

Go + 微服务: https://github.com/peterbourgon/go-microservices

[9]

Go + 微服务: https://github.com/peterbourgon/go-microservices

[10]

server: https://godoc.org/github.com/NYTimes/gizmo/server

[11]

server/kit: https://godoc.org/github.com/NYTimes/gizmo/server/kit

[12]

config: https://godoc.org/github.com/NYTimes/gizmo/config

[13]

pubsub: https://godoc.org/github.com/NYTimes/gizmo/pubsub

[14]

pubsub/pubsubtest: https://godoc.org/github.com/NYTimes/gizmo/pubsub/pubsubtest

[15]

web: https://godoc.org/github.com/NYTimes/gizmo/web

[16]

pubsub/aws: https://godoc.org/github.com/NYTimes/gizmo/pubsub/aws

[17]

pubsub/gcp: https://godoc.org/github.com/NYTimes/gizmo/pubsub/gcp

[18]

pubsub/kafka: https://godoc.org/github.com/NYTimes/gizmo/pubsub/kafka

[19]

pubsub/http: https://godoc.org/github.com/NYTimes/gizmo/pubsub/http

[20]

micro.mu: https://micro.mu/

[21]

microhq: https://medium.com/microhq

[22]

@MicroHQ: https://twitter.com/MicroHQ

[23]

Peter Bourgon’s blog: https://peter.bourgon.org/go-kit/

[24]

ru-rocker blog: http://www.ru-rocker.com/2017/04/17/micro-services-using-go-kit-service-discovery/

[25]

DigitalOcean: https://www.digitalocean.com/

[26]

Sixt: https://www.sixt.com/

[27]

Go 评级报告: https://goreportcard.com/report/github.com/go-kit/kit

[28]

Go 评级报告: https://goreportcard.com/report/github.com/NYTimes/gizmo

[29]

Go 评级报告: https://goreportcard.com/report/github.com/micro/go-micro

[30]

托管在 GitHub 上的源码: https://github.com/antklim/go-microservices

[31]

protoc: https://github.com/micro/protoc-gen-micro

[32]

example: http://www.ru-rocker.com/2017/04/17/micro-services-using-go-kit-service-discovery/

[33]

code repository: https://github.com/antklim/go-microservices


(0)

相关推荐