用 mongodb 储存多态消息/提醒类数据(转)
原文:http://codecampo.com/topics/66
前天看到 javaeye 计划采用mongoDB实现网站全站消息系统,很有同感,mongodb 很适合储存消息类数据。之前讨论了如何构建一个微博型广播,这次讨论一下怎么储存消息/提醒类数据。
下面的内容不涉及关于海量数据储存的问题,只讨论数据模式。
1. 需求
消息/提醒类数据有不少例子,比如豆瓣的好友广播(我说、电影/书籍已读状态、网址推荐等),Twitter 的推信息 Tweet,SNS 的好友状态。
这类信息的一个特点是模式多变,豆瓣的好友广播有好几种模式,“我说”以用户发布的文本为主;动作消息(上传了什么)不带文本,但是需要关联别的数 据,例如书本,图片;推荐消息则要带文本和关联数据。Twitter 推信息则需要保存多样的信息,比如 mention 到的用户、回复到哪条推、附带的 url、地理位置,但这些数据有时是为空的。可以在这里看读取一个 twitter 消息会带有多少内容。
总的来说,关键词就是“多变”,并且随着应用的升级,状态信息还会增加更多模式和更多的项。
2. 使用 Mongodb 储存多态的消息
现在直接拿 CodeCampo 的例子来说明怎么用 Mongodb 储存这类多态的数据。Campo 的代码使用 Ruby on Rails 和 mongoid,完整的代码可以在 github 仓库 看到。
CodeCampo 中对消息的定义是建议用户立即查看,阅后即焚,并且过期会被删除的,所以设计为内嵌入 user 文档中储存,并且有数量限制(自动删除最旧的)。如果需要持久的储存消息(比如微博消息),可以用引用(DbRef)取代内嵌(Embed),将 notification 单独储存在一个 collection。
2.1 mongodb 中的模式
理想中 mongodb 会这样保存 notification 的数据。(注:Notification::Follower 和 Notification::Other 并未实现,只是用作举例)
> db.users.findOne()
{
_id : ObjectId(...),
...
notifications : [
{
_id : ObjectId(...),
_type : 'Notification::Mention',
replyer_id : ObjectId(...),
topic_id : ObjectId(...),
reply_id : ObjectId(...),
text : '@rei some message'
}
{
_id : ObjectId(...),
_type : 'Notification::Follower',
follower_id : ObjectId(...)
}
{
_id : ObjectId(...),
_type : 'Notification::Other',
Other_column : 'value'
}
]
}
2.2 用 Mongoid 实现
如果你熟悉 Mongodb,应该对怎么操作上面的文档有了大概的想法。这里展示一下用 mongoid 实现这样的数据结构的方法(如果你不熟悉 mongoid,可能需要看它的文档,特别是继承章节。)
首先建立一个 Notification::Base 用于和 User 建立关联。
class Notification::Base
include Mongoid::Document
include Mongoid::Timestamps field :text embedded_in :user, :inverse_of => :notifications
end
当别的类继承 Notification::Base,会继承其所有关联定义。
然后在 User 中定义 embed。
Class User
include Mongoid::Document
include Mongoid::Timestamps ...
embeds_many :notifications, :class_name => 'Notification::Base'
...
end
现在,可以用 @user.notifications.create(attributes) 的方法建立一个消息提醒了。但默认使用的 Notification::Base 并不是最终需要创建的消息类型,所以继续新建一个 Notification::Mention。
class Notification::Mention < Notification::Base
referenced_in :topic
referenced_in :reply
referenced_in :reply_user, :class_name => 'User'
end
注意这个 Mention 类中并没有定义和 user 的 embed 关系,但因为它继承了 Notification::Base,所以将 Base 的模块和 embed 关联一并继承了。Mention 类只需要定义自有部分的逻辑。
现在,创建一个 Mention 消息的 Ruby 代码会是这样:
@user.notifications.create({:reply_user_id => user_id,
:topic_id => topic_id,
:reply_id => reply_id,
:text => 'summary text',
Notification::Mention)
保存到 mongodb 中的数据如下
> db.users.findOne()
{
_id : ObjectId(...),
...
notifications : [
{
_id : ObjectId(...),
_type : 'Notification::Mention',
replyer_id : ObjectId(...),
topic_id : ObjectId(...),
reply_id : ObjectId(...),
text : 'summary text'
}
....
]
}
保存的数据跟理想中的一样。需要新增消息类型,就仿照 Notification::Mention,建立新的 Notification::Base 子类就可以了。
3. 用 SQL 数据库如何实现?
豆瓣和 Twitter 都是使用 MySQL 储存广播和推数据,那么他们是怎么实现这样多态的数据结构呢?我并不知道他们的内部情况,不过 SQL 如何实现多态也有不少文章(例如铁道书里面介绍 ActiveRecord 就支持多态和继承),这里举一些方案做对比。
3.1 单表继承
简单的说就是把一个表映射到不同的模型上。怎么做到的呢?方法是在一个表内保存整个继承体系涉及的所有字段。例如
notifications(id, type, user_id, reply_id, topic_id, replyer_id, text, ...)
区别消息类型的字段就是 type,在应用层根据 type 的不同应用不同的逻辑。但是,即使某类消息(例如 follower 提醒)并不使用所有的字段,它都需要以数据库一行记录的方式保存在库中。
显而易见,这样会带来大量的空字段,影响表的纯洁性。即使尝试对一些字段进行合并重用,随着应用的发展,渐渐还是会带来维护和迁移的麻烦。需要指出的是,即使用方法2的多态关联,也有可能为了减少表的数量而渐渐走入字段重用的歧路。
3.2 多态关联
另一种实现异构对象聚合的方法是多态关联。它的原理是用一张表某个字段多态的引用多个表。例如:
notifications(id, user_id, type, entry_id)
mention_nofitications(id, reply_id, topic_id, replyer_id, text)
follower_notifications(id, follower_id)
other...
关联的逻辑依赖 notifications 的 type 和 entry_id 字段,type 的值可以取 “mention”、"follower"等等消息的类型,从而选择读取哪一个 xxx_notifications 表的数据。
多态关联很好的维护了表的纯洁性,但有一个缺点就是无法使用 JOIN 查询,会导致 N + 1 查询问题(也许SQL专家可以告诉我怎么在一个查询查出不同类型的消息,但可以预计SQL的逻辑比较复杂,而且JOIN的表太多也会影响效率)。
如果使用这种方法,最好给数据库加上一个缓存层,缓存取出的完整消息数据,减少数据库查询。Twitter 有一个 Row Cache 层,估计就是用来干这事。
3.3 序列化后保存
还有一种方案是将各种字段序列化后储存,每次读取出来先反序列化后判断内容类型。这样就可以节省很多表字段,也避免 N + 1 查询的问题。
notifications(id, user_id, serialized_entry)
这种方案其实也不错,一个缺点是不便于做后续处理,比如用序列化来保存一个推特信息的 mention 用户ID,那么就无法反过来查询有哪条信息 mention 了某用户。这样就要把需要查询的信息独立为字段,无法避免一些情况下空字段的问题。
4. 总结
比较了上面几种多态数据的实现方案之后,还是认为 MongoDb 的方案较为优雅。SQL 数据库在储存复杂结构的数据时,通常需要一个缓存层来掩护。而 MongoDb 内建对复杂结构的储存支持,开发的难度就小一些(少一个层,少一个烦恼)。所以用 MongoDb 开发 web 程序,真的能减少不少技术成本。
限于视野,可能有些好的方法我未曾见过和想过,欢迎留言告诉我这些方法。
用 mongodb 储存多态消息/提醒类数据(转)的更多相关文章
- PHP实现RTX发送消息提醒
RTX是腾讯公司推出的企业级即时通信平台,大多数公司都在使用它,但是我们很多时候需要将自己系统或者产品的一些通知实时推送给RTX,这就需要用到RTX的服务端SDK,建议先去看看RTX的SDK开发文档( ...
- 使用SignalR实现消息提醒
Asp.net SignalR是微软为实现实时通信的一个类库.一般情况下,SignalR会使用JavaScript的长轮询(long polling)的方式来实现客户端和服务器通信,随着Html5中W ...
- [Asp.net 开发系列之SignalR篇]专题六:使用SignalR实现消息提醒
一.引言 前面一篇文章我介绍了如何使用SignalR实现图片的传输,然后对于即时通讯应用来说,消息提醒是必不可少的.现在很多网站的都有新消息的提醒功能.自然对于SignalR系列也少不了这个功能的实现 ...
- android气泡消息提醒布局
无论是anroid还是ios,气泡消息提醒再正常不过了.然而要定义一个气泡消息提醒确要费一番周折.下面记录下气泡提醒布局. 定义气泡背景shape_unread_message_bg.xml < ...
- RTX发送消息提醒实现以及注意事项
一.RTX简介 RTX是腾讯公司推出的企业级即时通信平台.该平台定位于降低企业通信费用,增强企业内部沟通能力,改善企业与客户之间的沟通渠道,创造新兴的企业沟通文化,提高企业生产力.RTX平台的主要功能 ...
- jquery 消息提醒插件 toastmessage
最近做系统,想到使用后台要使用消息提醒,但是一直苦恼消息提醒的效果,于是找了一个toastmessage,还不错.记录下使用的方法. 第一步:引入需要的文件 <script type=" ...
- Android实例-设置消息提醒(XE8+小米2)
相关资料: 1.官网实例:http://docwiki.embarcadero.com/RADStudio/XE5/en/Mobile_Tutorial:_Using_the_Notification ...
- 实时显示数据 SignalR 及时消息提醒( 立即向其推送内容)
实时显示数据 SignalR 及时消息提醒( 立即向其推送内容) http://www.cnblogs.com/Leo_wl/p/5634910.html <!--Reference the ...
- python使用itchat发送微信消息提醒
最近在学习一点python,先找了找有趣的应用,实际修改跑了一下提高兴趣程度. 找到itchat,它的简介是这样的: “itchat是一个开源的微信个人号接口,使用python调用微信从未如此简单. ...
随机推荐
- SharePoint 2013 网站迁移流程
在新的Farm(场)里,创建一个新的Web Application(网站应用程序),不需要创建Site Collection(网站集) Copy(复制)自定义开发的WSP包到新的Farm Server ...
- JavaScript 事件参考手册
事件通常与函数配合使用,这样就可以通过发生的事件来驱动函数执行. 事件句柄 HTML 4.0 的新特性之一是有能力使 HTML 事件触发浏览器中的动作(action),比如当用户点击某个 HTML 元 ...
- php 用命令行导出和导入MySQL数据库
命令行导出数据库:1,进入MySQL目录下的bin文件夹:cd MySQL中到bin文件夹的目录如我输入的命令行:cd C:\Program Files\MySQL\MySQL Server 4.1\ ...
- PHP大小写是否敏感问题
一.大小写敏感 1. 变量名区分大小写 所有变量均区分大小写,包括普通变量以及$_GET,$_POST,$_REQUEST,$_COOKIE,$_SESSION,$GLOBALS,$_SERVER,$ ...
- MySQL<表单&集合查询>
表单查询 简单查询 SELECT语句 查询所有字段 指定所有字段:select 字段名1,字段名2,...from 表名; select * from 表名; 查询指定字段 select 字段名1,字 ...
- java中定义一个CloneUtil 工具类
其实所有的java对象都可以具备克隆能力,只是因为在基础类Object中被设定成了一个保留方法(protected),要想真正拥有克隆的能力, 就需要实现Cloneable接口,重写clone方法.通 ...
- brocadcastReceiver
用来接收广播, 可以根据系统发生的一些时间做出一些处理 系统的一些事件,比如来电,来短信,等等,会发广播:可监听这些广播,并进行一些处理: Android3.2以后,为了安全起见,对于刚安装的应用,需 ...
- JS 操作iframe
很多人一直都有个想法,要是可以随心所欲的操作iframe就好了.这样静态页面也就有了相当于后台动态页面php,jsp,asp中include,require实现统一多页面布局的能力. 通过Javasc ...
- iOS底层音频处理技术(带源代码)
本文由论坛会员artgolff分享 前几天搜索资料时发现一个网站: iPhone Core Audio Development ,里面有iOS底层 音频 技术的几个源 代码 ,如果你要实现VoIP电话 ...
- UIBarButtonItem
1.UINavigationController导航控制器如何使用 UINavigationController可以翻译为导航控制器,在IOS里经常用到. 我们看看它的如何使用: 下面的图显示了导航控 ...