LongLong's Blog

分享IT技术,分享生活感悟,热爱摄影,热爱航天。

MongoDB3.0配置与使用

MongoDB作为NoSQL数据库中功能最多也是最为接近传统关系数据库的的一种,有着较为广泛的应用,特别是基于文档的无Schema数据存储方式以及基于Replica Set的复制和故障转移机制,相比传统的关系数据库有着明显的优势。然而在之前的使用过程中发现,由于MongoDB采用mmap的方式将数据的缓存工作交给操作系统处理,导致缓存的效率较低,并且当数据量远大于内存后性能下降非常明显,经常出现慢查询导致崩溃的情况。

而在MongoDB 3.0中其增加类似MySQL存储引擎的概念,除了默认的mmapv1,增加了wiredTiger,其自身能够对数据进行缓存,类似InnoDB,而根据官方的测试说其性能要明显好于LevelDb和InnoDB,具体可以参考http://www.wiredtiger.com/。

1. 从低版本升级

根据官方提供的升级文档,只有2.6以上的版本可以直接升级到3.0,否则需要先升级到2.6再进行升级。如果只是升级mongod的版本,则升级过程非常简单,只需要下载最新版本的二进制包,暂时停止mongod,再替换掉老的二进制文件后重新启动即可,数据文件格式是可以向下兼容的,然后升级后不能再进行降级,这一点需要特别注意,条件允许的情况下做好数据的备份也是很有必要的。另外如果使用了Replica Set的复制可以先升级一个SECONDARY,然后再用相同的方法升级其他的SECONDARY,升级PRIMARY时则将其强制将为SECONDARY再进行升级操作,如此可以做到无间断的升级,MongoDB的Replica Set复制允许不同的实例之间存在版本的差异。

切换PRIMARY时,连接到当前的PRIMARY,并在mongo shell中执行一下操作即可强制进行PRIMARY的故障转移

rs.stepDown();

执行后当前的PRIMARY会被切换为SECONDARY,输出如下

upgrade:PRIMARY> rs.stepDown();
2015-10-14T21:45:30.800+0800 I NETWORK  DBClientCursor::init call() failed
2015-10-14T21:45:30.808+0800 E QUERY    Error: error doing query: failed
    at DBQuery._exec (src/mongo/shell/query.js:83:36)
    at DBQuery.hasNext (src/mongo/shell/query.js:240:10)
    at DBCollection.findOne (src/mongo/shell/collection.js:187:19)
    at DB.runCommand (src/mongo/shell/db.js:58:41)
    at DB.adminCommand (src/mongo/shell/db.js:66:41)
    at Function.rs.stepDown (src/mongo/shell/utils.js:1006:15)
    at (shell):1:4 at src/mongo/shell/query.js:83
2015-10-14T21:45:30.810+0800 I NETWORK  trying reconnect to 127.0.0.1:27017 (127.0.0.1) failed
2015-10-14T21:45:30.811+0800 I NETWORK  reconnect 127.0.0.1:27017 (127.0.0.1) ok
upgrade:SECONDARY>

2. 更换存储引擎

然而只是更新mongod的版本带来的提升是比较有限的,升级的主要目的还是将数据该用新的wiredTiger引擎进行存储,更换存储引擎的方法可以类似MySQL中使用的数据格式升级的方法,即dump出和存储引擎无关的原始数据后更换存储引擎后再进行数据的导入即可完成数据格式的升级。

mongodump -o backup
#修改配置后
mongorestore backup

然而使用这种升级的方法需要在操作的过程中中断服务,适用于小数据量同时没有使用Replica Set复制的情况。而如果使用了Replica Set复制就可以类似使用上面提到的升级mongod版本的方法进行。首先停止一个SECONDARY的mongod,修改数据文件的目录和使用的存储引擎。

storage:
    journal:
        enabled: true
#    dbPath: "/opt/mongo/3.0/data1-mmapv1"
    dbPath: "/opt/mongo/3.0/data1-wiredTiger"
    directoryPerDB: true
#   engine: "mmapv1"
    engine: "wiredTiger"
    wiredTiger:
        engineConfig:
            cacheSizeGB: 1
            directoryForIndexes: true

重新启动后即开始自动恢复数据,恢复的数据将使用新的存储引擎,依次进行处理,处理到PRIMARY节点的时候使用上面类似的方法强制故障转移后进行处理即可。

3. 配置和使用

在使用MongoDB较老的版本时发现如果oplog已经不完整,则增加新的节点的恢复过程会失败,即新的节点数据为空,而在3.0从2.6版本恢复的过程中没有遇到这一问题,官方的文档说恢复的过程是新节点会从其他的节点查询数据进行恢复,似乎是不再依赖于完整的oplog,感觉也是一个非常大的改进。

MongoDB能够进行的配置比较有限,新版本开始使用yaml文件作为配置文件,不过依然可以兼容以前类似ini方式的配置文件,一个简单的配置文件如下

systemLog:
    destination: file
    path: "/opt/mongo/3.0/log/mongod.1.log"
    logAppend: true
storage:
    journal:
        enabled: true
    dbPath: "/opt/mongo/3.0/data1"
    directoryPerDB: true
    engine: "wiredTiger"
    wiredTiger:
        engineConfig:
            cacheSizeGB: 1
            directoryForIndexes: true
processManagement:
    fork: true
net:
    bindIp: 127.0.0.1
    port: 27017
setParameter:
    enableLocalhostAuthBypass: false
replication:
    replSetName: "upgrade"
    oplogSizeMB: 1

之前在使用上也遇到一个问题——在Replica Set模式下,所有的查询都会走PRIMARY,而SECONDARY几乎完全没有作用,根据官方文档的说法为了保证数据的强一致性,在没有特殊声明的情况下客户端驱动会默认从PRIMARY进行查询,可以在客户端中做相应的设定使得驱动支持将一些查询分配到SECONDARY。如PHP中的操作方式如下


<?php

$m = new MongoClient();

// Prefer the nearest server in the "east" data center also used for reporting,
// but fall back to a server in the "west" data center
$m->setReadPreference(MongoClient::RP_NEAREST, array(
    array('dc' => 'east', 'use' => 'reporting'),
    array('dc' => 'west'),
));
?>

RP_NEAREST文档中的解释是优先选用延迟最低的节点,然后在优先选用拥有对应tag值的节点。

使用FIS3管理静态文件

在使用了CDN服务后,在更新静态文件后,一般需要通过更改引用处文件的URL的方式来实现文件的更新,如果是手动处理会非常麻烦,容易遗漏同时反复上线代码效率也比较低下。在PHP代码中一般可以通过使用统一的接口引入静态文件,通过数据库管理引用的静态文件的版本,代码上线后修改变化的静态文件的版本即可。但如果引用在静态文件中,主要是css文件中引用图片的情况,这里显然不使用PHP文件中的那种处理方法。一般来看这种情况需要借助一些前端的工具来处理这类问题。

FIS3是由百度开发的一款前端工具,能够实现前端文件管理(加载和依赖处理),优化和部署等操作,主页地址为http://fis.baidu.com/。其目前已经成为了一个npm包,在安装了nodejs的情况下直接食用npm安装即可。

1. 安装和原理

FIS3的安装非常简单,先安装nodejs和npm,然后使用npm安装FIS3

pacman -S nodejs npm
npm install -g fis3

FIS3对于静态文件引用的处理是其会对原始的静态文件进行一个编译的过程,重新再生成一个新的目录作为实际Web服务器输出文件的目录,并且FIS3不会修改静态文件的源代码。

2. 基本使用

在项目的目录下增加配置文件fis-conf.js,进行如下配置,将全部的png文件替换为绝对路径,增加/static目录开头,同时增加CDN域名,配置多个会根据散列算法选择一个

fis.match('*.png', {
  release: '/static/$0',
  domain: ['http://cdn-domain1', 'http://cdn-domain2']
});

在项目目录下执行以下命令编译生成正式的文件,生成到上一级目录下的dist目录中

fis3 release -d ../dist

编译后文件的变化如下

@@ -16,9 +16,9 @@
 }

 li.list-1::before {
-  background-image: url('./img/list-1.png');
+  background-image: url('http://cdn-domain1/static/img/list-1.png');
 }

 li.list-2::before {
-  background-image: url('./img/list-2.png');
+  background-image: url('http://cdn-domain1/static/img/list-2.png');
 }

文件会被替换为绝对路径,并且增加了CDN域名。

3. FIS3将文件指纹放在query中

FIS3默认的文件指纹是将文件的md5校验值的一部分作为文件名的后缀重新生成一份对应的文件,然后引用生成后的文件。但总是把图片文件保存两份,感觉不是一个非常好的处理办法,感觉还是将文件指纹放在query中会比较好,如果要实现将文件指纹放在query中需要简单修改fis3的源代码,需要修改lib/file.js文件,diff如下

@@ -462,7 +462,8 @@
   getUrl: function() {
     var url = this.url;
     if (this.useHash) {
-      url = addHash(url, this);
+      //url = addHash(url, this);
+      this.query = '?' + this.getHash();
     }
     // if (this.useDomain) {
       if (!this.domain) {

在query中增加了文件指纹,增加文件指纹需要在配置中增加useHash选项,同时如果任何图片类型都要做处理则匹配规则使用::image即可

fis.match('::image', {
  useHash: true,
  release: '/static/$0',
  domain: ['http://cdn-domain1', 'http://cdn-domain2']
});

修改后再运行效果如下

@@ -16,9 +16,9 @@
 }

 li.list-1::before {
-  background-image: url('./img/list-1.png');
+  background-image: url('http://cdn-domain1/static/img/list-1.png?69af273');
 }

 li.list-2::before {
-  background-image: url('./img/list-2.png');
+  background-image: url('http://cdn-domain1/static/img/list-2.png?543c384');
 }