已读消息和未读消息设计
“站内信”有两个基本功能:
- 点到点的消息传送。用户给用户发送站内信,管理员给用户发送站内信。
- 点到面的消息传送。管理员给用户(指定满足某一条件的用户群)群发消息
这两个功能实现起来也很简单{如图}。只需要设计一个消息内容表和一个用户通知表,当创建一条系统通知后,数据插入到消息内容表。消息内容包含了发送渠道,根据发送渠道决定后续动作。如果是站内渠道,在插入消息内容后异步的插入记录到用户通知表。
![](https://i-blog.csdnimg.cn/blog_migrate/a32f3fd8911733ed14d0c7e7bb7f062b.png)
这个方案看起来没什么问题,但实际上,我们把所有用户通知的消息全部放在一个表里面,如果有 10W 个用户,那么同样的消息需要存储 10W 条。
很明显,会带来两个问题:
- 随着用户量的增加,发送一次消息需要插入到数据库中的数据量会越来越大,导致耗时会越来越长
- 用户通知表的数据量会非常大,对未读消息的查询效率会严重下降
解决方案
方案一
所以上面这种方案很明显行不通,要解决这两个问题,我有两个参考解决思路:
第一个方式(如图),先取消用户通知表, 避免在发送平台消息的时候插入大量重复数据问题。其次增加一个“message_offset”站内消息进度表,每个用户维护一个消息消费的进度 Offset。每个用户去获取未读消息的时候,只需要查询大于当前维护的 msg_id_offset 的数据即可。在这种设计方式中,即便我们发送给 10W 人,也只需要在消息内容表里面插入一条记录即可。在性能上和数据量上都有较大的提升。
![](https://i-blog.csdnimg.cn/blog_migrate/8ed4d304457a3097ba077609bcfe896c.png)
方案二
第二种方式,和第一种方式类似,使用 Redis 中的 Set 集合来保存已经读取过的消息 id。使用 userid_read_message 作为 key,这样就可以为每个用户保存已经读取过的所有消息的 id。当用户读取了未读消息后, 就直接在 redis 的已读消息 id 的 set 中新增一条记录。这样,在已经得知到已读消息的数量和具体消息 id 的情况下,我们可以直接使用消息id 来查询没有消费过的数据。