Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

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

Go Kit 自上而下构建了三层模型分别是: 分别是 Transport 层、Endpoint 层、Service 层。

  • Transport 层:处理 HTTP、gRPC、Thrift 等协议相关的逻辑,主要对请求进行解码、对响应进行编码操作;
  • Endpoint 层:在 Service 的上层作为业务的中间件,可使用限流、熔断、监控等能力;
  • Service 层:用来处理业务逻辑;

利用三层模型可以构建单体服务 -> 微服务 -> 分布式

编写一个简单的 HTTP 单体服务

首先构造一个 service, 将服务的建模构造成接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package services

import (
"errors"
"strings"
)

var ErrEmpty = errors.New("empty string")

type StringService interface {
Uppercase(string) (string, error)
Count(string) int
}

type StrService struct{}

func (StrService) Uppercase(s string) (string, error) {
if s == "" {
return "", ErrEmpty
}
return strings.ToUpper(s), nil
}

func (StrService) Count(s string) int {
return len(s)
}

在 Go kit 中,主要的消息传递模式是 RPC。因此,我们接口中的每个方法都将被建模为远程过程调用。对于每个方法,我们定义请求和响应结构,分别捕获所有输入和输出参数。
然后组装 endpoint 端点, 它是外部服务与内部服务的桥梁, 往后可以在这里添加中间件的构造。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package transports

import (
"context"
"encoding/json"
service "kit/services"
"net/http"

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

// Request and Response
type UppercaseRequest struct {
S string `json:"s"`
}

type UppercaseResponse struct {
V string `json:"v"`
Err string `json:"err,omitempty"`
}

type CountRequest struct {
S string `json:"s"`
}

type CountResponse struct {
V int `json:"v"`
}

// Endpoint 创造端点 相当于一个RPC
func MakeUppercaseEndpoint(svc service.StringService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(UppercaseRequest)
v, err := svc.Uppercase(req.S)
if err != nil {
return UppercaseResponse{v, err.Error()}, nil
}
return UppercaseResponse{v, ""}, nil
}
}

func MakeCountEndpoint(svc service.StringService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(CountRequest)
v := svc.Count(req.S)
return CountResponse{v}, nil
}
}

func DecodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) {
var request UppercaseRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}

func DecodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) {
var request CountRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}

func EncodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
return json.NewEncoder(w).Encode(response)
}

为服务添加日志中间件: 如果没有彻底的日志记录和检测,任何服务都不能被认为是生产就绪的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package middlewares

import (
"kit/services"
"time"

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

type LoggingMiddleware struct {
Logger log.Logger
Next services.StringService
}

func NewLoggingMiddleware(logger log.Logger, next services.StringService) *LoggingMiddleware {
return &LoggingMiddleware{
Logger: logger,
Next: next,
}
}

func (mw LoggingMiddleware) Uppercase(s string) (output string, err error) {
defer func(begin time.Time) {
mw.Logger.Log(
"method", "uppercase",
"input", s,
"output", output,
"err", err,
"took", time.Since(begin),
)
}(time.Now())

output, err = mw.Next.Uppercase(s)
return
}

func (mw LoggingMiddleware) Count(s string) (n int) {
defer func(begin time.Time) {
mw.Logger.Log(
"method", "count",
"input", s,
"n", n,
"took", time.Since(begin),
)
}(time.Now())

n = mw.Next.Count(s)
return
}

现在我们需要将服务公开,以便调用它。您的组织可能已经对服务应该如何相互通信有了自己的看法。也许你使用Thrift,或者自定义JSON over HTTP。围棋套件支持许多开箱即用的传输工具。
对于这个最小的示例服务,让我们使用HTTP上的JSON。Go kit在package transport/http中提供了一个helper结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
"net/http"
"os"

service "kit/services"
transport "kit/transports"

middleware "kit/middlewares"

httptransport "github.com/go-kit/kit/transport/http"
"github.com/go-kit/log"
)

func main() {
logger := log.NewLogfmtLogger(os.Stderr)

svc := middleware.NewLoggingMiddleware(logger, service.StrService{})

uppercaseHandler := httptransport.NewServer(
transport.MakeUppercaseEndpoint(svc),
transport.DecodeUppercaseRequest,
transport.EncodeResponse,
)

countHandler := httptransport.NewServer(
transport.MakeCountEndpoint(svc),
transport.DecodeCountRequest,
transport.EncodeResponse,
)

http.Handle("/uppercase", uppercaseHandler)
http.Handle("/count", countHandler)
http.ListenAndServe(":25567", nil)
}