【后端开发】Go-kit与Gin框架

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的三层架构

  1. Transport

    主要负责与HTTP、gRPC、thrift等相关的逻辑,提供端到端的通信服务,不同的传输协议来实现这些功能,最常见的协议是TCP(传输控制协议)和UDP(用户数据报协议)。

  2. Endpoint

    定义Request和Response格式,并可以使用装饰器包装函数,以此来实现各种中间件嵌套。

  3. 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"
)

// StringService 定义了字符串处理服务的接口
// 这个地方给出接口,相当于是虚基类,后续的接口继承需要实现这部分的函数内容
type StringService interface {
Uppercase(string) (string, error) //返回的值是一个string和一个error
Count(string) int
}

// SStringService 实现了 StringService 接口
type SStringService struct{}

// Uppercase 将字符串转换为大写
func (SStringService) Uppercase(s string) (string, error) {
if s == "" {
return "", errors.New("Input string is empty")
}
return strings.ToUpper(s), nil //直接调用string的功能来实现大小写转换
}

// Count 返回字符串的字符数
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"
)

//这个地方借助go-kit来实现endpoint的内容
// uppercaseRequest 和 uppercaseResponse 用于 Uppercase 端点
type uppercaseRequest struct {
S string `json:"s"`
}

type uppercaseResponse struct {
V string `json:"v"`
Err string `json:"err,omitempty"`
}

// countRequest 和 countResponse 用于 Count 端点
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
}

// MakeEndpoints 创建并返回所有服务端点
func MakeEndpoints(svc services.StringService) Endpoints {
return Endpoints{
UppercaseEndpoint: MakeUppercaseEndpoint(svc),
CountEndpoint: MakeCountEndpoint(svc),
}
}

// MakeUppercaseEndpoint 创建 Uppercase 端点
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
}
}

// MakeCountEndpoint 创建 Count 端点
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层

主体的实现框架,分别给出了两个不同服务接口的responserequest的数据类型这部分给出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"`
}

// countRequest 和 countResponse 用于 Count 端点
type countRequest struct {
S string `json:"s"`
}

type countResponse struct {
V int `json:"v"`
}
// MakeHTTPHandler 创建 HTTP 处理程序
func MakeHTTPHandler(endpoints endpoints.Endpoints) http.Handler {
options := []httptransport.ServerOption{
httptransport.ServerErrorEncoder(errorEncoder),
}
//http.NewServeMux()返回一个新的http.ServeMux,这是一个用于处理HTTP请求的请求轮询器(request router)。
//接下来,为/uppercase和/count路径分别配置了httptransport.NewServer:
m := http.NewServeMux()
//httptransport.NewServer是一个函数,它接受几个参数来创建一个新的HTTP服务器端点:
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
}

// decodeCountRequest 解码 HTTP 请求并创建 Count 请求结构
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
}

// encodeResponse 将响应编码为 JSON 并写入 HTTP 响应
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{} //定义一个对象svc
eps := endpoints.MakeEndpoints(svc) //接收svc作为输入,创建endpoint端点
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端的应用程序进行实现,因此在这个架构之下我们设计的框架为:

Gin+Gokit架构图结构

其中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层

主要做的事情就是对requestresponse的解码和编码过程,因为通过对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) {
// 限定参数来源,我们直接从url的params中获取用户id
// 请求格式类似于:http://127.0.0.1?uid=101
if r.URL.Query().Get("uid") != "" {
uid, _ := strconv.Atoi(r.URL.Query().Get("uid")) //strconv.Atoi(...)将字符串转化为整数
return endpoints.UserRequest{Uid: uid}, nil
}
return nil, errors.New("参数错误")
}

// 响应编码函数
func EncodeUserResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
// 将返回体body置为json格式
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"
)
// 创建 Gin 路由
func MakeGinHandler(svc IUserService) *gin.Engine {
//初始化一个Gin引擎,并配置了一些中间件
r := gin.New()
r.Use(gin.Logger()) //用于记录每个请求的详细信息,例如请求方法、URL、执行时间等
r.Use(gin.Recovery()) //在程序发生 panic 时恢复运行,并返回一个500的HTTP状态码

// 创建 Go kit 的 endpoint
endpoint := GetUserEndPoint(svc)

// 创建 HTTP 传输层处理程序
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() {
// 创建 Go kit 服务
userSvc := UserService{}
// 创建 Gin 路由并绑定服务
r := MakeGinHandler(userSvc)
// 启动 Gin 服务器
if err := r.Run(":8080"); err != nil {
log.Fatal(err)
}
}
/*
说明,这个demo运行后,浏览器输入 http://127.0.0.1:8080/hello?uid=1999 即可测试,还可以输入下面的内容进行测试:
http://127.0.0.1:8080/hello
http://127.0.0.1:8080
*/

Gin统一收集路由

统一收集路由(unified routing collection)通常指的是将所有的路由配置集中到一个地方管理,这样可以更方便地管理和维护路由。Gin 本身已经提供了一个路由器(Router)对象,用于收集和管理所有的路由。统一收集路由的好处包括:

  • 易于维护:所有的路由都在一个地方,便于查看和修改。
  • 路由重用:可以定义全局中间件,这些中间件会在所有路由上应用,而不需要在每个路由上重复编写。
  • 更好的错误处理:可以在统一的地方处理路由错误,例如未找到路由或方法不允许等。
  • 性能优化:Gin 路由器会预编译路由,这可以提高处理请求的效率

局部的路由规则初始化

首先定义局部的具体路由规则,这里给出例子 GetUserRoutesGetVideoRoutes,在 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() {
// 创建 Go kit 服务
userSvc := UserService{}
// 创建 Gin 路由并绑定服务
r := gin.Default()
r = route.CollectRoute(r, userSvc)
//r := MakeGinHandler(userSvc)
// 启动 Gin 服务器
if err := r.Run(":8080"); err != nil {
log.Fatal(err)
}
}

实际运行结果

根据不同的url地址选择不同的输出


【后端开发】Go-kit与Gin框架
https://lihaibineric.github.io/2024/01/23/develop_go_kit/
Author
Haibin Li
Posted on
January 23, 2024
Updated on
February 22, 2024
Licensed under