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

下面是我要比较的框架:


Go Micro


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

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

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

Go 微体系结构可以描述为三层堆栈。
Go 微服务框架对比:Go Micro, Go Kit, Gizmo, Kite - 图2
图 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 + 微服务」幻灯片中,您将看到一个使用 Go Kit 构建的服务架构示例。为了快速入门,这里有一个服务架构图。
Go 微服务框架对比:Go Micro, Go Kit, Gizmo, Kite - 图3
图 2。使用 Go Kit 构建的服务架构示例 (原始图片在 「Go + 微服务」 幻灯片)

Gizmo


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

  • server - 提供两种服务器实现:SimpleServer(over 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 那样原始。它提供更高级的构建组件,如 config 和 pubsub 包。

Kite


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

框架对比


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

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

    GitHub statistics


Go 微服务框架对比:Go Micro, Go Kit, Gizmo, Kite - 图4
表 1. Go 微服务框架统计(2018 年 4 月收集)

文档和代码示例


简单来说,没有框架会提供可靠的文档,通常来说,唯一正式的文档是项目首页的 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 多颗 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 评级报告很高。

微框架示例


好啦,理论的东西讲的差不多了,接下来开始编码。为了更好地理解框架,我创建了三个简单的微服务:
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 Micro 中 forked 并进行了修改,以支持框架的某些功能。我在 greeter 服务中把这些连接在一起。此时,服务正在启动并向服务发现系统注册。它只支持 gRPC 传输协议:

go-micro-greeter-grpc-main.go

  1. package main
  2. import (
  3. "log"
  4. pb "github.com/antklim/go-microservices/go-micro-greeter/pb"
  5. "github.com/micro/go-micro"
  6. "golang.org/x/net/context"
  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-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 如果您对 Go 和微服务框架有任何经验,请在下面的评论中分享。


原文作者:Summer
转自链接:https://learnku.com/go/t/36973
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。