Go 微服务框架对比:Go Micro, Go Kit, Gizmo, Kite - 图1
我最近在墨尔本 Golang 会议上做了一个关于如何开发微服务和框架的演讲。在这篇文章中,我将与你分享我的知识(另外,这对我也是一个很好的提醒)。
下面是我要比较的框架:

  • Go Micro
  • Go Kit
  • Gizmo
  • Kite

框架简介


Go Micro


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

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

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

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

图 1. Go Micro 架构

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

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

  • 代理 - 为异步发布 / 订阅通信提供消息代理的接口。

  • 编解码器 - 用于编码 / 解码消息。支持的格式包括 jsonbsonprotobufmsgpack 等。

  • 注册表 - 提供服务发现机制(默认为 Consul)。

  • 选择器 - 建立在注册表上的负载平衡抽象。它允许使用诸如随机,轮循,最小康等算法来 “选择” 服务。

  • 传输 - 服务之间同步请求 / 响应通信的接口。

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

Go Kit


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

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

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

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

  • 认证 - Basic 认证和 JWT 认证
  • 传输 - HTTPNatsgRPC 等等。
  • 日志记录 - 用于结构化服务日志记录的通用接口。
  • 指标 - CloudWatchStatsdGraphite 等。
  • 追踪 - Zipkin Opentracing
  • 服务发现 - ConsulEtcdEureka 等等。
  • 断路器 - Hystrix Go 实现。

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

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

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

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

Gizmo


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

功能如下:

  • server - 提供两种服务器实现:SimpleServerover HTTP),RPCServer(在 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 那样原始。它提供更高级的构建组件,如 configpubsub包。

Kite


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

框架对比

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

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

GitHub statistics


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

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

文档和代码示例


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

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

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

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

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

用户和社区


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

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

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

代码质量


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

微框架示例


好啦,理论的东西讲的差不多了,接下来开始编码。为了更好地理解框架,我创建了三个简单的微服务:
Go 微服务框架对比:Go Micro, Go Kit, Gizmo, Kite - 图5

图 3. 实际示例架构

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

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

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

Go 微框架问候器


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

go-micro-greeter.proto

  1. syntax = "proto3";
  2. package pb;
  3. service Greeter {
  4. rpc Greeting(GreetingRequest) returns (GreetingResponse) {}
  5. }
  6. message GreetingRequest {
  7. string name = 1;
  8. }
  9. message GreetingResponse {
  10. string greeting = 2;
  11. }

接口定义了一个方法 Greeting。请求中有一个参数 name,响应中有一个参数 -greeting

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

go-micro-greeter-grpc-main.go

  1. package main
  2. import (
  3. pb "github.com/antklim/go-microservices/go-micro-greeter/pb"
  4. "github.com/micro/go-micro"
  5. "golang.org/x/net/context"
  6. "log"
  7. )
  8. // greeter 实现问候服务。
  9. type Greeter struct{}
  10. // 问候方法的实现
  11. func (g *Greeter) Greeting(ctx context.Context, in *pb.GreetingRequest, out *pb.GreetingResponse) error {
  12. out.Greeting = "GO-MICRO Hello " + in.Name
  13. return nil
  14. }
  15. func main() {
  16. service := micro.NewService(
  17. micro.Name("go-micro-srv-greeter"),
  18. micro.Version("latest"),
  19. )
  20. service.Init()
  21. pb.RegisterGreeterHandler(service.Server(), new(Greeter))
  22. if err := service.Run(); err != nil {
  23. log.Fatal(err)
  24. }
  25. }

要支持 HTTP 传输,我必须添加另一个模块。它将 HTTP 请求映射到 protobuf 定义的请求,并调用 gRPC 服务。然后它将服务响应映射到 HTTP 响应并回复给用户。

go-micro-greeter-http-main.go

  1. package main
  2. import (
  3. "context"
  4. "encoding/json"
  5. "log"
  6. "net/http"
  7. proto "github.com/antklim/go-microservices/go-micro-greeter/pb"
  8. "github.com/micro/go-micro/client"
  9. web "github.com/micro/go-web"
  10. )
  11. func main() {
  12. service := web.NewService(
  13. web.Name("go-micro-web-greeter"),
  14. )
  15. service.HandleFunc("/greeting", func(w http.ResponseWriter, r *http.Request) {
  16. if r.Method == "GET" {
  17. var name string
  18. vars := r.URL.Query()
  19. names, exists := vars["name"]
  20. if !exists || len(names) != 1 {
  21. name = ""
  22. } else {
  23. name = names[0]
  24. }
  25. cl := proto.NewGreeterClient("go-micro-srv-greeter", client.DefaultClient)
  26. rsp, err := cl.Greeting(context.Background(), &proto.GreetingRequest{Name: name})
  27. if err != nil {
  28. http.Error(w, err.Error(), 500)
  29. return
  30. }
  31. js, err := json.Marshal(rsp)
  32. if err != nil {
  33. http.Error(w, err.Error(), http.StatusInternalServerError)
  34. return
  35. }
  36. w.Header().Set("Content-Type", "application/json")
  37. w.Write(js)
  38. return
  39. }
  40. })
  41. if err := service.Init(); err != nil {
  42. log.Fatal(err)
  43. }
  44. if err := service.Run(); err != nil {
  45. log.Fatal(err)
  46. }
  47. }

非常简单和直接。很多事情都是由 Go Micro 在幕后处理的,例如在服务发现系统中注册。如果你自己创建一个纯净的 HTTP 服务器,这些开发起来还是挺费劲的 。

Go Kit greeter


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

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

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

go-kit-greeter-service.go

  1. package greeterservice
  2. // Service 描述了 greetings 这个服务
  3. type Service interface {
  4. Health() bool
  5. Greeting(name string) string
  6. }
  7. // GreeterService 是 Service 接口的实现
  8. type GreeterService struct{}
  9. // Service 的 Health 接口实现
  10. func (GreeterService) Health() bool {
  11. return true
  12. }
  13. // Service 的 Greeting 接口实现
  14. func (GreeterService) Greeting(name string) (greeting string) {
  15. greeting = "GO-KIT Hello " + name
  16. return
  17. }

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

go-kit-greeter-endpoints.go

  1. package greeterendpoint
  2. import (
  3. "context"
  4. "github.com/go-kit/kit/log"
  5. "github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterservice"
  6. "github.com/go-kit/kit/endpoint"
  7. )
  8. type (
  9. // Endpoints 收集构成 Greeting Service 的所有端点。
  10. // 它应该被用作助手 struct ,将所有端点收集到一个参数中。
  11. Endpoints struct {
  12. // Consul 用这个端点做健康检查(Health Check)
  13. HealthEndpoint endpoint.Endpoint
  14. GreetingEndpoint endpoint.Endpoint
  15. }
  16. HealthRequest struct {
  17. }
  18. HealthResponse struct {
  19. Healthy bool `json:"health,omitempty"`
  20. Err error `json:"err,omitempty"`
  21. }
  22. GreetingRequest struct {
  23. Name string `json:"name,omitempty"`
  24. }
  25. GreetingResponse struct {
  26. Greeting string `json:"greeting,omitempty"`
  27. Err error `json:"err,omitempty"`
  28. }
  29. // Failer是一个应该由响应类型实现的接口。
  30. // 响应编码器可以检查响应是否是一个 Failer。
  31. // 如果响应是一个 Failer, 那么就说明响应已经失败了,根据错误使用单独的写入路径对响应进行编码。
  32. Failer interface {
  33. Failed() error
  34. }
  35. )
  36. // MakeServiceEndpoints 返回服务端点, 将所有已提供的中间件连接起来
  37. func MakeServerEndpoints(s greeterservice.Service, logger log.Logger) Endpoints {
  38. var healthEndpoint endpoint.Endpoint
  39. {
  40. healthEndpoint = MakeHealthEndpoint(s)
  41. healthEndpoint = LoggingMiddleware(log.With(logger, "method", "Health"))(healthEndpoint)
  42. }
  43. var greetingEndpoint endpoint.Endpoint
  44. {
  45. greetingEndpoint = MakeGreetingEndpoint(s)
  46. greetingEndpoint = LoggingMiddleware(log.With(logger, "method", "Greeting"))(greetingEndpoint)
  47. }
  48. return Endpoints{
  49. HealthEndpoint: healthEndpoint,
  50. GreetingEndpoint: greetingEndpoint,
  51. }
  52. }
  53. // MakeHealthEndpoints 构造一个 Health 端点,将服务包装为一个端点
  54. func MakeHealthEndpoint(s greeterservice.Service) endpoint.Endpoint {
  55. return func(ctx context.Context, request interface{}) (response interface{}, err error) {
  56. healthy := s.Health()
  57. return HealthResponse{Healthy: healthy}, nil
  58. }
  59. }
  60. // MakeGreetingEndpoints 构造一个 Greeter 端点,将 Greeting 服务包装为一个端点
  61. func MakeGreetingEndpoint(s greeterservice.Service) endpoint.Endpoint {
  62. return func(ctx context.Context, request interface{}) (response interface{}, err error) {
  63. req := request.(GreetingRequest)
  64. greeting := s.Greeting(req.Name)
  65. return GreetingResponse{
  66. Greeting: greeting,
  67. }, nil
  68. }
  69. }
  70. // 实现 Failer.Failed 接口
  71. func (r HealthResponse) Failed() error {
  72. return r.Err
  73. }
  74. // 实现 Failer.Failed 接口
  75. func (r GreetingResponse) Failed() error {
  76. return r.Err
  77. }

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

go-kit-greeter-http.go

  1. package greetertransport
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "net/http"
  7. "github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint"
  8. "github.com/go-kit/kit/log"
  9. httptransport "github.com/go-kit/kit/transport/http"
  10. "github.com/gorilla/mux"
  11. )
  12. var (
  13. // 当缺少预期的路径变量时,将返回 ErrBadRouting。
  14. ErrBadRouting = errors.New("inconsistent mapping between route and handler")
  15. )
  16. // NewHTTPHandler返回一个使一组端点在预定义路径上可用的HTTP处理程序。
  17. func NewHTTPHandler(endpoints greeterendpoint.Endpoints, logger log.Logger) http.Handler {
  18. m := mux.NewRouter()
  19. options := []httptransport.ServerOption{
  20. httptransport.ServerErrorEncoder(encodeError),
  21. httptransport.ServerErrorLogger(logger),
  22. }
  23. // GET /health 获取服务健康信息
  24. // GET /greeting?name 获取 greeting
  25. // NewServer 方法需要端点,解码器,编码器作为参数
  26. m.Methods("GET").Path("/health").Handler(httptransport.NewServer(endpoints.HealthEndpoint, DecodeHTTPHealthRequest, EncodeHTTPGenericResponse, options...))
  27. m.Methods("GET").Path("/greeting").Handler(httptransport.NewServer(endpoints.GreetingEndpoint, DecodeHTTPGreetingRequest, EncodeHTTPGenericResponse, options...))
  28. return m
  29. }
  30. func EncodeHTTPGenericResponse(ctx context.Context, writer http.ResponseWriter, response interface{}) error {
  31. if f, ok := response.(greeterendpoint.Failer); ok && f.Failed() != nil {
  32. encodeError(ctx, f.Failed(), writer)
  33. return nil
  34. }
  35. writer.Header().Set("Content-Type", "application/json; charset=utf-8")
  36. return json.NewEncoder(writer).Encode(response)
  37. }
  38. // 解码 Health HTTP 请求的方法
  39. func DecodeHTTPHealthRequest(_ context.Context, _ *http.Request) (interface{}, error) {
  40. return greeterendpoint.HealthRequest{}, nil
  41. }
  42. // 解码 Greeting HTTP 请求的方法
  43. func DecodeHTTPGreetingRequest(_ context.Context, r *http.Request) (interface{}, error) {
  44. vars := r.URL.Query()
  45. names, exists := vars["name"]
  46. if !exists || len(names) != 1 {
  47. return nil, ErrBadRouting
  48. }
  49. req := greeterendpoint.GreetingRequest{Name: names[0]}
  50. return req, nil
  51. }
  52. // errorWrapper 将 error 封装为一个 json 结构体方便转换为 json
  53. type errorWrapper struct {
  54. Error string `json:"error"`
  55. }
  56. // 编码错误的方法
  57. func encodeError(_ context.Context, err error, w http.ResponseWriter) {
  58. w.WriteHeader(err2code(err))
  59. json.NewEncoder(w).Encode(errorWrapper{Error: err.Error()})
  60. }
  61. // err2code 函数将 error 转换为对应的 http 状态码
  62. func err2code(err error) int {
  63. switch err {
  64. default:
  65. return http.StatusInternalServerError
  66. }
  67. }

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

go-greeter-gen.sh

  1. #!/usr/bin/env sh
  2. protoc greeter.proto --go_out=plugins=grpc:.

go-kit-grpc.go

  1. package greetertransport
  2. import (
  3. "context"
  4. "github.com/antklim/go-microservices/go-kit-greeter/pb"
  5. "github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint"
  6. "github.com/go-kit/kit/log"
  7. grpctransport "github.com/go-kit/kit/transport/grpc"
  8. oldcontext "golang.org/x/net/context"
  9. )
  10. type (
  11. grpcServer struct {
  12. greeter grpctransport.Handler
  13. }
  14. )
  15. // NewGRPCServer 使一组端点可用作 gRPC Greeting 服务器。
  16. func NewGRPCServer(endpoints greeterendpoint.Endpoints, logger log.Logger) pb.GreeterServer {
  17. options := []grpctransport.ServerOption{
  18. grpctransport.ServerErrorLogger(logger),
  19. }
  20. return &grpcServer{greeter: grpctransport.NewServer(endpoints.GreetingEndpoint, decodeGRPCGreetingRequest, encodeGRPCGreetingResponse, options...)}
  21. }
  22. // encodeGRPCGreetingResponse 是一个 transport/grpc.EncodeResponseFunc 将用户域
  23. // 问Greeting响应转换为 gRPC Greeting 响应。
  24. func encodeGRPCGreetingResponse(i context.Context, i2 interface{}) (response interface{}, err error) {
  25. res := response.(greeterendpoint.GreetingResponse)
  26. return &pb.GreetingResponse{Greeting: res.Greeting}, nil
  27. }
  28. // decodeGRPCGreetingRequest 是一个 transport/grpc.DecodeRequestFunc 将 gRPC Greeting 请求转换为用户域 Greeting 请求
  29. func decodeGRPCGreetingRequest(context context.Context, grpcReq interface{}) (request interface{}, err error) {
  30. req := grpcReq.(*pb.GreetingRequest)
  31. return greeterendpoint.GreetingRequest{Name: req.Name}, nil
  32. }
  33. // 实现 GreeterService.Greeting 接口
  34. func (s *grpcServer) Greeting(ctx context.Context, req *pb.GreetingRequest) (*pb.GreetingResponse, error) {
  35. _, res, err := s.greeter.ServeGRPC(ctx, req)
  36. if err != nil {
  37. return nil, err
  38. }
  39. return res.(*pb.GreetingResponse), nil
  40. }

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

go-kit-sd.go

  1. package greetersd
  2. import (
  3. "math/rand"
  4. "os"
  5. "strconv"
  6. "time"
  7. "github.com/go-kit/kit/log"
  8. "github.com/go-kit/kit/sd"
  9. consulsd "github.com/go-kit/kit/sd/consul"
  10. "github.com/hashicorp/consul/api"
  11. )
  12. // ConsulRegister 方法
  13. func ConsulRegister(consulAddress string,
  14. consulPort string,
  15. advertiseAddress string,
  16. advertisePort string) (registar sd.Registrar) {
  17. // 日志相关
  18. var logger log.Logger
  19. {
  20. logger = log.NewLogfmtLogger(os.Stderr)
  21. logger = log.With(logger, "ts", log.DefaultTimestampUTC)
  22. logger = log.With(logger, "caller", log.DefaultCaller)
  23. }
  24. rand.Seed(time.Now().UTC().UnixNano())
  25. // 服务发现域。在本例中,我们使用 Consul.
  26. var client consulsd.Client
  27. {
  28. consulConfig := api.DefaultConfig()
  29. consulConfig.Address = consulAddress + ":" + consulPort
  30. consulClient, err := api.NewClient(consulConfig)
  31. if err != nil {
  32. logger.Log("err", err)
  33. os.Exit(1)
  34. }
  35. client = consulsd.NewClient(consulClient)
  36. }
  37. check := api.AgentServiceCheck{
  38. HTTP: "http://" + advertiseAddress + ":" + advertisePort + "/health",
  39. Interval: "10s",
  40. Timeout: "1s",
  41. Notes: "Basic health checks",
  42. }
  43. port, _ := strconv.Atoi(advertisePort)
  44. num := rand.Intn(100) // to make service ID unique
  45. asr := api.AgentServiceRegistration{
  46. ID: "go-kit-srv-greeter-" + strconv.Itoa(num), //unique service ID
  47. Name: "go-kit-srv-greeter",
  48. Address: advertiseAddress,
  49. Port: port,
  50. Tags: []string{"go-kit", "greeter"},
  51. Check: &check,
  52. }
  53. registar = consulsd.NewRegistrar(client, &asr, logger)
  54. return
  55. }

我在服务启动程序中将它们合并在一起:

go-kit-service-starter.go

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "net"
  6. "net/http"
  7. "os"
  8. "os/signal"
  9. "syscall"
  10. "text/tabwriter"
  11. "github.com/antklim/go-microservices/go-kit-greeter/pb"
  12. "google.golang.org/grpc"
  13. "github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint"
  14. "github.com/antklim/go-microservices/go-kit-greeter/pkg/greetersd"
  15. "github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterservice"
  16. "github.com/antklim/go-microservices/go-kit-greeter/pkg/greetertransport"
  17. "github.com/go-kit/kit/log"
  18. "github.com/oklog/oklog/pkg/group"
  19. )
  20. func main() {
  21. fs := flag.NewFlagSet("greetersvc", flag.ExitOnError)
  22. var (
  23. debugAddr = fs.String("debug.addr", ":9100", "Debug and metrics listen address")
  24. consulAddr = fs.String("consul.addr", "", "Consul Address")
  25. consulPort = fs.String("consul.port", "8500", "Consul Port")
  26. httpAddr = fs.String("http.addr", "", "HTTP Listen Address")
  27. httpPort = fs.String("http.port", "9110", "HTTP Listen Port")
  28. grpcAddr = fs.String("grpc-addr", ":9120", "gRPC listen address")
  29. )
  30. fs.Usage = usageFor(fs, os.Args[0]+" [flags]")
  31. fs.Parse(os.Args[1:])
  32. var logger log.Logger
  33. {
  34. logger = log.NewLogfmtLogger(os.Stderr)
  35. logger = log.With(logger, "ts", log.DefaultTimestampUTC)
  36. logger = log.With(logger, "caller", log.DefaultCaller)
  37. }
  38. var service greeterservice.Service
  39. {
  40. service = greeterservice.GreeterService{}
  41. service = greeterservice.LoggingMiddleware(logger)(service)
  42. }
  43. var (
  44. endpoints = greeterendpoint.MakeServerEndpoints(service, logger)
  45. httpHandler = greetertransport.NewHTTPHandler(endpoints, logger)
  46. registar = greetersd.ConsulRegister(*consulAddr, *consulPort, *httpAddr, *httpPort)
  47. grpcServer = greetertransport.NewGRPCServer(endpoints, logger)
  48. )
  49. var g group.Group
  50. {
  51. // The debug listener mounts the http.DefaultServeMux, and serves up
  52. // stuff like the Go debug and profiling routes, and so on.
  53. debugListener, err := net.Listen("tcp", *debugAddr)
  54. if err != nil {
  55. logger.Log("transport", "debug/HTTP", "during", "Listen", "err", err)
  56. os.Exit(1)
  57. }
  58. g.Add(func() error {
  59. logger.Log("transport", "debug/HTTP", "addr", *debugAddr)
  60. return http.Serve(debugListener, http.DefaultServeMux)
  61. }, func(error) {
  62. debugListener.Close()
  63. })
  64. }
  65. {
  66. // The service discovery registration.
  67. g.Add(func() error {
  68. logger.Log("transport", "HTTP", "addr", *httpAddr, "port", *httpPort)
  69. registar.Register()
  70. return http.ListenAndServe(":"+*httpPort, httpHandler)
  71. }, func(error) {
  72. registar.Deregister()
  73. })
  74. }
  75. {
  76. // The gRPC listener mounts the Go kit gRPC server we created.
  77. grpcListener, err := net.Listen("tcp", *grpcAddr)
  78. if err != nil {
  79. logger.Log("transport", "gRPC", "during", "Listen", "err", err)
  80. os.Exit(1)
  81. }
  82. g.Add(func() error {
  83. logger.Log("transport", "gRPC", "addr", *grpcAddr)
  84. baseServer := grpc.NewServer()
  85. pb.RegisterGreeterServer(baseServer, grpcServer)
  86. return baseServer.Serve(grpcListener)
  87. }, func(error) {
  88. grpcListener.Close()
  89. })
  90. }
  91. {
  92. // This function just sits and waits for ctrl-C.
  93. cancelInterrupt := make(chan struct{})
  94. g.Add(func() error {
  95. c := make(chan os.Signal, 1)
  96. signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
  97. select {
  98. case sig := <-c:
  99. return fmt.Errorf("received signal %s", sig)
  100. case <-cancelInterrupt:
  101. return nil
  102. }
  103. }, func(error) {
  104. close(cancelInterrupt)
  105. })
  106. }
  107. logger.Log("exit", g.Run())
  108. }
  109. func usageFor(fs *flag.FlagSet, short string) func() {
  110. return func() {
  111. fmt.Fprintf(os.Stderr, "USAGE\n")
  112. fmt.Fprintf(os.Stderr, " %s\n", short)
  113. fmt.Fprintf(os.Stderr, "\n")
  114. fmt.Fprintf(os.Stderr, "FLAGS\n")
  115. w := tabwriter.NewWriter(os.Stderr, 0, 2, 2, ' ', 0)
  116. fs.VisitAll(func(f *flag.Flag) {
  117. fmt.Fprintf(w, "\t-%s %s\t%s\n", f.Name, f.DefValue, f.Usage)
  118. })
  119. w.Flush()
  120. fmt.Fprintf(os.Stderr, "\n")
  121. }
  122. }

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

go-kit-greeter-service-middleware.go

  1. package greeterservice
  2. import (
  3. "time"
  4. "github.com/go-kit/kit/log"
  5. )
  6. // ServiceMiddleware 描述服务中间件
  7. type ServiceMiddleware func(Service) Service
  8. // LoggingMiddleware 将 logger 作为依赖项并返回 ServiceMiddleware
  9. func LoggingMiddleware(logger log.Logger) ServiceMiddleware {
  10. return func(next Service) Service {
  11. return loggingMiddleware{next, logger}
  12. }
  13. }
  14. type loggingMiddleware struct {
  15. Service
  16. logger log.Logger
  17. }
  18. func (m loggingMiddleware) Health() (healthy bool) {
  19. defer func(begin time.Time) {
  20. m.logger.Log(
  21. "method", "Health",
  22. "healthy", healthy,
  23. "took", time.Since(begin),
  24. )
  25. }(time.Now())
  26. healthy = m.Service.Health()
  27. return
  28. }
  29. func (m loggingMiddleware) Greeting(name string) (greeting string) {
  30. defer func(begin time.Time) {
  31. m.logger.Log(
  32. "method", "Greeting",
  33. "name", name,
  34. "greeting", greeting,
  35. "took", time.Since(begin),
  36. )
  37. }(time.Now())
  38. greeting = m.Service.Greeting(name)
  39. return
  40. }

go-kit-greeter-endpoints-middleware.go

  1. package greeterendpoint
  2. import (
  3. "context"
  4. "time"
  5. "github.com/go-kit/kit/endpoint"
  6. "github.com/go-kit/kit/log"
  7. )
  8. // 日志中间件返回 endpoint 中间件,记录每次调用的持续时间,
  9. // 以及产生的错误(如果有)。
  10. func LoggingMiddleware(logger log.Logger) endpoint.Middleware {
  11. return func(next endpoint.Endpoint) endpoint.Endpoint {
  12. return func(ctx context.Context, request interface{}) (response interface{}, err error) {
  13. defer func(begin time.Time) {
  14. logger.Log("transport_error", err, "took", time.Since(begin))
  15. }(time.Now())
  16. return next(ctx, request)
  17. }
  18. }
  19. }

Gizmo greeter


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

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

gizmo-greeter-endpoints.go

  1. package greeterendpoint
  2. import (
  3. "net/http"
  4. ocontext "golang.org/x/net/context"
  5. "github.com/NYTimes/gizmo/server"
  6. "github.com/antklim/go-microservices/gizmo-greeter/pkg/greeterservice"
  7. )
  8. // 收集组成 greeter 的所有 endpoints
  9. type Endpoints struct {
  10. HealthEndpoint server.JSONContextEndpoint
  11. GreetingEndpoint server.JSONContextEndpoint
  12. }
  13. // MakeServerEndpoints 返回服务 Endoints
  14. func MakeServerEndpoints(s greeterservice.Service) Endpoints {
  15. healthEndpoint := MakeHealthEndpoint(s)
  16. greetingEndpoint := MakeGreetingEndpoint(s)
  17. return Endpoints{
  18. HealthEndpoint: healthEndpoint,
  19. GreetingEndpoint: greetingEndpoint,
  20. }
  21. }
  22. // MakeHealthEndpoint 构建一个健康监控 endpoint
  23. func MakeHealthEndpoint(s greeterservice.Service) server.JSONContextEndpoint {
  24. return func(ctx ocontext.Context, r *http.Request) (int, interface{}, error) {
  25. healthy := s.Health()
  26. return http.StatusOK, HealthResponse{Healthy: healthy}, nil
  27. }
  28. }
  29. // MakeGreetingEndpoint 构建一个问候的 endpoint.
  30. func MakeGreetingEndpoint(s greeterservice.Service) server.JSONContextEndpoint {
  31. return func(ctx ocontext.Context, r *http.Request) (int, interface{}, error) {
  32. vars := r.URL.Query()
  33. names, exists := vars["name"]
  34. if !exists || len(names) != 1 {
  35. return http.StatusBadRequest, errorResponse{Error: "query parameter 'name' required"}, nil
  36. }
  37. greeting := s.Greeting(names[0])
  38. return http.StatusOK, GreetingResponse{Greeting: greeting}, nil
  39. }
  40. }
  41. // HealthRequest 收集 Health 方法的请求参数
  42. type HealthRequest struct{}
  43. // HealthResponse 收集 Health 方法的响应值
  44. type HealthResponse struct {
  45. Healthy bool `json:"healthy,omitempty"`
  46. }
  47. // GreetingRequest 收集问候语方法的请求参数
  48. type GreetingRequest struct {
  49. Name string `json:"name,omitempty"`
  50. }
  51. // GreetingResponse 收集问候语方法的响应值
  52. type GreetingResponse struct {
  53. Greeting string `json:"greeting,omitempty"`
  54. }
  55. type errorResponse struct {
  56. Error string `json:"error"`
  57. }

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

gizmo-greeter-http.go

  1. package greetertransport
  2. import (
  3. "context"
  4. "github.com/NYTimes/gizmo/server"
  5. "google.golang.org/grpc"
  6. "errors"
  7. "net/http"
  8. "github.com/NYTimes/gziphandler"
  9. pb "github.com/antklim/go-microservices/gizmo-greeter/pb"
  10. "github.com/antklim/go-microservices/gizmo-greeter/pkg/greeterendpoint"
  11. "github.com/sirupsen/logrus"
  12. )
  13. type (
  14. // TService 将实现 server.RPCService 并处理对服务器的所有请求。
  15. TService struct {
  16. Endpoints greeterendpoint.Endpoints
  17. }
  18. // Config 包含 JSONService 所需的所有配置的结构。
  19. Config struct {
  20. Server *server.Config
  21. }
  22. )
  23. // NewTService 将用给定配置实例化 RPCService
  24. func NewTService(cfg *Config, endpoints greeterendpoint.Endpoints) *TService {
  25. return &TService{Endpoints: endpoints}
  26. }
  27. // Prefix 返回此服务中所有端点使用的字符串前缀
  28. func (s *TService) Prefix() string {
  29. return ""
  30. }
  31. // Service 向 TService 提供要服务的描述和实现
  32. func (s *TService) Service() (*grpc.ServiceDesc, interface{}) {
  33. return &pb.Greeter_serviceDesc, s
  34. }
  35. // 中间件提供了一个 http.Handler 钩子,它包装了所有请求。
  36. // 在这个实现中,我们使用 GzipHandler 中间件来压缩我们的响应。
  37. func (s *TService) Middleware(h http.Handler) http.Handler {
  38. return gziphandler.GzipHandler(h)
  39. }
  40. // ContextMiddleware 为所有请求提供一个 server.ContextHAndler 钩子。
  41. // 如果需要修饰请求上下文,这可能很方便。
  42. func (s *TService) ContextMiddleware(h server.ContextHandler) server.ContextHandler {
  43. return h
  44. }
  45. // JSONMiddleware 为所有请求提供一个 JSONEndpoint 钩子。
  46. // 我们使用它来提供日志记录和检查错误,并提供一般响应。
  47. func (s *TService) JSONMiddleware(j server.JSONContextEndpoint) server.JSONContextEndpoint {
  48. return func(ctx context.Context, r *http.Request) (int, interface{}, error) {
  49. status, res, err := j(ctx, r)
  50. if err != nil {
  51. server.LogWithFields(r).WithFields(logrus.Fields{
  52. "error": err,
  53. }).Error("problems with serving request")
  54. return http.StatusServiceUnavailable, nil, errors.New("sorry, this service is unavailable")
  55. }
  56. server.LogWithFields(r).Info("success!")
  57. return status, res, nil
  58. }
  59. }
  60. // ContextEndpoints may be needed if your server has any non-RPC-able
  61. // endpoints. In this case, we have none but still need this method to
  62. // satisfy the server.RPCService interface.
  63. func (s *TService) ContextEndpoints() map[string]map[string]server.ContextHandlerFunc {
  64. return map[string]map[string]server.ContextHandlerFunc{}
  65. }
  66. // JSONEndpoints is a listing of all endpoints available in the TService.
  67. func (s *TService) JSONEndpoints() map[string]map[string]server.JSONContextEndpoint {
  68. return map[string]map[string]server.JSONContextEndpoint{
  69. "/health": map[string]server.JSONContextEndpoint{
  70. "GET": s.Endpoints.HealthEndpoint,
  71. },
  72. "/greeting": map[string]server.JSONContextEndpoint{
  73. "GET": s.Endpoints.GreetingEndpoint,
  74. },
  75. }
  76. }

gizmo-greeter-grpc.go

  1. package greetertransport
  2. import (
  3. pb "github.com/antklim/go-microservices/gizmo-greeter/pb"
  4. ocontext "golang.org/x/net/context"
  5. )
  6. // Greeting implementation of the gRPC service.
  7. func (s *TService) Greeting(ctx ocontext.Context, r *pb.GreetingRequest) (*pb.GreetingResponse, error) {
  8. return &pb.GreetingResponse{Greeting: "Hola Gizmo RPC " + r.Name}, nil
  9. }

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

结论


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

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

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

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

原文链接

https://learnku.com/go/t/36973 https://medium.com/seek-blog/microservices-in-go-2fc1570f6800