使用 ServiceWeaver 构建 go 服务

TimLiuDream · 2023-09-14 14:29:55 · 1228 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2023-09-14 14:29:55 的主题,其中的信息可能已经有所发展或是发生改变。

单体或微服务或两者兼有:ServiceWeaver 简介

如果我们能利用一种编程模型或运行时,将代码的逻辑边界与部署的物理边界解耦,这样在开始开发时就不需要将应用程序紧耦合为单体或微服务来部署,那将会非常棒。谷歌的新开源项目 Service Weaver 提供了将代码与代码部署方式解耦的想法。Service Weaver 是一个编程框架,用于用 Go 编程语言编写和部署云应用程序,其中的部署决策可以委托给自动运行时。Service Weaver 可以将应用程序部署为单体和微服务。因此,它是单体和微服务的完美结合。

使用 Service Weaver,您可以将应用程序编写成模块化单体,使用组件将应用程序模块化。Service Weaver 中的组件以 Go 接口为模型,您可以为业务逻辑提供具体实现,而无需与网络或序列化代码耦合。组件是一种代表计算实体的 Actor。这些围绕核心业务逻辑构建的模块化组件可以像本地方法调用一样调用其他组件的方法,无论这些组件是以模块化二进制形式运行还是以微服务形式运行,都无需使用 HTTP 或 RPC。当这些组件作为微服务或在不同机器上运行时,Service Weaver 会使用 RPC 机制进行方法调用。否则,将是本地方法调用。您不必担心您的组件如何调用其他组件的方法。您可以在本地测试 Service Weaver 应用程序,然后将其部署到云中。Service Weaver 可让您专注于代码的工作,而不必担心代码在哪里运行。您可以在同一进程中以单体二进制形式运行组件,也可以在不同机器上以微服务形式运行组件,还可以轻松地扩大或缩小规模,以应对可扩展性挑战。在构建现代应用程序时,添加可观察性组件非常重要。Service Weaver 集成了可观察性,并拥有日志、度量和跟踪库。

例子

让我们编写一个简单的演示,了解如何使用 Service Weaver 编写应用程序。这个示例演示通过去除实现过程中的所有复杂因素,只关注 Service Weaver 的基本原理。

安装 ServiceWeaver

以下命令会将 weaver 安装到 $GOBIN:

go install github.com/ServiceWeaver/weaver/cmd/weaver@latest

Service Weaver Components

使用 Service Weaver,应用程序由一组组件组成。其中包括实现 weaver.Main 接口的组件,以及 weaver.Main 构成系统所需的其他组件。模块化二进制文件将由实现 weaver.Main 接口的组件创建。

演示应用程序由以下组件组成:

  • studyService: 执行 weaver.Main 接口。该组件用于运行学习的 HTTP 服务器。
  • interviewService: 该组件为每次学习的面试。
  • workingService:该组件为面试通过后进入工作。

下面的代码块为 interviewService 提供了组件接口及其相应的实现:

  1. 组件接口以及它的实现
type Service interface {
    MakeInterview(ctx context.Context, golang model.Golang) error
}

type implementation struct {
    weaver.Implements[Service]
}

func (s *implementation) MakeInterview(ctx context.Context, golang model.Golang) error {
    defer s.Logger(ctx).Info(
        "talk about golang",
        "channel: ", golang.Channel,
        "goroutine: ", golang.Goroutine,
    )
    return nil
}

在前面的代码块中,我们定义了一个 Service 接口,将其作为 interview 组件的模型,并通过嵌入通用类型 weaver.Implements[T],在 implementation 结构中提供了具体的实现。

与 interview 服务类似,我们实现一个 working 的服务。

  1. working 服务的实现
type Service interface {
    Working(ctx context.Context, golang model.Golang) error
}

type implementation struct {
    weaver.Implements[Service]
}

func (s *implementation) Working(ctx context.Context, golang model.Golang) error {
    defer s.Logger(ctx).Info(
        "working, fixing golang  bug",
        "channel bug: ", golang.Channel,
        "goroutines bug: ", golang.Goroutine,
    )
    return nil
}

通过嵌入 weaver.Implements[T],我们可以在 Service Weaver 中创建组件。模块化二进制组件将由实现 weaver.Implements[weaver.Main] 类型的主组件创建。让我们编写一个组件,在 studyService 组件中嵌入 weaver.Implements[weaver.Main] 类型,该组件用于运行 HTTP 服务器进行学习。

  1. Server 结构体将 weaver.Implements[weaver.Main] 嵌入其中
type Server struct {
    weaver.Implements[weaver.Main]

    handler http.Handler

    interviewService weaver.Ref[interview.Service]
    working          weaver.Ref[working.Service]

    studyapi weaver.Listener `weaver:"studyapi"`
}

与其他组件比较

weaver.Main 组件外,weaver.Main 临时需要的其他组件。无论是否是 weaver.Main 组件,任何组件都可以引用其他组件。在列表 3 中,表示 study 组件的结构体 Server 需要 interview 组件来进行面试,还需要 working 组件来工作。一个 Service Weaver 组件可以通过引用 weaver.Ref[T] 调用其他组件的方法,其中 T 是要引用的组件的接口。在这里,我们要对 interviewworking 进行引用,以便在结构服务器中创建属性。

interviewService weaver.Ref[interview.Service]
working          weaver.Ref[working.Service]

study 组件通过公开 HTTP 服务器来允许网络流量,因此我们保留了 http.Handler 类型的属性处理程序来创建 HTTP 服务器。

handler http.Handler

网络监听

Service Weaver 组件的实现可能会使用网络监听器。例如,study 组件需要为 HTTP 网络流量提供服务。为此,必须为实现结构提供一个类型为 weaver.Listener 的属性。

studyapi weaver.Listener `weaver:"studyapi"`

weaver.Listener 字段可以从 .toml 配置文件中读取监听器地址。结构标记 weaver: "studyapi" 从名为 weaver.toml 的配置文件中读取监听器地址。Weaver Services 使用 .toml 文件进行配置。

  1. weaver.toml 配置文件
[serviceweaver]
binary = "./studyapp"

[single]
listeners.studyapi = {address = "localhost:13000"}

[multi]
listeners.studyapi = {address = "localhost:3000"}

初始化组件

Service Weaver 组件可选择在组件实现的结构中提供一个 Init 方法。如果有 Init 方法,Service Weaver 自动创建组件实例时就会调用该方法。

  1. 初始化 Server
func (s *Server) Init(ctx context.Context) error {
    s.Logger(ctx).Info("Init")
    r := chi.NewRouter()
    r.Route("/api/study", func(r chi.Router) {
        r.Post("/", s.Study)
        r.Get("/{type}", s.GetKnowledges)
    })
    s.handler = r
    return nil
}

为 Service Weaver 应用程序提供服务

Service Weaver 应用程序的运行需要调用函数 weaver.Run,该函数需要一个函数作为参数。

让我们编写一个具有适当签名的函数,用于 weaver.Run 函数,以运行 Service Weaver 应用程序。

  1. 服务启动方法
func Serve(ctx context.Context, s *Server) error {
    s.Logger(ctx).Info("StudyApi listener available.", "addr:", s.studyapi)
    httpServer := &http.Server{
        Handler: s.handler,
    }
    httpServer.Serve(s.studyapi)
    return nil
}

Serve 函数中,我们创建了一个 HTTP 服务器,并使用 weaver.Listener 属性为其提供服务。

下面是服务器结构(主组件)的完整实现:

type Server struct {
    weaver.Implements[weaver.Main]

    handler http.Handler

    interviewService weaver.Ref[interview.Service]
    working          weaver.Ref[working.Service]

    studyapi weaver.Listener `weaver:"studyapi"`
}

func (s *Server) Init(ctx context.Context) error {
    s.Logger(ctx).Info("Init")
    r := chi.NewRouter()
    r.Route("/api/study", func(r chi.Router) {
        r.Post("/", s.Study)
        r.Get("/{type}", s.GetKnowledges)
    })
    s.handler = r
    return nil
}

func Serve(ctx context.Context, s *Server) error {
    s.Logger(ctx).Info("StudyApi listener available.", "addr:", s.studyapi)
    httpServer := &http.Server{
        Handler: s.handler,
    }
    httpServer.Serve(s.studyapi)
    return nil
}

调用引用组件的方法

在 Server 结构体中,组件 interviewworking 是通过 weaver.Ref[T] 引用的,其中 T 是组件的接口。

interviewService weaver.Ref[interview.Service]
working          weaver.Ref[working.Service]

当 Service Weaver 创建组件实例时,它也会自动创建引用的组件(weaver.Ref[T] 字段)。为了从 weaver.Ref[T] 字段中获取组件实例,调用组件的方法,只需调用 weaver.Ref[T] 字段中的 Get 方法即可。例如,下面的代码块获取了 interview 组件:

  1. 获取 interview 组件
interview := s.interviewService.Get()

在本示例演示中,学习时,interview 组件用于面试,working 用于工作。这是在 HTTP 处理程序代码中实现的,该代码用于处理 study 的 HTTP Post 请求。

  1. 调用引用组件方法的 HTTP 处理程序代码
var ctx = context.Background()

func (s *Server) Study(writer http.ResponseWriter, request *http.Request) {
    var golang model.Golang
    err := json.NewDecoder(request.Body).Decode(&golang)
    if err != nil {
        http.Error(writer, "Invalid Course Data", 500)
        return
    }
    golang.Goroutine = "GMP"
    golang.Channel = "blocking"
    if err := s.interviewService.Get().MakeInterview(ctx, golang); err != nil {
        s.Logger(ctx).Error(
            "interview failed",
            "error:", err,
        )
        http.Error(writer, "interview failed", http.StatusInternalServerError)
        return
    }
    golang.Goroutine = "fixing goroutine bug"
    golang.Channel = "non-blocking"
    // send notification using notificationService component
    if err := s.working.Get().Working(ctx, golang); err != nil {
        s.Logger(ctx).Error(
            "fixing bug failed",
            "error:", err,
        )
    }
    s.Logger(ctx).Info("those bugs has been fixed")
    writer.Header().Set("Content-Type", "application/json")
    writer.WriteHeader(http.StatusOK)
}

在前面的代码块中,通过调用 weaver.Ref[T] 字段的 Get 方法和调用组件的方法,创建了引用组件。

if err := s.interviewService.Get().MakeInterview(ctx, golang); err != nil {}
if err := s.working.Get().Working(ctx, golang); err != nil {}

使用 weaver generate 命令生成代码

无论什么时候,当你使用 Service Weaver 实现某些功能时,都应该在构建(go build)或使用 go run 运行应用程序之前运行 weaver 生成命令。这会生成一些代码,文件名为 weaver_gen.go

下面的命令会生成当前目录下的代码

weaver generate .

下面的命令为当前目录及其所有子目录生成代码:

weaver generate ./...

基于上面的代码,当我们执行上面语句的时候,会提示我们没有序列化:

interview\interview.go:10:2: Method `MakeInterview(ctx context.Context, golang model.Golang) (error)` of Service Weaver component "Service" has incorrect argument types. Argument 1 has type model.Golang, which is not serializable. All arguments, besides the initial context.Context, must be serializable.
model.Golang: named structs are not serializable by default. Consider using weaver.AutoMarshal.
working\working.go:10:2: Method `Working(ctx context.Context, golang model.Golang) (error)` of Service Weaver component "Service" has incorrect argument types. Argument 1 has type model.Golang, which is not serializable. All arguments, besides the initial context.Context, must be serializable.
model.Golang: named structs are not serializable by default. Consider using weaver.AutoMarshal.
-: # github.com/timliudream/go-test/serviceWeaverDemo/study
study\study.go:30:22: s.GetKnowledges undefined (type *Server has no field or method GetKnowledges)
D:\gopath\src\github.com\timliudream\go-test\serviceWeaverDemo\study\study.go:30:22: s.GetKnowledges undefined (type *Server has no field or method GetKnowledges)

下面我们来讲讲序列化的事情。

用于调用组件方法的可序列化类型

在调用组件的方法时,必须对方法的参数和返回类型进行序列化,以便通过网络发送。通过嵌入 weaver.AutoMarshal 类型,可以将结构类型序列化。

  1. 结构类型使其可序列化
type Golang struct {
    weaver.AutoMarshal
    Channel   string
    Goroutine string
}

运行 Service Weaver 应用程序

weaver 运行函数将 weaver.Main 组件作为 Service Weaver 应用程序运行。

下面的代码块将运行 Service Weaver 应用程序:

func main() {
    if err := weaver.Run(context.Background(), study.Serve); err != nil {
        log.Fatal(err)
    }
}

在前面的代码块中,study.Serve 是一个函数,其签名为 func(context.Context, *T) error,其中 T 是主组件的结构实现。函数 weaver.Run 会自动创建一个实现 weaver.Main 的主组件实例。如果组件实现中提供了任何 Init 方法,则将用于创建实例。

在单进程中运行应用程序

在编译代码或使用 go run 命令运行应用程序之前,请确保执行 weaver generate 命令来生成代码,这对 Service Weaver 应用程序至关重要。

下面的命令运行应用程序:

go run .

服务运行在“127.0.0.1:50076”,输出如下:

╭───────────────────────────────────────────────────╮
│ app        : serviceWeaverDemo.exe                │
│ deployment : 615d74c6-0e33-4f18-ae0c-f380b49c8e91 │
╰───────────────────────────────────────────────────╯
I0914 14:01:50.363086 weaver.Main          85b8796b study.go:26          │ Init
I0914 14:01:50.363086 weaver.Main          85b8796b study.go:76          │ StudyApi listener available. addr:="[::]:50076"

下面的命令显示 Service Weaver 应用程序的状态:

weaver single status

如下图所示,状态显示了每个部署、组件和监听器:

╭──────────────────────────────────────────────────────────────────────╮
│ DEPLOYMENTS                                                          │
├───────────────────────┬──────────────────────────────────────┬───────┤
│ APP                   │ DEPLOYMENT                           │ AGE   │
├───────────────────────┼──────────────────────────────────────┼───────┤
│ serviceWeaverDemo.exe │ 615d74c6-0e33-4f18-ae0c-f380b49c8e91 │ 2m33s │
╰───────────────────────┴──────────────────────────────────────┴───────╯
╭───────────────────────────────────────────────────────────────────────╮
│ COMPONENTS                                                            │
├───────────────────────┬────────────┬───────────────────┬──────────────┤
│ APP                   │ DEPLOYMENT │ COMPONENT         │ REPLICA PIDS │
├───────────────────────┼────────────┼───────────────────┼──────────────┤
│ serviceWeaverDemo.exe │ 615d74c6   │ weaver.Main       │ 4356         │
│ serviceWeaverDemo.exe │ 615d74c6   │ interview.Service │ 4356         │
│ serviceWeaverDemo.exe │ 615d74c6   │ working.Service   │ 4356         │
╰───────────────────────┴────────────┴───────────────────┴──────────────╯
╭────────────────────────────────────────────────────────────╮
│ LISTENERS                                                  │
├───────────────────────┬────────────┬──────────┬────────────┤
│ APP                   │ DEPLOYMENT │ LISTENER │ ADDRESS    │
├───────────────────────┼────────────┼──────────┼────────────┤
│ serviceWeaverDemo.exe │ 615d74c6   │ studyapi │ [::]:50076 │
╰───────────────────────┴────────────┴──────────┴────────────╯

你也可以运行 weaver single dashboard 来打开仪表盘,在浏览器中打开仪表盘。

Snipaste_2023-09-14_14-06-47.png

可以看到非常详细的信息,以及监控、trace 等等。

以多进程运行程序

Service Weaver 应用程序可在单进程和多进程中运行。要在多进程中运行,请使用 go build 编译代码。然后,使用下面的命令和配置文件 weaver.toml 在多进程中运行应用程序:

weaver multi deploy weaver.toml

Snipaste_2023-09-14_14-14-18.png

下面的命令显示了 Service Weaver 应用程序在多个进程中的状态:

weaver multi status
╭──────────────────────────────────────────────────────────────────────╮
│ DEPLOYMENTS                                                          │
├───────────────────────┬──────────────────────────────────────┬───────┤
│ APP                   │ DEPLOYMENT                           │ AGE   │
├───────────────────────┼──────────────────────────────────────┼───────┤
│ serviceWeaverDemo.exe │ 615d74c6-0e33-4f18-ae0c-f380b49c8e91 │ 2m33s │
╰───────────────────────┴──────────────────────────────────────┴───────╯
╭───────────────────────────────────────────────────────────────────────╮
│ COMPONENTS                                                            │
├───────────────────────┬────────────┬───────────────────┬──────────────┤
│ APP                   │ DEPLOYMENT │ COMPONENT         │ REPLICA PIDS │
├───────────────────────┼────────────┼───────────────────┼──────────────┤
│ serviceWeaverDemo.exe │ 615d74c6   │ weaver.Main       │ 4356         │
│ serviceWeaverDemo.exe │ 615d74c6   │ interview.Service │ 4356         │
│ serviceWeaverDemo.exe │ 615d74c6   │ working.Service   │ 4356         │
╰───────────────────────┴────────────┴───────────────────┴──────────────╯
╭────────────────────────────────────────────────────────────╮
│ LISTENERS                                                  │
├──────────────┼──────────────────────────────────────┼──────┤
│ studyapp.exe │ 0f15e18b-a336-44b6-ac92-b7c9436ad8ce │ 1m4s │
╰──────────────┴──────────────────────────────────────┴──────╯
╭──────────────────────────────────────────────────────────────╮
│ COMPONENTS                                                   │
├──────────────┬────────────┬───────────────────┬──────────────┤
│ APP          │ DEPLOYMENT │ COMPONENT         │ REPLICA PIDS │
├──────────────┼────────────┼───────────────────┼──────────────┤
│ studyapp.exe │ 0f15e18b   │ weaver.Main       │ 21500, 23724 │
│ studyapp.exe │ 0f15e18b   │ interview.Service │ 23908, 24372 │
│ studyapp.exe │ 0f15e18b   │ working.Service   │ 22936, 26616 │
╰──────────────┴────────────┴───────────────────┴──────────────╯
╭───────────────────────────────────────────────────────╮
│ LISTENERS                                             │
├──────────────┬────────────┬──────────┬────────────────┤
│ APP          │ DEPLOYMENT │ LISTENER │ ADDRESS        │
├──────────────┼────────────┼──────────┼────────────────┤
│ studyapp.exe │ 0f15e18b   │ studyapi │ 127.0.0.1:3000 │
╰──────────────┴────────────┴──────────┴────────────────╯

代码

此文章的代码已放到 github 地址上:

https://github.com/TimLiuDream/go-test/tree/master/serviceWeaverDemo


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1228 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传