之前我写了关于使用RabbitMQ和Kafka的事件,这次我采取了类似的方法,但使用了另一个工具的消息代理功能,叫做。Redis。
什么是Redis?
根据他们的网站,Redis 是(强调是我的)。
是一个开源(BSD许可)的内存数据结构存储,用作数据库、缓存和消息代理。
Redis 它在大负荷下工作得非常好,我已经使用它超过五年了,它从来没有让人失望过,它提供了不同的功能,可能与你试图解决的问题有关。
这一次,我将描述使用Pub/Sub支持在不同的服务之间传递消息。
什么是Pub/Sub?
Pub/Sub是一种消息传递范式,包括定义发布者和订阅者之间的通道,其中发布者充当 "消息发送者",订阅者充当 "消息接收者"。发布者不直接向订阅者发送消息,而是向通道发送消息;这些通道作为发布者和订阅者之间的中介,其目的是让这些订阅者只接收他们感兴趣的消息,并让他们与发布者脱钩。
Redis 浏览器支持这种模式,并为发布、订阅和取消订阅定义了不同的命令;它甚至支持模式匹配的订阅。
关于Redis ,一个真正重要的事情是,通道不存储任何消息,如果没有订阅者,消息就不会发送给任何人,因此它们会丢失。

但是,正如预期的那样,如果有订阅者存在,那么消息就会被发送到监听这些值的客户端。

使用存储库的发布者实现
对于Go,我推荐使用 go-redis/redis包,因为它允许你以一种类型安全的方式访问Redis。
我们的redis.Task类型是负责将事件发布到不同通道的 存储库,代码与我们过去所做的类似。
func (t *Task) publish(ctx context.Context, spanName, channel string, e interface{}) error {
var b bytes.Buffer
if err := json.NewEncoder(&b).Encode(e); err != nil {
return internal.WrapErrorf(err, internal.ErrorCodeUnknown, "json.Encode")
}
res := t.client.Publish(ctx, channel, b.Bytes())
if err := res.Err(); err != nil {
return internal.WrapErrorf(err, internal.ErrorCodeUnknown, "client.Publish")
}
return nil
}
最大的区别是,在这种情况下,每个消息类型都在使用它们的下行通道,这一点在我们看订阅者的时候会更清楚一些。
订阅者的实现
与过去的做法类似,我必须建立一个新的服务,负责接收这些事件,以更新Elasticsearch的记录。这个新服务的关键部分是使用模式匹配来订阅多个事件。
func (s *Server) ListenAndServe() error {
pubsub := s.rdb.PSubscribe(context.Background(), "tasks.*") // Pattern-matching subscription
_, _ = pubsub.Receive(context.Background()) // XXX: error checking omitted for brevity
s.pubsub = pubsub
go func() {
for msg := range pubsub.Channel() {
switch msg.Channel {
case "tasks.event.updated", "tasks.event.created":
// ...
case "tasks.event.deleted":
// ...
}
}
// ...
}()
return nil
}
有了这两部分,每次客户与我们的REST API互动时,都会有一条消息被发布,然后由订阅者消费,最后更新ElasticSearch的记录。
总结
使用Pub/Sub与Redis ,是在不同服务之间实现消息传递的一种简单方法;但是与类似的工具,如Kafka或RabbitMQ相比,我们需要注意的是,这些消息并不存储在Redis ,甚至不是暂时的,所以如果没有订阅者已经在监听这些消息,那么所有发布的消息都会丢失,所以根据用例,这也许不是最好的主意。
好好想想你要解决的问题,也许Redis 才是你应该使用的轻量级解决方案。
稍后再谈。