消息功能,站内信的设计实现思路汇总

消息功能和站内信的需求概括

B站消息功能界面截图

一般项目的消息功能都会有多种类型,比如订单状态推送,点赞推送,收藏,系统通知,到期提醒,系统公告(后台会有发送消息的需求)。同时可能会伴随多种渠道(短信,邮件,app push)的消息推送。

按推送用户数量分为,发送给全部用户,发送给部分用户,实现发送消息和已读状态更能,在用户数量不多(中小型项目)的情况下,最适合的方法是存中间表。

当用户数量增长之后,比如100万人同时点赞,每天产生的数据量非常大,不存数据库该如何实现?

方案一 使用 redis 的 bitmap

参考 一看就懂系列之 详解redis的bitmap在亿级项目中的应用

redis bitmap介绍

bitmap不是一个实际的数据类型,而是一组定义在String类型上的面向位的操作。由于字符串是二进制安全的blobs,其最大长度为512MB,所以它们适合设置2^32个不同的位。

位操作分为两组:恒定时间的单位操作,如将一个位设置为1或0,或获得其值,以及对位组的操作,如在给定的位范围内计算设置的位的数量(如人口计数)。

位图最大的优点之一是,它们在存储信息时往往能极大地节省空间。例如在一个系统中,不同的用户由递增的用户ID代表,只需使用512MB的内存就可以记住40亿用户的一个比特信息(例如,知道一个用户是否想收到通讯)。

位是用SETBIT和GETBIT命令来设置和检索的。

setbit key 10 1
(整数) 1
getbit key 10
(整数) 1
getbit key 11
(整数) 0
SETBIT命令的第一个参数是位号,第二个参数是要将该位设置为的值,即1或0。

GETBIT只是返回指定索引处的位的值。超出范围的位(寻址的位在存储到目标键的字符串长度之外)总是被认为是零。

有三个命令对位组进行操作。

BITOP在不同的字符串之间进行位的操作。提供的操作有AND、OR、XOR和NOT。
BITCOUNT执行群体计数,报告设置为1的位的数量。
BITPOS找到第一个具有指定值0或1的位。
BITPOS和BITCOUNT都能对字符串的字节范围进行操作,而不是对字符串的整个长度进行操作。下面是一个调用BITCOUNT的微不足道的例子。

setbit key 0 1
(integer) 0
setbit key 100 1
(整数) 0
bitcount key
(整数) 2
位图的常见用户案例有。

各种类型的实时分析。
储存与对象ID相关的节省空间但性能高的布尔信息。
例如,想象一下,你想知道你的网站用户每天访问的最长连贯时间。你从零开始计算天数,也就是你公开网站的那一天,并在每次用户访问网站的时候用SETBIT设置一个位。作为一个比特索引,你只需取当前的unix时间,减去初始偏移量,然后除以3600*24。

这样,对于每个用户,你都有一个包含每天访问信息的小字符串。通过BITCOUNT可以很容易地得到一个给定的用户访问网站的天数,而通过几个BITPOS调用,或者简单地获取和分析客户端的位图,就可以很容易地计算出最长的连绵时间。

将位图分割成多个键是很容易的,例如,为了分片数据集,以及在一般情况下,最好避免使用巨大的键。为了将一个位图分割成不同的键,而不是将所有的位设置成一个键,一个简单的策略就是每个键存储M个位,用位数/M获得键的名称,用位数MOD M获得键内的第N位。

//设置用户已读:
$redis->setBit('message:'.$msg_id, $uid, 1);

//获取是否读取状态:
$redis->getBit('message:'.$msg_id, $uid);

//支持千万级用户,并且不会有数据存储方面的压力

另外:bitmap 还可以做签到,活跃统计,在线状态等等

参考文章

站内信需求设计——人人都是产品经理

技术方案参考文章

消息系统(功能)的设计与实现

消息功能中系统通知这一类信息的已读未读除了用数据表之外有没有比较好的解决方案

站内信设计总结——掘金

站内信设计思路——人手一本PHP工具书

37 | 计数系统设计(一):面对海量数据的计数器要如何做?——极客时间

38 | 计数系统设计(二):50万QPS下如何设计未读数系统?——极客时间

一看就懂系列之 详解redis的bitmap在亿级项目中的应用