用 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 储存多态消息/提醒类数据(转),布布扣,bubuko.com

时间: 2024-10-20 17:38:00

用 mongodb 储存多态消息/提醒类数据(转)的相关文章

使用SignalR实现消息提醒

Asp.net SignalR是微软为实现实时通信的一个类库.一般情况下,SignalR会使用JavaScript的长轮询(long polling)的方式来实现客户端和服务器通信,随着Html5中WebSockets出现,SignalR也支持WebSockets通信.另外SignalR开发的程序不仅仅限制于宿主在IIS中,也可以宿主在任何应用程序,包括控制台,客户端程序和Windows服务等,另外还支持Mono,这意味着它可以实现跨平台部署在Linux环境下. SignalR内部有两类对象:

RTX消息提醒工具设计文档

为什么要做 项目上线后,系统依然由各业务模块负责人自己维护.而后台运行的各种业务服务结果,不能及时反馈到业务负责人.而等到客户反馈时则会太被动.为了能及时发现并解决项目问题,设计了该工具. 可利用资源 1.公司内不能连接外网,沟通主要使用RTX.可以提供RTX服务器信息 2.公司内可以直接连到生产环境. 怎么做1.软件结构图 2.时时获取服务日志 开发环境可以直接连接到生产环境,则可以通过一个独立线程定时获取错误日志信息.错误日志信息包括:错误模块,错误描述.3.配置RTX与业务模块的关系 RT

页面即时消息提醒的实现

具体功能需求: 当有人给你留言或者私信的时候,你这边会即时有提醒说有人给你留言或者有私信. 分析:该功能类似于QQ的消息提醒,当有人找你聊天的时候,这边有头像闪动的提醒.由于在页面要做到这个效果,并且页面不全部刷新,故可以使用ajax来解决.那么问题又来了,ajax只会调用一次,不会重复调用,所以需要一个函数实现重复调用ajax函数.经过查资料,JavaScript中的 self的setInterval方法可以实现该效果,重复调用一个函数. self.setInterval("函数",

PHP实现RTX发送消息提醒

RTX是腾讯公司推出的企业级即时通信平台,大多数公司都在使用它,但是我们很多时候需要将自己系统或者产品的一些通知实时推送给RTX,这就需要用到RTX的服务端SDK,建议先去看看RTX的SDK开发文档(客户端,服务器),我们先看看功能效果:                   当然,现在很多公司都已经在RTX的基础上升级成了企业微信,没关系,这个API同样可以使用,还是同样的接口,只是展示效果不一样而已: 下面是用PHP实现RTX发送消息提醒: 1.首先在服务器端安装RTX的服务端和客户端,再安装

如何去掉论坛右下角的新短消息提醒

如何去掉论坛右下角的新短消息提醒打开\template\default\common\footer.htm文件,然后找到137-150行的代码删除即可 <script type="text/javascript"> var h5n = new Html5notification(); if(h5n.issupport()) { <!--{if $_G[member][newpm] && $_GET[do] != 'pm'}--> h5n.show

高仿微信5.2.1主界面及消息提醒

好久没更新博客了,最近在做公司的项目,这也算是我接触的第一个正式项目.通过项目的检验,发现自己积累了一年的知识还是远远不够,想要提高,好的方法是 :项目+书+视频+博客.最重要一点:勤动手.最近发现了慕课网的视频,居然都是高清无码免费的!而且满满的干货!我用业余时间跟着视频中大神的讲解学习了不少知识,下面就将这些小demo与大家分享,当然,我做了一些优化,代码与视频中有些出入,但功能可以完全实现. 这是一个模仿5.2.1版本的显示界面,如下图所示: 功能及实现思路简介 主要功能很简单: 1.上面

RTX发送消息提醒实现以及注意事项

一.RTX简介 RTX是腾讯公司推出的企业级即时通信平台.该平台定位于降低企业通信费用,增强企业内部沟通能力,改善企业与客户之间的沟通渠道,创造新兴的企业沟通文化,提高企业生产力.RTX平台的主要功能,包括企业内部实时信息交互.视频语音网络会议.企业短信中心.标准目录服务支持等等.RTX平台具有很高的实用性.易用性和可管理性.除了底层采用128位对称加密技术之外,在实际应用中,RTX可以通过员工实名制.记录对外交互信息等措施,确保企业应用的通信安全. 同时,腾讯公司为所有的RTX用户提供企业级的

Android实例-设置消息提醒(XE8+小米2)

结果: 1.二个按钮可以新建消息提醒,最小化也是新建消息提醒. 2.程序必须最小化后才能点击消息提醒Label2才会有反映. 实例代码: 1 unit Unit1; 2 3 interface 4 5 uses 6 System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 7 FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialog

ViewPage和Fragment上 实现BadgeView消息提醒(仿旧微信)

先上图: 这里只是使用了viewpage 和 Fragment,没有用GitHob上viewpagerindicator,而是自己写了个指示器,使用了badgeView显示消息提醒. 首先, 是上面的titleBar  没什么好说的  带过... <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/