使用 ServiceWeaver 构建 go 服务

简介: 使用 ServiceWeaver 构建 go 服务

单体或微服务或两者兼有: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 来打开仪表盘,在浏览器中打开仪表盘。


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


以多进程运行程序


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


weaver multi deploy weaver.toml


下面的命令显示了 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

相关文章
|
2月前
|
缓存 弹性计算 API
用 Go 快速开发一个 RESTful API 服务
用 Go 快速开发一个 RESTful API 服务
|
7天前
|
中间件 Go API
使用Go语言构建高性能RESTful API
在现代软件开发中,RESTful API因其简洁和高效而成为构建网络服务的首选。Go语言以其并发处理能力和高性能著称,是开发RESTful API的理想选择。本文将介绍如何使用Go语言构建RESTful API,包括基础的路由设置、中间件的使用、数据验证、错误处理以及性能优化。通过实际代码示例,我们将展示Go语言在API开发中的强大功能和灵活性。
|
29天前
|
JSON Go API
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
|
1月前
|
Go API 开发者
深入探讨:使用Go语言构建高性能RESTful API服务
在本文中,我们将探索Go语言在构建高效、可靠的RESTful API服务中的独特优势。通过实际案例分析,我们将展示Go如何通过其并发模型、简洁的语法和内置的http包,成为现代后端服务开发的有力工具。
|
2月前
|
安全 Go Docker
Go服务Docker Pod不断重启排查和解决
该文章分享了Go服务在Docker Pod中不断重启的问题排查过程和解决方案,识别出并发写map导致fatal error的问题,并提供了使用sync.Map或concurrent-map库作为并发安全的替代方案。
32 4
|
2月前
|
Linux Shell Go
如何构建和安装 Go 程序
如何构建和安装 Go 程序
35 1
|
2月前
|
算法 Go
Go 构建高效的二叉搜索树联系簿
Go 构建高效的二叉搜索树联系簿
|
2月前
|
Kubernetes Cloud Native Go
云原生之旅:构建和部署一个简单的Go应用程序
【8月更文挑战第31天】在本文中,我们将探索如何利用云原生技术构建和部署一个Go语言编写的简单Web应用。通过实际操作示例,我们不仅能够了解云原生的基本概念,还能学习到如何在Kubernetes集群上运行和管理容器化应用。文章将引导读者从零开始,逐步搭建起自己的云原生环境,并实现代码的容器化与自动化部署,最终达到持续交付的目的。
|
2月前
|
运维 监控 程序员
Go 服务自动收集线上问题现场
Go 服务自动收集线上问题现场
|
2月前
|
SQL JavaScript Go
Go Web 服务框架实现详解
Go Web 服务框架实现详解