微服务开发

微服务开发 - 图1 提示

微服务完整特性及相关组件从 v2.4 版本开始提供。

基本介绍

GoFrame 框架支持微服务模式开发,提供了常用的微服务组件、开发工具、开发教程帮助团队快速微服务转型。

简单示例

GoFrame 微服务组件的低藕及通用化设计的,组件化使用支持大部分的微服务通信协议。在官方文档中,我们以 HTTP 及 GRPC 协议为示例,介绍微服务的开发以及组件工具的使用。由于 HTTP Web 开发已经有比较丰富完善的独立章节介绍,因此微服务章节大部分介绍以 GRPC 为主。

HTTP 微服务示例

https://github.com/gogf/gf/tree/master/example/registry/file

server.go

package main

import (
    "github.com/gogf/gf/contrib/registry/file/v2"
    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/net/ghttp"
    "github.com/gogf/gf/v2/net/gsvc"
    "github.com/gogf/gf/v2/os/gfile"
)

func main() {
    gsvc.SetRegistry(file.New(gfile.Temp("gsvc")))

    s := g.Server(`hello.svc`)
    s.BindHandler("/", func(r *ghttp.Request) {
        g.Log().Info(r.Context(), `request received`)
        r.Response.Write(`Hello world`)
    })
    s.Run()
}

可以看到,一个 HTTP 的微服务端和一个普通的 Web Server 端没什么差异,但是顶部多了一行代码:

gsvc.SetRegistry(file.New(gfile.Temp("gsvc")))

这行代码用于启用并注册当前服务使用的注册发现组件,在该示例中使用的 file.New(gfile.Temp("gsvc")) 是基于本地系统文件的服务注册发现组件,其中的 gfile.Temp("gsvc") 指定的是存放服务文件的路径,例如在 Linux/MacOS 系统下,指向的是 /tmp/gsvc 目录。基于文件系统的注册发现仅用于本地微服务示例,不能用于跨节点通信。在生产环境时,我们往往会使用其他的服务注册发现组件,例如 etcd, polaris, zookeeper 等,框架的社区组件中已经提供了常用的服务注册发现组件的实现。

其次,在该示例中,我们给 Server 设置了一个名字 hello.svc,该名字表示该 Server 绑定的微服务的名称,服务名称作为微服务的唯一标识,用于服务间的识别通信。当服务注册组件注册启用时, HTTP Server 在运行时将会把自己的访问地址注册到服务注册组件中,方便其他服务通过相同组件按照服务名称进行访问。

client.go

package main

import (
    "time"

    "github.com/gogf/gf/contrib/registry/file/v2"
    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/net/gsvc"
    "github.com/gogf/gf/v2/os/gctx"
    "github.com/gogf/gf/v2/os/gfile"
)

func main() {
    gsvc.SetRegistry(file.New(gfile.Temp("gsvc")))

    client := g.Client()
    for i := 0; i < 10; i++ {
        ctx := gctx.New()
        res, err := client.Get(ctx, `http://hello.svc/`)
        if err != nil {
            panic(err)
        }
        g.Log().Debug(ctx, res.ReadAllString())
        res.Close()
        time.Sleep(time.Second)
    }
}

客户端通过 g.Client() 创建一个 HTTP Client,并通过 http://hello.svc/ 地址访问服务端,其中的 hello.svc 即先前的 Server 端绑定的微服务名称。当客户端通过微服务名称访问的时候,服务注册发现组件将会在底层进行检索,并找到对应的服务端地址进行通信。

执行结果

先执行 server.go 服务端运行一个简单的服务,随后再执行 client.go 通过服务名称请求服务。

执行后,客户端输出:

$ go run client.go
2023-03-14 20:22:10.006 [DEBU] {8054f3a48c484c1760fb416bb3df20a4} Hello world
2023-03-14 20:22:11.007 [DEBU] {6831cae08c484c1761fb416b9d4df851} Hello world
2023-03-14 20:22:12.008 [DEBU] {9035761c8d484c1762fb416b1e648b81} Hello world
2023-03-14 20:22:13.011 [DEBU] {a05a32588d484c1763fb416bc19ff667} Hello world
2023-03-14 20:22:14.012 [DEBU] {40fdea938d484c1764fb416b8459fc43} Hello world
2023-03-14 20:22:15.014 [DEBU] {686c9acf8d484c1765fb416b3697d369} Hello world
2023-03-14 20:22:16.015 [DEBU] {906a470b8e484c1766fb416b85b9867e} Hello world
2023-03-14 20:22:17.017 [DEBU] {28c7fd468e484c1767fb416b86e5557f} Hello world
2023-03-14 20:22:18.018 [DEBU] {90d2ad828e484c1768fb416bfcde738f} Hello world
2023-03-14 20:22:19.019 [DEBU] {d05559be8e484c1769fb416baad06f23} Hello world

服务端输出:

$ go run server.go
2023-03-14 20:20:06.364 [INFO] pid[96421]: http server started listening on [:61589]
2023-03-14 20:20:06.364 [INFO] openapi specification is disabled
2023-03-14 20:20:06.364 [DEBU] service register: &{Head: Deployment: Namespace: Name:hello.svc Version: Endpoints:10.35.12.81:61589 Metadata:map[insecure:true protocol:http]}

   SERVER   | DOMAIN  | ADDRESS | METHOD | ROUTE |                             HANDLER                             |    MIDDLEWARE
------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
  hello.svc | default | :61589  | ALL    | /     | main.main.func1                                                 |
------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------
  hello.svc | default | :61589  | ALL    | /*    | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE
------------|---------|---------|--------|-------|-----------------------------------------------------------------|--------------------

2023-03-14 20:22:10.006 [INFO] {8054f3a48c484c1760fb416bb3df20a4} request received
2023-03-14 20:22:11.007 [INFO] {6831cae08c484c1761fb416b9d4df851} request received
2023-03-14 20:22:12.008 [INFO] {9035761c8d484c1762fb416b1e648b81} request received
2023-03-14 20:22:13.010 [INFO] {a05a32588d484c1763fb416bc19ff667} request received
2023-03-14 20:22:14.012 [INFO] {40fdea938d484c1764fb416b8459fc43} request received
2023-03-14 20:22:15.013 [INFO] {686c9acf8d484c1765fb416b3697d369} request received
2023-03-14 20:22:16.015 [INFO] {906a470b8e484c1766fb416b85b9867e} request received
2023-03-14 20:22:17.016 [INFO] {28c7fd468e484c1767fb416b86e5557f} request received
2023-03-14 20:22:18.017 [INFO] {90d2ad828e484c1768fb416bfcde738f} request received
2023-03-14 20:22:19.019 [INFO] {d05559be8e484c1769fb416baad06f23} request received

GRPC 微服务示例

https://github.com/gogf/gf/tree/master/example/rpc/grpcx/basic

helloworld.proto

grpc 和 http 协议比较大的差异在于 grpc 需要通过 protobuf 来实现 API 接口以及数据结构的定义。

syntax = "proto3";

package protobuf;

option go_package = "github.com/gogf/gf/grpc/example/helloworld/protobuf";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

以上 protobuf 文件通过以下命令执行编译(请提前安装 protoc 工具):

gf gen pb

将会生成对应的 proto go 数据结构文件以及 grpc 接口文件:

helloworld.pb.go
helloworld_grpc.pb.go

controller.go

控制器用于实现 proto 中定义的接口方法(如果使用框架标准化工程目录结构,该控制器代码文件也是由框架的 gf gen pb 工具自动生成,开发者进需要填充对应方法的具体实现即可):

type Controller struct {
    protobuf.UnimplementedGreeterServer
}

func Register(s *grpcx.GrpcServer) {
    protobuf.RegisterGreeterServer(s.Server, &Controller{})
}

// SayHello implements helloworld.GreeterServer
func (s *Controller) SayHello(ctx context.Context, in *protobuf.HelloRequest) (*protobuf.HelloReply, error) {
    return &protobuf.HelloReply{Message: "Hello " + in.GetName()}, nil
}

config.yaml

服务端配置文件,在该配置文件中指定了该服务的名称为 demo。微服务的名称用于服务间通信的唯一识别标识。在不显式配置服务端的监听端口时,服务端将会随机监听可用的本地端口。在微服务模式下,由于使用服务名称进行通信,服务端口往往不需要显式指定,随机监听即可。

grpc:
  name:            "demo"
  logPath:          "./log"
  logStdout:        true
  errorLogEnabled:  true
  accessLogEnabled: true
  errorStack:       true

server.go

grpc 服务端,在不显式指定服务端使用的服务注册发现组件时,服务端默认使用系统文件注册发现组件,该组件仅用于单机测试。其中的 controller.Register 即调用我们通过工具生成的控制器注册方法将具体的接口实现注册到服务端中。

package main

import (
    "github.com/gogf/gf/contrib/rpc/grpcx/v2"
    "github.com/gogf/gf/example/rpc/grpcx/basic/controller"
)

func main() {
    s := grpcx.Server.New()
    controller.Register(s)
    s.Run()
}

client.go

grpc 客户端,在创建连接时需要给定服务端服务的具体名称。这里的服务端服务名称为 demo,指定的是上面提到的微服务名称。在不显式指定客户端使用的服务注册发现组件时,客户端默认使用系统文件注册发现组件,该组件仅用于单机测试。

package main

import (
    "github.com/gogf/gf/contrib/rpc/grpcx/v2"
    "github.com/gogf/gf/example/rpc/grpcx/basic/protobuf"
    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/os/gctx"
)

func main() {
    var (
        ctx    = gctx.New()
        conn   = grpcx.Client.MustNewGrpcClientConn("demo")
        client = protobuf.NewGreeterClient(conn)
    )
    res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"})
    if err != nil {
        g.Log().Error(ctx, err)
        return
    }
    g.Log().Debug(ctx, "Response:", res.Message)
}

执行结果

服务端输出:可以看到,服务端输出了一下 DEBU 调试信息,用于显示一些服务注册的细节。同时,由于没有显式指定服务端的监听端口,这里随机监听了本地端口 64517。

$ go run server.go
2023-03-14 20:50:58.465 [DEBU] set default registry using file registry as no custom registry set
2023-03-14 20:50:58.466 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:64517 Metadata:map[protocol:grpc]}
2023-03-14 20:50:58.466 [INFO] pid[98982]: grpc server started listening on [:64517]
2023-03-14 20:52:37.059 {9898c809364a4c17da79e47f3e6c3b8f} /protobuf.Greeter/SayHello, 0.003ms, name:"World", message:"Hello World"

客户端输出:客户端通过微服务名称访问,并接收到了服务端的返回。注意客户端的日志和服务端的日志中,链路跟踪的 TraceID 是相同的( 9898c809364a4c17da79e47f3e6c3b8f),表示同一个请求产生的日志。 GoFrame 的微服务特性默认开启了链路跟踪能力。

$ go run client.go
2023-03-14 20:52:37.060 [DEBU] {9898c809364a4c17da79e47f3e6c3b8f} Response: Hello World

相关文档

📄️ 环境准备为微服务开发准备环境,主要包括GRPC协议的依赖安装指南和GoFrame框架的CLI工具。学习者需确保GRPC相关工具的安装,并掌握GRPC协议在GoFrame框架中微服务开发中的基本应用。

📄️ 工程管理使用GoFrame框架进行微服务开发的标准工程目录结构,包括协议文件和接口文件的管理。详细描述了如何利用GoFrame框架的开发工具生成数据表结构对应的protobuf文件,以及如何编译协议文件生成接口和控制器。同时,说明了服务的启动和接口实现的具体步骤,并介绍了标签注入和数据校验插件的使用方法。

📄️ 脚手架模块GoFrame框架中的脚手架模块提供了便捷的GRPC服务端和客户端实现,包含上下文、负载均衡和服务注册发现等功能。通过grpcx组件,用户可以轻松地构建和管理微服务应用,支持多种负载均衡策略以及etcd注册中心,简化服务间通信与数据传递。

📄️ 服务端配置服务端的配置方式,包括如何通过GoFrame框架读取和管理配置文件。提供了一份完整的配置模板示例,涵盖服务名称、服务监听地址、日志存储目录、错误日志记录及访问日志记录的设置方法。该配置与框架自动读取逻辑一致,确保了便捷的服务部署和高效的日志管理,以及如何设置和使用参数日志组件的配置进行独立的grpc server日志管理。

📄️ 拦截器组件在GoFrame框架中使用GRPC拦截器的方法,详细说明了服务端和客户端的拦截器实现,包括链路跟踪、错误处理和自动错误校验等功能。通过使用grpcx组件,用户可以灵活地添加和自定义拦截器,提高GRPC服务和客户端的可扩展性与稳定性。

📄️ 服务注册发现GoFrame框架内服务注册发现组件的使用和实现,由gsvc组件管理,支持etcd、zookeeper、polaris等多种实现方式。通过合理配置实现全局服务注册发现,提高服务调用的灵活性和可扩展性,是开发微服务架构的重要指南。

📄️ 服务负载均衡GoFrame框架中的负载均衡组件,gsel组件提供了多种内置负载均衡策略,包括LeastConnection、Random、RoundRobin和Weight。开发者可以根据需求选择合适的策略或自定义实现,并通过HTTP和GRPC示例展示了负载均衡策略的实际应用。

📄️ 服务配置管理在GoFrame框架中使用配置管理组件,通过解耦化设计,支持多种第三方配置中心如Polaris、Apollo、Nacos、Consul等。通过详细代码展示了如何初始化和启用Polaris配置客户端,并提供了使用示例和错误处理方式。