分布式系统的消息传递
在分布式系统中,多个服务器经常需要共享同一数据,或相互传递数据。
服务器间相互传递数据的方式有很多,这里我们介绍redis中的订阅和发布机制,通过redis中的Subscribe
和Publish
机制来实现服务器间的消息传递。
发布/订阅(pub/sub)消息传递模式基本概念
首先我们要了解发布/订阅(pub/sub)消息传递模式的一些基本概念。
频道
频道是一个命名的管道,发布者通过它发送消息,订阅者从中接收消息。频道的名称是一个字符串,客户端可以根据这个名称来订阅感兴趣的频道。
发布者
发布者是发送消息到指定频道的客户端。在Redis中,发布者使用PUBLISH命令将消息发送到一个频道上。所有订阅了该频道的客户端都将接收到这条消息。
订阅者
订阅者是希望接收来自一个或多个频道的消息的客户端。在Redis中,订阅者使用SUBSCRIBE命令来监听一个或多个频道。当一个频道上有消息发布时,所有订阅了该频道的客户端都会收到这条消息。
在Redis中,发布/订阅(pub/sub)消息传递模式允许发布者(publishers)发送消息到一个中央服务器,而订阅者(subscribers)可以订阅这些消息,无需知道是哪个发布者发布的消息。这种方式非常适合用于分布式系统中的消息广播。
下图展示了一个订阅频道的例子,在这个例子中,客户端client-1、client-2和client-3都订阅了频道"nihao"。
那么当发布者发布一个消息时,所有的订阅者都将收到该消息。
基本工作原理
- 订阅者使用SUBSCRIBE channel_name来订阅一个频道。
- 发布者使用PUBLISH channel_name message来发布消息到指定频道。
- Redis服务器处理这些命令,确保所有订阅了channel_name频道的客户端都收到message消息。
go-rueidis实现
我们可以使用go-ruedis实现redis中的消息订阅发布机制。
首先要获取ruedis数据包。
go get github.com/rueian/rueidis
然后可以通过rueidis
中的内置函数来实现redis中的订阅功能。
当在Redis中订阅一个频道时,我们只需要提供一个频道名。如果该频道名不存在,Redis会自动创建一个新的频道,频道的创建是隐式的,发生在第一次有客户端订阅该频道名时。
下面是一个订阅频道的go代码示例。
订阅频道
我们可以通过rueidis.Client中的Receive函数来订阅并持续监听一个频道,并在函数中处理频道中传来的消息。
如下代码,我们订阅了一个名叫nihao
的频道,并通过func(msg rueidis.PubSubMessage)
处理频道中传来的信息。
func subscribeGlobalID(rdb rueidis.Client, ctx context.Context, log *logrus.Entry) {
err := rdb.Receive(ctx,
rdb.B().Subscribe().Channel("nihao").Build(), func(msg rueidis.PubSubMessage) {
// 频道中传来消息的处理函数
res:=msg.message // msg.message为`nihao`频道传来的数据,为字符串类型。
log.Debugf("receive a message %s",res)
// to do 更多消息处理逻辑
})
if err != nil {
log.Errorf("Failed to subscribe graph channel.")
}
}
发布消息
同样,我们也可以通过rueidis.Client中的Publish函数,创建发布者,向频道中发布消息。
func GlobalIDPublish(rdb rueidis.Client, ctx context.Context, log *logrus.Entry) error {
msg:="这是一条消息"
// to do 更多的msg处理逻辑,注意,最后生成的msg为字符串形式
cmd = rdb.B().Publish().Channel("nihao").Message(msg).Build()
_ = rdb.Do(ctx, cmd)
// 向'nihao'频道发出信息。
log.Debugf("Global ID incremented to %d and published to channel NewNode\n", newID)
return nil
}
通过以上函数可以向频道nihao
发送信息,所有订阅了该频道的订阅者都可以收到信息msg。
需要注意的是
-
订阅者使用Receive方法来持续监听nihao频道上的消息。这个监听是持续的,它会一直运行,直到上下文(ctx)被取消或者发生错误。这意味着订阅者会保持监听状态,等待新的消息到来,并在消息到达时进行处理。
-
发布操作是一次性的,发布者发布消息后并不保持在频道上的任何状态,也不会继续监听该频道。
-
一个Redis客户端不能同时在同一个连接中同时充当订阅者和发布者两个身份。当一个客户端订阅了一个频道后,该连接会进入一个特殊的订阅状态,在这种状态下,它只能接收消息和执行与订阅/取消订阅相关的命令。
-
一旦客户端订阅了一个或多个频道,它将不能执行常规的 Redis 命令,如 GET, SET, HGET, ZADD 等。它只能接收消息和执行与订阅/取消订阅相关的命令(如 SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE 等)。
-
如果客户端想要既发布消息又订阅消息,它需要使用两个不同的连接:一个用于订阅频道并接收消息,另一个用于发布消息到频道。
-
在使用Redis的发布/订阅(pub/sub)消息传递模式时,所有的分布式服务器需要连接到同一个Redis服务器或Redis集群。