温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

go-kit该如何入门

发布时间:2022-01-17 16:59:49 来源:亿速云 阅读:262 作者:柒染 栏目:大数据

这篇文章跟大家分析一下“go-kit该如何入门”。内容详细易懂,对“go-kit该如何入门”感兴趣的朋友可以跟着小编的思路慢慢深入来阅读一下,希望阅读后能够对大家有所帮助。下面跟着小编一起深入学习“go-kit该如何入门”的知识吧。

第一原则

让我们创建一个最小的 Go-kit 服务。现在,我们将使用单独的 main.go 文件。

你的商业逻辑

您的服务从您的业务逻辑开始。 在Go kit中,我们将服务建模为接口.

// StringService provides operations on strings.
import "context"

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

该接口将有一个实现。

import (
	"context"
	"errors"
	"strings"
)

type stringService struct{}

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

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

// ErrEmpty is returned when input string is empty
var ErrEmpty = errors.New("Empty string")

请求和响应

在Go-kit中,主要的消息传递模式是 RPC。因此,接口中的每个方法都将被建模为远程过程调用请求和响应(request and response)结构,分别捕获所有输入和输出参数。

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

type uppercaseResponse struct {
	V   string `json:"v"`
	Err string `json:"err,omitempty"` // errors don't JSON-marshal, so we use a string
}

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

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

端点

gokit 通过一个称为 端点(endpoint)的抽象概念来提供功能。

端点的定义如下(不必将其放在你的代码中,它由 go-kit 提供):

type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)

它表示一个单独的 RPC。就是服务接口中的一个方法。 我们将编写简单的适配器,将服务的每个方法转换为一个端点。 每个适配器接受一个 StringService,并返回一个与其中一个方法对应的端点。

import (
	"context"
	"github.com/go-kit/kit/endpoint"
)

func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint {
	return func(_ 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 StringService) endpoint.Endpoint {
	return func(_ context.Context, request interface{}) (interface{}, error) {
		req := request.(countRequest)
		v := svc.Count(req.S)
		return countResponse{v}, nil
	}
}

传输

现在,我们需要将你的服务公开给外部世界,以便可以调用它。 您的组织可能已经对服务应该如何相互通信有自己的看法。 也许您使用Thrift,或HTTP上的自定义JSON。 Go kit支持许多传输协议开箱即用。

对于这个最小的示例服务,让我们使用JSON over HTTP。 Go kit提供了一个helper结构,在包transport/HTTP中。

import (
	"context"
	"encoding/json"
	"log"
	"net/http"

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

func main() {
	svc := stringService{}

	uppercaseHandler := httptransport.NewServer(
		makeUppercaseEndpoint(svc),
		decodeUppercaseRequest,
		encodeResponse,
	)

	countHandler := httptransport.NewServer(
		makeCountEndpoint(svc),
		decodeCountRequest,
		encodeResponse,
	)

	http.Handle("/uppercase", uppercaseHandler)
	http.Handle("/count", countHandler)
	log.Fatal(http.ListenAndServe(":8080", 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)
}

字符串SVC1

到目前为止,完整的服务是字符串SVC1.

$ go get github.com/go-kit/kit/examples/stringsvc1
$ stringsvc1

$ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/uppercase
{"v":"HELLO, WORLD"}
$ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/count
{"v":12}

中间软件

没有彻底的日志记录和检测,任何服务都不能被视为可以在生产上使用。

关注点分离

当你不断增加你的服务endpoint 数量, 为了可以更容易地 阅读 go-kit 的项目 , 我们将每个项目的调用分离成单独的服务层文件。我们的第一个样例 [字符串SVC1] 将所有这些层都放在一个主文件中。 在增加复杂性之前,让我们将代码分成以下文件,并将所有剩余代码保留在main.go中

把你的服务包含以下函数和类型的 service.go 文件。

type StringService
type stringService
func Uppercase
func Count
var ErrEmpty

把你的传输层变成一个 transport.go 具有以下函数和类型的文件。

func makeUppercaseEndpoint
func makeCountEndpoint
func decodeUppercaseRequest
func decodeCountRequest
func encodeResponse
type uppercaseRequest
type uppercaseResponse
type countRequest
type countResponse

传输日志

任何需要日志记录的组件都应该将 logger 作为依赖项,就像数据库连接一样。 我们构造我们的logger在我们的 func main ,并将其传递给需要它的组件。 我们从不使用全局范围的 logger。

我们可以将一个 logger 直接传递到我们的 stringService 实现中,但是还有一个更好的方法中间件 , 也叫装饰器,中间件是一个接受端点 (endpoint) 并返回端点(endpoint) 的函数。

type Middleware func(Endpoint) Endpoint

注意,中间件类型是由go-kit提供的。

在这两者之间,它可以做任何事情。 下面您可以看到如何实现一个基本的日志中间件(您不需要在任何地方复制/粘贴此代码):

func loggingMiddleware(logger log.Logger) Middleware {
	return func(next endpoint.Endpoint) endpoint.Endpoint {
		return func(ctx context.Context, request interface{}) (interface{}, error) {
			logger.Log("msg", "calling endpoint")
			defer logger.Log("msg", "called endpoint")
			return next(ctx, request)
		}
	}
}

使用 go-kit log 包并删除标准库 log , 您需要从 main.go 文件底部移除 log.Fatal 。

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

并将其连接到我们的每个处理程序中. 下边的代码不会编译,直到您遵循应用程序日志记录章节,它定义loggingMiddleware 日志中间件的章节。

logger := log.NewLogfmtLogger(os.Stderr)

svc := stringService{}

var uppercase endpoint.Endpoint
uppercase = makeUppercaseEndpoint(svc)
uppercase = loggingMiddleware(log.With(logger, "method", "uppercase"))(uppercase)

var count endpoint.Endpoint
count = makeCountEndpoint(svc)
count = loggingMiddleware(log.With(logger, "method", "count"))(count)

uppercaseHandler := httptransport.NewServer(
	uppercase,
	// ...
)

countHandler := httptransport.NewServer(
	count,
	// ...
)

事实证明,这种技术不仅仅适用于日志记录,许多Go-kit组件都是作为端点中间件实现的。

应用程序日志记录

但是如果我们想在我们的应用程序域进行 log ,比如传入的参数呢?其实我们可以为我们的服务定义一个中间件,并获得相同的良好和可组合的效果。 由于我们的StringService被定义为一个接口,我们只需要创建一个新的类型 来包装现有的StringService,并执行额外的日志记录任务。

type loggingMiddleware struct {
	logger log.Logger
	next   StringService
}

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
}

链接我们的代码

import (
	"os"

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

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

	var svc StringService
	svc = stringService{}
	svc = loggingMiddleware{logger, svc}

	// ...

	uppercaseHandler := httptransport.NewServer(
		makeUppercaseEndpoint(svc),
		// ...
	)

	countHandler := httptransport.NewServer(
		makeCountEndpoint(svc),
		// ...
	)
}

将端点中间件用于传输域问题,如熔断和速率限制。 将服务中间件用于业务域问题,如日志记录和检测。 谈到检测…

应用程序仪表

在Go-kit中,仪器意味着使用软件包指标要记录服务运行时行为的统计信息,统计已处理的作业数,记录请求完成后的持续时间,以及跟踪正在执行的操作数,这些都被视为工具。

我们可以使用与日志记录相同的中间件模式。

type instrumentingMiddleware struct {
	requestCount   metrics.Counter
	requestLatency metrics.Histogram
	countResult    metrics.Histogram
	next           StringService
}

func (mw instrumentingMiddleware) Uppercase(s string) (output string, err error) {
	defer func(begin time.Time) {
		lvs := []string{"method", "uppercase", "error", fmt.Sprint(err != nil)}
		mw.requestCount.With(lvs...).Add(1)
		mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
	}(time.Now())

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

func (mw instrumentingMiddleware) Count(s string) (n int) {
	defer func(begin time.Time) {
		lvs := []string{"method", "count", "error", "false"}
		mw.requestCount.With(lvs...).Add(1)
		mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
		mw.countResult.Observe(float64(n))
	}(time.Now())

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

把它接入我们的服务。

import (
	stdprometheus "github.com/prometheus/client_golang/prometheus"
	kitprometheus "github.com/go-kit/kit/metrics/prometheus"
	"github.com/go-kit/kit/metrics"
)

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

	fieldKeys := []string{"method", "error"}
	requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
		Namespace: "my_group",
		Subsystem: "string_service",
		Name:      "request_count",
		Help:      "Number of requests received.",
	}, fieldKeys)
	requestLatency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
		Namespace: "my_group",
		Subsystem: "string_service",
		Name:      "request_latency_microseconds",
		Help:      "Total duration of requests in microseconds.",
	}, fieldKeys)
	countResult := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
		Namespace: "my_group",
		Subsystem: "string_service",
		Name:      "count_result",
		Help:      "The result of each count method.",
	}, []string{}) // no fields here

	var svc StringService
	svc = stringService{}
	svc = loggingMiddleware{logger, svc}
	svc = instrumentingMiddleware{requestCount, requestLatency, countResult, svc}

	uppercaseHandler := httptransport.NewServer(
		makeUppercaseEndpoint(svc),
		decodeUppercaseRequest,
		encodeResponse,
	)

	countHandler := httptransport.NewServer(
		makeCountEndpoint(svc),
		decodeCountRequest,
		encodeResponse,
	)

	http.Handle("/uppercase", uppercaseHandler)
	http.Handle("/count", countHandler)
	http.Handle("/metrics", promhttp.Handler())
	logger.Log("msg", "HTTP", "addr", ":8080")
	logger.Log("err", http.ListenAndServe(":8080", nil))
}

字符串SVC2

到目前为止,完整的服务是字符串SVC2.

$ go get github.com/go-kit/kit/examples/stringsvc2
$ stringsvc2
msg=HTTP addr=:8080

$ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/uppercase
{"v":"HELLO, WORLD"}

$ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/count
{"v":12}

method=uppercase input="hello, world" output="HELLO, WORLD" err=null took=2.455µs
method=count input="hello, world" n=12 took=743ns

呼叫其他服务

很少有服务存在于真空中而不依赖其他服务。通常,您需要调用其他服务。这就是Go kit闪耀的地方. 我们提供传输中间件,以解决出现的许多问题。

假设我们希望字符串服务调用不同的字符串服务 以满足大写方法。 实际上,将请求代理到另一个服务。 让我们将代理中间件实现为服务中间件,与日志或检测中间件相同。

// proxymw implements StringService, forwarding Uppercase requests to the
// provided endpoint, and serving all other (i.e. Count) requests via the
// next StringService.
type proxymw struct {
	next      StringService     // Serve most requests via this service...
	uppercase endpoint.Endpoint // ...except Uppercase, which gets served by this endpoint
}

客户端终结点

我们得到的端点与我们已经知道的完全相同,但是我们将使用它来调用而不是服务请求客户为了调用客户端端点,我们只需进行一些简单的转换。

func (mw proxymw) Uppercase(s string) (string, error) {
	response, err := mw.uppercase(uppercaseRequest{S: s})
	if err != nil {
		return "", err
	}
	resp := response.(uppercaseResponse)
	if resp.Err != "" {
		return resp.V, errors.New(resp.Err)
	}
	return resp.V, nil
}

现在,要构建这些代理中间件之一,我们将一个代理URL字符串转换为一个端点。

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

func proxyingMiddleware(proxyURL string) ServiceMiddleware {
	return func(next StringService) StringService {
		return proxymw{next, makeUppercaseProxy(proxyURL)}
	}
}

func makeUppercaseProxy(proxyURL string) endpoint.Endpoint {
	return httptransport.NewClient(
		"GET",
		mustParseURL(proxyURL),
		encodeUppercaseRequest,
		decodeUppercaseResponse,
	).Endpoint()
}

服务发现和负载平衡

如果我们只有一个远程服务,那就好了。 但实际上,我们可能会有许多可用的服务实例。 我们希望通过某种服务发现机制来发现它们,并将我们的负载分散到所有这些实例上。 如果这些实例中有任何一个开始表现糟糕,我们就要处理它,不会影响我们自己服务的可靠性。

Go-kit为不同的服务发现系统提供适配器,以获取最新的实例集,这些实例集公开为单个端点。

type Subscriber interface {
	Endpoints() ([]endpoint.Endpoint, error)
}

在内部,订阅服务器使用提供的工厂函数将每个发现的实例字符串(通常是主机:端口)转换为可用的端点。

type Factory func(instance string) (endpoint.Endpoint, error)

到目前为止,我们的工厂函数makeUppercaseProxy只直接调用URL。但是在工厂中也要安装一些安全中间件,如断路器和速率限制器。

var e endpoint.Endpoint
e = makeUppercaseProxy(instance)
e = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(e)
e = kitratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(float64(maxQPS), int64(maxQPS)))(e)

现在我们已经有了一组端点,我们需要选择一个。 负载平衡器包装订阅者,并从多个端点中选择一个端点。 Go kit提供了两个基本的负载平衡器,如果您需要更高级的启发,您可以轻松地编写自己的端点。

type Balancer interface {
	Endpoint() (endpoint.Endpoint, error)
}

现在,我们可以根据一些启发来选择终结点。 我们可以使用它为使用者提供一个单一的、逻辑的、健壮的端点。 重试策略包装负载平衡器,并返回一个可用的端点。 重试策略将重试失败的请求,直到达到最大尝试次数或超时。

func Retry(max int, timeout time.Duration, lb Balancer) endpoint.Endpoint

让我们把最后的代理中间件连接起来。 为了简单起见,我们假设用户将使用一个标志指定多个逗号分隔的实例端点。

func proxyingMiddleware(instances string, logger log.Logger) ServiceMiddleware {
	// If instances is empty, don't proxy. 如果实例为空,不做代理
	if instances == "" {
		logger.Log("proxy_to", "none")
		return func(next StringService) StringService { return next }
	}

	// Set some parameters for our client.我们客户的一些参数
	var (
		qps         = 100                    // beyond which we will return an error
		maxAttempts = 3                      // per request, before giving up
		maxTime     = 250 * time.Millisecond // wallclock time, before giving up
	)

	// Otherwise, construct an endpoint for each instance in the list, and add
	// it to a fixed set of endpoints. In a real service, rather than doing this
	// by hand, you'd probably use package sd's support for your service
	// discovery system.
	var (
		instanceList = split(instances)
		subscriber   sd.FixedSubscriber
	)
	logger.Log("proxy_to", fmt.Sprint(instanceList))
	for _, instance := range instanceList {
		var e endpoint.Endpoint
		e = makeUppercaseProxy(instance)
		e = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(e)
		e = kitratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(float64(qps), int64(qps)))(e)
		subscriber = append(subscriber, e)
	}

	// Now, build a single, retrying, load-balancing endpoint out of all of
	// those individual endpoints.
	balancer := lb.NewRoundRobin(subscriber)
	retry := lb.Retry(maxAttempts, maxTime, balancer)

	// And finally, return the ServiceMiddleware, implemented by proxymw.
	return func(next StringService) StringService {
		return proxymw{next, retry}
	}
}

字符串SVC3

到目前为止,完整的服务是字符串SVC3.

$ go get github.com/go-kit/kit/examples/stringsvc3

$ stringsvc3 -listen=:8001 &
listen=:8001 caller=proxying.go:25 proxy_to=none
listen=:8001 caller=main.go:72 msg=HTTP addr=:8001

$ stringsvc3 -listen=:8002 &
listen=:8002 caller=proxying.go:25 proxy_to=none
listen=:8002 caller=main.go:72 msg=HTTP addr=:8002

$ stringsvc3 -listen=:8003 &
listen=:8003 caller=proxying.go:25 proxy_to=none
listen=:8003 caller=main.go:72 msg=HTTP addr=:8003

$ stringsvc3 -listen=:8080 -proxy=localhost:8001,localhost:8002,localhost:8003
listen=:8080 caller=proxying.go:29 proxy_to="[localhost:8001 localhost:8002 localhost:8003]"
listen=:8080 caller=main.go:72 msg=HTTP addr=:8080
$ for s in foo bar baz ; do curl -d"{\"s\":\"$s\"}" localhost:8080/uppercase ; done
{"v":"FOO"}
{"v":"BAR"}
{"v":"BAZ"}
listen=:8001 caller=logging.go:28 method=uppercase input=foo output=FOO err=null took=5.168µs
listen=:8080 caller=logging.go:28 method=uppercase input=foo output=FOO err=null took=4.39012ms
listen=:8002 caller=logging.go:28 method=uppercase input=bar output=BAR err=null took=5.445µs
listen=:8080 caller=logging.go:28 method=uppercase input=bar output=BAR err=null took=2.04831ms
listen=:8003 caller=logging.go:28 method=uppercase input=baz output=BAZ err=null took=3.285µs
listen=:8080 caller=logging.go:28 method=uppercase input=baz output=BAZ err=null took=1.388155ms

高级主题

穿插上下文

context对象用于在单个请求的范围内跨概念边界传递信息。 在我们的示例中,我们还没有将上下文贯穿到业务逻辑中。 但这几乎总是一个好主意。 它允许您在业务逻辑和中间件之间传递请求范围内的信息, 这是实现更多功能所必需的复杂的任务,比如细粒度的分布式跟踪注释。

具体地说,这意味着您的业务逻辑接口看起来像

type MyService interface {
	Foo(context.Context, string, int) (string, error)
	Bar(context.Context, string) error
	Baz(context.Context) (int, error)
}

分布式跟踪

一旦您的基础设施发展到一定规模之后,通过多个服务跟踪请求就变得非常重要,这样您就可以识别热点并排除故障。 请参阅[包跟踪 ] package tracing 了解更多信息。

创建客户端包

可以使用Go kit为您的服务创建一个客户端包, 以便从其他Go程序更轻松地使用您的服务。 实际上,您的客户端包将提供服务接口的实现,该接口使用特定的传输调用远程服务实例。 请参阅 addsvc/cmd/addcli 或 package profilesvc/clien 例如。

关于go-kit该如何入门就分享到这里啦,希望上述内容能够让大家有所提升。如果想要学习更多知识,请大家多多留意小编的更新。谢谢大家关注一下亿速云网站!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI