我开发了一个基于 Beancount 的账本托管服务 HostedBeans,欢迎大家来了解纯文本复式记账并试用我的服务。
归档 2014 年 4 月

MongoDB 使用经验

最初听说 MongoDB 的时候,我总是觉得它的稳定性堪忧,后来用了差不多一年的时间,其实也没有遇到过什么问题,反而是 MySQL 出现过几次丢失数据的情况。配合 Node.js 使用 MongoDB 是一件非常舒畅的事情,从前端,到后端,再到数据库,统统全是 JSON.

本文的定位是一篇对 MongoDB 的一个概览性的介绍,告诉你 MongoDB 的特点和功能,以及如果需要了解某个功能,应当搜索什么关键词,并不直接涉及技术细节。

特点

  • 无模式

    MongoDB 中的每一条文档,都是一个 JSON 对象,因此你无需预定义一个集合的结构,集合中的每个文档也可以有不同的结构。

  • 异步写入

    MongoDB 默认所有的写操作都是『不安全』的,即当请求被 MongoDB 收到时,不等写入操作完成,就返回一个『成功』的响应。
    这是默认的行为,当然你设置一些选项,让操作等待等待写入完成后再返回响应。不过对于大多数应用,这种『不安全』已经足够安全了。

  • 简单查询

    MongoDB 只支持简单的查询,MongoDB 只储存数据,更多的逻辑应该在应用中解决。因此 MongoDB 有着简单的且在各编程语言下高度一致的 API 接口。

  • 无需运维

    MongoDB 几乎没有什么选项可以设置,集群也是设置一次之后就可以自动地解决故障,极少需要维护。

安装和备份

MongoDB 的安装很简单,直接在官网下载二进制版本,运行其中的 mongod 即可启动数据库,运行 mongo 即可启动客户端 Shell, 或者你也可以从软件源中安装。
MongoDB 被设计运行于 64bit 的操作系统,在 32bit 的情况下,数据文件最大限制为 2GiB.

MongoDB 在运行时并不会实时地将修改写入磁盘,因此在关闭服务器时需要给 MongoDB 时间将所有数据写入磁盘。当出现服务器突然掉电的情况时,MongoDB 的数据库文件会损坏,需要进行修复才能重新运行,这个过程中会丢失掉电时正在进行的写入操作。在对运行中的 MongoDB 进行备份时,需要使用自带的 mongodump 工具,不能直接复制其数据库文件。

设计文档结构

  • ObjectID

    MongoDB 会为每一个文档默认添加一个名为 _id, 类型为 Object 的字段,这个字段用来唯一地标识每一个文档。这个 ID 通过时间,服务器,进程号被生成,甚至可以认为是全世界唯一的。
    除了 MongoDB 默认为 _id 添加 ObjectID, 你也可以自己在文档中创建 ObjectID, 来起到唯一地标识某个对象的功能。

引用关系

例如我们有一个 topic 集合,topic 都是 account 创建的,所以 topics 中要引用来自 accounts 中的 account.

// accounts
{
    _id: <ObjectID 1>,
    name: "jysperm"
}

// topics
{
    _id: <ObjectID 2>,
    account_id: <ObjectID 1>
}

嵌入关系

每个 topic 会有一些 reply, 所以在 topic 中可以嵌入 reply.

// topics
{
    _id: <ObjectID 2>,
    account_id: <ObjectID 1>

    replies: [
        {
            _id: <ObjectID 3>,
            content: "xxoo"
        }
    ]
}

引用 Vs 嵌入

在上面第一个例子中,topic 其实也可以嵌入 account 中;而第二个例子中,reply 也可以使用一个新的集合,然后来引用 topic, 那么应该如何选择这两种关系呢。

我们主要从几个出发点来考虑:

  • 查询

    我们可以考虑查询 account 时,是否需要他所有的 topic, 或者查询 topic 时,是否需要它所有的 reply.
    从查询的角度来考虑,如果需要一同获得这两种数据,那么就应该嵌入,如果不需要,就应该引用。

  • 数据的增长性

    我们还可以考虑 account 的 topic 数量在今后会有怎样的增长,topic 的 reply 数量会有怎样的增长。
    如果增长是没有限度的,那么就应该引用,如果增长是有限的,那么就可以嵌入。

  • 对应关系

    如果是一对一关系,或者一对多关系,那么可以考虑嵌入,如果是多对多关系,那么应该引用。

  • 原子性

    MongoDB 仅在文档层面提供原子性,如果有两个非常敏感的数据需要同时被更新,那么他们有必要存在于同一个文档中。

查询和更新

所谓『增删查改』在 MongoDB 里对应:

  • find/findOne: 查询
  • update/save: 修改
  • insert/save: 新增
  • remove: 删除

在 MongoDB 中,操作符以 $ 开头,主要分为三类:查询操作符,更新操作符,聚合查询操作符。

  • 查询操作符

    用于 find 和 update 中的查询器,如 $gt(大于), $ne(不等于), $in(匹配几个值之一), 逻辑运算:$and, $not.

  • 更新操作符

    用于 update 中的更新器,如 $inc(对数字进行增量), $set(修改文档的一部分), $unset(删除一个字段).
    MongoDB 对嵌入式的文档和数组有非常好的支持:$addToSet(向集合中添加元素), $push(向数组添加元素), $pull(从数组移除元素).

  • 聚合查询(Aggregation)

    类似于 SQL 数据库中的 GROUP, 提供统计和计算的功能,要多强大有多强大,毕竟可以直接在数据库中运行 JavaScript 代码,不过因为性能的关系,不适合在应用中频繁调用。

查询命令还有几个选项:

  • limit: 限制返回的结果数
  • skip: 跳过一些结果
  • sort: 对结果进行排序
  • fields: 只返回指定的字段

副本集(Replica Set)和分片(Sharding)

MongoDB 的副本集采用一主多从的的集群方式。副本集中只有主节点可以写入,其他节点从主节点同步。当主节点故障时,从节点中会自动推选出一个新的主节点。当故障的节点恢复后,会向其他节点同步到最新的数据,然后成为一个从节点。

MongoDB 的分片是指,按照某个字段的值,将数据分为多份,储存在不同的服务器上。客户端会运行一个 mongos 代替 mongod, mongos 相当于一个路由,会根据请求和分片的设置,将请求拆分后发给不同的服务器,得到结果后再将结果组合起来发给客户端。

分片可以与副本集同时使用,此时,每个分片都是一个副本级,这样可以提供非常高的可用率和扩展性。

1

精子生于 1995 年,英文 ID jysperm.

订阅推送

通过 Telegram Channel 订阅我的博客日志、产品和项目的动态:

王子亭的博客 @ Telegram


通过邮件订阅订阅我的博客日志、产品和项目的动态(历史邮件):

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-ND  授权发布。最后生成于 2023-12-20.