go-kit该如何入门

go-kit该如何入门

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

第一原则

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

go-kit该如何入门

你的商业逻辑

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

//StringServiceprovidesoperationsonstrings.import"context"typeStringServiceinterface{Uppercase(string)(string,error)Count(string)int}

该接口将有一个实现。

import("context""errors""strings")typestringServicestruct{}func(stringService)Uppercase(sstring)(string,error){ifs==""{return"",ErrEmpty}returnstrings.ToUpper(s),nil}func(stringService)Count(sstring)int{returnlen(s)}//ErrEmptyisreturnedwheninputstringisemptyvarErrEmpty=errors.New("Emptystring")

请求和响应

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

typeuppercaseRequeststruct{Sstring`json:"s"`}typeuppercaseResponsestruct{Vstring`json:"v"`Errstring`json:"err,omitempty"`//errorsdon'tJSON-marshal,soweuseastring}typecountRequeststruct{Sstring`json:"s"`}typecountResponsestruct{Vint`json:"v"`}

端点

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

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

typeEndpointfunc(ctxcontext.Context,requestinterface{})(responseinterface{},errerror)

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

import("context""github.com/go-kit/kit/endpoint")funcmakeUppercaseEndpoint(svcStringService)endpoint.Endpoint{returnfunc(_context.Context,requestinterface{})(interface{},error){req:=request.(uppercaseRequest)v,err:=svc.Uppercase(req.S)iferr!=nil{returnuppercaseResponse{v,err.Error()},nil}returnuppercaseResponse{v,""},nil}}funcmakeCountEndpoint(svcStringService)endpoint.Endpoint{returnfunc(_context.Context,requestinterface{})(interface{},error){req:=request.(countRequest)v:=svc.Count(req.S)returncountResponse{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")funcmain(){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))}funcdecodeUppercaseRequest(_context.Context,r*http.Request)(interface{},error){varrequestuppercaseRequestiferr:=json.NewDecoder(r.Body).Decode(&request);err!=nil{returnnil,err}returnrequest,nil}funcdecodeCountRequest(_context.Context,r*http.Request)(interface{},error){varrequestcountRequestiferr:=json.NewDecoder(r.Body).Decode(&request);err!=nil{returnnil,err}returnrequest,nil}funcencodeResponse(_context.Context,whttp.ResponseWriter,responseinterface{})error{returnjson.NewEncoder(w).Encode(response)}

字符串SVC1

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

$gogetgithub.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 文件。

typeStringServicetypestringServicefuncUppercasefuncCountvarErrEmpty

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

funcmakeUppercaseEndpointfuncmakeCountEndpointfuncdecodeUppercaseRequestfuncdecodeCountRequestfuncencodeResponsetypeuppercaseRequesttypeuppercaseResponsetypecountRequesttypecountResponse

传输日志

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

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

typeMiddlewarefunc(Endpoint)Endpoint

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

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

funcloggingMiddleware(loggerlog.Logger)Middleware{returnfunc(nextendpoint.Endpoint)endpoint.Endpoint{returnfunc(ctxcontext.Context,requestinterface{})(interface{},error){logger.Log("msg","callingendpoint")deferlogger.Log("msg","calledendpoint")returnnext(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{}varuppercaseendpoint.Endpointuppercase=makeUppercaseEndpoint(svc)uppercase=loggingMiddleware(log.With(logger,"method","uppercase"))(uppercase)varcountendpoint.Endpointcount=makeCountEndpoint(svc)count=loggingMiddleware(log.With(logger,"method","count"))(count)uppercaseHandler:=httptransport.NewServer(uppercase,//...)countHandler:=httptransport.NewServer(count,//...)

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

应用程序日志记录

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

typeloggingMiddlewarestruct{loggerlog.LoggernextStringService}func(mwloggingMiddleware)Uppercase(sstring)(outputstring,errerror){deferfunc(begintime.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(mwloggingMiddleware)Count(sstring)(nint){deferfunc(begintime.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")funcmain(){logger:=log.NewLogfmtLogger(os.Stderr)varsvcStringServicesvc=stringService{}svc=loggingMiddleware{logger,svc}//...uppercaseHandler:=httptransport.NewServer(makeUppercaseEndpoint(svc),//...)countHandler:=httptransport.NewServer(makeCountEndpoint(svc),//...)}

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

应用程序仪表

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

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

typeinstrumentingMiddlewarestruct{requestCountmetrics.CounterrequestLatencymetrics.HistogramcountResultmetrics.HistogramnextStringService}func(mwinstrumentingMiddleware)Uppercase(sstring)(outputstring,errerror){deferfunc(begintime.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(mwinstrumentingMiddleware)Count(sstring)(nint){deferfunc(begintime.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")funcmain(){logger:=log.NewLogfmtLogger(os.Stderr)fieldKeys:=[]string{"method","error"}requestCount:=kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{Namespace:"my_group",Subsystem:"string_service",Name:"request_count",Help:"Numberofrequestsreceived.",},fieldKeys)requestLatency:=kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{Namespace:"my_group",Subsystem:"string_service",Name:"request_latency_microseconds",Help:"Totaldurationofrequestsinmicroseconds.",},fieldKeys)countResult:=kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{Namespace:"my_group",Subsystem:"string_service",Name:"count_result",Help:"Theresultofeachcountmethod.",},[]string{})//nofieldsherevarsvcStringServicesvc=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.

$gogetgithub.com/go-kit/kit/examples/stringsvc2$stringsvc2msg=HTTPaddr=: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=uppercaseinput="hello,world"output="HELLO,WORLD"err=nulltook=2.455µsmethod=countinput="hello,world"n=12took=743ns

呼叫其他服务

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

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

//proxymwimplementsStringService,forwardingUppercaserequeststothe//providedendpoint,andservingallother(i.e.Count)requestsviathe//nextStringService.typeproxymwstruct{nextStringService//Servemostrequestsviathisservice...uppercaseendpoint.Endpoint//...exceptUppercase,whichgetsservedbythisendpoint}

客户端终结点

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

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

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

import(httptransport"github.com/go-kit/kit/transport/http")funcproxyingMiddleware(proxyURLstring)ServiceMiddleware{returnfunc(nextStringService)StringService{returnproxymw{next,makeUppercaseProxy(proxyURL)}}}funcmakeUppercaseProxy(proxyURLstring)endpoint.Endpoint{returnhttptransport.NewClient("GET",mustParseURL(proxyURL),encodeUppercaseRequest,decodeUppercaseResponse,).Endpoint()}

服务发现和负载平衡

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

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

typeSubscriberinterface{Endpoints()([]endpoint.Endpoint,error)}

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

typeFactoryfunc(instancestring)(endpoint.Endpoint,error)

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

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

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

typeBalancerinterface{Endpoint()(endpoint.Endpoint,error)}

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

funcRetry(maxint,timeouttime.Duration,lbBalancer)endpoint.Endpoint

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

funcproxyingMiddleware(instancesstring,loggerlog.Logger)ServiceMiddleware{//Ifinstancesisempty,don'tproxy.如果实例为空,不做代理ifinstances==""{logger.Log("proxy_to","none")returnfunc(nextStringService)StringService{returnnext}}//Setsomeparametersforourclient.我们客户的一些参数var(qps=100//beyondwhichwewillreturnanerrormaxAttempts=3//perrequest,beforegivingupmaxTime=250*time.Millisecond//wallclocktime,beforegivingup)//Otherwise,constructanendpointforeachinstanceinthelist,andadd//ittoafixedsetofendpoints.Inarealservice,ratherthandoingthis//byhand,you'dprobablyusepackagesd'ssupportforyourservice//discoverysystem.var(instanceList=split(instances)subscribersd.FixedSubscriber)logger.Log("proxy_to",fmt.Sprint(instanceList))for_,instance:=rangeinstanceList{vareendpoint.Endpointe=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,buildasingle,retrying,load-balancingendpointoutofallof//thoseindividualendpoints.balancer:=lb.NewRoundRobin(subscriber)retry:=lb.Retry(maxAttempts,maxTime,balancer)//Andfinally,returntheServiceMiddleware,implementedbyproxymw.returnfunc(nextStringService)StringService{returnproxymw{next,retry}}}

字符串SVC3

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

$gogetgithub.com/go-kit/kit/examples/stringsvc3$stringsvc3-listen=:8001&listen=:8001caller=proxying.go:25proxy_to=nonelisten=:8001caller=main.go:72msg=HTTPaddr=:8001$stringsvc3-listen=:8002&listen=:8002caller=proxying.go:25proxy_to=nonelisten=:8002caller=main.go:72msg=HTTPaddr=:8002$stringsvc3-listen=:8003&listen=:8003caller=proxying.go:25proxy_to=nonelisten=:8003caller=main.go:72msg=HTTPaddr=:8003$stringsvc3-listen=:8080-proxy=localhost:8001,localhost:8002,localhost:8003listen=:8080caller=proxying.go:29proxy_to="[localhost:8001localhost:8002localhost:8003]"listen=:8080caller=main.go:72msg=HTTPaddr=:8080

$forsinfoobarbaz;docurl-d"{\"s\":\"$s\"}"localhost:8080/uppercase;done{"v":"FOO"}{"v":"BAR"}{"v":"BAZ"}

listen=:8001caller=logging.go:28method=uppercaseinput=foooutput=FOOerr=nulltook=5.168µslisten=:8080caller=logging.go:28method=uppercaseinput=foooutput=FOOerr=nulltook=4.39012mslisten=:8002caller=logging.go:28method=uppercaseinput=baroutput=BARerr=nulltook=5.445µslisten=:8080caller=logging.go:28method=uppercaseinput=baroutput=BARerr=nulltook=2.04831mslisten=:8003caller=logging.go:28method=uppercaseinput=bazoutput=BAZerr=nulltook=3.285µslisten=:8080caller=logging.go:28method=uppercaseinput=bazoutput=BAZerr=nulltook=1.388155ms

高级主题

穿插上下文

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

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

typeMyServiceinterface{Foo(context.Context,string,int)(string,error)Bar(context.Context,string)errorBaz(context.Context)(int,error)}

分布式跟踪

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

创建客户端包

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

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

发布于 2022-01-17 21:58:37
收藏
分享
海报
0 条评论
49
上一篇:Google的AI七项原则分别是什么 下一篇:如何进行AWS、Google和Azure容器服务的分析
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码