Last updated on February 22, 2024 am
Go-kit框架
Go-kit
是一个功能丰富、易于使用的分布式微服务框架,旨在帮助开发者构建健壮、可维护和可测试的分布式系统。它通过提供一系列可组合的组件,解决了分布式系统中的常见问题,使开发者能够专注于业务逻辑。Go-kit
的核心理念是通过可插拔的组件来实现微服务的功能,这些组件包括服务发现、负载均衡、请求追踪、日志记录和监控等。
Go-kit基本架构
Go-kit
包含了一些基本的组件,如服务端点(Endpoint)、传输层(Transport)、服务发现、负载均衡等。服务端点是
Go-kit 中微服务的基本构建块,用于定义 RPC
消息模式。传输层提供了将特定序列化算法绑定到端点的辅助方法,目前支持
JSON 和 HTTP 协议。
Go-kit的架构来源:https://github.com/go-kit/kit
基本的Go-kit的三层架构
Transport
主要负责与HTTP、gRPC、thrift等相关的逻辑,提供端到端的通信服务,不同的传输协议来实现这些功能,最常见的协议是TCP(传输控制协议)和UDP(用户数据报协议)。
Endpoint
定义Request和Response格式,并可以使用装饰器包装函数,以此来实现各种中间件嵌套。
Service
主要职责是处理业务逻辑,也就是应用程序的核心功能和规则,这里就是我们的业务类、接口等。
Go-kit的demo
接下来我们将给出一个简单的demo(对输入的字符串大写转换和字符串个数计数的功能)来对Go-kit的基本架构的实现和功能展示:
Service层
首先这个层会给出业务的逻辑实现,核心功能和规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package services
import ( "errors" "strings" )
type StringService interface { Uppercase(string) (string, error) Count(string) int }
type SStringService struct{}
func (SStringService) Uppercase(s string) (string, error) { if s == "" { return "", errors.New("Input string is empty") } return strings.ToUpper(s), nil }
func (SStringService) Count(s string) int { return len(s) }
|
实现功能service的方式一般总结来说是:
- 定义服务接口Interface{},再定义结构体来实现这个接口的函数来实现继承
- 具体的函数内容就表示了业务的实际功能
Endpoint层
这部分会给出不同的endpoint接口的类型定义,使用不同的type定义request和response的json格式类型,用于后续的变量的选择命名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package endpoints
import ( "context" "github.com/go-kit/kit/endpoint" "go-kit-test/services" )
type uppercaseRequest struct { S string `json:"s"` }
type uppercaseResponse struct { V string `json:"v"` Err string `json:"err,omitempty"` }
type countRequest struct { S string `json:"s"` }
type countResponse struct { V int `json:"v"` }
|
定义总的Endpoints接口,type Endpoints
这个地方将会用于后续的对象的创建,func MakeEndpoints
函数将被其他的函数进行调用用于创建内部的端点对象,其中在创建的过程中会调用以下的函数来分别创建两个功能的端口类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| type Endpoints struct { UppercaseEndpoint endpoint.Endpoint CountEndpoint endpoint.Endpoint }
func MakeEndpoints(svc services.StringService) Endpoints { return Endpoints{ UppercaseEndpoint: MakeUppercaseEndpoint(svc), CountEndpoint: MakeCountEndpoint(svc), } }
func MakeUppercaseEndpoint(svc services.StringService) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(uppercaseRequest) result, err := svc.Uppercase(req.S) if err != nil { return uppercaseResponse{result, err.Error()}, nil } return uppercaseResponse{result, ""}, nil } }
func MakeCountEndpoint(svc services.StringService) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(countRequest) result := svc.Count(req.S) return countResponse{result}, nil } }
|
Transport层
主体的实现框架,分别给出了两个不同服务接口的response
和request
的数据类型这部分给出json
格式
http.NewServeMux()
返回一个新的http.ServeMux
,这是一个用于处理HTTP请求的请求轮询器(request
router)。接下来,为/uppercase和/count
路径分别配置了新的httptransport.NewServer
服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| type uppercaseRequest struct { S string `json:"s"` }
type uppercaseResponse struct { V string `json:"v"` Err string `json:"err,omitempty"` }
type countRequest struct { S string `json:"s"` }
type countResponse struct { V int `json:"v"` }
func MakeHTTPHandler(endpoints endpoints.Endpoints) http.Handler { options := []httptransport.ServerOption{ httptransport.ServerErrorEncoder(errorEncoder), } m := http.NewServeMux() m.Handle("/uppercase", httptransport.NewServer( endpoints.UppercaseEndpoint, decodeUppercaseRequest, encodeResponse, options..., )) m.Handle("/count", httptransport.NewServer( endpoints.CountEndpoint, decodeCountRequest, encodeResponse, options..., ))
return m }
|
注意在定义endpoint的时候,要对request编码,对response解码
具体的解码和编码的过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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) }
|
Main主层
这一层是最外层的调用层:go run main.go
,这一层的逻辑实现主要是:
- 创建一个service服务接口对象
- 将接口对象作为参数传入并生成endpoint相应的端点
httpHandler := transports.MakeHTTPHandler(eps)
这一步是产生http
的根据提供的Endpoint配置来创建一个HTTP处理程序,这个处理程序负责处理来自客户端的HTTP请求,并将响应返回给客户端
- 对于任何来到根路径(
"/"
)的请求,都应该由httpHandler
来处理。httpHandler是在之前的步骤中创建的HTTP处理程序,它知道如何处理HTTP
请求并生成响应。这意味着当你访问服务器的根URL(例如,http://localhost:8080/)
时,httpHandler中定义的处理逻辑将会被执行
- 其中端口8080可以根据自己的需求进行指定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import ( "go-kit-test/endpoints" "go-kit-test/services" "go-kit-test/transports" "net/http" "os" )
func main() { svc := services.SStringService{} eps := endpoints.MakeEndpoints(svc) httpHandler := transports.MakeHTTPHandler(eps)
http.Handle("/", httpHandler) port := ":8080" if p := os.Getenv("PORT"); p != "" { port = ":" + p }
println("Service listening on port" + port) http.ListenAndServe(port, nil) }
|
Go-kit+Gin架构
Go-kit是一个微服务工具集,它提供了服务发现、负载均衡、请求追踪、日志记录和监控等功能,帮助开发者构建健壮、可维护、可测试的分布式系统。
Gin是一个轻量级的Go
Web框架,它提供了一个优雅的API和中间件支持,使得编写Web应用程序变得更加快速和简单
将两者进行结合:Gin负责处理HTTP请求和响应,提供路由和中间件功能,而go-kit则可以处理服务间的通信,提供服务发现、负载均衡等分布式系统功能。go-kit和Gin的结合使用,可以使得Go
Web应用的开发更加高效和模块化,有助于构建高性能、可伸缩且易于维护的分布式系统。
ApiService实现
我们需要利用gin对web端的应用程序进行实现,因此在这个架构之下我们设计的框架为:

其中services、endpoints、transports
的功能和原来的go-kit
基本一样,而handler
主要处理的是gin的关于web
端内容的控制,下面给出具体的实现
Service层
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package services type IUserService interface { GetName(userId int) string }
type UserService struct { }
func (s UserService) GetName(userId int) string { if userId == 101 { return "calvin" } return "guest" }
|
这个地方给出了具体的业务的实现接口功能
Endpoint层
在实现端口的过程中一般都需要对相关的type
类型进行定义和对应的json
格式的描述,函数GetUserEndPoint
返回一个endpoint.Endpoint
的类型,这个函数用于后续的调用产生endpoint
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package endpoints
import ( "ApiService/services" "context" "github.com/go-kit/kit/endpoint" ) type UserRequest struct { Uid int `json:"uid"` }
type UserResponse struct { Result string `json:"result"` } func GetUserEndPoint(userService services.IUserService) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { r := request.(UserRequest) result := userService.GetName(r.Uid) return UserResponse{Result: result}, nil } }
|
Transport层
主要做的事情就是对request
和response
的解码和编码过程,因为通过对request
的解码才能转换成能识别的json格式,才能进行业务端的使用,而后面的编码是为了能够进入web
端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package transports import ( "ApiService/endpoints" "context" "errors" httptransport "github.com/go-kit/kit/transport/http" "net/http" "strconv" )
func DecodeUserRequest(ctx context.Context, r *http.Request) (interface{}, error) { if r.URL.Query().Get("uid") != "" { uid, _ := strconv.Atoi(r.URL.Query().Get("uid")) return endpoints.UserRequest{Uid: uid}, nil } return nil, errors.New("参数错误") }
func EncodeUserResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { return httptransport.EncodeJSONResponse(context.Background(), w, response) }
|
Handle层
这一层主要的作用就是利用gin来对web的实现,传递的参数分别是
handler := httptransport.NewServer(endpoint, transports.DecodeUserRequest, transports.EncodeUserResponse)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package handler
import ( . "ApiService/endpoints" . "ApiService/services" "ApiService/transports" "github.com/gin-gonic/gin" httptransport "github.com/go-kit/kit/transport/http" )
func MakeGinHandler(svc IUserService) *gin.Engine { r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery())
endpoint := GetUserEndPoint(svc)
handler := httptransport.NewServer(endpoint, transports.DecodeUserRequest, transports.EncodeUserResponse) r.GET("/hello", gin.WrapH(handler)) return r }
|
注意在调用包的时候:
. "ApiService/endpoints"
那么在引用函数的时候就不需要endpoints.XXX()
"ApiService/transports"
那么在引用函数的时候需要transports.XXX()
Main层
这一层的实现是通过以下三个步骤实现的:
- 创建go -kit服务
- 创建gin路由并绑定服务
- 启动gin服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main import ( . "ApiService/handler" . "ApiService/services" "log" ) func main() { userSvc := UserService{} r := MakeGinHandler(userSvc) if err := r.Run(":8080"); err != nil { log.Fatal(err) } }
|
Gin统一收集路由
统一收集路由(unified routing
collection)通常指的是将所有的路由配置集中到一个地方管理,这样可以更方便地管理和维护路由。Gin
本身已经提供了一个路由器(Router)对象,用于收集和管理所有的路由。统一收集路由的好处包括:
- 易于维护:所有的路由都在一个地方,便于查看和修改。
- 路由重用:可以定义全局中间件,这些中间件会在所有路由上应用,而不需要在每个路由上重复编写。
- 更好的错误处理:可以在统一的地方处理路由错误,例如未找到路由或方法不允许等。
- 性能优化:Gin 路由器会预编译路由,这可以提高处理请求的效率
局部的路由规则初始化
首先定义局部的具体路由规则,这里给出例子
GetUserRoutes
和GetVideoRoutes
,在 Gin
路由器中注册与视频相关的路由规则。这个函数接受一个*gin.RouterGroup
参数
它创建了一个新的子路由组
video,这是通过调用route.Group("/videos")
实现的。这个子路由组将会处理所有以/videos
开头的请求。
然后,video.GET("/getVideoId", handler.GetVideoId)
这行代码为路径/videos/getVideoId
注册了一个新的 GET
请求处理函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package route
import ( "ApiService/handler" "github.com/gin-gonic/gin" ) func GetUserRoutes(route *gin.RouterGroup) { user := route.Group("/users") { user.GET("/getUserId", handler.GetUserId) } } import ( "ApiService/handler" "github.com/gin-gonic/gin" ) func GetVideoRoutes(route *gin.RouterGroup) { video := route.Group("/videos") { video.GET("/getVideoId", handler.GetVideoId) } }
|
全局路由规则统一
设置Gin路由统一收集的
CollectRoute
的函数,它的作用是将一系列的路由规则注册到
Gin 路由器实例中。r 是 Gin
的路由器实例,这部分内容可以忽略(svc
是某个服务接口的实例,这里是指 IUserService 接口的实例)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package route
import ( . "ApiService/handler" . "ApiService/services" "ApiService/transports" "github.com/gin-gonic/gin" httptransport "github.com/go-kit/kit/transport/http" )
func CollectRoute(r *gin.Engine, svc IUserService) *gin.Engine { r.Use(gin.Logger()) r.Use(gin.Recovery())
handler := httptransport.NewServer(MakeGinHandler(svc), transports.DecodeUserRequest, transports.EncodeUserResponse) api := r.Group("") { GetUserRoutes(api) GetVideoRoutes(api) api.GET("/hello", gin.WrapH(handler)) } api.POST("/ping", Ping) api.GET("/ping", Ping)
return r }
|
主函数初始化调用
在 Gin Web 框架中,gin.Default()
创建了一个默认的 Gin
路由器实例
r = route.CollectRoute(r, userSvc)
这行代码的作用是将一系列的路由规则注册到
Gin 路由器实例中
1 2 3 4 5 6 7 8 9 10 11 12
| func main() { userSvc := UserService{} r := gin.Default() r = route.CollectRoute(r, userSvc) if err := r.Run(":8080"); err != nil { log.Fatal(err) } }
|
实际运行结果
