分布式消息通知和单机异步通知的实现
分布式消息通知
套件需要实现的功能是这样的:存在一单点服务能够生产消息,消息生产后希望广播到所有的订阅者中处理该消息。经典的订阅广播模型(一对多)。业务场景是这样的:第三方平台产生一条道具发放的消息,携带道具发放的相关信息(能够定位到消费者的服务器位置,但是消息队列不知道消费者的位置)。希望这条消息能够被指定的服务消费处理。
在分服架构下,玩家可能位于不同的服务器。每个游戏服务启动时,向消息队列订阅主题。产生消息的服务向消息队列的指定主题发布消息,所有的服务端都会收到这条消息,但是由于玩家的分布式唯一性(需要具备分布式唯一性,但是也存在不具备的情况,比如玩家同时出现在多个服务器上,这个问题后面讨论),接收到消息的服务端需要识别消息,判断自己是不是消息的处理者并处理消息。
这样一个基本的分布式消息通知框架就搭建好了。
但是这里存在一个问题,消息容易被重复消费,即多个服务器都认为自己可以处理这条消息并处理了它。如何解决这个问题呢?首先我们要保证消息处理对象在分布式环境中具备唯一标识,也就是拥有全局唯一ID。在消息发送时得到这个唯一ID,并将其附加到消息中。接收消息的服务端在处理消息时,首先检查自己是否已经处理过该消息,如果处理过则直接返回,避免重复处理。
如何保证不会出现两个具有相同ID的对象生成在分布式环境呢?答案就是使用分布式锁控制这个对象的创建和销毁。当需要创建对象时,先尝试获取分布式锁,成功获取锁后再进行对象的创建和初始化,最后释放锁。这样就能保证在分布式环境中,同一时间只有一个服务能够创建该对象。
单机异步通知的实现
有这样一个需求,需要对用户进行消息通知,如果用户在线就立即通知,如果用户不在线需要把通知缓存起来等待用户上线后异步通知。这样的异步通知在游戏,通讯中是非常常见的需求。
这里介绍几个常见的解决方案。
消息持久化方案
在检测到用户并未在线时,将消息和消息内容持久化到数据库中,等待用户上线时,查表建立通知操作,通知用户消息。通知完删除记录。
1
2
3
4
5
6
type Notify struct {
NotifyId string
UserId string
Content string
Timestamp int64
}
如果消息是非常频繁的,那么这样的方案的压力会集中在数据库上,这是非常危险的。比如消息是聊天记录,那么一个用户可能会用几十几百条消息等待异步通知,在用户量非常大的情况下,可能会带来数据库的崩溃。
消息缓存方案
消息持久化的地方如果位于redis缓存中,可以承受的压力就会大很多。
1
2
key := "notify:" + userId
value := content
但是KV型缓存无法储存复杂的消息内容,可能需要对消息结构进行序列化。频繁的序列化和反序列化会带来计算压力的增大。
重复消息的处理
在消息处理中,通常会出现多条消息可以被合并为一条消息的情况。在这种情况下,需要对消息表结构进行唯一性约束设计,