当前位置: 首页 > 新闻 > 信息荟萃
编号:1737
大数据存储mongodb实战指南.pdf
http://www.100md.com 2020年1月15日
第1页
第5页
第18页
第21页
第31页
第125页

    参见附件(7499KB,239页)。

     大数据存储mongodb实战指南,这是一本针对MongoDB的学习,作者以将复杂的事情化为简单的方式进行讲解,帮助学习者能够更快的理解整个特点及应用。

    mongodb实战指南内容提要

    MongoDB是一种面向文档的分布式数据库,可扩展,表结构自由,并且支持丰富的查询语句和数据类型。时至今日,MongoDB以其灵活的数据存储方式逐渐成为IT行业非常流行的一种非关系型数据库(NoSql)。

    《大数据存储MongoDB实战指南》从学习与实践者的视角出发,本着通俗精简、注重实践、突出精髓的原则,精准剖析了MongoDB的诸多概念和要点。全书共分4个部分,分别从基础知识、深入理解MongoDB、监控与管理MongoDB和应用实践几个维度详细地介绍了MongoDB的特点及应用实例。

    《大数据存储MongoDB实战指南》适合有海量数据存储需求的人员、数据库管理开发人员、数据挖掘与分析人员以及各类基于数据库的应用开发人员。读者将从书中获得诸多实用的知识和开发技巧。

    mongodb实战指南作者资料

    郭远威,高级软件工程师,现任职于华为公司,擅长大数据存储相关工作。自幼好学、勤专研,熟悉大数据存储,精通MySql、Oracle、MongoDB等数据库;曾开发云计算存储平台、内存数据库等产品,管理、迁移过海外大型电信运营商的数据库系统;热爱开源技术,对新技术保持高度关注。

    mongodb实战指南目录信息

    第一部份、分基础知识

    第1章 大数据与云计算

    第2章 查询语言系统

    第3章 索引与查询优化

    第4章 增改删操作

    第二部分、深入理解MongoDB

    第5章 Journaling日志功能

    第6章 聚集分析

    第7章 复制集

    第8章 分片集群

    第9章 分布式文件存储系统

    第三部分、监控与管理MongoDB

    第10章 管理与监控

    第11章 权限控制

    第四部分、应用实践

    第12章 PHP驱动接口

    第13章 案例:高度可定制化的电商平台

    大数据存储mongodb实战指南截图

    目 录

    封面

    扉页

    前言

    第一部分 基础知识

    第1章 大数据与云计算

    1.1 什么是大数据

    1.2 什么是云计算

    1.3 大数据与云计算

    1.4 什么是MongoDB

    1.5 大数据与MongoDB

    1.6 MongoDB特点

    1.7 安装MongoDB

    1.8 几个重要的进程介绍

    1.8.1 mongod进程

    1.8.2 mongo进程

    1.8.3 其他进程

    1.9 适合哪些业务

    1.10 小结

    第2章 查询语言系统

    2.1 查询选择器

    2.2 查询投射

    2.3 数组操作

    2.4 小结

    第3章 索引与查询优化3.1 索引

    3.1.1 单字段索引

    3.1.2 复合索引

    3.1.3 数组的多键索引

    3.1.4 索引管理

    3.2 查询优化

    3.3 小结

    第4章 增改删操作

    4.1 插入语句

    4.2 修改语句

    4.3 删除语句

    4.4 锁机制

    4.5 小结

    第二部分 深入理解MongoDB

    第5章 Journaling日志功能

    5.1 两个重要的存储视图

    5.2 Journaling工作原理

    5.3 小结

    第6章 聚集分析

    6.1 管道模式进行聚集

    6.2 MapReduce模式聚集

    6.3 简单聚集函数

    6.4 小结

    第7章 复制集

    7.1 复制集概述

    7.2 复制集工作机制

    7.2.1 数据同步7.2.2 故障转移

    7.2.3 写关注

    7.2.4 读参考

    7.3 小结

    第8章 分片集群

    8.1 分片部署架构

    8.2 分片工作机制

    8.2.1 使集合分片

    8.2.2 集群平衡器

    8.2.3 集群的写与读

    8.2.4 片键选择策略

    8.3 小结

    第9章 分布式文件存储系统

    9.1 小文件存储

    9.2 GridFS文件存储

    9.3 小结

    第三部分 监控与管理MongoDB

    第10章 管理与监控

    10.1 数据的导入导出

    10.2 备份与恢复

    10.2.1 单节点dump备份与恢复

    10.2.2 集群dump备份恢复策略

    10.3 监控

    10.3.1 数据库角度监控命令

    10.3.2 操作系统角度监控命令

    10.3.3 Web控制台监控

    10.4 小结第11章 权限控制

    11.1 权限控制API

    11.1.1 针对所有数据库的角色

    11.1.2 针对单个数据库的角色

    11.2 复制集与集群的权限控制

    11.3 小结

    第四部分 应用实践

    第12章 PHP驱动接口

    12.1 开发环境安装

    12.2 驱动介绍

    12.3 单实例上的增删改查

    12.4 几个重要的类、方法与参数

    12.5 复制集上的操作

    12.6 分片集群上的操作

    12.7 分布式小文件存取操作

    12.8 分布式大文件存取操作

    12.9 小结

    第13章 案例:高度可定制化的电商平台

    13.1 功能需求

    13.2 数据库表设计

    13.3 编写MongoDB_driver类

    13.4 CodeIgniter框架

    13.4.1 基本介绍

    13.4.2 下载与安装

    13.4.3 执行原理

    13.4.4 代码示范

    13.5 Bootstrap框架13.6 前台界面原型图

    附录 常见问题

    版权大数据存储 MongoDB实战指南

    郭远威 著

    人民邮电出版社

    北京前言

    多年来,我一直在和数据库存储技术打交道,深知数据存储技术在

    整个IT系统中起着至关重要的作用,尤其是随着云计算时代的到来,所

    有企业都面临着海量的数据信息,如何处理这些数据成为当前研究的热

    点。在过去二十几年中,数据的存储是关系数据库的天下,它以高效、稳定、支持事务的优势几乎统治了整个行业的存储业务;但是随着互联

    网的发展,许多新兴产业如社交网络、微博、数据挖掘等业务快速增

    长,数据规模变得越来越庞大,高效存储、检索、分析这些海量的数

    据,关系数据库变得不再适用。前几年我们还可以看到网络上关于关系

    数据库与NoSQL数据库谁优谁劣的激烈讨论,如今NoSQL几乎占据了

    各大数据库论坛讨论的大部分版块。一些行业领头公司也逐渐将业务迁

    移到非关系数据库上,NoSQL类型的数据库也变得越来越成熟。当然,在未来一段时间里关系数据库如Oracle、DB、SQL Server等仍会在事务

    性要求比较高的行业(如银行、电信等)发挥它的作用。

    另一方面,在信息技术领域,计算与存储一直是密不可分的,当前

    我们身处云计算的浪潮中,因此对应的各种云存储技术也呼之欲出。本

    书将介绍的NoSQL数据库MongoDB正是众多分布式海量数据存储技术

    中最出色的一种。MongoDB是一种面向文档的分布式数据库,可扩

    展,表结构自由,支持丰富的查询语句与数据类型,旨在为未来的大数

    据应用提供高性能的云存储解决方案。当然MongoDB幵不是万能的,随着了解的深入,我们也会发现它的缺点,这也是本书的宗旨,尽量让

    读者明白它的长处与短处,对于特定的业务选择最合适的数据库存储方案。最后我们希望本书介绍的MongoDB知识能为您在未来的项目中处

    理海量数据时提供帮助。

    本书内容

    本书尽量仍一个学习与实践者的角度,本着力求精简、突出精髓的

    原则,剖析了MongoDB在生产环境中使用需要知道的所有内容,全书

    分 部分,共章,每章的内容简单介绍如下。

    第1章 本章主要从什么是MongoDB以及几个核心迚程两方面概述了

    MongoDB,使读者整体上对MongoDB的体系结构有个认识。

    第2章 本章主要介绍了MongoDB的查询语言系统,包含各种查询选

    择器以及查询选项,这是对任何一个数据库都有的内容。

    第3章 本章主要介绍了MongoDB的索引与查询优化。

    第4章 本章主要介绍了MongoDB的增、删、改语句。

    第5章 本章主要仍底层存储视图与写操作流程剖析了MongoDB的

    Journaling日志功能。

    第6章 本章主要介绍了MongoDB的聚集分析框架与MapReduce的编

    程模型。

    第7章 本章主要介绍了复制集的功能与工作机制,包含数据同步、故障转移、写关注等,这些是MongoDB的核心。

    第8章 本章主要介绍了分片集群,包含部署架构、分片、读写分

    离、片键选择等内容,这是MongoDB不同于传统关系数据库地方,也

    是实现海量数据分布式存储的关键。

    第9章 本章主要介绍了分布式文件系统的GridFS文件,实现二迚制

    数据的存储。

    第10章 本章主要介绍了对MongoDB的管理与监控,包括数据的导

    入导出、备份恢复以及运行状态的监控。

    第11章 本章主要介绍权限控制,实现不同数据库对不同角色用户

    的权限分配。第12章 本章主要仍应用开发角度,介绍了MongoDB的PHP驱动接

    口。

    第13章 本章主要介绍了一个完整的电商平台,数据库使用的是

    MongoDB幵对前面所有章节的知识迚行总结,内容包含电商平台数据

    库表的设计、核心代码的编写、前台界面的原型图设计等,还介绍了开

    发Web应用程序常用的PHp框架Codeigniter和前端开发框架Bootstrap

    等。

    本书特色

    ● 注重实践,本书为多年一线数据库存储,部署开发经验的总结。

    ● 注重效率,本书用最精简的篇幅直接阐明问题的本质,节省宝贵

    的阅读时间。

    ● 注重基础,本书用计算机领域相关的基础理论知识来解释某些难

    于理解的概念。

    ● 案例丰富,本书使用完整的例子与代码注释,使读者可以直接上

    手操作。

    ● 把握未来,大数据势不可挡,本书介绍的MongoDB特性与此息息

    相关。

    读者对象

    ● 有海量数据存储需求的人员。

    ● 数据库管理与开发人员。

    ● 数据挖掘与分析人员。

    ● 各类基于数据库的应用程序开发人员。

    谨以此书献给热爱技术、热爱MongoDB的朋友们!第一部分 基础知识

    这一部分主要介绍 MongoDB 方面的基础知识,熟悉关系数据库的

    读者能够快速地认识到 MongoDB 是什么以及与其他数据库的区别,这

    一部分的基础知识很重要,贯穿整本书,建议多实践和测试。

    第1章 本章介绍了大数据、云计算的基本概念以及云存储与

    MongoDB的关系,还介绍了 MongoDB 是什么、它的特点以及如何在各

    种平台上部署MongoDB等,最后介绍了MongoDB部署启动后一些关键

    的进程。

    第2章 本章介绍了各种查询操作,这是数据库上最常用的一个操

    作。MongoDB的查询与关系数据库的语法区别很大,但它们很多设计

    思想是相同的,查询选择器相当于关系数据库中经常用到的where语

    句,查询选项相当于过滤出需要返回的字段。最后介绍了一种特殊对象

    的查询操作,这在关系数据库中是没有的。

    第3章 本章介绍了查询用到的索引以及利用索引对查询的优化,这

    个思想和关系数据库也是一致的,利用索引来提高查询效率。

    第4章 本章介绍了对 MongoDB 插入、删除、修改操作,至此一系

    列完整的增删改查的操作都介绍完了,对于一般的应用程序开发都能支

    持了。

    第1章 大数据与云计算

    1.1 什么是大数据对于各种规模大小的组织机构而言,由于数据爆炸式的增长,传统

    的数据处理技术变得越来越难适应,需要有变革的技术来存储、分析这

    些大数据。谁能够掌握这些存储、分析技术,谁就有可能成为未来市场

    的主导者。财富500强公司在这个方面已走在前列,他们认识到大数据

    不仅仅是一门技术,而且是未来商业的发展趋势,并且已经开始从创新

    的大数据业务中受益。例如,企业能够分析用户的Web

    MongoDB只通过6年时间就将公司市值发展到12亿美元,其成果相

    当于著名开源公司Red Hat 20 年的发展。MongoDB 的成功之路,一大

    部分归功于Web开发者。作为一个面向文档数据库,在许多场景下它都

    优于 RDBMS,同时还可以获得非常高的读写性能。此外,动态、灵活

    的模式更可以让用户在商用服务器上轻松地进行横向扩展。1.5 大数据与MongoDB

    大数据意味着新的机会,企业能够创造新的商业价值。MongoDB

    这样的数据库可以支撑很多大数据系统,它不仅可以作为一个实时的可

    操作的大数据存储系统,也能在离线大数据分析系统中使用。利用

    MongoDB作为大数据的云存储系统,企业能够在全世界范围内存储更

    多的数据,吸引更多的用户,挖掘更多用户的喜好,创造更多的价值。

    选择正确的大数据存储技术,对使用者的应用和目标是非常重要

    的。MongoDB 公司提供的产品和服务能让使用者担更少的风险、花更

    少的精力提供更好的生产系统产品。事实上,MongoDB 天生就是为云

    计算而生的,其原生的可扩展架构,通过启用分片和水平扩展,能提供

    云存储所需的技术;此外,它的自动管理被称为“副本集”的冗余服务

    器,以保持数据的可用性和完整性。MongoDB 目前已经成为多家领先

    的云计算供应商,其中包括亚马逊网络服务、微软和SoftLayer等。

    MongoDB还支持Google提出的MapReduce并行编程模式,为大数据

    的分析提供了强有力的保障。MongoDB同时提供了与Hadoop的接口,与其他第三方数据分析工具完美结合。

    1.6 MongoDB特点

    它的存储模型与关系数据库的比较如表1-1所示。

    表1-1 MongoDB存储模型与MySQL的对比

    关系数据库中最基本的单元是行,而MongoDB中最基本存储单元是document,典型结构如下所示。

    {

    _id : ObjectId(51e0c391820fdb628ad4635a),author : { name : Jordan,email : Jordan@123.com },postcontent : jordan is the god of basketball,comments : [

    {user : xiaoming, text : great player},{ user : xiaoliang, text : nice action }

    ]

    }

    它用与JSON格式类似的键值对来存储(在MongoDB中叫BSON对

    象),其中值的数据类型有常见的字符串、数字、日期,还可以是

    BSON对象、数组以及数组的元素,也可以是BSON对象,通过这种嵌

    套的方式,使MongoDB的数据类型变得相当丰富。

    MongoDB 与传统关系数据库还有一个重大区别就是:可扩展的表

    结构。也就是说collection(表)中的document(一行记录)所拥有的字

    段(列)是可以变化的,下面文档对象document(一行记录)比上面列

    出的文档对象document(一行记录)多一个time字段,但它们可以共存

    在同一个collection(表)中。

    {

    _id : ObjectId(51e0c391820fdb628ad4635a),author : { name : Jordan,email : Jordan@123.com },postcontent : jordan is the god of basketball,comments : [

    {user : xiaoming, text : great player},{ user : xiaoliang, text : nice action }

    ],time: 2013-07-13

    }

    MongoDB查询语句不是按照SQL的标准来开发的,它围绕JSON这

    种特殊格式的文档型存储模型开发了一套自己的查询体系,这就是现在

    非常流行的 NoSQL 体系。关系数据库中常用的 SQL 语句在 MongoDB

    中都有对应的解决方案。当然也有例外,MongoDB不支持JOIN语句。

    我们知道传统关系数据库中JOIN操作可能会产生笛卡尔积的虚拟表,消

    耗较多系统资源,而MongoDB的文档对象集合collection可以是任何结

    构,我们可以通过设计较好的数据模型尽量避开这样的操作需求。如果

    真的需要从多个collection(表)中检索数据,那我们可以通过多次查询

    得到。

    在关系数据库中经常用到的 group by 等分组聚集函数,在

    MongoDB 中也有,而且MongoDB提供了更加强大的MapReduce方案

    (GOOGLE提出的并行编程),为海量数据的统计、分析提供了便利。

    MongoDB支持日志功能Journaling,对数据库的增、删、改操作会

    记录在日志文件中。MongoDB 每100ms将内存中的数据刷到磁盘上,如

    果意外停机,在数据库重新启动时, MongoDB能通过Journaling日志功

    能恢复。

    MongoDB支持复制集(Replset),一个复制集在生产环境中最少

    需要3台独立的机器(测试的时候为了方便可能都部署在一台机器

    上),一台作主节点(primary),一台作次节点(secondary),一台

    作仲裁节点(只负责选出主节点),备份、自动故障转移,这些特性都

    是复制集支持的。

    MongoDB支持自动分片Sharding,分片的功能实现海量数据的分布

    式存储,分片通常与复制集配合起来使用,实现读写分离、负载均衡,当然如何选择片键是实现分片功能的关键。如何实现读写分离我们后面

    会详细分析。总之,MongoDB 最吸引人的地方应该就是自由的表结构、MapReduce、分片、复制集,通过这些功能实现海量数据的存储、高效

    地读写以及数据的分析。

    1.7 安装MongoDB

    MongoDB 官方已经提供了Linux、Windows、Mac OS X 以及Solaris

    4 种平台的二进制分发包,最新的稳定版本是 2.6.3,下载地址是:

    http:www.mongodb.orgdownloads ,如图1-2所示。

    图1-2 各平台二进制分发包

    下载完成后,解压,我们就能直接运行里面的二进制文件,这里所

    讨论的安装MongoDB,一般指的是运行MongoDB服务器端的进程

    mongod。

    解压后,在bin目录下,我们可以看到一个名为mongod.exe的可执行

    程序,这个就是服务器端进程对应的程序。因为MongoDB启动时需要

    指定数据文件所在的目录,所以先要建立一个保存数据文件的目录,如

    D:\mongodb-win32-i386-2.6.3\ test_single_instance\data;启动时也可以指

    定一个日志文件,如D:\ mongodb-win32-i386-

    2.6.3\test_single_instance\logs\ 123.log,我们通过以下命令就可以启动。

    > mongod --config E:\MongoDB-win32-i386-

    2.6.3\test_single_instance\123.conf

    上述步骤在Linux平台上也是一样的,只不过要注意目录和文件的

    读写权限。还有一种安装方式就是直接通过各 Linux 分发版本对应的包管理

    器,如 RedHat、Debian、Ubuntu等都有自己的包管理器,通过包管理器

    安装时,系统会自动创建数据目录和日志文件,找到这些目录和文件所

    在的位置,后续分析问题可能会经常要读取日志文件。

    1.8 几个重要的进程介绍

    通过官网下载的二进制包中有几个重要的可执行文件,这些可执行

    文件运行后都会对应一个相应的进程。

    1.8.1 mongod进程

    Mongod.exe为启动此数据库实例进程对应的可执行文件,是整个

    MongoDB中最核心的内容,负责数据库的创建、删除等各项管理工

    作,运行在服务器端为客户端提供监听,相当于MySQL数据库中的

    mysqld进程。

    启动数据库实例会用到以下命令。

    >mongod --config E:\MongoDB-win32-i386-

    2.6.3\test_single_instance\123.conf

    配置文件123.conf内容如下所示。

    dbpath = E:\MongoDB-win32-i386-2.6.3\test_single_instance\data

    logpath = E:\MongoDB-win32-i386-

    2.6.3\test_single_instance\logs\123.log

    journal = true

    port = 50000

    auth = true

    dbpath为数据库文件存储路径;logpath为数据库实例启动、运行、错误日志文件;journal启动数据库实例的日志功能,数据库宕机后重启时依赖它恢复;port 数据库实例的服务监听端口;auth启动数据库实例

    的权限控制功能。其他可选参数可以通过mongod–help查看。

    1.8.2 mongo进程

    mongo是一个与mongod 进程进行交互的JavaScript Shell进程,它提

    供了一些交互的接口函数用于系统管理员对数据库系统进行管理,如下

    面命令所示。

    >mongo --port 50000–username xxx–password xxx–

    authenticationDatabase admin

    mongo的参数port为mongod进程监听的端口,参数username为连接

    数据库的用户名,参数password为连接数据库的密码,参数

    authenticationDatabase为要连接的数据库。上述命令连接成功后,进程

    就会提供给用户一个JavaScript Shell环境,通过一些函数接口来管理数

    据库,其他参数可通过mongo--help选项查看。

    1.8.3 其他进程

    1.mongodump 提供了一种从 mongod 实例上创建 BSON dump 文

    件的方法,mongorestore能够利用这些dump文件重建数据库,常用命令

    格式如下。

    mongodump --port 50000 --db eshop --out e:\bak

    参数--port表示mongod实例监听端口,--db表示数据库名称,--out表

    示备份文件保存目录,更多可选参数可通过mongodump–help查看。

    2.mongoexport是一个将MongoDB数据库实例中的数据导出来生产

    JSON或CSV文件的工具,常用命令格式如下。

    mongoexport --port 50000 --db eshop --collection goods --out

    e:\goods.json

    3.mongoimport是一个将JSON或CSV文件内容导入到MongoDB实例中的工具,常用命令格式如下。

    mongoimport --port 50000 --db eshop --collection goods --file

    e:\goods.json

    4.mongos 是一个在分片中用到的进程。所有应用程序端的查询操

    作都会先由它分析,然后将查询定位到具体某一个分片上,它的作用与

    mongod类似,客户端的mongo与它连接。

    5.mongofiles提供了一个操作MongoDB分布式文件存储系统的命

    令行接口,常用命令如下。

    mongofiles--port 40009 --db mydocs --local D:\算法导论学习资料.pdf

    put algorithm_introduction.pdf

    它表示将本地文件D:\算法导论学习资料.pdf上传到数据库mydoc中

    保存。

    6.mongostat 提供了一个展示当前正在运行的 mongod 实例的状态

    工具,相当于UNIXLinux上的文件系统工具vmstat,但是它提供的数据

    只与运行着的mongod或mongos的实例相关。

    7.mongotop提供了一个分析MongoDB 实例花在读写数据上的时间

    的跟踪方法。它提供的统计数据在每一个collection(表)级别上。

    1.9 适合哪些业务

    当前各行各业都离不开数据的存储与检索需求,传统关系数据库发

    展了这么多年,在有些垄断性行业如电信、银行等仍然是首选,因为这

    些行业需要数据的高度一致性,只有支持事务的数据库才能满足它们的

    要求。但随着这几年互联网业务的发展,数据量越来越大,并发请求也

    越来越高,一个大系统中只用一种数据库并不能很好地满足全部业务的

    发展,同时以MongoDB为代表的NoSQL数据库快速发展,在某些方面

    展示了它们的优越性,逐渐被采用并取代了系统中的某些部件,总的来说以下几个方面比较适合使用 MongoDB这类的数据库。

    1.Web应用程序

    Web应用是一种基于BS模式的程序,业务的特点是读写请求都比较

    高,早期系统的数据量可能很少,但是发展到一定程度后数据量会暴

    增,这就需要数据存储架构能够适应业务的扩展。传统的关系数据库表

    结构都是固定的,增加一个业务或者横向扩展数据库都会带来巨大的工

    作量。MongoDB 支持无固定结构的表模型,因此很容易增加或减少表

    中的字段,适应业务的变化;同时MongoDB本身就支持分片集群,很

    容易实现水平扩展,将数据分散到集群中的各个片上,提高了系统的存

    储容量和读写吞吐量。Web应用程序还有一个特点就是“热数据”读并发

    很高,也就是说最新的数据被请求的次数会最多。为了提供读的性能,在传统的关系数据将中会采用其他的缓存技术来将这部分数据放在内存

    中,而MongoDB本身就支持这一点,它是通过内存映射数据文件来实

    现的。它会维护一个工作集,将最热的数据放在内存中,不需要其他技

    术的协助,这为系统开发提供了简便性,如图1-3所示。图1-3 Web应用中的MongoDB架构

    2.缓存系统

    这种使用场景是与关系数据库搭配使用,作为关系数据库的缓存前

    端。目前缓存技术有很多种,最常见的就是使用memcached,但是这些

    缓存系统都有个缺点,就是支持的数据类型有限,查询语句也有限,只

    能保存少量的数据且不能持久化。而MongoDB这些都能支持,因此可

    以作为缓存使用,如图1-4所示。图1-4 MongoDB作为关系数据库的缓存使用

    3.日志分析系统

    这类系统的特点是数据量大,允许部分数据丢失,不会影响整个系

    统的可靠性。以前将日志直接保存到操作系统的文件上,我们需要用其

    他工具打开日志文件或编写工具读日志进行分析,这样的话对于大量的

    日志查询会比较困难。如果用MongoDB数据库来保存这些日志,一来

    可以利用分片集群使日志系统的容量海量大,二来使用MongoDB特有

    的查询语句能够快速找到某条日志记录。最重要的是MongoDB支持聚

    集分析甚至 MapReduce的能力,为大数据的分析和决策提供了强有力的

    支持,如图1-5所示。图1-5 日志系统中的MongoDB应用

    1.10 小结

    MongoDB 是一个面向文档的数据库,不支持关系数据库中的 join

    操作和事务。它用集合的概念代替了关系数据库中的表,用最小逻辑单

    元文档代替关系数据库中的行。它的集合结构是动态的,没有必要像关

    系数据库一样插入数据前先定义表结构,而且可以随时增加、修改、删

    除组成文档的字段。

    MongoDB 支持当前所有主流编程语言的客户端驱动,使用方便,应用广泛,非常适合文档管理系统的应用、移动APP应用、游戏开发、电子商务应用、分析决策系统、归档和日志系统等应用。MongoDB支

    持所有主流平台的安装,但在32位的平台上部署时会有所限制,这是由

    它采用内存映射数据文件机制决定的,生产环境中最好部署在64位平台

    上。

    第2章 查询语言系统

    查询就是获取存储在数据库中的数据。在MongoDB中,查询通常

    针对一个集合来操作。查询可以指定查询条件,只返回匹配的文档;还

    可以指定投影项,只返回指定的字段,减少返回数据到客户端的网络流

    量。

    为了进行测试,我们先假想一个常用的电子商务网站上可能用到的

    数据结构模型。在关系数据库MySQL中我们可能需要设计3个表如客户

    表customers、订单表orders、商品表products,其中 customers表中的主

    键为cust_id,products表中的主键为prod_id,orders表中主键order_id,外键cust_id和prod_id分别与客户和产品关联,这就是在关系数据库中经

    常干的事情,整个结构如图2-1所示。图2-1 数据库模型图

    查询某个客户所订购的所有商品名称的SQL语句则为以下格式。

    select t1.name,t3.prod_name from customer t1

    join orders t2 on t1.cust_id = t2.cust_id

    join products t3 on t2.prod_id = t3.prod_id;

    在MongoDB中我们抛弃了这种关联的思路来设计表结构,正如

    10gen公司的工程师所说:“如果还用以前关系数据库的思路来建模,有

    时会适得其反”。在MongoDB中提倡的设计思路可能是建立一个客户

    表,在表中包含业务需要的尽可能多的数据信息,这样最终会产生一些

    冗余信息,但是在 NoSQL 的世界里是提倡这样做的。与上面的业务需

    求相同,则最终插入的文档对象document结构如下所示。

    db.customers.insert

    ({

    cust_id:123,name:xiaoming,address:china chasha ,mobile:999999,orders:[

    {order_id:1,createTime:2013-7-13,products:[{prod_name:surface

    Pro64G,prod_manufacture:micosoft},{prod_name:mini Apple,prod_manufacture:Apple}

    ]

    },{order_id:2,createTime:2013-7-12,products:[{prod_name:xbox,prod_manufacture:micosoft},{prod_name:iphone,prod_manufacture:Apple}]

    }]

    })

    查询某个客户下的所订购的商品信息的查询语句则为如下格式。

    db.customers.find({cust_id:123},{name:1,orders:1})

    上面从一个简单的业务需求对比了关系数据库和MongoDB的不

    同,当然为了支持企业级的业务需求,MongoDB能做的绝不只是这

    些,下面我们将系统地介绍MongoDB的各种查询语句。

    2.1 查询选择器

    lte表示的是小于或等于。我们先插入10条记录,便于测试。

    for(var i =1; i<11; i++)db.customers.insert({id:i,name:xiaoming,age:100+i})

    最简单的查询语句为:db.customers.find,按照插入的顺序返回前

    20 个文档,如果记录总数比20大,则我们可以通过命令“it”获取更多文

    档。

    > db.customers.find({id:9})

    精确匹配选择器,返回包含键值对id:9的文档。

    > db.customers.find({name:xiaoming,age:101})

    精确匹配选择器,但查询条件是要返回同时匹配键值对

    name:xiaoming且age:101的文档。

    > db.customers.find({age:{lt:102}})

    lt表示的是小于

    > db.customers.find({age:{lte:102}})

    lte表示的小于或等于

    > db.customers.find({age:{gt:119}})

    gt表示的是大于

    > db.customers.find({age:{gte:119}})

    gte表示的是大于或等于

    > db.customers.find({age:{lt:120,gte:119}})

    范围选择器,age:{lt:120,gte:119}表示的是小于120,大于或等于

    119

    > db.customers.find({id:{in:[1,2]}})

    in表示返回key的值在某些value范围内

    > db.customers.find({id:{nin:[1,2]}})

    nin表示返回key的值不在某些value范围内,nin是一种比较低效的

    查询选择器,它会进行全表扫描,因此最好不要单独使用nin

    > db.customers.find({id:{ne:1}})

    ne表示不等于。单独使用ne,它也不会利用索引的优势,反而会进行全表扫描,我们最好与其他查询选择器配合使用。

    > db.customers.find({or:[{id:11},{age:119}]})

    or表示或运算的选择器,主要用于对两个不同key对应的文档进行

    连接。

    > db.customers.find({and:[{id:11},{age:111}]})

    and表示与运算的选择器,对于两个不同的key,要同时满足条

    件。

    > db.customers.find({id:{exists:false}})

    exists与关系数据库中的exists不一样,因为MongoDB的表结构不

    是固定的,有的时候需要返回包含有某个字段的所有记录或者不包含某

    个字段的所有记录,exists这时就可以派上用场了,上面的语句就是返

    回不包含字段id的所有记录。当然为了实现这种需求,还有另外一种可

    替代的语句即db.customers.find({id:null})。

    > db.customers.find({'detail.age':105})

    这是一种嵌套查询的形式,detail字段的值也是一个BSON对象,文

    档结构如下。

    {

    _id : ObjectId(51e3dd1791fa05a27697ab4b),id : 1, name : xiaohong,

    detail : { sex : male, age : 105 }

    }

    嵌套查询时匹配的key如果有多级嵌套深度,一级一级地用点号展

    开。

    最后我们看一个比较复杂的查询来结束本话题,假设系统中有如下

    所示的这样一种文档类型。

    {

    _id : ObjectId(51e3e28e91fa05a27697ab55),id : 1, name : xiaohong,

    detail : [ { sex : femle, age : 105 },{ address : china, post : 5}

    ]

    }

    查询post等于5的文档,则查询语句如下。

    > db.customers.find({'detail.1.post':5})

    匹配字符串中先取需要匹配的key(detail),由于detail键对应的value

    为数组,detail.1表示要取数组中第二个位置处的元素,又鉴于数组的元

    素也是个BSON文档对象,我们可以通过detail.1.post定位到需要匹配的

    键。

    2.2 查询投射

    上面介绍的查询选择器用来返回匹配的文档集,有时我们需要对返

    回的结果集作进一步的处理,如只需要返回指定的字段,此时查询投射

    项就可发挥它的功效了,下面我们逐一剖析。

    > db.customers.find({'detail.1.post':5},{_id:0,id:1,name:1})

    此条语句执行的效果就是按照条件'detail.1.post':5 来返回结果集,但是只选择 id 和name 两个字段的值进行显示,同时过滤掉默认生成的

    字段_id,如果不加上_ id:0 则会显示此默认的字段。总的来说第一个{}

    内为查询选择器;第二个{}内为对前面返回的结果集进行进一步过滤的

    条件,即投射项。

    > db.customers.find({}).sort({id:-1})

    对查询的结果集按照id的降序进行排序后返回。我们知道排序是很

    费时间的,对于排序有一点很重要,就是确保排序的字段上建立了索

    引,而且排序执行计划能够高效地利用索引。> db.customers.find({}).skip(10).limit(5).sort({id:-1})

    此条语句执行的过程是先对结果集进行排序,然后跳过 10 行,从

    这个位置开始返回接下来的5行。但是我们要注意如果传递给skip的参数

    很大,那么查询语句将会扫描大量的文档,这样执行性能将会很低下。

    因此我们在查询语句中尽量不要用skip,用其他办法替换skip想要实现

    的功能。

    2.3 数组操作

    我们来看下面这个查询语句。

    db.customers.find({'detail.1.post':5},{_id:0,id:1,name:1})

    投射选项{_id:0,id:1,name:1}针对的值是简单类型,包括查询选择器

    也是嵌套文档,可以通过操作符“.”一点点嵌套进去,如果值为数组类型

    并且数组的元素又是文档类型,查询语句将有所变化。下面我们展开分

    析。

    假如有一个类似下面结构的数据。

    {

    _id : 4,AttributeName : material,AttributeValue : [牛仔, 织锦, 雪纺, 蕾丝],IsOptional : 1

    }

    {

    _id : 5,AttributeName : version,AttributeValue : [收腰型, 修身型, 直筒型, 宽松型, 其

    他],IsOptional : 1

    }

    1.精确匹配数组值

    我们可以通过简单的精确匹配得到某条记录,如以下语句所示。

    > db.DictGoodsAttribute.find({AttributeValue: [收腰型, 修身型,直筒型,宽松型, 其他]})

    返回值如下。

    { _id : 5, AttributeName : version, AttributeValue : [ 收腰型,修身型, 直筒型, 宽松型, 其他 ], IsOptional : 1 }

    2.匹配数组中的一个元素值

    假如数组有多个元素,只要这些元素中包含有这个值,就会返回这

    条文档,如下面语句所示。

    > db.DictGoodsAttribute.find({AttributeValue :收腰型})

    假如此时集合中有如下两条记录:{_id: 5,AttributeName:version, AttributeValue:[ 收腰型, 修身型 ]}和

    {_id: 5, AttributeName:version, AttributeValue: [修身型, 收腰

    型, 直筒型]},则返回值中这两条记录都会存在。

    3.匹配指定位置的元素值

    如下面语句所示。

    > db.DictGoodsAttribute.find({AttributeValue.0 :收腰型})

    它表示数组中第0个位置的元素值为收腰型的记录才返回。上面

    查询结果只返回如下记录:{_id: 5, AttributeName:version,Attribute Value: [ 收腰型, 修身型 ]}。

    下面我们再看一个更复杂的数据模型,数组的元素不是简单的值类

    型,而是一个文档,如下记录。

    {_id : 1,StatusInfo : [

    {

    status : 9,desc : 已取消

    },{

    status : 2,desc : 已付款

    }]

    }

    字段StatusInfo是一个嵌套文档的数组。

    4.指定数组索引并匹配嵌套文档中的字段值

    如下面语句所示。

    >db.Order.find({StatusInfo.0.status:2})

    返回数组中索引0处且嵌套文档中status值为2的所有文档。如果不

    指定索引值,效果与第2种情况中介绍的一样,只要数组中包含有status

    值为2的文档,都会被返回。这里与第1、2、3种情况有点不一样的地方

    是数组的元素值是文档,而不是简单的值,所以要指定键名。

    上面对数组操作的语句会返回所有字段,我们可以通过投影只返回

    指定的字段值,如下面语句所示。

    >db.Order.find({_id : 2},{_id:0,StatusInfo:1})

    返回值如下。

    {

    StatusInfo : [

    {

    status : 9,desc : 已取消

    },{

    status : 2,desc : 已付款

    }]

    }

    返回的结果中数组包含的信息仍然较多,需求可能只需要返回状态

    的描述desc即可,我们可以通过下面语句实现。

    >db.Order.find({StatusInfo.status:2},{_id:0,StatusInfo.desc:1})

    加上了一个键名来过滤,返回值如以下所示。

    {

    StatusInfo : [{ desc : 已付款 }, { desc : 已发货 } ]

    }

    现实的需求可能更挑剔,需要返回当前订单的最新状态(数组中最

    后一个元素),此时需要用专门针对数组投射的操作符slice来完成

    了,如下语句所示。

    >db.Order.find({_id:2},{_id:0,StatusInfo:

    {slice:-1},StatusInfo.desc:1}

    返回值如下所示。

    {

    StatusInfo : [ { desc : 已发货 } ]

    }

    流程如图2-2所示。图2-2 执行流程

    关系数据库中常用的查询语句,在MongoDB中一般都有类似的实

    现。同所有其他数据库一样,索引选择的数据结构是一样的,查询语句

    的性能问题应该引起足够的重视,第3章我们将分析索引与查询优化方

    面的问题。

    2.4 小结

    MongoDB不支持关系数据库中标准的SQL查询,它有一套自己的查

    询语言,基本上能实现关系数据库中那样的查询要求;查询选择器就相

    当于关系数据库中where语句后面的内容,查询投射项相当于关系数据

    库中select语句后面需要返回的字段。MongoDB的字段数据类型可以嵌

    套,可以为数组等多种复杂的结构模型,用于弥补没有join操作的不

    足,因此针对字段值类型为数组的查询,它提供了一些特殊的查询方

    式。

    第3章 索引与查询优化

    索引是个与数据存储和查询相关的古老话题,目的只有一个:“提

    高数据获取的性能”。我们知道一本书的前面几页肯定会有一个目录,这个目录式的索引能使我们快速查询想看的内容;把目光转移到计算机

    上,索引则变得抽象,有时候不好理解。索引保存在哪里,是个什么样的数据结构,计算机领域的索引无外乎也是这两个主题。磁盘上保存有

    大量的文件,文件系统对这些文件进行管理。文件系统将磁盘抽象为4

    个部分,依次如下所示。

    这当中索引节点表保存了所有文件或目录对应的inode节点(Linux

    文件系统),通过文件名或目录找到对应的inode节点,通过inode节点

    定位到文件数据在文件系统中的逻辑块号,最后根据磁盘驱动程序将逻

    辑块号映射到磁盘上具体的块号。回到数据库方面,数据库保存记录的

    机制是建立在文件系统上的,索引也是以文件的形式存储在磁盘上,在

    数据库中用到的最多的索引结构就是B树。尽管索引在数据库领域是不

    可缺少的,但是对一个表建立过多的索引也会带来一些问题,索引的建

    立要花费系统时间,同时索引文件也会占用磁盘空间。如果并发写入的

    量很大,每个插入的文档都要建立索引,可想而知,性能会较低。因此

    合理地建立索引是关键,搞清楚哪些字段上需要建立索引、索引以什么

    样的方式建立,我们需要对每个查询过程进行分析,才能得出合理的结

    论。

    3.1 索引

    在MongoDB上,索引能够提高读操作及查询性能。没有索引,MongoDB必须扫描集合中的每一个文档,然后选择与查询条件匹配的

    文档,这种全表扫描的方式是非常低效的。MongoDB索引的数据结构

    也是B+树,它能存储一小部分集合的数据,具体来说就是存储集合中

    建有索引的一个或多个字段的值,而且按照值的升序或降序排列。对于

    一个查询来说,如果存在合适的索引,MongoDB 能够利用这个索引减

    少文档的扫描数量,如图 3-1所示查询商品价格低于 50 的所有商品;甚

    至对于某些查询能够直接从索引中返回结果,不需要再去扫描数据集合,这种查询是非常高效的,如图3-2所示。

    图3-1 利用了索引的查询

    图3-2 利用索引的查询只返回索引字段并从索引文件返回数据

    3.1.1 单字段索引

    MongoDB默认为所有集合都创建了一个_id字段的单字段索引,而

    且这个索引是唯一的,不能被删除,_id 字段作为一个集合的主键,值

    是唯一的,对于一个集合来说,也可以在其他字段上创建单字段的唯一

    索引,如下面所述。

    为了测试展开,我们先按照下面的语句插入如下一些数据。

    >for(vari=1;i<10;i++)db.customers.insert({name:jordan+i,country:American})

    > for(var i = 1;i < 10;i++)db.customers.insert({name:gaga+i,country:American})

    > for(var i = 1;i < 10;i++)

    db.customers.insert({name:ham+i,country:UK})

    > for(var i = 1;i < 10;i++)

    db.customers.insert({name:brown+i,country:UK})

    > for(var i = 1;i < 10;i++)

    db.customers.insert({name:ramda+i,country:Malaysia})

    建立单字段唯一索引或者去掉{unique:true}选项就是一个普通的单

    字段索引。

    > db.customers.ensureIndex({name:1},{unique:true})

    索引结构如图3-3所示。

    图3-3 单字段唯一索引

    唯一索引成功创建后,会在相应数据库的系统集合 system.indexes

    中增加一条索引记录,如下所示。

    > db.system.indexes.find

    {v: 1, key:{_id: 1 }, ns:eshop.customers, name:_id_}

    {v: 1, key:{name: 1 }, unique: true,ns:eshop.customers,name : name_1 }

    我们可以看到有两条记录,第一条为系统默认创建在生成的键_id

    上;第二条为以上创建的唯一索引。索引记录中v表示索引的版本;key

    表示索引建立在哪个字段上;1表示索引按照升序排列;索引记录所在的命名空间,name 表示唯一的索引名称。唯一索引与普通索引的区别

    是要求插入的所有记录在创建索引的键值上唯一。

    执行查询,一个用索引字段作为查询选择器;一个不用索引字段作

    为查询选择器进行比较。

    > db.customers.find({name:ramda9}) .explain

    {

    cursor : BtreeCursor name

    isMultiKey : false,n : 1,nscannedObjects : 1,nscanned : 1,nscannedObjectsAllPlans :1

    nscannedAllPlans : 1,scanAndOrder : false,indexOnly : false,nYields : 0,nChunkSkips : 0,millis : 7,indexBounds : {

    name : [

    [

    ramda9

    ramda9

    ]

    ]

    },server : GUO:50000}

    以上查询语句执行返回的结果中cursor:BtreeCursor name表示此

    查询用到了索引;isMultiKey 表示查询是否用到了多键复合索引;n 反

    映了查询选择器匹配的文档数量;nscannedObjects表示查询过程中扫描

    的总的文档数;nscanned表示在数据库操作中扫描的文档或索引条目的

    总数量;nscannedObjectsAllPlans 表示反映扫描文档总数在所有查询计

    划中;nscannedAllPlans 表示在所有查询计划中扫描的文档或索引条目

    的总数量;scanAndOrder表示MongoDB从游标取回数据时,是否对数据

    排序;nYields表示产生的读锁数;millis表示查询所需的时间,单位是

    毫秒。

    > db.customers.find({country:Malaysia}).explain

    {

    cursor : BasicCursor,isMultiKey : false,n : 9,nscannedObjects : 45,nscanned : 45,nscannedObjectsAllPlans : 45,nscannedAllPlans : 45,scanAndOrder : false,indexOnly : false,nYields : 0,nChunkSkips : 0,millis : 0,indexBounds : {

    },server : GUO:50000}

    这是一个没用到索引的查询,匹配的文档数为9,但是扫描的总文

    档数为45,进行了全表扫描。

    3.1.2 复合索引

    MongoDB支持多个字段的复合索引,复合索引支持匹配多个字段

    的查询。

    在customers表中再插入一些测试数据。

    >for(vari=1;i<10;i++)db.customers.insert({name:lanbo+i,country:Malaysia})

    查询语句如下。

    > db.customers.find({country:Malaysia}).explain

    该查询会扫描54个文档,全表扫描,匹配上的文档只有18个,没有

    用到索引。

    接着我们在 country 字段上建立一个索引,db.customers.ensureIndex({country:1}),重新执行以下查询语句。

    >db.customers.find({country:Malaysia}).explain

    该查询则会扫描 18 个文档,同时匹配 18 个文档,查询用到了刚才

    创建的索引BtreeCursor country_1。

    到此数据库已有的索引如下所示。

    > db.system.indexes.find

    {v: 1, key:{_id: 1 }, ns:eshop.customers, name:_id_}

    {v: 1, key:{name: 1 }, unique: true,ns:eshop.customers,name : name_1 }

    {v: 1, key:{country: 1 }, ns:eshop.customers,name:country_1 }

    最后创建一个复合索引,索引结构如图3-4所示。

    > db.customers.ensureIndex({name:1,country:1})执行如下查询语句。

    > db.customers.find({name:lanbo2,country:Malaysia}).explain

    扫描的文档数为 1;匹配的文档数也为 1 ,查询用到了复合索

    引BtreeCursor name_1_country_1。

    图3-4 一个复合索引

    3.1.3 数组的多键索引

    如果对一个值为数组类型的字段创建索引,则会默认对数组中的每

    一个元素创建索引,我们来看下面结构的文档集合。

    {

    _id : 1,AttributeName : price,AttributeValue : [0-99, 100-299, 300-499, 500-899, 900-

    1499, 1500 以上],IsOptional : 1

    }

    字段AttributeValue值为数组类型,在其上面创建如下一个索引。

    > db.DictGoodsAttribute.ensureIndex({AttributeValue:1})

    创建成功后,字段会添加一个如下索引条目,结果如图3-5所示:

    {

    v : 1, key : { AttributeValue : 1 },name : AttributeValue_1, ns : haoyf.DictGoodsAttribute

    }

    图3-5 基本数组索引

    如果数组的元素值为一个嵌套的文档,如下面文档结构所示。

    {

    _id : 1,StatusInfo : [{

    status : 9,desc : 已取消

    }]

    }

    我们可以创建一个{StatusInfo.desc: 1 }的多键索引,如下命令所

    示。

    >db.Order.ensureIndex({StatusInfo.desc:1})

    索引结构如图3-6所示。

    图3-6 数组多键索引查询订单状态为已取消的执行计划如下。

    > db.Order.find({StatusInfo.desc:已取消}.explain

    cursor:BtreeCursor StatusInfo.desc,nscannedObjects: 1,nscanned: 1,说明利用了刚才创建的索引。

    3.1.4 索引管理

    通过上面创建的索引我们可以看到,索引记录都保存在特殊的集合

    system.indexes中。此文档集合的表结构在上面我们已经有过描述。创建

    索引的语法也很简单,如下所示。

    >db.collection.ensureIndex(keys, options)

    keys 是一个document文档,包含需要添加索引的字段和索引的排序

    方向;option 是可选参数,控制索引的创建方式。

    索引的删除并不是直接找到索引所在的集合system.indexes,通过在

    集合上执行remove命令来删除,而是通过执行集合上的命令dropIndex来

    删除的。如删除上面创建的如下复合索引。

    > db.customers.dropIndex(name_1_country_1)

    其中参数为索引的名称。

    3.2 查询优化

    查询优化的目的就是找出慢的查询语句,分析慢的原因,然后优化

    此查询语句。

    MongoDB 对于超过100ms 的查询语句,会自动地输出到日志文件

    里面,因此找出慢查询的第一步是查看MongoDB的日志文件,如果觉

    得这100ms阈值过大,我们可以通过mongod的服务启动选项slowms来设

    置,它的默认值是100ms。

    用上面的方法找出慢查询可能比较粗糙,第二种定位慢查询的方法是打开数据库的监视功能,它默认是关闭的,我们可以通过下面的命令

    打开。

    db.setProfilingLevel(level,[ slowms] )

    参数level是监视级别,值为0表示关闭数据库的监视功能,为1表示

    只记录慢查询,为2表示记录所有的操作;slowms为可选参数,设定慢

    查询的阈值。

    所有监视的结果都将保存到一个特殊的集合system.profile中。

    通过上面的两种方法我们可以找出慢查询的语句,然后通过建立相

    应的索引基本可以解决绝大部分的问题。但是我们有时需要更加精细的

    优化代码,这就需要分析这些慢查询的执行计划,查看查询是否用到索

    引,是否与我们想要的执行计划相同,用MongoDB的explain命令可以

    查看执行计划,正如3.1.1小节所做的那样。

    3.3 小结

    MongoDB可以在一个集合上建立一个或多个索引,而且必须为在

    字段_id建立一个索引,建索引的目的与关系数据库一样,就是为了提

    高对数据库的查询效率;一旦索引创建好,MongoDB 会自动地根据数

    据的变化维护索引,如果索引太大而不能全部保存在内存中,将被移到

    磁盘文件上,这样会影响查询性能,因此要时刻监控索引的大小,保证

    合适的索引在内存中;监控一个查询是否用到索引,可以在查询语句后

    用 explain 命令。并不是所有的字段都要建立索引,我们应该根据自己

    业务所涉及的查询,建立合适的索引。

    如果系统有大量的写操作,由于需要维护索引的变化,会导致系统

    性能降低。我们在对大数据建立索引时最好在后台进行,否则会导致数

    据库停止响应。要注意虽然我们在某些字段上建了索引,但是查询时可

    能用不上索引,如使用ne和nin表达式等。第4章 增改删操作

    4.1 插入语句

    与其他关系数据库类似,增加记录可以使用insert语句来完成,下

    面来看一个例子。

    >

    db.customer.insert({name:gyw,mobile:12345678901,email:xxx@163.com})

    查询看是否插入成功,如下所示。

    > db.customer.find

    {

    _id: ObjectId(528c67f5c95c154966513547), name:gyw,mobile:1234

    5678901, email : xxx@163.com

    }

    这里有几点需要说明。

    1.第一次插入数据时,不需要预先创建一个集合(customer),插

    入数据时会自动创建。

    2.每次插入数据时如果没有显示的指定字段_id,则会默认创建

    一个主键_id。在关系数据库中主键大多数是数值类型,且是自动增长

    的序列。而MongoDB中的主键值类型则为ObjectId类型,这样设计的好

    处是能更好的支持分布式存储。其中ObjectId类型的值由12个字节组

    成,前面4个字节表示的是一个时间截,精确到秒,紧接着的3个字节表

    示的是机器唯一标示,接着2个字节表示的进程id,最后 3个字节是一个

    随机的计数器。

    3.在MongoDB中,每一个集合都必须有一个_id字段,不管是自

    动生成的还是指定的,值都必须唯一,如果插入重复值将会抛出异常。下面是一个指定_id插入的例子。

    >

    db.customer.insert({_id:1,name:wjb,mobile:12345678901,email:xxx@123.com})

    如果再次插入_id:1的记录,则会抛出如下错误。

    E11000 duplicate key error index: eshop.customer._id_ dup key:{: 1.0

    }

    很明显提示主键值重复了。

    4.2 修改语句

    与关系数据库类似,修改也是由update来完成的,只是monogDB中

    的update有一些可选参数。总体里说修改分为两种,一种是只针对具体

    的目标字段,其他不变;另一种是取代性的更改,即修改具体目标字段

    后,其他的字段会被删除。update语法格式如下。

    db.collection.update(query, update, , )

    query参数是一个查询选择器,值类型为document。

    update参数为需要修改的地方,值类型为document,如果update参

    数只包含字段选项,没有操作符,则会发生取代性的更改。

    upsert为一个可选参数,boolen类型,默认值为false。当值为true

    时,update方法将更新匹配到的记录,如果找不到匹配的文档,则将插

    入一个新的文档到集合中。

    multi为一个可选参数,boolean类型,表示是否更新匹配到的多个

    文档,默认值为false,此时update方法只会更新匹配到的第一个文档;

    当为true时,update方法将更新所有匹配到的文档。

    下面通过一些例子来使用update语句。

    1.更改指定的字段值。

    > db.goods.update({name:apple},{set:{name:apple5s},inc:{price:4000}})

    这个操作将更改集合中与 name:apple匹配的第一个文档,将其中

    的字段 name 设为apple5s,字段price增加4000,其他字段保持不变。

    2.更改指定字段而其他字段被清除掉。

    > db.goods.update({name:htc},{name:htc one})

    这个操作将更改集合中与name:htc匹配的第一个文档,将其中的

    字段name设为htc one,文档中除了主键_id字段外,其他字段都被清

    除。

    3.更改多个文档中的指定字段。

    > db.goods.update({name:surface},{set:{price:6999}},{multi:true})

    由于利用了可选参数multi,这个操作将更改集合中与name: surface

    匹配的所有文档,将其中的字段price设为6999,其他字段不变。

    4.update找不到匹配的文档时则插入新文档。

    >db.goods.update({name:iphone},{set:{price:5999}},{upsert:true})

    因为利用了可选参数upsert,这个操作如果找不到匹配的文档,就

    会插入一个新的文档。

    4.3 删除语句

    MongoDB中的删除操作remove也是数据库CRUD操作中最基本的一

    种,与关系数据库中的delete类似,remove方法的格式如下。

    >db.collection.remove( , )

    参数query为可选参数,查询选择器,类似关系数据库中where条件

    语句。

    justOne参数也是可选参数,是一个boolean类型的值,表示是否只

    删除匹配的第一个文档,相当于关系数据库中的limit 1 条件。

    有一点要注意:如果 remove 没有指定任何参数,它将删除集合中的所有文档,但是不会删除集合对应的索引数据。如果想删除集合中的

    所有文档,同时也删除集合的索引,我们可以使用MongoDB针对集合

    提供的drop方法。下面看几个例子。

    1.删除匹配的所有文档。

    > db.goods.remove({name:htc })

    2.删除匹配的第一个文档。

    > db.goods.remove({name:huawei},1)

    3.删除所有文档,但不会删除索引。

    > db.goods.remove

    当利用remove删除一个文档后,文档对象也会从磁盘上相应的数据

    文件中删去。

    4.4 锁机制

    结合第2章的读操作,我们有必要介绍一下MongoDB中的锁机制。

    与关系数据一样,MongoDB也是通过锁机制来保证数据的完整性和一

    致性,MongoDB利用读写锁来支持并发操作,读锁可以共享,写锁具

    有排它性。当一个读锁存在时,其他读操作也可以用这个读锁;但是当

    一个写锁存在时,其他任何读写操作都不能共享这把锁,当一个读和写

    都等待一个锁时,MongoDB将优先分配锁给写操作。

    从版本2.2开始,MongoDB在每一个数据库上实现锁的粒度,当然

    对于某些极少数的操作,在实例上的全局锁仍然存在,锁粒度的降低能

    够提高系统的并发性。成熟的关系数据库锁的粒度更低,它可以在表中

    的某一行上,即“行级锁”。常见的操作和产生的锁类型如下:查询产生

    读锁、增删改产生写锁、默认情况下在前台创建索引会产生写锁、聚集

    aggregate操作产生读锁等。4.5 小结

    本章介绍了MongoDB数据库中最常用的3种操作,即插入操作,修

    改操作和删除操作,对每一种操作给出了详尽的实例参考,类似于关系

    数据库中的SQL语句编写。本章最后也介绍了MongoDB的锁机制,包括

    锁的粒度和各种数据库操作产生的不同锁类型。第二部分 深入理解

    MongoDB

    这一部分介绍的内容对深入理解 MongoDB 很有帮助,如果能完全

    掌握这部分内容,对整个 MongoDB 数据库的设计有很大帮助,同时对

    于日常运维中的监控以及遇到各种疑难问题都能快速地定位问题,找到

    方法解决。

    第5章 本章介绍了 Journaling 日志功能,对 MongoDB 的读写操作

    所发生的所有动作都将透彻分析。这个日志相当于关系数据库中的事物

    日志或redo日志,保证了数据的完整性和一致性。

    第6章 本章将介绍 3 种聚集分析,针对不同的数据处理需求灵活选

    择对应的分析方法,这部分的内容对大数据分析有借鉴意义。

    第7章 本章全面介绍了 MongoDB 的复制集功能,它能实现数据库

    的故障自动转移、数据的同步备份,保证了系统的可靠性。

    第8章 本章介绍了 MongoDB 的集群功能,它能实现海量数据的分

    布式存储,能够提高系统的吞吐量。

    第9章 本章介绍了 MongoDB 所特有的分布式文件存储功能,我们

    利用它的二进制数据类型可以构造一个分布式文件系统。

    第5章 Journaling日志功能

    MongoDB 的 Journaling 日志功能与常见的 log 日志是不一样的。MongoDB 也有 log日志,它只是简单记录了数据库在服务器上的启动信

    息,慢查询记录,数据库异常信息,客户端与数据库服务器连接、断开

    等信息。Journaling 日志功能则是 MongoDB 里面非常重要的一个功

    能,它保证了数据库服务器在意外断电、自然灾害等情况下数据的完整

    性。尽管 MongoDB 还提供了其他的复制集等备份措施(后面会分

    析),但Journaling 的功能在生产环境中是不可缺少的,它依靠了较小

    的 CPU 和内存消耗,带来的是数据库的持久性和稳定性。本篇章将分

    析 Journaling 涉及的功能细节问题和Journaling的工作流程。

    5.1 两个重要的存储视图

    Journaling 功能用到了两个重要的内存视图:private view和shared

    view。这两个内存视图都是通过MMAP(内存映射)来实现的,其中对

    private view的映射的内存修改不会影响到磁盘上;shared view 中数据的

    变化会影响到磁盘上的文件,系统会周期性地刷新shared view 中的数据

    到磁盘。

    shared view 在MongoDB 启动的过程中,操作系统会将磁盘上的数

    据文件映射到内存中的 shared view。操作系统只是完成映射,并没有立

    即加载数据到内存,MongoDB 会根据需要加载数据到shared view。

    private view 内存视图是为读操作保存数据的位置,是MongoDB 保

    存新的写操作的第一个地方。

    磁盘上的Journaling日志文件是实现写操作持久化保存的地方,MongoDB实例启动时会读这个文件。

    5.2 Journaling工作原理

    当 mongod 进程启动后,首先将数据文件映射到 shared 视图中,假

    如数据文件的大小为 4000 个字节,它会将此大小的数据文件映射到内存中,地址可能为 1000000~1004000。如果直接读取地址为1000060的

    内存,我们将得到数据文件中第60个字节处的内容。有一点要注意,这

    里只是完成了数据文件的内存映射,并不是将全部文件加载到内存中,只有读取到某个地址时才会将相应的文件内容加载到内存中,相当于按

    需加载,如图5-1所示。图5-1 mongod启动时的内存映射

    当写操作或修改操作发生时,进程首先会修改内存中的数据,此时

    磁盘上的文件数据就与内存中的数据不一致了。如果mongod启动时没

    有打开Journaling功能,操作系统将每 60 秒刷新 shared 视图对应的内存

    中变化的数据并将它写到磁盘上。如果打开了Journaling日志功能,mongod将额外产生一个private视图,MongoDB会将private视图与shared

    视图同步,如图5-2所示。

    当写操作发生时,MongoDB首先将数据写到内存中的private视图

    处,注意private视图并没有直接与磁盘上的文件连接,因此此时操作系

    统不会将变化刷新到磁盘上,如图5-3所示。

    然后MongoDB将写操作批量复制到journal,journal会将写操作存储

    到磁盘上的文件上,使其持久化保存,journal日志文件上的每一个条目都描述了写操作更改了数据文件上的哪些字节,如图5-4所示。

    图5-2 shared视图与private视图保持同步

    图5-3 MongoDB写数据到private视图图5-4 将数据文件的变化写到journal日志文件中

    由于数据文件的变化(如在哪个位置数据变成了什么)被持久化到

    了 journal 日志文件中,即使此时MongoDB服务器崩溃了,写操作也是

    安全的。因为当数据库重新启动时,会先读 journal 日志文件,将写操

    作引起的变化重新同步到数据文件中去。我们通过下面的启动日志也可

    以看到这个动作,日志截图如图5-5所示。

    当上面的步骤完成后,接下来MongoDB会利用journal日志中的写操

    作记录引起的数据文件变化来更新shared视图中的数据,如图5-6所示。

    当所有的变化操作都更新到 shared视图中后,MongoDB将重新利用

    shared视图来映射private视图,防止private视图变得“太脏”,使其占用的

    内存空间恢复到初始值,约为0。此时shared视图内存中的数据与磁盘上

    的数据变得不一致。按照默认值60秒,MongoDB会周期性地要求操作

    系统将shared视图中变化的数据刷新到磁盘上,使磁盘上的数据与内存

    中的数据保持一致,如图5-7所示。图5-5 mongod启动时利用journal日志进行恢复处理

    图5-6 刷新shared视图图5-7 重新同步private视图并flush到磁盘

    当执行完刷新内存中变化的数据到磁盘后,MongoDB会删除掉

    journal中这个时间点后面的所有写操作,这一点与关系数据库中的

    checkpoint类似。

    MongoDB的Journaling日志功能,在2.0版本后是默认启动的,可以

    在实例mongod启动时通过启动选项控制;上面提到的步骤中,有一个

    地方是将写操作周期性批量写到journal日志文件中,这个周期的大小是

    通过可选启动参数journalCommitInterval来控制的,默认值是100ms。

    MongoDB经过60s的周期刷新内存中变化的数据到磁盘,这个值是通过

    启动可选参数 syncdelay 来控制的。这些默认值一般适用于大多数情

    况,不要轻易更改。通过上面的分析,数据库服务器仍然有100ms的丢

    失数据的风险,因为Journaling日志写到磁盘上的周期是100ms,假如刚

    好一批写操作还在内存中,还没来得及刷新到Journaling在磁盘上对应

    的文件上,服务器突然故障,这些在内存中的写操作就会丢失。

    MongoDB在启动时,专门初始化一个线程不断循环,用于在一定

    时间周期内将从defer队列中获取要持久化的数据写入到磁盘的

    journal(日志)和 mongofile(数据)处。当然因为它不是在用户添加记录时就写到磁盘上,所以从MongoDB开发来说,它不会造成性能上的

    损耗,因为看过代码发现,当进行CUD操作时,记录(Record类型)都

    被放入到defer 队列中以供延时批量(group commit)提交写入。

    5.3 小结

    Journaling 是 MongoDB 中非常重要的一项功能,类似于关系数据

    库中的事务日志。Journaling能够使数据库由于其他意外原因故障后快

    速恢复。从MongoDB版本2.0以后,启动mongd实例时这项功能就自动

    打开了,数据库实例每次启动时都会检查磁盘上journal文件看是否需要

    恢复。尽管Journaling对写操作会有一些性能方面的影响,但对读操作

    没有任何影响,在生产环境中开启它是很有必要的。MongoDB 的复制

    集功能通过冗余数据保护数据的安全,但Journaling更是通过日志的方

    式保证数据库的一致性,两者的关注点不一样。

    第6章 聚集分析

    聚集操作是对数据进行分析的有效手段。MongoDB 主要提供了三

    种对数据进行分析计算的方式:管道模式聚集分析、MapReduce聚集分

    析、简单函数和命令的聚集分析。

    6.1 管道模式进行聚集

    这里所说的管道类似于UNIX上的管道命令。数据通过一个多步骤

    的管道,每个步骤都会对数据进行处理,最后返回需要的结果集。管道

    提供了高效的数据分析流程,是MongoDB中首选的数据分析方法。一

    个典型的管道操作流程如图6-1所示。图6-1 管道聚集操作流程图

    图6-1对应的操作语句如下。

    db.books.aggregate(

    [

    {

    match: { status: normal}

    },{

    group: {_id: book_id, total:{ sum: num}}

    }

    ])

    数据依次通过数组中的各管道操作符进行处理,常用的管道操作符有以下几个。

    match:过滤文档,只传递匹配的文档到管道中的下一个步骤。

    limit:限制管道中文档的数量。

    skip:跳过指定数量的文档,返回剩下的文档。

    sort:对所有输入的文档进行排序。

    group:对所有文档进行分组然后计算聚集结果。

    out:将管道中的文档输出到一个具体的集合中,这个必须是管道

    操作中的最后一步。

    与group操作一起使用的计算聚集值的操作符有以下几个。

    first:返回group操作后的第一值。

    first:返回group操作后的最后一个值。

    max:返回group操作后的最大值。

    min:返回group操作后的最小值。

    avg:返回group操作后的平均值。

    sum:返回group操作后所有值的和。

    常用的关系数据库中的SQL语句与MongoDB聚集操作语句比较如表

    6-1所示。

    表6-1 常用SQL语句与MongoDB语句对比

    续表6.2 MapReduce模式聚集

    MongoDB也提供了当前流行的MapReduce的并行编程模型,为海量

    数据的查询分析提供了一种更加高效的方法,用MongoDB做分布式存

    储,然后再用MapReduce来做分析。典型MapReduce流程如图6-2所示。图6-2 MapReduce操作流程图

    典型代码如下所示。

    db.books.mapReduce (

    function

    {

    emit ( this.boo_id,this.num) ;

    },function(key, values)

    {

    return Array.sum( values )

    },{

    query: { status: normal },outresult: books_totals})

    上面的需求实际上是要统计出每种类型的书可以销售的总数。

    在传统关系数据库上SQL语句如下所示。

    select sum(num) as value, book_id as _id

    from books where status = normalgroup by book_id;

    接下来我们看看MapReduce方式如何解决这种问题,首先定义了一

    个map函数,如下所示。

    function

    {

    emit ( this.boo_id,this.num) ;

    }

    接着定义reduce函数,如下所示。

    function(key, values)

    {

    return Array.sum( values )

    }

    最后在集合上执行MapReduce函数,如下所示。

    >db.books.mapReduce (

    function

    {

    emit ( this.boo_id,this.num) ;

    },function(key, values)

    {

    return Array.sum( values )

    },{

    query: { status: normal },outresult: books_totals

    })

    这里有一个查询过滤条件query:{status:normal},返回状态为

    normal的值,同时定义了保存结果的集合名,最后的输出结果将保存在

    集合books_totals中,执行以下命令可以看到结果。

    > db.books_totals.find

    { _id : 1, value : 300 }

    { _id : 2, value : 200 }

    这里的map、reduce函数都是利用JavaScript编写的函数,其中map

    函数的关键部分是emit(key, value)函数,此函数的调用使集合中的

    document对象按照key值生成一个value,形成一个键值对。其中key可以

    单一filed,也可以由多个filed组成,MongoDB会按照key生成对应的

    value值,value为一个数组。

    reduce函数的定义中有参数key和value,其中 key就是上面map函数

    中指定的key值, value就是对应key对应的值,Array.sum(value)这里是

    对数组中的值求和,按照不同的业务需要,我们可以编写自己的

    javascript函数来处理。

    6.3 简单聚集函数

    管道模式和MapReduce模式都是重型武器,基本上可以解决数据分

    析中的所有问题,但有时在数据量不是很大的情况下,直接调用基于集

    合的函数会更简单,常用的简单聚集函数有以下几种。

    1.distinct函数,用于返回不重复的记录,返回值是数组,函数原型如下。

    db.orders.distinct( key,)

    第一个参数为filed,第二个参数为查询选择器,返回值不能大于系

    统规定的单个文档的最大值,如图6-3所示。

    图6-3 distinct简单聚集函数

    2.count函数,用于统计查询返回的记录总数,函数原型如下。

    db.collection.find().count

    执行如下命令。

    > db.goods.find.count

    统计集合goods中的商品总数为99。

    3.group函数与distinct一样,返回的结果集不能大于16MB,不能在分片集群上进行操作且group不能处理超过10000个唯一键值。如果我

    们的聚集操作超过了这个限制,只有使用上面介绍的管道聚集或

    MapReduce方案。

    group的函数原型如下。

    db.collection.group( { key : ..., initial: ..., reduce : ...[, cond: ...] } )

    我们假设有如下结构的文档集合。

    { _id : 1, value : 100 }

    { _id : 4, value : 200 }

    { _id : 2, value : 200 }

    { _id : 1, value : 500 }

    { _id : 2, value : 100 }

    如果需要统计_id小于3,按照_id分组求value值的和,执行如下命

    令即可。

    db.books.group(

    {

    key: { _id: 1 },cond: { _id: { lt: 3 } },reduce: function(cur, result)

    {

    result.count += cur.count

    },initial: { count: 0 }

    })

    得到的结果如下所示。

    [

    { _id: 1, count: 600},{ _id: 2, count: 300}

    ]

    6.4 小结

    MongoDB的聚集操作是为大数据分析准备的,尤其是MapReduce可

    以在分片集群上进行操作。MongoDB既提供了简单的类似于关系数据

    库中的聚集函数,2.1版本后又提供了增强版的聚集框架,以满足一般

    的统计分析应用。

    MapReduce是一种编程模型,最早有Google提出,MongoDB也在这

    方面不断地完善、改进,提高其性能,使之成为处理大规模数据集(大

    于1TB)的利器。Map(映射)和Reduce(归约),和它们的主要思

    想,都是从函数式编程语言里借来的,还有从矢量编程语言里借来的特

    性,它极大地方便了编程人员,使他们在不会分布式并行编程的情况下

    可以将自己的程序运行在分布式系统上。当前的软件实现是指定一个

    Map(映射)函数,用来把一组键值对映射成一组新的键值对,指定并

    发的Reduce(归约)函数,用来保证所有映射的键值对中的每一个共享

    相同的键组。

    第7章 复制集

    复制集Replica Sets与第8章要介绍的分片Sharding 是MongoDB 最具

    特色的功能,其中复制集实现了数据库的冗余备份、故障转移,这两大

    功能应该是所有数据库管理人员追求的目标;分片实现了数据的分布式

    存储、负载均衡,这些都是海量数据的云存储平台不可或缺的功能,下

    面我们先从复制集介绍。7.1 复制集概述

    数据库总是会遇到各种失败的场景,如网络连接断开、断电等。尽

    管 Journaling 日志功能也提供了数据恢复的功能,但它通常是针对单个

    节点来说的,只能保证单节点数据的一致性。而复制集通常是由多个节

    点组成,每个节点除了 Journaling 日志恢复功能外,整个复制集还具有

    故障自动转移的功能,这样能保证数据库的高可用性。在生产环境中一

    个复制集最少应该包含三个节点,其中有一个必须是主节点,典型的部

    署结构如图7-1所示。

    图7-1 复制集结构图

    每个节点都是一个mongod进程对应的实例,节点之间互相周期性

    地通过心跳检查对方的状态。默认情况下primary节点负责数据的读、写,second节点备份primary节点上的数据,但是arbiter节点不会从

    primary节点同步数据。从它的名字arbiter可以看出,它起到的作用只是

    当primary节点故障时,能够参与到复制集剩下的节点中,选择出一个新

    的primary节点,它自己永远不会变为primary节点,也不会参与数据的

    读写。也就是说,数据库的数据会存在primary和second节点中,second

    节点相当于一个备份。当然second节点可以有多个,当primary节点故障时,second节点有可能变为primary节点,典型流程如图7-2所示。

    图7-2 故障转移流程图

    下面我们就配置一个这样的复制集,后面很多操作都会依赖于这个

    复制集。

    1.创建复制集中每个节点存放数据的目录。

    E:\MongoDB-win32-i386-2.6.3\db_rs0\data\rs0_0

    E:\MongoDB-win32-i386-2.6.3\db_rs0\data\rs0_1

    E:\MongoDB-win32-i386-2.6.3\db_rs0\data\rs0_2

    2.创建复制集中每个节点的日志文件。

    E:\MongoDB-win32-i386-2.6.3\db_rs0\logs\rs0_0.log

    E:\MongoDB-win32-i386-2.6.3\db_rs0\logs\rs0_1.log

    E:\MongoDB-win32-i386-2.6.3\db_rs0\logs\rs0_2.log

    3.创建复制集中的每个节点启动时所需的配置文件。

    第一个节点配置文件为:E:\MongoDB-win32-i386-

    2.6.3\configs_rs0\rs0_0.conf,内容如下所示。

    dbpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\data\rs0_0logpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\logs\rs0_0.log

    journal = true

    port = 40000

    replSet = rs0

    文件中dbpath指向数据库数据文件存放的路径(在第1步中已创建

    好),logpath指向数据库的日志文件路径(第2步中已创建好),journal表示对于此mongod实例是否启动日志功能,port 为实例监听的端

    口号,rs0 为实例所在的复制集名称,更多参数的意思可以参考

    MongoDB手册。

    第二个节点配置文件为:E:\MongoDB-win32-i386-

    2.6.3\configs_rs0\rs0_1.conf,内容如下所示。

    dbpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\data\rs0_1

    logpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\logs\rs0_1.log

    journal = true

    port = 40001

    replSet = rs0

    第三个节点配置文件为:E:\MongoDB-win32-i386-

    2.6.3\configs_rs0\rs0_2.conf,内容如下所示。

    dbpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\data\rs0_2

    logpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\logs\rs0_2.log

    journal = true

    port = 40002

    replSet = rs0

    4.启动上面三个节点对应的MongoDB实例。

    mongod–config E:\MongoDB-win32-i386-2.6.3\configs_rs0\rs0_0.conf

    mongod–config E:\MongoDB-win32-i386-2.6.3\configs_rs0\rs0_1.conf

    mongod–config E:\MongoDB-win32-i386-2.6.3\configs_rs0\rs0_2.conf我们观察一下每个实例的启动日志,日志中都有如下内容。

    [rsStart] replSet can't get local.system.replset config from self or any

    seed (EMPTYCONFIG)

    [rsStart] replSet info you may need to run replSetInitiate -rs.initiate in

    the shell -- if that is not already done

    上面日志说明虽然已经成功启动了3个实例,但是复制集还没配置

    好,复制集的信息会保存在每个mongod实例上的local数据库中,即

    local.system.replset上。按照图7-1所描述的那样,我们应该通过配置确

    定哪个节点为 primary、哪个为 second、哪个为 arbiter。下面开始配置

    复制集。

    5.启动一个mongo客户端,连接到上面的一个mongod实例。

    >mongo --port 40000

    我们来运行以下命令初始化复制集。

    > rs.initiate

    {

    info2 : no configuration explicitly specified -- making one,me : Guo:40000,info:Config now saved locally. Should come online in about a min

    e.,ok : 1

    }

    这个时候的复制集还只有刚才这个初始化的成员,通过如下命令查

    看到。

    > rs.conf

    {

    _id : rs0,version : 1,members : [

    {

    _id : 0,host : Guo:40000

    }

    ]

    }

    按照MongoDB的默认设置,刚才执行初始化命令的这个mongod实

    例将成为复制集中的primary节点。

    6.接下来在复制集中添加图7-1中的second节点和arbiter节点,继

    续在上面的mongod实例上执行如下命令。

    rs0:PRIMARY> rs.add(Guo:40001)

    { ok : 1 }

    rs0:PRIMARY> rs.addArb(Guo:40002)

    { ok : 1 }

    注意此时命令的前缀已变为 rs0:PRIMARY ,说明当前执行命令的

    机器是复制集中primary机器,上面的命令通过rs.add添加一个默认的

    second节点,rs.addArb添加一个默认的arbiter节点,命令成功执行后,就会生成图7-1所示那样的一个复制集。

    7.观察整个复制集的状态信息,几个重要参数我们会在后面说

    明。

    rs0:PRIMARY> rs.status

    {

    set : rs0,复制集的名称

    date : ISODate(2013-08-18T09:03:49Z),myState:1,当前节点成员在复制集中的位置,如1表示primary,2

    表示secondrymembers : [复制集的所有成员信息

    {

    _id : 0, 成员编号

    name : Guo:40000,成员所在的服务器名称

    health : 1,成员在复制集中是否运行,1表示运行,0失败

    state : 1,成员在复制集中的状态,1是primary

    stateStr : PRIMARY,成员在复制集中的状态名称

    uptime : 2186,成员的在线时间,单位是秒

    optime : {这个是用来进行同步用的,后面重点分析

    t : 1376816431,i : 1

    },optimeDate : ISODate(2013-08-18T09:00:31Z),self : true 成员为当前命令所在的服务器

    },{

    _id : 1,name : Guo:40001,health : 1, ,成员在复制集中是否运行,1表示运行

    state : 2 ,成员在复制集中的状态,2是secondary

    stateStr : SECONDARY,uptime : 306,optime : {

    t : 1376816431,i : 1

    },optimeDate : ISODate(2013-08-18T09:00:31Z),lastHeartbeat : ISODate(2013-08-18T09:03:47Z),lastHeartbeatRecv : ISODate(2013-08-18T09:03:47Z),pingMs : 0,此远端成员到本实例间一个路由包的来回时间

    syncingTo : Guo:40000此成员需要从哪个实例同步数据

    },{

    _id : 2,name : Guo:40002,health : 1,state : 7, 成员在复制集中的状态位置,7是arbiter

    stateStr : ARBITER,uptime : 198,lastHeartbeat : ISODate(2013-08-18T09:03:49Z),lastHeartbeatRecv : ISODate(1970-01-01T00:00:00Z),pingMs : 0,此远端成员到本实例间一个路由包的来回时间

    }

    ],ok : 1

    }

    上面复制集状态信息的输出是基于 primary 实例的,也可以在

    secondary 实例上输出复制集的状态信息,包含的字段与上面大致相

    同。上面的输出有些地方还需进一步解释,如在arbiter成员节点上没有

    字段syncingTo,说明它不需要从primary节点上同步数据,因为它只是

    一个当主节点发生故障时、在复制集剩下的secondary节点中选择一个新

    priamry节点的仲裁者,因此运行此实例的机器不需要太多的存储空间。

    上面输出的字段中还有几个时间相关的字段,如date表示当前实

    例所在服务器的时间,lastHeartbeat表示当前实例到此远端成员最近一次成功发送与接收心跳包的时间,通过比较这个两个时间我们可以判断

    当前实例与此成员相差的时间间隔。比如某个成员宕机了,本实例发像

    此宕机成员的心跳包就不会被成功接收,随着时间推移,本实例的 data

    字段值与此成员上的lastHeartbeat差值就会逐渐增加。

    上面还有一个optime字段,这个字段的值说明了本实例最近一次更

    改数据库的时间t:1376816431 以及每秒执行的操作数据库的次数i :

    1。此字段的值实际上是从本实例上的local数据库中的oplog.rs集合上读

    取的,这个集合还详细记录了具体是什么操作,如插入语句、修改语句

    等。复制集中的每一个实例都会有一个这样的数据库和集合,如果复制

    集运行正常,理论上来说,每一个mongod实例上此集合中的记录应该

    相同。实际上MongoDB也是根据此集合来实现复制集中primary节点与

    secondary节点间的数据同步。

    7.2 复制集工作机制

    7.2.1 数据同步

    7.1节概述了复制集,我们整体上对复制集有了个概念,但是复制

    集最重要的功能—自动故障转移是怎么实现的?复制集又是怎样实现数

    据同步的呢?带着这两个问题,我们下面展开分析。

    我们先利用mongo客户端登录到复制集的primary节点上。

    >mongo --port 40000

    查看实例上所有数据库。

    rs0:PRIMARY> show dbs

    local 0.09375GB

    我们可以看到只有一个 local 数据库,因为此时还没有在复制集上

    创建其他任何数据库,local数据库为复制集所有成员节点上默认创建了

    一个数据库。在primary节点上查看local数据上的集合,如下所示。rs0:PRIMARY> show collections

    oplog.rs

    slaves

    startup_log

    system.indexes

    system.replset

    如果是在secondary节点,则local数据库上的集合与上面有点不同,secondary节点上没有slaves集合,因为这个集合保存的是需要从primary

    节点同步数据的secondary节点。secondary节点上会有一个me集合,保

    存了实例本身所在的服务器名称;同时上面还有一个 minvalid 集合,用

    于保存对数据库的最新操作的时间截。其他集合 primary 节点和

    secondary 节点都有,其中 startup_log 集合表示的是 mongod 实例每一次

    的启动信息;system.indexes集合保存的是当前数据库(local)上的所有

    索引信息;system.replset集合保存的是复制集的成员配置信息,复制集

    上的命令 rs.conf实际上是从这个集合取的数据返回的。最后我们要介

    绍的集合是oplog.rs,这个可是重中之重。

    MongoDB就是通过oplog.rs来实现复制集间数据同步的。为了分析

    数据的变化,我们先在复制集上的primary节点上创建一个数据库

    students,然后插入一条记录。

    rs0:PRIMARY> use students

    switched to db students

    rs0:PRIMARY>

    db.scores.insert({stuid:1,subject:math,score:99});

    接着查看一下primary节点上oplog.rs集合的内容。

    rs0:PRIMARY> use local

    switched to db local

    rs0:PRIMARY> db.oplog.rs.find;返回记录中会多出一条下面这样的记录(里面还有几条记录是复制

    集初始化时创建的)。

    {ts:{t: 1376838296, i: 1 }, h: NumberLong

    (6357586994520331181),v: 2, op:i, ns:students.scores, o:{_id: ObjectId(5210e2

    98d7b419b44afa58cc), stuid: 1, subject:math, score: 99 }}

    里面有几个重要字段,其中ts表示这条记录的时间截,t是秒

    数,i是每秒操作的次数;字段op表示的是操作码,值i表示的是

    insert操作;ns表示插入操作发生的命名空间,这里值

    为:students.scores,由数据库和集合名构成;o表示的是此插入操作

    包含的文档对象。

    当primary节点完成插入操作后,secondary节点为了保证数据的同

    步也会完成一些动作。所有secondary节点检查自己的local数据上

    oplog.rs集合,找出最近的一条记录的时间截;接着它会查询primary节

    点上的oplog.rs集合,找出所有大于此时间截的记录;最后它将这些找

    到的记录插入到自己的oplog.rs集合中并执行这些记录所代表的操作;

    完成这三步策略,就能保证 secondary 节点上的数据与 primary 节点上

    的数据同步了,整个流程如图7-3所示。图7-3 复制集数据同步流程

    查看一下secondary节点上的数据,我们发现在secondary节点上新插

    入了一个数据库students,这就实现了复制集间的数据同步,可以证明

    上面的分析是正确的。

    rs0:SECONDARY> show dbs

    local 0.09375GB

    students 0.0625GB

    但是有一点要注意,现在还不能在secondary节点上直接查询

    students集合上的内容,默认情况下MongoDB 的所有读写操作都是在

    primary 节点上完成的,后面我们也会介绍通过设置从secondary节点上

    来读,这将引入一个新的主题,即读写分离。

    关于oplog.rs集合还有一个很重要的方面,那就是它的大小是固定

    的。MongoDB这样设置也是有道理的,假如大小没限制,那么随着时

    间的推移,数据库上的操作会逐渐累积, oplog.rs 集合中保存的记录也

    会逐渐增多,这样会消耗大量的存储空间;同时对于某个时间点以前的

    操作记录,早已同步到 secondary 节点上,也没有必要一直保存这些记

    录。因此MongoDB将oplog.rs集合设置成一个capped类型的集合,实际

    上就是一个循环使用的缓冲区。

    固定大小的 oplog.rs 会带来新的问题,我们考虑下面这种场景:假

    如一个 secondary节点因为宕机,长时间不能恢复,而此时大量的写操

    作发生在primary节点上;当secondary节点恢复时,利用自己oplog.rs集

    合上最新的时间截去查找primary节点上的oplog.rs集合,会出现找不到

    任何记录。因为长时间不在线,primary节点上的oplog.rs集合中的记录

    早已全部刷新了一遍,这样我们就不得不手动重新同步数据了。因此

    oplog.rs的大小很重要,在32位的系统上默认大小是50MB,在64位的机

    器上默认是5%的空闲磁盘空间大小,我们也可以在mongod启动命令中

    通过项--oplogSize设置其大小。7.2.2 故障转移

    上面介绍的数据同步相当于传统数据库中的备份策略,MongoDB

    在此基础还有自动故障转移的功能。在前面复制集概述中我们提到过心

    跳lastHeartbeat字段,MongoDB 就是靠它来实现自动故障转移的。

    mongod 实例每隔两秒就向其他成员发送一个心跳包并且通过rs.staus中

    返回的成员的”health”值来判断成员的状态。如果出现复制集中primary

    节点不可用了,那么复制集中所有 secondary 的节点就会触发一次选举

    操作,选出一个新的primary 节点。如上所配置的复制集中如果 primary

    节点宕机了,那么系统就会选举secondary节点成为primary节点。arbiter

    节点只是参与选举其他成员成为primary节点,自己永远不会成为

    primary 节点。如果 secondary 节点有多个则会选择拥有最新时间截的

    oplog记录或较高权限的节点成为primary节点。oplog记录我们在前面复

    制集概述中已经描述过,关于复制集中节点权限配置的问题可在复制集

    启动的时候进行设置,也可以在启动后重新配置,这里先略过这一点,集中精力讨论故障转移。

    如果是某个 secondary 节点失败了,只要复制集中还有其他

    secondary 节点或 arbiter节点存在,就不会发生重新选举primary节点的

    过程。

    下面我们模拟两种失败场景:一是secondary节点的失败,然后过一

    段时间后重启(时间不能无限期,否则会导致 oplog.rs 集合严重滞后的

    问题,需要手动才能同步);二是primary节点失败,故障转移发生。

    当前复制集的配置情况如下所示。

    1.rs0:PRIMARY> rs.conf。

    {

    _id : rs0,version : 3,members : [

    {

    _id : 0,host : Guo:40000 primary节点

    },{

    _id : 1,host : Guo:40001 secondary节点

    },{

    _id : 2,host : Guo:40002, arbiter节点

    arbiterOnly : true

    }

    ]

    }

    2.通过Kill掉secondary节点所在的mongod实例,模拟第一种故障

    情况,如图7-4所示,通过rs.status命令查看复制集状态,secondary节

    点状态信息如下。

    _id : 1,name : Guo:40001,health : 0,state : 8, 表示成员已经down机

    stateStr : (not reachablehealthy),uptime : 0,optime : {

    t : 1376838296,i : 1

    },optimeDate : ISODate(2013-08-18T15:04:56Z)

    图7-4 模拟secondary节点故障

    3.接着通过primary节点插入一条记录。

    rs0:PRIMARY> db.scores.insert({stuid:2,subject:english,score:100})

    4.再次查看复制集状态信息rs.status,我们可以看到primary成员

    节点上oplpog信息如下。

    optime : {

    t : 1376922730,i : 1

    },optimeDate : ISODate(2013-08-19T14:32:10Z),与上面down机的成员节点比较,optime已经不一样,primary节点上

    要新于down机的节点。

    5.重新启动Kill掉的Secondary节点。

    >mongod --config E:\MongoDB-win32-i386-

    2.6.3\configs_rs0\rs0_1.conf

    查询复制集状态信息rs.status,观看节点Guo:40001的状态信息

    如下。

    _id : 1,name : GUO:40001,health : 1,state : 2,stateStr : SECONDARY,uptime : 136,optime : {

    t : 1376922730, 与上面primary节点一致了

    i : 1

    },optimeDate : ISODate(2013-08-19T14:32:10Z),这说明 secondary 节点已经恢复并且从 primary 节点同步到了最新

    的操作数据。进一步通过查询secondary节点上local数据库上的oplog.rs

    集合来进行验证,我们发现多了一条下面这样的记录。

    { ts : { t : 1376922730, i : 1 }, h : NumberLong

    (-451684574732211704),v: 2, op:i, ns:students.scores, o:{

    _id : ObjectId(52122c6a99c5a3ae472a6900), stuid : 2, subject

    :english, score : 100 } }

    这正是在primary节点上插入的记录,再次证明数据确实同步过来

    了。

    接下来测试第二种情况,假如Primary节点故障,流程变化如图7-5

    所示。

    1.将primary节点Kill掉。

    查询复制集的状态信息rs.status。

    name : Guo:40000,health : 0,state : 8,stateStr : (not reachablehealthy)图7-5 模拟primary节点失败并恢复后

    字段health的值为0,说明原来的primary节点已经down机了。

    name : Guo:40001,health : 1,state : 1,stateStr : PRIMARY

    字段stateStr值为PRIMARY,说明原来secondary节点变成了

    primary节点。

    2.在新的primary节点上插入一条记录如下。

    rs0:PRIMARY> db.scores.insert({stuid:3,subject:computer,score:99})

    3.重新恢复Guo:40000节点(原来的primary节点)。

    >mongod --config E:\MongoDB-win32-i386-

    2.6.3\configs_rs0\rs0_0.conf再次查看复制集状态rs.status。

    name : Guo:40000,health : 1,state : 2,stateStr : SECONDARY,uptime : 33,optime : {

    t : 1376924110,i : 1

    },当Guo:40000实例被重新激活后,变成了secondary节点,oplog也

    被同步成最新的了。这说明当 primary 节点故障时,复制集能自动转移

    故障,将其中一个 secondary 节点变为primary节点,读写操作继续在新

    的primary节点上进行,原来primary节点恢复后,在复制集中变成了

    secondary 节点,上面两种情况都得到了验证。但是有一点我们要注

    意, mongDB默认情况下只能在primary节点上进行读写操作,如图7-6

    所示。图7-6 默认的读写流程图

    对于客户端应用程序来说,对复制集的读写操作是透明的,默认情

    况下它总是在primary节点上进行。MongoDB提供了很多种常见编程语

    言的驱动程序,驱动程序位于应用程序与mongod实例之间,应用程发

    起与复制集的连接,驱动程序自动选择primary节点。当primary节点失

    效、复制集发生故障转移时,复制集将先关闭与所有客户端的socket连

    接,驱动程序将返回一个异常,应用程序收到这个异常,这个时候需要

    应用程序开发人员去处理这些异常,同时驱动程序会尝试重新与primary

    节点建立连接(这个动作对应用程序来说是透明的)。假如这个时候正

    在发生一个读操作,在异常处理中我们可以重新发起读数据命令,因为

    读操作不会改变数据库的数据;假如这个时候发生的是写操作,情况就变得微妙起来。如果是非安全模式下的写操作,就会产生不确定因素,写操作是否成功不确定;如果是安全模式,驱动程序会通过

    getLastError 命令知道哪些写操作成功,哪些失败了,驱动程序会返回

    失败的信息给应用程序。针对这个异常信息,应用程序可以决定怎样处

    置这个写操作,可以重新执行写操作,也可以直接给用户报出这个错

    误。

    对于一个健壮的应用程序来说,安全模式的写操作总是应该被提倡

    的,7.2.3节将研究写这方面的内容。关于驱动程序如何处理这些繁琐的

    异常我们会在第12章中详细介绍。

    7.2.3 写关注

    对于某些应用程序来说,写关注是重要的。它能判断哪些写操作成

    功写入了,哪些失败了。对于失败的操作,驱动程序能返回错误,由应

    用程序决定怎么处理。如果没有写关注,应用程序发送一个写操作到

    socket后,就不会管后面发送了什么情况,不知道是否成功写入数据

    库,这种情形对于日志类型的应用程序还是可以接受的,因为偶尔的写

    失败不会影响整个日志的监控情况。带有写关注的操作会等到数据库确

    认成功写入后才能返回,因此写关注会带来一点性能的损失。下面我们

    先分析复制集上写关注配置。

    默认情况下复制集的写关注只针对primary节点,如图7-7所示,当

    应用程序发送一个写操作请求时,驱动程序会调用 getLastError 命令返

    回写操作的执行情况(这一动作对应用程序来说是透明的),getLastError命令会根据我们配置的写关注选项来执行。写关注选项的配

    置是针对当前客户端与数据库的socket连接来说的,因此配置项需要通

    过应用程序传递给驱动程序。当然如果我们没有传递任何选项参数给驱

    动程序,getLastError命令会根据配置在复制集中的默认配置

    local.system.replset.settings.getLastErrorDefaults来执行。getLastError命令的常用选项如下。

    1.选项w

    当取值为?1时,驱动程序不会使用写关注,忽略掉所有的网络或

    socket错误。

    当取值为0时,驱动程序不会使用写关注,只返回网络和socket的错

    误。

    当取值为1时,驱动程序使用写关注,但是只针对primary节点,这

    个配置项是对于复制集或单mongod实例默认写关注配置。

    当取值为整数且大于1时,写关注将针对复制集中的n个节点,当客

    户端收到这些节点的反馈信息后,命令才返回给客户端继续执行。如图

    7-8所示就是一个w值等于2的写关注执行流程图。

    图7-7 默认写关注w:1图7-8 写关注w:2的执行流程图

    2.选项wtimeout

    指定写关注应在多长时间内返回,如果你没有指定这个值,复制集

    可能因为不确定因素导致应用程序的写操作一直阻塞。

    下面我们通过一段代码对上面的描述做个回顾,在C驱动程序下连

    接复制集并插入一条记录。代码向复制集中插入一条数据,但是客户端

    的配置属性都是默认的,写关注w选项值为1,可以 在C的驱动程序中

    通过MongoClientSettings这个类来设置客户端的连接属性,包括写关注

    等。

    下面的代码没有具体指定连接到哪个节点,但驱动程序会默认地选

    择primary节点,当primary节点宕机时,复制集重新选择出新的primary

    节点,驱动程序尝试重新连接新的primary节点并完成插入,这个动作对

    应用程序是透明的。

    实例化一个客户端的连接属性实例

    MongoClientSettings clientSetting = new MongoClientSettings;设置属性准备Servers为要连接的复制集中的所有成员实例

    List Servers = new List

    ;

    Servers.Add(new MongoServerAddress(Guo,40000));

    Servers.Add(new MongoServerAddress(Guo,40001));

    Servers.Add(new MongoServerAddress(Guo,40002));

    clientSetting.Servers = Servers;

    clientSetting.ReplicaSetName = rs0; 设置属性复制集的名称

    MongoClient client = new MongoClient(clientSetting);根据设置的属

    性,实例化客户端

    得到一个与复制集连接的实例

    MongoServer server = client.GetServer;

    获得一个与具体数据库连接对象,数据库名为students

    MongoDatabase mydb = server.GetDatabase(students);

    获得数据库中的表对象,即scores表

    MongoCollection mydbTable = mydb.GetCollection(scores);

    准备一条数据,即声明一个文档对象

    BsonDocument doc = new BsonDocument

    {

    {stuid,5},{subject,sports},{score,99}

    };

    将文档插入到数据库中

    mydbTable.Insert(doc);7.2.4 读参考

    读参考是指MongoDB将客户端的读请求路由到复制集中指定的成

    员上,默认情况下读操作的请求被路由到复制集中的primary节点上,如

    图7-6所示。从primary节点上进行读取能够保证读到的数据是最新的,但是将读操作路由到其他 secondary 节点上去后,由于从primary节点同

    步数据到secondary节点会产生时间差,可能导致从secondary节点上读到

    的数据不是最新的。当然这对于实时性要求不是很高的绝大部分应用程

    序来说,并不是大问题。

    关于读参考还有一点要注意,因为每一个 secondary 节点都会从

    primary 节点同步数据,所有 secondary 节点一般有相同的写操作流量,同时 priamry 节点上的读操作量也并没有减少,所以读参考并不能提高

    系统读写的容量。它最大的好处是能够使客户端的读请求路由到最佳的

    secondary 节点上(如最近的节点),提高客户端的读效率,MongoDB

    驱动支持的读参考模式如下。

    1.primary模式

    这是默认的读操作模式,所有的读请求都路由到复制集中的

    primary 节点上。如果priamry节点故障了,读操作将会产生一个错误或

    者抛出一个异常。

    2.priamrypreferred模式

    在大多数模式下,读操作从primary节点上进行,如果primary节点

    故障无法读取,读操作将被路由到secondary节点上。

    3.secondary模式

    读操作只能从secondary节点上进行,如果没有可用的secondary节

    点,读操作将产生错误或抛出异常。

    4.secondaryPreferred模式

    在大多数情况下,读操作在 secondary 节点上进行,但当复制集中只有一个 primary节点时,读操作将用这个复制集的primary节点。

    5.nearest模式

    读操作从最近的节点上进行,有可能是 primary 节点,也有可能是

    secondary 节点,并不会考虑节点的类型。

    7.3 小结

    当 MongoDB 向复制集中的 primary 节点写数据时,也会将写操作

    日志 oplog 写到primary节点所在的local数据库中,因此对复制集的写操

    作会产生两个锁,一个是集合数据所在的数据库上的写锁,还有一个是

    local数据库上的写锁。

    复制集中的 secondary 节点并不是实时地同步 oplog 日志、将数据

    的变化反映到secondary节点上,而是采取周期延迟批量写入的方式;

    secondary节点当应用写操作变化时,会锁在数据库,不允许读操作发

    生。

    第8章 分片集群

    上一章的分析复制集解决了数据库的备份与自动故障转移,但是围

    绕数据库的业务中当前还有两个方面的问题变得越来越重要,一是海量

    数据如何存储,二是如何高效地读写海量数据。尽管复制集也可以实现

    读写分析,如在 primary 节点上写,在 secondary 节点上读,但在这种

    方式下客户端读出来的数据有可能不是最新的,因为 primary 节点到

    secondary 节点间的数据同步会带来一定延迟,而且这种方式也不能处

    理大量数据。MongoDB 从设计之初就考虑了上面所提到的两个问题,引入了分片机制,实现了海量数据的分布式存储与高效的读写分离。复制集中的每个成员是一个mongod实例,但在分片部署上,每一个片可

    能就是一个复制集。

    上面谈到了分片的优点,但分片的使用会使数据库系统变得复杂。

    什么时候使用分片也是需要考虑的问题。MongoDB 使用内存映射文件

    的方式来读写数据库,对内存的管理由操作系统来负责。随着运行时间

    的推移,数据库的索引和数据文件会变得越来越大,对于单节点的机器

    来说,迟早会突破内存的限制。当磁盘上的数据文件和索引远大于内存

    的大小时,操作系统会频繁地进行内存交换,导致整个数据库系统的读

    写性能下降。因此对于大数据的处理,要时刻监控MongoDB的磁盘IO

    性能、可用内存的大小,在数据库内存使用率达到一定程度时就要考虑

    分片了,通过分片使整个数据库分布在各个片上,每个片拥有数据库的

    一部分数据,从而降低内存使用率,提高读写性能。

    8.1 分片部署架构

    下面我们看看一个具体的分片部署架构是什么,为了后续的研究,先部署一个如图8-1所示的分片集群。由图可知,分片集群主要由

    mongos路由进程、复制集组成的片shards、一组配置(Configure)服务

    器构成,下面我们对这些模块一一解释。图8-1 分片集群

    分片集群中的一个片shard实际上就是一个复制集,当然一个片也可

    以是单个mongod实例,只是在分片集群的生产环境中,每个片只是保

    存整个数据库数据的一部分,如果这部分数据丢失了,那么整个数据库

    就不完整了。因此我们应该保证每个片上数据的稳定性和完整性,通过

    第7章对复制集的分析可知,复制集能够达到这样的要求。我们通过将

    片配置为复制集的形式,使片shard在默认情况下读写都在复制集的

    primary节点上,每个片同时具有自动故障转移、冗余备份的功能,总之

    复制集所具有的的特性在片上都能得到体现。

    mongos 路由进程是一个轻量级且非持久性的进程。轻量级表示它

    不会保存任何数据库中的数据,它只是将整个分片集群看成一个整体,使分片集群对整个客户端程序来说是透明的。当客户端发起读写操作时,由mongos路由进程将该操作路由到具体的片上进行;为了实现对读

    写请求的路由,mongos 进程必须知道整个分片集群上所有数据库的分

    片情况,即元信息。这些信息是从配置服务器上同步过来的,每次进程

    启动时都会从configure服务器上读元信息,mongos并非持久化保存这些

    信息。

    配置服务器configure在整个分片集群中相当重要。上面说到mongos

    会从配置服务器同步元信息,因此配置服务器要能实现这些元信息的持

    久化。配置服务器上的数据如果丢失,那么整个分片集群就无法使用,因此在生产环境中通常利用三台配置服务器来实现冗余备份,这三台服

    务器是独立的,并不是复制集架构。

    下面按照如图8-1所示来配置一个这样的分片集群。

    1.配置复制集rs0并启动,参考7.1节中介绍的6个步骤。

    先创建好rs0中各节点的数据文件存放路径、日志文件路径以及配

    置文件,其中配置文件的内容如下。

    rs0中primary节点的配置文件为rs0_0.conf。

    dbpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\data\rs0_0

    logpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\logs\rs0_0.log

    journal = true

    port = 40000

    replSet = rs0

    rs0中secondary节点的配置文件为rs0_1.conf如下所示。

    dbpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\data\rs0_1

    logpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\logs\rs0_1.log

    journal = true

    port = 40001

    replSet = rs0

    rs0中arbiter节点的配置文件为rs0_2.conf如下所示。dbpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\data\rs0_2

    logpath = E:\MongoDB-win32-i386-2.6.3\db_rs0\logs\rs0_2.log

    journal = true

    port = 40002

    replSet = rs0

    按照7.1节介绍的步骤启动复制集rs0。

    2.配置复制集rs1并启动,步骤与上面相同,这里给出rs2中各节点

    对应的配置文件内容。

    rs1中primary节点的配置文件为rs1_0.conf。

    dbpath = E:\MongoDB-win32-i386-2.6.3\db_rs1\data\rs1_0

    logpath = E:\MongoDB-win32-i386-2.6.3\db_rs1\logs\rs1_0.log

    journal = true

    port = 40003

    replSet = rs1

    rs1中primary节点的配置文件为rs1_1.conf。

    dbpath = E:\MongoDB-win32-i386-2.6.3\db_rs1\data\rs1_1

    logpath = E:\MongoDB-win32-i386-2.6.3\db_rs1\logs\rs1_1.log

    journal = true

    port = 40004

    replSet = rs1

    rs1中primary节点的配置文件为rs1_2.conf如下所示。

    dbpath = E:\MongoDB-win32-i386-2.6.3\db_rs1\data\rs1_2

    logpath = E:\MongoDB-win32-i386-2.6.3\db_rs1\logs\rs1_2.log

    journal = true

    port = 40005

    replSet = rs1

    按照7.1节介绍的步骤启动复制集rs0。通过rs.status检查并确认上述复制集已启动且配置正确。

    3.配置configure服务器。

    configure服务器也是一个mongod进程,它与普通的mongod实例没

    有本质区别,只是它上面的数据库以及集合是特意给分片集群用的,其

    内容我们会在后面详细介绍。三个独立的配置服务器对应的启动配置文

    件内容如下。

    configure服务器1的配置文件cfgserver_0.conf如下所示。

    dbpath = E:\MongoDB-win32-i386-2.6.3\db_configs\data\db_config0

    logpath = E:\MongoDB-win32-i386-

    2.6.3\db_configs\logs\db_config0.log

    journal = true

    port = 40006

    configsvr = true

    configure服务器2的配置文件cfgserver_1.conf如下所示。

    dbpath = E:\MongoDB-win32-i386-2.6.3\db_configs\data\db_config1

    logpath = E:\MongoDB-win32-i386-

    2.6.3\db_configs\logs\db_config1.log

    journal = true

    port = 40007

    configsvr = true

    configure服务器3的配置文件cfgserver_2.conf如下所示。

    dbpath = E:\MongoDB-win32-i386-2.6.3\db_configs\data\db_config2

    logpath = E:\MongoDB-win32-i386-

    2.6.3\db_configs\logs\db_config2.log

    journal = true

    port = 40008

    configsvr = true配置服务器上的mongod实例启动时的配置选项与普通的mongod实

    例差不多,这里只是多了一个configsvr=true的选择,说明这个mongod实

    例是一个configure类型的mongod实例。

    启动上面三个配置服务器。

    >mongod --config E:\MongoDB-win32-i386-

    2.6.3\configs_cfgservers\cfgserver_0.conf

    >mongod --config E:\MongoDB-win32-i386-

    2.6.3\configs_cfgservers\cfgserver_1.conf

    >mongod --config E:\MongoDB-win32-i386-

    2.6.3\configs_cfgservers\cfgserver_2.conf

    4.配置mongos路由服务器。

    配置文件cfg_mongos.conf内容如下所示。

    logpath = E:\MongoDB-win32-i386-2.6.3\mongos\logs\mongos.log

    port = 40009

    configdb = Guo:40006,GuO:40007,GuO:40008

    启动路由服务器。

    >mongos --config E:\MongoDB-win32-i386-

    2.6.3\mongos\cfg_mongos.conf

    实例对应的进程为 mongos,路由服务器只是一个轻量级和非持久

    化操作的进程,因此上面的配置文件里面没有像其他 mongod 实例那样

    有一个存放数据文件的路径选项dbpath。

    5.添加各分片到集群。

    前面已经完成了两个片(复制集)、三个配置服务器、一个路由服

    务器且它已经知道从哪些配置服务器上同步元数据(configdb =

    Guo:40006,GuO:40007,GuO:40008),接下来 我们要做的是将各个片添

    加到集群中。

    打开一个mongo客户端连接到mongos服务器。>mongo --port 40009

    添加两个分片如下所示。

    mongos> sh.addShard(rs0GUO:40000,GUO:40001)

    { shardAdded : rs0, ok : 1 }

    mongos> sh.addShard(rs1GUO:40003,GUO:40004)

    { shardAdded : rs1, ok : 1 }

    这里添加分片的命令是sh.addShard,参数是复制集名以及复制集

    中不包含arbiter类型的所有节点。

    6.最后通过命令sh.status检查上面的配置是否正确,正常的话输

    出信息类似下面。

    mongos> sh.status--- Sharding Status --

    sharding version: {

    _id : 1,version : 3,minCompatibleVersion : 3,currentVersion : 4,clusterId : ObjectId(521b11e0a663075416070c04)

    }

    shards:

    { _id : rs0, host : rs0Guo:40000,Guo:40001 }

    { _id : rs1, host : rs1Guo:40003,Guo:40004 }

    databases:

    { _id : admin, partitioned : false, primary : config }

    上面输出的信息中clusterId字段表示此分片集群的唯一标示;shards

    为分片集群中包含的所有片,其中_id为此片的名称,host为片中的主机

    的host信息;databases为集群中的所有数据库,其中_id 为数据库名称,partitioned 表示此数据库是否支持分片;primary节点表示当数据库支持

    分片,此数据库上所有未分片的集合所在的片。

    此时在整个分片集群中还没有创建任何其他数据库,通过路由进程

    mongos连接集群,执行命令show dbs 我们可以看到集群中只有系统默认

    创建的一个config 数据库,而且这个数据库只存在于三个配置服务器

    上,config数据库中的集合包含了整个集群的配置信息。执行命令show

    collections,我们可以看到有如下集合。

    mongos> show collections

    changelog:保存被分片的集合的任何元数据的改变,例如chunks的

    迁移、分割等。

    Chunks:保存集群中分片集合的所有块的信息,包含块的数据范围

    与块所在的片。

    Databases:保存集群中的所有数据库,包含分片与未分片的。

    Lockpings:保存跟踪集群中的激活组件。

    locks:均衡器balancer执行时会生产锁,在此集合中插入一条记

    录。

    mongos:保存了集群中所有路由mongos的信息。

    Settings:保存分片集群的配置信息,如每个chunk的大小

    (64MB)、均衡器的状态。

    Shards:保存了集群中的所有片的信息。

    system.indexes:保存config数据库中的所有索引信息。

    Version:保存当前所有元信息的版本。

    由上所述得知,配置服务器中的config数据库的信息对于整个集群

    来说是至关重要的,这也是生产环境中最少需要3个配置服务器做冗余

    备份的原因;同时上面对config数据库的所有操作,都是通过客户端连

    接 mongos 后再进行的,尽管 config 数据库也是在单个mongod 实例

    上,我们可以直接通过客户端连接到这个实例然后做操作,但是这样会出现配置服务器上的信息不一致的风险,因此我们对集群的所有操作应

    该是通过客户端连接mongos来执行。

    最后我们探讨一下实际部署的问题,通过图8-1和前面的分析可

    知,一个生产环境最少需要9个mongod实例进程,一个mongos进程实

    例,理论上说最少需要由10台机器才能组成。但是这些进程中有些并不

    需要很多软硬件资源,它们可以与其他进程共存部署在同一个机器上,如复制集中arbiter进程、mongos进程可以部署到应用程序所在的服务

    器,综合考虑后我们可以得到如图8-2所示的典型部署。

    图8-2 集群典型部署

    上图部署的总体原则是使每一个片(复制集)中的primary节点、secondary节点、arbiter节点分开以及三台配置服务器分开,当图中的四

    台机器任何一台宕机时,集群都能够正常运行。

    8.2 分片工作机制前面我们部署了一 ......

您现在查看是摘要介绍页, 详见PDF附件(7499KB,239页)