[转载] 站内消息DB设计思路

[转载]:点击打开链接  原文标题:两年后,再议“站内信”的实现  注:仅因为在工作中用到相似的东西,故借鉴作者的文章,如有不便请告知,即刻删除

两年前,万仓一黍在博客园发了两篇关于站内信的设计实现博文,《群发“站内信”的实现》、《群发“站内信”的实现(续)》,其中阐述了他关于站内信群发的设计思想,很具有借鉴意义。他在设计时考虑到用户量和存储空间的占用等问题。当然,在他的两篇博文中强调了站内信的设计要考虑具体情况,没有理想的设计方案,他的设计只是对于群发(点到面)的解决方案。 在此简述一下他的设计方案,详细的可以移步万仓一黍的博客。

万仓一黍的设计方案:

站内信分为“点到点”和“点到面”,“点到点”属于私信,用户之间传递的信息,一对一传递。“点到面”,属于系统消息或者公共信息,属于一对多发送。

站内信的设计既要考虑到投递的准确性(也就是该收到的人能收到信息),也要考虑信息持久化存储空间占用问题,在他的第一篇博文中详细进行了介绍。

我们在此仅把第三种情况拿出来说明,也就是用户量为百万级,活跃用户只占其中的一部分。

数据库的设计:

表名:Message

ID:编号;SendID:发送者编号;RecID:接受者编号(如为0,则接受者为所有人);MessageID:站内信编号;Statue:站内信的查看状态;

表名:MessageText

ID:编号;Message:站内信的内容;PDate:站内信发送时间;

将一封Message分为两部分,一是存储内容,另一个是存储用户的查看状态。也就解决了关于群发信息的存储空间占用问题,不需要为每个用户插入相关数据。

另外考虑到百万级用户量,活跃用户只占其中的一部分,不可能在发送一封系统消息时,在Message表中为每一个用户插入一条状态(标记为未读),如果一百万用户,那么发送一条消息,就得往Message表中插入一百万条标记状态的数据,显然不具有可行性。所以万仓一黍提出换下思路:

在用户登录时检索Message和MessgaeText,将MessgaeText的ID 和Messgae 的MessageID 相匹配, 这样就有两种情况:

一、没有找到  RecID= 自己ID 并且MessageText中的消息ID不包含在Messgae的MessageID中

将此部分消息取出来,显示为该用户未读,在用户点击阅读的时候,将消息阅读状态写入Messgae表,Status=已读。

二、找到RecID=自己 并且 MessageText中的消息ID包含在Messgae的MessageID中,Status标记为已读

将此部分消息提前出来,显示为用户已读,如果想“删除”(当然是逻辑上的删除,并非物理数据库删除),设置该Status=删除。

对于上面的设计方案,设计系统消息群发全部用户,是很适合的。但是受众面越小(即“点到面”的面越小),就不太合适,所以我们需要在此设计方案上进行扩展。

只对上面提到的用户百万级且活跃用户只占一部分这种情况探讨。还是采用将消息内容和阅读状态分开设计。

我们将点到点和点到面综合到一起考虑,并且精细化这个“面”,不再是笼统的全部用户。“面”可以是具有某一角色的用户、某一用户组的用户甚至一些不具有任何公共特征的散列用户。

设计思路

概述如下:我们将消息分为私信(Private)、公共消息(Public)、系统消息(Global)(或者将公共消息和系统消息合并为公共消息也可以),视情况而定。

  1. 点到点:一对一发送,属于私信Private
  2. 点到个别:(接收面为百位用户)一对多(几百)发送,采用私信方式(Private)
  3. 点到局部:(接收面为具有某些公共特征如用户组、用户角色),属于公共消息(Public)
  4. 点到全部:一对全部发送,属于系统消息(Global)

数据库设计

表名:Message

ID:编号;RecID:接收者编号;MessageID:站内信编号;Statue:站内信的查看状态

表名:MessageText

ID:编号;SendID:发送者编号;Message:站内信的内容;Type:信息类型;Group:用户组ID;  PostDate:站内信发送时间

其中Status状态有未读、已读、删除

Type类型有Private(私信)、Public(公共消息)、Global(系统消息)

第一种 点到点

点到点发送属于私信,比如A用户发送给B用户,首先在MessageText表中插入消息内容并且设置Type=Private,同时在Message表中插入一条记录设置RecID=B,Status=未读

用户B查找RecID=B,并且Staus为未读,Type=Private,显示为私信未读,点击阅读后改变Status=已读

用户B查找RecID=B,并且Staus为已读,Type=Private,显示为私信已读,删除设置Status=删除

第二种 点到个别

采用和私信相同的方式,在发送一条消息时在MessageText表中插入消息内容并且设置Type=Private,同时在Message表中插入多条记录设置RecID=各接收者ID,Status=未读

每个接收采用和私信一样的方式读取处理。

第三种 点到局部

点到局部是一对某角色或某用户组发送,例如管理员向普通用户组发送,在MessageText表插入消息内容,且设置Type=Public 和Group为用户组ID

用户登录后分两种情况:

1、未找到RecId=自己ID 且 MessageText中(Type=Public 和Group=自己所在组 )  的消息ID不包含在Messgae的MessageID中

提取出来显示为用户公共消息未读,在用户点击阅读的时候,将消息阅读状态写入Messgae表,Status=已读。

2、找到RecId=自己ID 且 MessageText中(Type=Public 和Group=自己所在组 ) 的消息ID包含在Messgae的MessageID中

将此部分消息提取出来,显示为用户公共消息已读,如果想“删除”(当然是逻辑上的删除,并非物理数据库删除),设置该Status=删除。

注:此时可以不验证Group=自己所在组

第四种 点到全部

点到全部和点到局部采用类似的处理方式。例如管理员向普通用户组发送,在MessageText表插入消息内容,且设置Type=Global

用户登录后分两种情况:

1、未找到RecId=自己ID 且 MessageText中(Type=Global ) 的消息ID不包含在Messgae的MessageID中

提取出来显示为用户系统消息未读,在用户点击阅读的时候,将消息阅读状态写入Messgae表,Status=已读。

2、找到RecId=自己ID 且 MessageText中(Type=Global ) 的消息ID包含在Messgae的MessageID中

将此部分消息提取出来,显示为用户系统消息已读,如果想“删除”(逻辑上的删除,并非物理数据库删除),设置该Status=删除。

处理流程

我们再来看下整个处理流程,用户登录后系统是怎样提取和显示信息的。

用户登录后,采用Ajax异步加载、统计用户站内信

  • Messgae表中RecId=自己ID 且Status=未读,显示为私信未读
  • Messgae表中RecId=自己ID 且Status=已读 且 Type=Private,显示为私信已读
  • Messgae表中未找到RecId=自己ID 且 MessageText中(Type=Public 和Group=自己所在组 ) 的消息ID不包含在Messgae的MessageID中,显示为公共消息未读
  • Messgae表中找到RecId=自己ID 且 MessageText中(Type=Public ) 的消息ID包含在Messgae的MessageID中 ,显示为公共消息已读
  • Messgae表中未找到RecId=自己ID 且 MessageText中(Type=Global ) 的消息ID不包含在Messgae的MessageID中 ,显示为系统消息未读
  • Messgae表中找到RecId=自己ID 且 MessageText中(Type=Global ) 的消息ID包含在Messgae的MessageID中 ,显示为系统消息已读

或许,大家会想这种方案同样没有解决物理删除的问题或者更加精细化的接收的“面”,在用户显示消息时需要很多复杂的判断影响用户体验和性能等等问题。其实我们还可以设置消息的有效期,过期的消息不显示。或者自己设计一个消息清除机制。当然,这些都需要以后进一步完善和改进,本文中暂时没有涉及。以上只是本人关于站内信的一些浅薄之见,能力有限必然存在各种问题,恳请园内的各位能提出批评、建议。如果您觉得本文很好可以 点击推荐,让更多的人参与讨论。 同时向万仓一黍致以谢意,感谢他分享了自己的宝贵思想。

时间: 2024-12-08 18:22:30

[转载] 站内消息DB设计思路的相关文章

站内邮件的设计思路

这个是做中国电信天天生活助理的时候做的,还是大四实习的时候弄得,当时设计的时候想了半天,后来觉得这个思路还是不错的,所以记录下来, 一般的话就是用户表,消息表和用户消息表了,用户消息表主要就是记录用户是否已读或者是否删除从而使得用户表和消息表独立了起来,查的时候只需要关联查询就好了 消息表:消息编号(message_id).消息标题(message_title).消息内容(message_content)消息创建时间(message_createDate).是否审核通过(flag:通过:1,不通

Lucene站内搜索的设计思路

正好近期部门有一个小需求需要做商品的搜索,虽然最终由于工作量等原因先做数据库搜索,我依然用刚接触的Lucene弄了一套自嗨. 首先看需求:搜索:根据商品标题和内容搜索 没错,就这么简单! 我想了想,数据源大概有以下两种: 1.根据需要搜索的字段,从数据库读取信息加载,并创建Lucene索引 2.利用爬虫定时抓取数据,并创建Lucene索引 由于是非正式的,数据库不方便访问,因此我选择利用爬虫去抓取指定网页,并清理内容来建立索引 其中,针对链接,做商品详情页的过滤,这样能保证最快速.最精准的建立我

Linux进程内消息总线设计

文章目录 Windows平台进程内消息总线 如果没有消息总线,会产生什么问题 死循环包含关系 高耦合.低内聚 消息总线 结构图 原理 生产者与总线的关系 总线与消费者的关系 Linux进程内消息总线设计 使用进程间实时信号来实现进程内消息总线 参考文档 整体流程 主线程注册总线消息处理函数 生产者线程产生并发送消息到总线 接收并处理消息 总线接收到消息 总线消息处理函数 消费者总线消息处理虚函数 消费者消息处理实函数 核心原理 sigqueue发送消息 sigaction处理消息 存在的问题 消

EQueue文件持久化消息关键点设计思路

要持久化的关键数据有三种 消息: 队列,队列中存放的是消息索引信息,即消息在文件中的物理位置(messageOffset)和在队列中的逻辑位置(queueOffset)的映射信息: 队列消费进度,表示当前队列中的消息消费到第几个了: 发送消息的设计 producer将消息的二进制数据发送到broker: broker做的事情: 单线程持久化消息到内存映射文件: 将当前消息的索引信息放入缓冲区,可以使用disruptor的ringbuffer实现,单线程写,无锁. 单线程从缓冲区读取消息索引信息,

PHP 站内消息的表结构设计

1)添加全站通知:信息存入到 tb_message 2)用户点开信息或者设置信息为已阅读:插入记录到 tb_message_readlog 如何设计存储的表?求最佳方案 CREATE TABLE `message` ( `message_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '消息id', `subject` varchar(250) NOT NULL DEFAULT '' COMMENT '主题', `content` varchar(5

【设计用例】站内信

[站内信]也被称作[站内消息],如果验证它呢?需要考虑的无非是2个方面: 功能验证 - 是否覆盖了所有的场景(开发人员会在代码里注释出来 会触发站内消息的场景 把这些沟通并记录下来 以免设计用例有所遗漏) 样式验证 - 站内消息里面包含的文字描述是否得到了正常的展示

“站内信”的实现

站内信分为"点到点"和"点到面","点到点"属于私信,用户之间传递的信息,一对一传递."点到面",属于系统消息或者公共信息,属于一对多发送. 站内信的设计既要考虑到投递的准确性(也就是该收到的人能收到信息),也要考虑信息持久化存储空间占用问题,在他的第一篇博文中详细进行了介绍. 我们在此仅把第三种情况拿出来说明,也就是用户量为百万级,活跃用户只占其中的一部分. 数据库的设计: 表名:Message ID:编号:SendID:发

ASP.NET 实现站内信功能(点对点发送,管理员群发)

正好这段时间在研究这个功能,还是得感谢这位大神,没有他的引路,我就不可能把站内信做出来. http://www.cnblogs.com/grenet/archive/2010/03/08/1680655.html 哈哈,再次感谢. 我们先来说说单点传送站内信,所谓的单点传送,就是用户与用户之间的短信发送,这里的用户可以是一个人,也可以是多个人, 上面的文章大家如果看了的话,想必有一个疑问,就是用户的阅读状态怎么定义? 我这里给大家一种解决方案,再建立一张表,也就是说,建立一张用户阅读状态表,因为

消息队列设计精要(转载)

消息队列已经逐渐成为企业IT系统内部通信的核心手段.它具有低耦合.可靠投递.广播.流量控制.最终一致性等一系列功能,成为异步RPC的主要手段之一.当今市面上有很多主流的消息中间件,如老牌的ActiveMQ.RabbitMQ,炙手可热的Kafka,阿里巴巴自主开发的Notify.MetaQ.RocketMQ等.本文不会一一介绍这些消息队列的所有特性,而是探讨一下自主开发设计一个消息队列时,你需要思考和设计的重要方面.过程中我们会参考这些成熟消息队列的很多重要思想.本文首先会阐述什么时候你需要一个消