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逻辑自己决定。

下面的解释基于上面服务里面的两个业务: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的实现方式可以理解为是一个输入输出链模型:

其实kit对endpoint的实现是比较巧妙的,具体体现在endpoint.New和endpoint.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是一致的。

最后编辑: Simon  文档更新时间: 2020-09-08 11:22   作者:Simon