只需几行代码就能将Redis流变成打字的Go通道

103 阅读1分钟

Go类型的Redis流

有效地读取Redis流需要一些工作:计算ID、预取和缓冲、异步发送确认和解析条目。如果仅仅是以下这些呢?

consumer := NewGroupConsumer[MyType](...)
for msg := range consumer.Chan() {
  // Handle mssage
  consumer.Ack(msg)
}

等等......是这样的!🔥

快速启动

定义一个代表你的流数据的类型。它将被自动解析,所有字段名都被转换成蛇形大小写。缺少的字段将被无声地跳过。你也可以使用ConvertibleFromConvertibleTo 接口来进行自定义解析。

// maps to {"name": , "priority": }
type Event struct {
  Name     string
  Priority int
}

消耗者

消耗者允许通过Go通道读取redis流。指定上下文、redis客户端和开始读取的位置。如果你不喜欢默认的或者想获得最佳性能,请确保指定StreamConsumerConfig 。新条目是异步获取的,以提供快速流🚂。

consumer := NewConsumer[Event](ctx, rdb, StreamIDs{"my-stream": "$"})

for msg := range cs.Chan() {
  if msg.Err != nil {
    continue
  }
  var event Event = msg.Data
}

不要忘记Close() 消费者。如果你想从你离开的地方重新开始阅读,你可以保存最后的StreamIDs:

ids := cs.Close()

组消费者

它们的工作方式与普通消费者一样,允许异步发送确认。请注意,只有当你不断处理新的消息时,才可以使用Ack ,也就是在一个消费循环中或从另一个goroutine中。尽管这引入了一个两边的依赖,但消费者可以避免死锁:

cs := NewGroupConsumer[Event](ctx, rdb, "group", "consumer", "stream", ">")

for msg := range cs.Chan() {
  cs.Ack(msg)
}

停止处理了?检查你的错误 🔎

// Wait for all acknowledgements to complete
errors := cs.AwaitAcks()

// Acknowledgements that were not sent yet or their errors were not consumed
remaining := cs.Close()

错误处理

这是简单的地方,但也只是简单的地方 🙂 通道不仅提供值,还提供错误。这些错误只能有三种类型:

  • ReadError 报告一个失败的XRead/XReadGroup请求。消费者将在这个错误后关闭通道
  • AckError 报告一个失败的XAck请求
  • ParseError 不言自明

消费者不在取消时发送错误,并立即关闭通道:

switch errv := msg.Err.(type) {
case nil: // This interface-nil comparison in safe
  fmt.Println("Got", msg.Data)
case ReadError:
  fmt.Println("ReadError caused by", errv.Err)
  return // last message in channel
case AckError:
  fmt.Printf("Ack failed %v-%v caused by %v\n", msg.Stream, msg.ID, errv.Err)
case ParseError:
  fmt.Println("Failed to parse", errv.Data)
}

所有这些类型都是包装错误。例如,ParseError ,可以解包为:

  • 通过FieldParseError 找出默认解析器失败的原因(例如,将字符串分配给int字段)
  • 捕捉自定义错误,从ConvertibleFrom
var fpe FieldParseError
if errors.As(msg.Err, &fpe) {
  fmt.Printf("Failed to parse field %v because %v", fpe.Field, fpe.Err)
}

errors.Is(msg.Err, errMyTypeFailedToParse)

Streams是对流中的基本redis命令的简单包装器:

stream := NewStream[Event](rdb, "my-stream")
stream.Add(ctx, Event{
  Kind:     "Example event",
  Priority: 1,
})

安装

go get github.com/dranikpg/gtrs

Gtrs仍然处于早期阶段,可能会在进一步的版本中发生变化。

实例

性能

go test -run ^$ -bench BenchmarkConsumer -cpu=1

一个模拟的客户端的迭代成本大约是500-700ns,取决于缓冲区的大小,这使得它的吞吐量接近每秒200万条🚀。得到不好的结果?请确保在选项中设置大的缓冲区尺寸。