当前位置: 首页 > 工具软件 > kit > 使用案例 >

kit框架详解(基于go-kit)

邹坚壁
2023-12-01

kit是一个能够自动生成基于go-kit组件的框架,可以帮助我们快速创建微服务,而让我们只关注自己的业务逻辑和中间件实现。

仓库地址 github.com/kujtimiihoxha/kit

1 生成命令

// 1. 创建一个service模板
kit new service hello //kit n s hello
------
// 2. 编辑hello/pkg/service/service.go添加service
type HelloService interface {
	// Add your methods here
	// e.x: Foo(ctx context.Context,s string)(rs string, err error)
	Foo(ctx context.Context, s string) (rs string, err error)
	Test(ctx context.Context, s string, i int64) (string, error)
}
// 3. 创建transport、endpoint、service层
kit g s hello --dmw
//生成service目录结构
hello
├── cmd
│	├── service
│	│   ├── service.go
│	│   └── service_gen.go 
│	└── main.go
└── pkg
	├── endpoint //endpoint layer
	│   ├── endpoint.go
	│   ├── endpoint_gen.go
	│   └── middleware.go
    ├── http //transport layer
	│   ├── handler.go
	│   └── handler_gen.go
	└── service //service layer
	    ├── middleware.go
	    └── service.go
// 4. 生成client library
kit g c hello
// 生成client目录结构
hello
└── client
	└── http
	   └── http.go  //client library

2 代码解释(http)

主要讲讲kit生成的transport层、endpoint层、service层分别是怎么实现的,又是怎么协同工作的。最后解释下service/middleware endpoint/middleware的工作原理。

go-kit的调用关系:
client访问微服务http server;经过transport层是decode获得http包request数据;然后交个endpoint处理,endpoint在go-kit是一个重要的概念,代表了一个可执行的服务,表现形式是一个函数type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error);endpoint将request传给业务函数处理,返回response,最后在一层层返回给client。

这里顺便解释下middleware,endpoint的middleware会在endpoint调用业务函数之前处理,service的middleware由service逻辑自己决定。

client
server
decode
encode
call
return
host:port/request
response
client
transport
endpoint
service

下面的解释基于上面服务里面的两个业务:Foo/Test

transport层

http通过ListenAndServer监听连接,连接产生时转交给http.Handler处理,transport的核心就是将endpoint、decodeReq、encodeResp告诉http.Handler

比如下面的代码,处理路由为/foo的业务,"github.com/go-kit/kit/transport/http".NewServer创建一个server,里面包含一个http.Handler的实现。

// makeFooHandler creates the handler logic
func makeFooHandler(m *http.ServeMux, endpoints endpoint.Endpoints, options []http1.ServerOption) {
	m.Handle("/foo", http1.NewServer(endpoints.FooEndpoint, decodeFooRequest, encodeFooResponse, options...))
}

http.Handler的实现

在文件/go-kit/kit/transport/http/server.go 中的ServeHTTP(w http.ResponseWriter, r *http.Request),核心代码为:

// Server wraps an endpoint and implements http.Handler.
type Server struct {
	e            endpoint.Endpoint
	dec          DecodeRequestFunc
	enc          EncodeResponseFunc
	before       []RequestFunc
	after        []ServerResponseFunc
	errorEncoder ErrorEncoder
	finalizer    []ServerFinalizerFunc
	logger       log.Logger
}

// ServeHTTP implements http.Handler.
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context() //request.Context

	request, err := s.dec(ctx, r) //decode request

	response, err := s.e(ctx, request) //endpoint function call

	s.enc(ctx, w, response) //encode response
}

endpoint层

endpoint本质是一个处理函数类型,代表了一个RPC方法,每个服务需要针对服务进行具体实现
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error),下面是一个实现的例子:

func MakeFooEndpoint(s service.HelloService) endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (interface{}, error) {
		req := request.(FooRequest)
		rs, err := s.Foo(ctx, req.S)
		return FooResponse{
			Err: err,
			Rs:  rs,
		}, nil
	}
}

然后通过传入service创建endpoint:

func New(s service.HelloService) Endpoints {
	eps := Endpoints{
		FooEndpoint:  MakeFooEndpoint(s),
		TestEndpoint: MakeTestEndpoint(s),
	}
	return eps
}

然后传入一个service创建一个endpoint实例:

svc := service.New() // service 部分会介绍
eps := endpoint.New(svc)

最后将endpoint实例传给上面的"github.com/go-kit/kit/transport/http".NewServer(),创建一个http.Handler()实现。

service层

service.go应该是go-kit里面最好理解的一层,也是定义业务最纯粹的地方,所有的业务定义在RPC接口HelloService中。

type HelloService interface {
	Foo(ctx context.Context, s string) (rs string, err error)
	Test(ctx context.Context, s string, i int64) (string, error)
}

实现接口

type basicHelloService struct{}

func (b *basicHelloService) Foo(ctx context.Context, s string) (rs string, err error) {
	return rs, err
}
func (b *basicHelloService) Test(ctx context.Context, s string, i int64) (s0 string, e1 error) {
	return s0, e1
}

创建service

func New() HelloService {
	return &basicHelloService{}
}

启动一个微服务的详细流程

import http1 "github.com/go-kit/kit/transport/http"

// 1. 创建一个service,此service为当前目录下service包
var svc HelloService = service.New()

// 2. 创建一个Endpoints,此endpoint为当前目录下的endpoint包
eps := endpoint.New(svc) 

// 3. 创建一个microserver
//   3.1 创建http.Handler 
//这里省略(decodeFooRequest和encodeFooResponse实现)
// type DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error)
// type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error
serv := http1.NewServer(eps.FooEndpoint, decodeFooRequest, encodeFooResponse, options...)
//   3.2 创建Mux server
mux := http.NewServeMux()
mux.Handle("/foo", serv)
//   3.3 启动微服务server
http.ListenAndServe("localhost:8080", mux)

3 middleware(service/endpoint)

middleware模块应该是go-kit最强大的地方,middleware通常是一个chain处理,比如go-kit中对于endpoint层的Middleware的定义endpoint.Middleware => type Middleware func(Endpoint) Endpoint。endpoint的middleware通常是对request做一些预处理,比如对header中的Authorization的处理。

对于service层的Middleware的定义需要根据service本身的业务接口定义,比如HelloService的Middleware可以定义为type Middleware func(HelloService) HelloService。go-kit提供了一些供service用的middleware组件。
通常service Middleware的实现是用对基类HelloService的扩展来实现的,比如

type loggingMiddleware struct {
	logger log.Logger
	next   HelloService
}

这样就可以传入一个Base HelloService而返回一个loggingMiddleware,比如func(HelloService) loggingMiddleware

对于transport层没有明确是说明是middleware,但是可以添加option,比如记录log,trace等。

含有middleware的endpoint和service是go-kit的服务的标准创建方式,也就是说上面的介绍为了理解简化了创建流程,接下来将详细介绍含有middleware的服务创建。

包含middleware的服务创建流程

由于middleware的创建有些绕,这里先给出一个具体的service的创建的主要流程,将忽略具体的业务的实现,下面的介绍将围绕该流程展开,微服务包含两个方法Foo Test


启动一个带middleware微服务的详细流程

import http1 "github.com/go-kit/kit/transport/http"

// 1. 创建一个Service Middleware类型定义
type Middleware func(HelloService) HelloService

// 2. 扩展一个基于HelloService的middleware,并重写HelloService的所有RPC接口
type loggingMiddleware struct {
	logger log.Logger
	next   HelloService
}
func (l loggingMiddleware) Foo(ctx context.Context, s string) (rs string, err error) {
	// something
	return l.next.Foo(ctx, s)
}
func (l loggingMiddleware) Test(ctx context.Context, s string, i int64) (s0 string, e1 error) {
	// somethin
	return l.next.Test(ctx, s, i)
}

// 3. 实例化一个Service Middleware,并append到一个[]Middleware
var logger log.Logger
logMW := func(next HelloService) HelloService {
		    return &loggingMiddleware{logger, next}
    	}

// 4. 创建一个service,这里需要重新定义service.New()为service.New(mws []Middleware),传入的为需要处理的middleware,后面会具体介绍这些middleware是怎么串起来的形成一个service chain的。
mws := []Middleware{logMW}
var svc HelloService = service.New(mws)

// 5. 实例化一个endpoint.Middleware => type Middleware func(Endpoint) Endpoint
logeMW := func(next endpoint.Endpoint) endpoint.Endpoint {
		return func(ctx context.Context, request interface{}) (response interface{}, err error) {
			// something TODO about log
			// ctx = ...
			// request = ...
			logger.Log("...")
			return next(ctx, request)
		}
	}

// 6. 创建一个Endpoint,这里需要重新定义`endpoint.New(service.HelloService)`为`endpoint.New(service.HelloService,map[string][]endpoint.Middleware)`,传入一个`map[string][]endpoint.Middleware{}`。

emws := map[string][]endpoint.Middleware{}
emws["Foo"] = []endpoint.Middleware{logeMW} // 这里的"Foo"是HelloService里面定义的RPC方法,也可以更多的类似logeMW的实现;
//如果"Test"也有endpoint.Middleware,则也应该添加
// emws["Test"] = []endpoint.Middleware{logeMW}
eps := endpoint.New(svc,emws)

// eps为Endpoints类型,即所有Service服务接口对应的endpoint.Endpoint集合:
// type Endpoints struct {
//  	FooEndpoint  endpoint.Endpoint
//  	TestEndpoint endpoint.Endpoint
// }

// 7. 创建一个microserver
//   7.1 创建http.Handler 
//这里省略(decodeFooRequest和encodeFooResponse实现)
// type DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error)
// type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error
servfoo := http1.NewServer(eps.FooEndpoint, decodeFooRequest, encodeFooResponse, options...)
servtest := http1.NewServer(eps.TestEndpoint, decodeFooRequest, encodeFooResponse, options...)
//   7.2 创建Mux server
mux := http.NewServeMux()
mux.Handle("/foo", servfoo)
mux.Handle("/test", servtest)
//   7.3 启动微服务server
http.ListenAndServe("localhost:8080", mux)

endpoint/middleware

endpoint.Endpoint定义:

// Endpoint is the fundamental building block of servers and clients.
// It represents a single RPC method.
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)

endpoint.Middleware定义:

// Middleware is a chainable behavior modifier for endpoints.
type Middleware func(Endpoint) Endpoint

所以Middleware的实现方式可以理解为是一个输入输出链模型:

http/req
ctx,req
ctx1,req1...ctxN,reqN
客户端请求
serverHTTP
ctx1,req1
response

其实kit对endpoint的实现是比较巧妙的,具体体现在endpoint.Newendpoint.Middleware,看下他们的实现:

// New returns a Endpoints struct that wraps the provided service, and wires in all of the
// expected endpoint middlewares
func New(s service.HelloService, mdw map[string][]endpoint.Middleware) Endpoints {
	eps := Endpoints{
		FooEndpoint:  MakeFooEndpoint(s),
		TestEndpoint: MakeTestEndpoint(s),
	}
	for _, m := range mdw["Foo"] {
		eps.FooEndpoint = m(eps.FooEndpoint)
	}
	for _, m := range mdw["Test"] {
		eps.TestEndpoint = m(eps.TestEndpoint)
	}
	return eps
}
func(next endpoint.Endpoint) endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) response interface{}, err error) {
		// something need to do
		return next(ctx, request)
	}
}

来分析下核心代码:

第一段代码中的:
	for _, m := range mdw["Foo"] {
		eps.FooEndpoint = m(eps.FooEndpoint)
	}
	//--------
第二段代码中的:
	return next(ctx, request)

也就是说在New创建的时候,会创建一个endpoint.Endpoint的链式调用,最后创建的将会先执行。当收到http的请求时,http.Handler会调用endpoint.Endpoint(ctx,req),从而依次往后调,最后将调用业务接口的Endpoint。

service/middleware

service的middleware和endpoint的middleware是类似的,也是一个链式调用,先来看几个概念:

Middleware的定义:

// Middleware describes a service middleware.
type Middleware func(HelloService) HelloService

具体某个Middleware的定义:

type loggingMiddleware struct {
	logger log.Logger
	next   HelloService
}

我们来看看其中实现的奥秘,共三段代码:

loggingMiddleware包含的HelloService接口的实现:

func (l loggingMiddleware) Foo(ctx context.Context, s string) (rs string, err error) {
	defer func() {
		l.logger.Log("method", "Foo", "s", s, "rs", rs, "err", err)
	}()
	return l.next.Foo(ctx, s)
}
func (l loggingMiddleware) Test(ctx context.Context, s string, i int64) (s0 string, e1 error) {
	defer func() {
		l.logger.Log("method", "Test", "s", s, "i", i, "s0", s0, "e1", e1)
	}()
	return l.next.Test(ctx, s, i)
}

Middleware的实现:

var logger log.Logger
func(next HelloService) HelloService {
		return &loggingMiddleware{logger, next}
	}

创建一个service的时候

// New returns a HelloService with all of the expected middleware wired in.
func New(middleware []Middleware) HelloService {
	var svc HelloService = NewBasicHelloService()
	for _, m := range middleware {
		svc = m(svc)
	}
	return svc
}

来分析下核心代码:

第一段代码中的:
	return l.next.Foo(ctx, s)
	//--------
第二段代码中的:
	return &loggingMiddleware{logger, next}
	//--------
第三段代码中的:
    for _, m := range middleware {
		svc = m(svc)
	}

也就是说每个Middleware会接收一个老的HelloService返回一个包含老的HelloService的新的HelloService,同时新的HelloService的接口实现会调用老的HelloService的接口return l.next.Foo(ctx, s)
在创建的时候,会依次调用,从而将这些Middleware串起来。

4 Client

kit g c hello
client的连接具体参考生成client库。

package main

import (
	"context"
	"fmt"

	client "hello/client/http"
	"github.com/go-kit/kit/transport/http"
)

func main() {
	svc, err := client.New("http://localhost:8081", map[string][]http.ClientOption{})
	if err != nil {
		panic(err)
	}

	r, err := svc.Foo(context.Background(), "hello")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Println("Result:", r)
}

5 多协议支持

go-kit 除了支持http还支持grpc,amqp,nats,thrift。比如,grpc使用kit g s hello -t grpc,nats使用kit g s hello -t nats

这些的实现框架与http是一致的。

 类似资料: