您好,登錄后才能下訂單哦!
這篇文章跟大家分析一下“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.
$ 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.
$ 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.
$ 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該如何入門就分享到這里啦,希望上述內容能夠讓大家有所提升。如果想要學習更多知識,請大家多多留意小編的更新。謝謝大家關注一下億速云網站!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。