将您的应用程序编写为模块化二进制文件,部署的时候它是一个微服务集合!
长话短说: Service Weaver 是一个编程框架,用于编写、部署和 在 Go 中管理分布式应用程序。使用 Service Weaver,您可以编写您的 应用程序就像它是一个传统的单进程 Go 可执行文件,运行在 你的本地机器。然后,你将它部署到云端,框架就崩溃了 它分解成一组连接的微服务并将其与云集成 提供者(例如,监控、跟踪、日志记录)。
为什么选择 Service Weaver?
当我们编写在笔记本电脑上运行的应用程序时,我们依赖于函数和接口等传统编程概念,我们的主要挑战集中在应用程序业务逻辑上。
然而,当一个应用程序超出了单个计算机的限制,是时候将其部署到云中了,上述传统的编程模型就会崩溃。我们漂亮的应用程序最终将成为一场噩梦,业务逻辑崩溃,网络调用、云集成点和配置文件散布其中。
是时候与云集成了!!!
我们的云集成之旅要求我们首先学习如何为应用程序二进制文件配置和构建容器。接下来,我们了解云工具(例如 gcloud、 kubectl)以及如何在云中配置应用程序(例如 Kubernetes YAML)。最后,我们向应用程序添加适当的云服务 API 调用。
这些步骤通常很繁琐、费时,甚至对于更有经验的开发人员来说也很难理解。即便如此,我们仍然必须处理许多与云的分布式特性相关的挑战。例如,我们必须实现与服务发现、安全性、负载平衡和跨服务通信(例如,JSON 编码/解码、 HTTP 服务器)相关的功能。
因此,我们的应用程序代码变得更加复杂,我们慢慢地从最初的重点: 应用程序业务逻辑上分心。
对于 ServiceWeaver,主要关注应用程序业务逻辑。ServiceWeaver 负责云配置和与云提供商的集成。
嗯,什么是微服务,我们真的需要它们吗?
一旦我们将应用程序迁移到云中,由于云的分布式特性,我们通常会问自己如何构建应用程序代码。
一种广泛采用的策略是将应用程序二进制文件分割为微服务。这种分离有多种好处,例如更有效的应用程序扩展行为、减少错误爆炸半径、分离安全域和建立良好的模块边界。
另一方面,找到正确的边界来执行分割并不是件容易的事。例如,我们是否应该基于资源使用、组织原因、数据结构或未来增长的准备进行分割?因此,我们经常会得到比我们需要的更多的微服务,导致额外的故障点、更大的延迟,以及开发、部署和管理本身更加困难的应用程序。
使用 ServiceWeaver,您可以根据需要将应用程序拆分为多个组件。在部署期间,您可以轻松地配置哪些组件在同一微服务中一起运行,哪些组件在单独的微服务中运行。
OMG,我们如何管理我们的应用程序?
如上所述,云的复杂特性导致开发、部署和管理应用程序更加困难。
例如,通过将应用程序代码分割成微服务,我们最终得到多个应用程序二进制文件,每个微服务一个。我们不是单一部署,而是运行 N 个不同的 CI/CD 管道来获得 N 个微服务部署。我们不再为每个微服务编写一个单独的配置文件,而是为每个微服务编写一个单独的配置文件,这使得我们很容易忽略设置某些参数,或者更糟糕的是,设置了错误的值。
由于微服务二进制文件是独立部署的,因此我们在编写应用程序时必须假设每个微服务运行在不同的代码版本上。这使开发复杂化,并且需要谨慎的部署策略来降低风险。它还使测试复杂化,因为它需要测试微服务版本及其交互的所有可能组合。
最后,由于我们的应用程序逻辑跨多个二进制文件(其中也包含特定于云的调用)进行分割,因此在本地进行端到端测试具有挑战性。在部署应用程序服务时,我们更可能依赖于检测错误,而不是运行端到端测试。
使用 ServiceWeaver,可以部署和管理单个应用程序二进制文件。它在云中作为独立的微服务运行的事实是一个实现细节: 所有工具都保留了单个应用程序二进制的印象。在将应用程序部署到云中之前,您还可以轻松地在本地运行和测试它。
最后它起作用了。哦,性能?
一旦我们让基于微服务的应用程序在云中正确运行,我们就会意识到它的性能非常糟糕。我们做了一些调查,发现我们的大部分计算周期都花在了通过网络序列化和发送数据上。我们不想重新发明网络传输和序列化协议,所以我们只是让应用程序保持原样。它的运行成本更高,但我们不想在优化性能方面投入额外的工程时间。
Service Weaver 使用自定义序列化和传输协议,其成本效益是最佳行业解决方案(即 gRPC 和 protocol buffers)的三倍。这意味着您的云开销大大减少,您可以加倍关注应用程序逻辑。
如何使用 Service Weaver 编写应用程序?
ServiceWeaver 的核心抽象是一个组件,一个类似于actor的计算单元。组件表示为常规 Go 接口,组件通过调用这些接口定义的方法相互交互。例如,这里有一个简单的逆转器组件,顾名思义,它可以逆转字符串。
// The interface of the Reverser component.
type Reverser interface {
Reverse(context.Context, string) (string, error)
}
// The implementation of the Reverser component.
type reverser struct{
weaver.Implements[Reverser]
}
func (r reverser) Reverse(_ context.Context, s string) (string, error) {
runes := []rune(s)
n := len(runes)
for i := 0; i < n/2; i++ {
runes[i], runes[n-i-1] = runes[n-i-1], runes[i]
}
return string(runes), nil
}
其他组件可以与逆转器组件进行交互,只需调用其方法:
reversed, err := reverser.Reverse(ctx, "Hello, World!")
组件的主要优点是它们不依赖于操作系统进程。尽管我们没有编写任何网络或序列化代码,但组件可以在单独的进程中运行,也可以在完全不同的机器上运行。下面的图表说明了这个概念:
当两个组件在一个进程中一起运行时,它们之间的方法调用作为常规 Go 方法调用执行。当两个组件在单独的进程中运行时,它们之间的方法调用作为 RPC 执行。
将应用程序分割为多个组件非常类似于将应用程序分割为多个微服务。它鼓励清晰的抽象边界,允许不同的组件独立扩展,等等。然而,组件避免了微服务的许多缺点:
- 所有组件运行在相同的版本。您不必推断微妙的跨版本兼容性,这是导致系统失败的主要原因之一。
- 您可以像运行、运行和测试一样轻松地运行和测试整个应用程序。您不必弄清楚如何构建和运行 n 个不同的微服务,所有这些微服务都按照正确的顺序和正确的环境和依赖关系进行。
- 拆分和合并微服务可能是痛苦的。这导致人们完全避免使用微服务,或者将他们的应用程序分割成不必要的大量微服务,因为担心“以后分割起来会更困难”。ServiceWeaver 使添加新组件或将两个组件合并在一起变得简单。您不必担心如何完善组件边界。
如何管理我的 Service Weaver 应用程序?
ServiceWeaver 提供了一些机制,可以轻松地测试、调试和部署应用程序的新版本,而不必像今天的云开发人员那样头疼和负担重重。
部署
ServiceWeaver 使得在云上运行应用程序和在笔记本电脑本地运行应用程序一样容易:
$ go run . # Run locally, in the same OS process.
$ weaver multi deploy weaver.toml # Run locally, in multiple OS processes.
$ weaver gke deploy weaver.toml # Run in the cloud.
配置
ServiceWeaver 需要很少的配置才能部署到云中。在线精品演示应用程序有超过1,500行的配置。用 ServiceWeaver 编写的同一个应用程序只有不到10个:
[weaver]
binary = "./online_boutique"
rollout = "6h"
[gke]
regions = ["us-west1", "us-east2"]
public_listener = [
{name = "boutique", hostname = "online-boutique.net"},
]
我们只需要指定应用程序二进制文件、首次推出的持续时间、应该部署应用程序的区域以及应该公开访问哪些网络侦听器。就是这样。
推出新版本
服务系统随着时间的推移而发展。无论您是在修复 bug 还是添加新特性,都不可避免地需要推出新版本的系统来替换当前运行的版本。为了维护系统的可用性,人们通常执行滚动更新。
在滚动更新期间,运行旧版本代码的节点必须与运行新版本代码的其他节点通信。在理解和检测分布式系统中的软件升级故障方面,Zhang 等人对8个广泛使用的系统中的123个失败的升级进行了案例研究。他们发现,大多数故障是由系统的多个版本之间的交互造成的:
大约三分之二的更新失败是由两个软件版本之间的交互造成的,这两个版本的数据语法或语义假设不兼容。
ServiceWeaver 采用不同的方法进行展示。它确保客户端请求完全在系统的单个版本中执行。一个版本中的组件永远不会与另一个版本中的组件通信。这消除了导致更新失败的主要原因,使您可以安全地生成应用程序的新版本,并且减少了麻烦。
对应用程序进行更改时,只需重新生成并重新运行它。ServiceWeaver 将负责从旧版本向新版本逐步转移流量。
检测
ServiceWeaver 提供了用于日志记录、度量和跟踪的库。这种遥测技术自动集成到部署应用程序的环境中。例如,如果我们在 Google Cloud 上部署一个应用程序,日志会自动导出到 Google Cloud Logging,指标会自动导出到 Google Cloud Monitor,跟踪信息会上传到 Google Cloud Trace。下面是一个如何将计数器添加到逆转器组件的示例:
var reverseCount = weaver.NewCounter(
"reverse_count",
"The number of times Reverser.Reverse has been called",
)
func (reverser) Reverse(_ context.Context, s string) (string, error) {
reverseCount.Add(1.0)
// ...
}
测试
基于微服务的应用程序的开发周期很慢。如果您想要对应用程序的业务逻辑进行迭代,那么您必须安装严重的云依赖关系,安装复杂的测试框架,或者将应用程序部署到云中。所有这些方法都会对开发速度产生重大影响。
另一方面,ServiceWeaver 应用程序可以像常规 Go 程序那样构建、运行和测试。您可以对应用程序进行更改,然后简单地运行。立即看到变化的影响。此外,ServiceWeaver 提供了一个 weavertest 包,它使编写端到端测试与编写单元测试一样容易。
高性能
ServiceWeaver 应用程序不仅更容易编写、运行和管理,而且速度更快。
我们发现,与基于 gRPC 和协议缓冲的典型微服务解决方案相比,Service Weaver 提高了15倍的应用程序延迟,降低了9倍的虚拟机成本。
没有 RPC,没有 Protos,一个高效的网络协议
所有应用程序组件运行在相同的代码版本,这使得 ServiceWeaver 能够大大优化其序列化和通信协议。例如,位于同一地点的组件通过直接方法调用进行通信; 不位于同一地点的组件使用语言本机数据结构和自定义 RPC 协议进行通信。序列化和 RPC 协议都比 gRPC 和协议缓冲更有效,因为它们避免了解决版本控制问题所需的开销。
高效的自动缩放和负载均衡
在单个应用程序二进制文件中以相同版本运行所有组件的另一个好处是,ServiceWeaver 可以鸟瞰应用程序和组件之间的交互。这使 ServiceWeaver 能够做出智能组件布局决策,并采用高效的自动伸缩解决方案。
灵活安置
ServiceWeaver 提供灵活的组件放置。例如,我们可以将两个经常在同一进程或机器中相互通信的组件共同定位。相反,我们可以将具有相同资源瓶颈的组件放在不同的机器上,以提高资源利用率或改进故障隔离。ServiceWeaver 使我们能够通过单行配置更改完成所有这些工作。
分片
ServiceWeaver 允许您使用应用程序特定的分片键跨组件副本分片应用程序请求。这使您能够更智能地将负载分散到组件副本之间。亲和性还提高了缓存效率,从而降低了应用程序延迟并减少了热点。