River:Go中快速可靠的事务性后台作业


使用像 PostgreSQL 这样的关系数据库作为作业队列是一种可以接受的方法,一些公司在生产中成功使用了这种方法。虽然 Redis 等更传统的队列可能更适合高吞吐量低延迟用例,但数据库可以为作业处理提供事务保证。不需要长时间超时的短作业可以很好地工作。前提是如果每秒处理 10K 事务并且作业平均不需要花费大量时间来完成,那么基于 Postgres 的作业队列工作得很好。

事务和后台PostgreSQL作业是天作之合,可以完全避免一大堆分布式系统问题,否则这些问题很难修复。

本文讨论了一种名为 River for Go 和 Postgres 的新作业队列。它旨在提供快速且强大的解决方案。一些关键点:

  • River 利用 Postgres 的事务支持来提供可靠的作业处理。作业以事务方式插入和处理,避免了许多分布式系统问题。
  • 它使用 Go 泛型来提供强类型作业参数,避免类型转换。作业是简单的 Go 结构。
  • River 专为性能而设计。它利用 pgx 的二进制协议并最大限度地减少数据库往返次数。像作业插入这样的批量操作使用 COPY FROM。

River真正想充分利用的 Go 中相对较新的功能(自 1.18 起)之一是泛型的使用。River Worker 采用一个river.Job[JobArgs]参数,该参数提供对以下参数的强类型访问:

type SortWorker struct {
    river.WorkerDefaults[SortArgs]
}

func (w *SortWorker) Work(ctx context.Context, job *river.Job[SortArgs]) error {
    sort.Strings(job.Args.Strings)
    fmt.Printf("Sorted strings: %+v\n", job.Args.Strings)
    return nil
}

没有类型转换。100% 无反射。

队列 是原始的 Go 结构,没有嵌入、魔法或恶作剧。只有一个Kind实现能够提供唯一、稳定的字符串来识别往返于数据库的作业:

type SortArgs struct {
    // Strings is a slice of strings to sort.
    Strings []string `json:
"strings"`
}

func (SortArgs) Kind() string { return
"sort" }

除了基础功能之外,River 还支持批量插入、错误和恐慌处理程序、定期作业、遥测订阅挂钩、独特作业以及许多其他功能。

我们喜欢用 Go 编写东西的原因之一是它速度很快。我们希望 River 成为生态系统的好公民,并将其设计为使用快速技术,我们可以:

  • 它利用 pgx 对 Postgres 二进制协议的实现,避免大量对字符串进行编组和解析。
  • 它最大限度地减少了数据库的往返次数,执行批量选择和更新以合并工作。
  • 批量作业插入等操作可提高COPY FROM效率。