解决ubuntu16.04中mongodb远程连接不上的问题

系统版本:

  • lsb_release -a
  • No LSB modules are available.
  • Distributor ID: Ubuntu
  • Description: Ubuntu 16.04.2 LTS
  • Release: 16.04
  • Codename: xenial

MongoDB版本:

> db.version()
2.6.10

ps:使用yum安装的mongodb

1、查看网络端口情况,发现mongodb服务绑定中本地ip上

netstat -tunlp
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.1.1:53            0.0.0.0:*               LISTEN      -               
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -               
tcp        0      0 127.0.0.1:27017         0.0.0.0:*               LISTEN      -               
tcp6       0      0 :::22                   :::*                    LISTEN      -               
udp        0      0 0.0.0.0:60855           0.0.0.0:*                           -               
udp        0      0 127.0.1.1:53            0.0.0.0:*                           -               
udp        0      0 0.0.0.0:68              0.0.0.0:*                           -               
udp        0      0 0.0.0.0:631             0.0.0.0:*                           -               
udp        0      0 0.0.0.0:5353            0.0.0.0:*                           -               
udp6       0      0 :::44164                :::*                                -               
udp6       0      0 :::5353                 :::*                                -   

2、修改/etc/mongodb.con文件中mongodb绑定端口的ip,绑定到任何ip上:

bind_ip = 0.0.0.0

3、查看系统网络端口情况,mongodb服务监听的端口绑定中所有ip上,这样能够被内网的主机能够访问到

netstat -tunlp
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.1.1:53            0.0.0.0:*               LISTEN      -               
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -               
tcp        0      0 0.0.0.0:27017           0.0.0.0:*               LISTEN      -               
tcp6       0      0 :::22                   :::*                    LISTEN      -               
udp        0      0 0.0.0.0:60855           0.0.0.0:*                           -               
udp        0      0 127.0.1.1:53            0.0.0.0:*                           -               
udp        0      0 0.0.0.0:68              0.0.0.0:*                           -               
udp        0      0 0.0.0.0:631             0.0.0.0:*                           -               
udp        0      0 0.0.0.0:5353            0.0.0.0:*                           -               
udp6       0      0 :::44164                :::*                                -               
udp6       0      0 :::5353                 :::*                                -  

4、使用内网的另外一台ubuntu系统机子:

输入 curl 192.168.31.172:27017 ,返回It looks like you are trying to access MongoDB over HTTP on the native driver port.

说明连接成功

或者中浏览器地址栏中输入http://192.168.31.172:27017/,enter后,回复上面相同的内容。

Docker安装部署MongoDB的两种方法

方法一、通过 Dockerfile 构建

创建Dockerfile

首先,创建目录mongo,用于存放后面的相关东西。

runoob@runoob:~$ mkdir -p ~/mongo  ~/mongo/db

db目录将映射为mongo容器配置的/data/db目录,作为mongo数据的存储目录

进入创建的mongo目录,创建Dockerfile

FROM debian:wheezy

# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r mongodb && useradd -r -g mongodb mongodb

RUN apt-get update 
    && apt-get install -y --no-install-recommends 
        numactl 
    && rm -rf /var/lib/apt/lists/*

# grab gosu for easy step-down from root
ENV GOSU_VERSION 1.7
RUN set -x 
    && apt-get update && apt-get install -y --no-install-recommends ca-certificates wget && rm -rf /var/lib/apt/lists/* 
    && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture)" 
    && wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture).asc" 
    && export GNUPGHOME="$(mktemp -d)" 
    && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 
    && gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu 
    && rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc 
    && chmod +x /usr/local/bin/gosu 
    && gosu nobody true 
    && apt-get purge -y --auto-remove ca-certificates wget

# gpg: key 7F0CEB10: public key "Richard Kreuter <[email protected]>" imported
RUN apt-key adv --keyserver ha.pool.sks-keyservers.net --recv-keys 492EAFE8CD016A07919F1D2B9ECBEC467F0CEB10

ENV MONGO_MAJOR 3.0
ENV MONGO_VERSION 3.0.12

RUN echo "deb http://repo.mongodb.org/apt/debian wheezy/mongodb-org/$MONGO_MAJOR main" > /etc/apt/sources.list.d/mongodb-org.list

RUN set -x 
    && apt-get update 
    && apt-get install -y 
        mongodb-org=$MONGO_VERSION 
        mongodb-org-server=$MONGO_VERSION 
        mongodb-org-shell=$MONGO_VERSION 
        mongodb-org-mongos=$MONGO_VERSION 
        mongodb-org-tools=$MONGO_VERSION 
    && rm -rf /var/lib/apt/lists/* 
    && rm -rf /var/lib/mongodb 
    && mv /etc/mongod.conf /etc/mongod.conf.orig

RUN mkdir -p /data/db /data/configdb 
    && chown -R mongodb:mongodb /data/db /data/configdb
VOLUME /data/db /data/configdb

COPY docker-entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

EXPOSE 27017
CMD ["mongod"]

通过Dockerfile创建一个镜像,替换成你自己的名字

runoob@runoob:~/mongo$ docker build -t mongo:3.2 .

创建完成后,我们可以在本地的镜像列表里查找到刚刚创建的镜像

runoob@runoob:~/mongo$ docker images  mongo:3.2
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mongo               3.2                 282fd552add6        9 days ago          336.1 MB

方法二、docker pull mongo:3.2

查找Docker Hub上的mongo镜像

runoob@runoob:~/mongo$ docker search mongo
NAME                              DESCRIPTION                      STARS     OFFICIAL   AUTOMATED
mongo                             MongoDB document databases ...   1989      [OK]       
mongo-express                     Web-based MongoDB admin int...   22        [OK]       
mvertes/alpine-mongo              light MongoDB container          19                   [OK]
mongooseim/mongooseim-docker      MongooseIM server the lates...   9                    [OK]
torusware/speedus-mongo           Always updated official Mon...   9                    [OK]
jacksoncage/mongo                 Instant MongoDB sharded cluster  6                    [OK]
mongoclient/mongoclient           Official docker image for M...   4                    [OK]
jadsonlourenco/mongo-rocks        Percona Mongodb with Rocksd...   4                    [OK]
asteris/apache-php-mongo          Apache2.4 + PHP + Mongo + m...   2                    [OK]
19hz/mongo-container              Mongodb replicaset for coreos    1                    [OK]
nitra/mongo                       Mongo3 centos7                   1                    [OK]
ackee/mongo                       MongoDB with fixed Bluemix p...  1                    [OK]
kobotoolbox/mongo                 https://github.com/kobotoolb...  1                    [OK]
valtlfelipe/mongo                 Docker Image based on the la...  1                    [OK]

这里我们拉取官方的镜像,标签为3.2

runoob@runoob:~/mongo$ docker pull mongo:3.2

等待下载完成后,我们就可以在本地镜像列表里查到REPOSITORY为mongo,标签为3.2的镜像。

使用mongo镜像

运行容器

runoob@runoob:~/mongo$ docker run -p 27017:27017 -v $PWD/db:/data/db -d mongo:3.2
cda8830cad5fe35e9c4aed037bbd5434b69b19bf2075c8626911e6ebb08cad51
runoob@runoob:~/mongo$

命令说明:

-p 27017:27017 :将容器的27017 端口映射到主机的27017 端口

-v $PWD/db:/data/db :将主机中当前目录下的db挂载到容器的/data/db,作为mongo数据存储目录

查看容器启动情况

runoob@runoob:~/mongo$ docker ps 
CONTAINER ID   IMAGE        COMMAND                   ...    PORTS                      NAMES
cda8830cad5f   mongo:3.2    "/entrypoint.sh mongo"    ...    0.0.0.0:27017->27017/tcp   suspicious_goodall

使用mongo镜像执行mongo 命令连接到刚启动的容器,主机IP为172.17.0.1

runoob@runoob:~/mongo$ docker run -it mongo:3.2 mongo --host 172.17.0.1
MongoDB shell version: 3.2.7
connecting to: 172.17.0.1:27017/test
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
  http://docs.mongodb.org/
Questions? Try the support group
  http://groups.google.com/group/mongodb-user
>

CentOS 6.5使用yum安装MongoDB数据库

mongodb是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

安装MongoDB

1、创建repo

vi /etc/yum.repos.d/mongodb-org-3.2.repo
[mongodb-org-3.2]  
name=MongoDB Repository  
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.2/x86_64/  
gpgcheck=0  
enabled=1  

2、安装MongoDB和相关工具

sudo yum install -y mongodb-org

3、启动MongoDB

4、验证MongoDB是否启动成功

cat /var/log/mongodb/mongod.log

查看是否有一句:[initandlisten] waiting for connections on port
其中是在/etc/mongod.conf中配置的,默认情况下是27017端口。

还有另一种方式:

# sudo service mongod status  
Redirecting to /bin/systemctl status  mongod.service  
● mongod.service - High-performance, schema-free document-oriented database  
   Loaded: loaded (/usr/lib/systemd/system/mongod.service; disabled; vendor preset: disabled)  
   Active: active (running) since 一 2016-09-12 09:50:16 CST; 15s ago  
  Process: 8787 ExecStart=/usr/bin/mongod $OPTIONS run (code=exited, status=0/SUCCESS)  
 Main PID: 8842 (mongod)  
   CGroup: /system.slice/mongod.service  
           └─8842 /usr/bin/mongod --quiet -f /etc/mongod.conf run  

9月 12 09:50:13 192.168.1.155 systemd[1]: Starting High-performance, schema-free document-oriented database...  
9月 12 09:50:14 192.168.1.155 mongod[8787]: about to fork child process, waiting until server is ready for connections.  
9月 12 09:50:14 192.168.1.155 mongod[8787]: forked process: 8842  
9月 12 09:50:16 192.168.1.155 mongod[8787]: child process started successfully, parent exiting  
9月 12 09:50:16 192.168.1.155 systemd[1]: Started High-performance, schema-free document-oriented database. 

会看到“Active: active (running)”,说明正在运行。

没有运行的情况:

# sudo service mongod status  
Redirecting to /bin/systemctl status  mongod.service  
● mongod.service - High-performance, schema-free document-oriented database  
   Loaded: loaded (/usr/lib/systemd/system/mongod.service; disabled; vendor preset: disabled)  
   Active: inactive (dead)  

Active: inactive (dead)

5、使MongoDB开机自动启动

sudo chkconfig mongod on

6、停止MongoDB

sudo service mongod stop

7、重启MongoDB

sudo service mongod restart

进入Mongo命令行

# mongo  
MongoDB shell version: 2.6.12  
connecting to: test  
Welcome to the MongoDB shell.  
For interactive help, type "help".  
For more comprehensive documentation, see  
    http://docs.mongodb.org/  
Questions? Try the support group  
    http://groups.google.com/group/mongodb-user  
Server has startup warnings:   
2016-09-12T09:50:14.195+0800 [initandlisten]   
2016-09-12T09:50:14.195+0800 [initandlisten] ** WARNING: Readahead for /var/lib/mongodb is set to 4096KB  
2016-09-12T09:50:14.195+0800 [initandlisten] **          We suggest setting it to 256KB (512 sectors) or less  
2016-09-12T09:50:14.195+0800 [initandlisten] **          http://dochub.mongodb.org/core/readahead  
>   

Linux下安装PHP的MongoDB扩展

因为是手动安装的MongoDB,所以也需要编译安装MongoDB扩展,步骤如下:

1、下载最新的PHP mongodb扩展源码,源码可以在http://pecl.php.net/package/mongo下载.

2、解压,进入安装目录

wget http://pecl.php.net/get/mongo-1.4.0.tgz 
tar -zxvf mongo-1.4.0.tgz
cd mongo-1.4.0   

3、进入文件夹后,首先运行phpize来编译扩展的环境

/usr/bin/phpize
PHP Api Version: 20121113
Zend Module Api No: 20121212
Zend Extension Api No: 220121212

4、运行后,我们运行./configure脚本来进行配置

./configure --with-php-config=/usr/local/php/bin/php-config && make && make install

–with-php-config 这个参数是告诉配置脚本 php-config 这个程序的路径

5、完成后,请编辑你php.ini文件增加一行extension=mongo.so`

一般默认的编译php的ini文件/usr/local/php/etc/php.ini重启Apache/Nginx[或者/etc/init.d/php-fpm restart] 打开 phpinfo看到mongo模块,证明mongodb的php扩展安装成功。

OK ,至此你可以使用php来操作 MongoDB 了。

MongoDB索引的创建和使用详解

索引通常能够极大的提高查询的效率。在系统中使用查询时,应该考虑建立相关的索引。在MongoDB中创建索引相对比较容易。

mongodb中的索引在概念上和大多数关系型数据库如MySQL是一样的。当你在某种情况下需要在MySQL中建立索引,这样的情景同样适合于MongoDB。

基本操作

索引是一种数据结构,他搜集一个集合中文档特定字段的值。MongoDB的查询优化器能够使用这种数据结构来快速的对集合(collection)中的文档(collection)进行寻找和排序.准确来说,这些索引是通过B-Tree索引来实现的。

在命令行中,可以通过调用ensureIndex()函数来建立索引,该函数指定一个到多个需要索引的字段。沿用在前面的随笔中的例子,我们再things集合中的j字段上建立索引:

> db.things.ensureIndex({j:1}) 

EnsureIndex()函数自是在索引不存在的情况下才会创建。

一旦集合在某一个字段上建立索引后,对该字段的随机查询的访问速度会很快。如果没有索引,MongoDB会在遍历所有的键值对,然后去对应检查相关的字段。

> db.things.find({j:2}); //在建立了索引的字段上查询,速度快  

{ "_id" : ObjectId("4e24433dcac1e3490b9033be"), "x" : 4, "j" : 2 }  

> db.things.find({x:3});//在未建立索引的字段上查询,需要逐个字段匹配,速度慢  

{ "_id" : ObjectId("4e244315cac1e3490b9033bc"), "x" : 3 } 

通过在命令行中输入getIndexs()能够查看当前集合中的所有索引。

> db.things.getIndexes()  

[  

{  

"name" : "_id_",  

"ns" : "things.things",  

"key" : {  

"_id" : 1  

},  

"v" : 0  

},  

{  

"_id" : ObjectId("4e244382cac1e3490b9033d0  

"ns" : "things.things",  

"key" : {  

"j" : 1  

},  

"name" : "j_1",  

"v" : 0  

}  

] 

通过db.system.indexes.find()能够返回当前数据库中的所有索引

> db.system.indexes.find()  

{ "name" : "_id_", "ns" : "things.things", "key" : { "_id" : 1 }, "v" : 0 }  

{ "_id" : ObjectId("4e244382cac1e3490b9033d0"), "ns" : "things.things", "key" :{ "j" : 1 }, "name" : "j_1", "v" : 0 } 

默认索引

对于每一个集合(除了capped集合),默认会在_id字段上创建索引,而且这个特别的索引不能删除。_id字段是强制唯一的,由数据库维护。

嵌套关键字

在MongoDB中,甚至能够在一个嵌入的文档上(embedded)建立索引.

> db.things.ensureIndex({"address.city":1}) 

文档作为索引

任何类型,包括文档(document)都能作为索引:

> db.factories.insert({name:"xyz",metro:{city:"New York",state:"NY"}});  

> db.factories.ensureIndex({metro:1});  

> db.factories.find({metro:{city:"New York",state:"NY"}});//能够利用索引进行查询  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : < 

{ "city" : "New York", "state" : "NY" } }  

> db.factories.find({metro:{$gte:{city:"New York"}}});//能够利用索引进行查询  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : { "city" : "New York", "state" : "NY" } }  

> db.factories.find({metro:{state:"NY",city:"New York"}})//不能够返回结果,字段的顺序不对 

创建文档索引的一个替代方法是创建复合索引,例如:

> db.factories.ensureIndex({"metro.city":1,"metro.state":1})  

> db.factories.find({"metro.city":"New York","metro.state":"NY"});  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : { "city" : "New York", "state" : "NY" } }  

> db.factories.find({"metro.city":"New York"});  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : { "city" : "New York", "state" : "NY" } }  

> db.factories.find().sort({"metro.city":1,"New York":1});  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : { "city" : "New York", "state" : "NY" } }  

> db.factories.find().sort({"metro.city":1});  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : { "city" : "New York", "state" : "NY" } } 

组合关键字索引

除了基本的以单个关键字作为索引外,MongoDB也支持多个关键字的组合索引,和基本的索引一样,也是用ensureIndex()函数,该函数可以指定多个键。

> db.things.ensureIndex({j:1,name:-1}) 

当创建索引时,键后面的数字表明了索引的方向,取值为1或者-1,1表示升序,-1表示降序。升序或者降序在随机访问的时候关系不大,当时在做排序或者范围查询的时候就很重要了。

如果在建立了a,b,c这样一个复合索引,那么你可以在a,A,b和a,b,c上使用索引查询。

稀疏索引

和稀疏矩阵类似,稀疏索引就是索引至包含被索引字段的文档。

任何一个稀疏的缺失某一个字段的文档将不会存储在索引中,之所以称之为稀疏索引就是说缺失字段的文档的值会丢失。

稀疏索引的创建和完全索引的创建没有什么不同。使用稀疏索引进行查询的时候,某些由于缺失了字段的文档记录可能不会被返回,这是由于稀疏索引子返回被索引了的字段。可能比较难以理解,不过看几个例子就好理解了。

> db.people.ensureIndex({title:1},{sparse:true}) //在title字段上建立稀疏索引  

> db.people.save({name:"Jim"})  

> db.people.save({name:"yang",title:"prince"})  

> db.people.find();  

{ "_id" : ObjectId("4e244dc5cac1e3490b9033d7"), "name" : "Jim" }  

{ "_id" : ObjectId("4e244debcac1e3490b9033d8"), "name" : "yang", "title" : "prince" }  

> db.people.find().sort({title:1})//自有包含有索引字段的记录才被返回  

{ "_id" : ObjectId("4e244debcac1e3490b9033d8"), "name" : "yang", "title" : "prince" }  

> db.people.dropIndex({title:1})//删除稀疏索引之后,所有的记录均显示  

{ "nIndexesWas" : 2, "ok" : 1 }  

> db.people.find().sort({title:1})  

{ "_id" : ObjectId("4e244dc5cac1e3490b9033d7"), "name" : "Jim" }  

{ "_id" : ObjectId("4e244debcac1e3490b9033d8"), "name" : "yang", "title" : "prince" } 

唯一索引

MongoDB支持唯一索引,这使得不能插入在唯一索引项上已经存在的记录。例如,要保证firstname和lastname都是唯一的,命令如下

> db.things.ensureIndex({firstname:1,lastname:1},{unique:true}) 

缺失的键

当一个文档以唯一索引的方式保存到集合中去的时候,任何缺失的索引字段都会一null值代替,因此,不能在唯一索引上同时插入两条缺省的记录。如下:

>db.things.ensureIndex({firstname: 1}, {unique: true});  

>db.things.save({lastname: "Smith"});  

>db.things.save({lastname: "Jones"});// 会产生错误,因为firstname会有两个null. 

重复值

唯一索引不能够创建在具有重复值的键上,如果你一定要在这样的键上创建,那么想系统将保存第一条记录,剩下的记录会被删除,只需要在创建索引的时候加上dropDups这个可选项即可

>db.things.ensureIndex({firstname : 1}, {unique : true, dropDups : true})  

Dropping Indexes 

删除一个特定集合上的索引:

>db.collection.dropIndexes(); 

删除集合中的某一个索引:

db.collection.dropIndex({x: 1, y: -1}) 

也可以直接执行命令进性删除

db.runCommand({dropIndexes:‘foo‘, index : {y:1}})//删除集合foo中{y:1}的索引  

// remove all indexes:  

db.runCommand({dropIndexes:‘foo‘, index : ‘*‘})//删除集合foo中所有的索引 

重建索引

可以所用如下命令重建索引:

db.myCollection.reIndex()  

// same as:  

db.runCommand( { reIndex : ‘myCollection‘ } ) 

通常这是不必要的,但是在集合的大小变动很大及集合在磁盘空间上占用很多空间时重建索引才有用。对于大数据量的集合来说,重建索引可能会很慢。

注:

MongoDB中索引是大小写敏感的。

当更新对象是,只有在索引上的这些key发生变化时才会更新。着极大地提高了性能。当对象增长了或者必须移动时,所有的索引必须更新,这回很慢 。

索引信息会保存在system.indexes 集合中,运行 db.system.indexes.find() 能够看到这些示例数据。

索引的字段的大小有最大限制,目前接近800 bytes. 可在大于这个值的字段上建立索引是可以的,但是该字段不会被索引,这种限制在以后的版本中可能被移除。

索引的性能

索引使得可以通过关键字段获取数据,能够使得快速查询和更新数据。

但是,必须注意的是,索引也会在插入和删除的时候增加一些系统的负担。往集合中插入数据的时候,索引的字段必须加入到B-Tree中去,因此,索引适合建立在读远多于写的数据集上,对于写入频繁的集合,在某些情况下,索引反而有副作用。不过大多数集合都是读频繁的集合,所以集合在大多数情况下是有用的。

使用sort()而不需要索引

如果数据集合比较小(通常小于4M),使用sort()而不需要建立索引就能够返回数据。在这种情况下,做好联合使用limit()和sort()。

MongoDB导出场景查询优化

引言

前段时间遇到一个类似导出数据场景,观察下来发现速度会越来越慢,导出100万数据需要耗费40-60分钟,从日志观察发现,耗时也是越来越高。

原因

从代码逻辑上看,这里采取了分批次导出的方式,类似前端的分页,具体是通过skip+limit的方式实现的,那么采用这种方式会有什么问题呢?我们google一下这两个接口的文档:

The cursor.skip() method is often expensive because it requires the server to walk from the beginning of the collection or index to get the offset or skip position before beginning to return results. As the offset (e.g. pageNumber above) increases, cursor.skip() will become slower and more CPU intensive. With larger collections, cursor.skip() may become IO bound.

简单来说,随着页数的增长,skip()会变得越来越慢,但是具体就我们这里导出的场景来说,按理说应该没必要每次都去重复计算,做一些无用功,我的理解应该可以拿到一个指针,慢慢遍历,简单google之后,我们发现果然是可以这样做的。

我们可以在持久层新增一个方法,返回一个cursor专门供上层去遍历数据,这样就不用再去遍历已经导出过的结果集,从O(N2)优化到了O(N),这里还可以指定一个batchSize,设置一次从MongoDB中抓取的数据量(元素个数),注意这里最大是4M.

/**
     * <p>Limits the number of elements returned in one batch. A cursor 
     * typically fetches a batch of result objects and store them
     * locally.</p>
     *
     * <p>If {@code batchSize} is positive, it represents the size of each batch of objects retrieved. It can be adjusted to optimize
     * performance and limit data transfer.</p>
     *
     * <p>If {@code batchSize} is negative, it will limit of number objects returned, that fit within the max batch size limit (usually
     * 4MB), and cursor will be closed. For example if {@code batchSize} is -10, then the server will return a maximum of 10 documents and
     * as many as can fit in 4MB, then close the cursor. Note that this feature is different from limit() in that documents must fit within
     * a maximum size, and it removes the need to send a request to close the cursor server-side.</p>
*/

比如说我这里配置的8000,那么mongo客户端就会去默认抓取这么多的数据量:

未分类

经过本地简单的测试,我们发现性能已经有了飞跃的提升,导出30万数据,采用之前的方式,翻页到后面平均要500ms,总耗时60039ms。而优化后的方式,平均耗时在100ms-200ms之间,总耗时16667ms(中间包括业务逻辑的耗时)。

使用

DBCursor cursor = collection.find(query).batchSize(8000);
while (dbCursor.hasNext()) {
  DBObject nextItem = dbCursor.next();
  //业务代码
  ... 
  //
}

那么我们再看看hasNext内部的逻辑好吗?好的.

    @Override
    public boolean hasNext() {
        if (closed) {
            throw new IllegalStateException("Cursor has been closed");
        }

        if (nextBatch != null) {
            return true;
        }

        if (limitReached()) {
            return false;
        }

        while (serverCursor != null) {
            //这里会向mongo发送一条指令去抓取数据
            getMore();
            if (nextBatch != null) {
                return true;
            }
        }

        return false;
    }


    private void getMore() {
        Connection connection = connectionSource.getConnection();
        try {
            if(serverIsAtLeastVersionThreeDotTwo(connection.getDescription()){
                try {
//可以看到这里其实是调用了`nextBatch`指令        
initFromCommandResult(connection.command(namespace.getDatabaseName(),
                                                             asGetMoreCommandDocument(),
                                                             false,
                                                             new NoOpFieldNameValidator(),
                                                             CommandResultDocumentCodec.create(decoder, "nextBatch")));
                } catch (MongoCommandException e) {
                    throw translateCommandException(e, serverCursor);
                }
            } else {
                initFromQueryResult(connection.getMore(namespace, serverCursor.getId(),
                                                       getNumberToReturn(limit, batchSize, count),
                                                       decoder));
            }
            if (limitReached()) {
                killCursor(connection);
            }
        } finally {
            connection.release();
        }
    }

最后initFromCommandResult 拿到结果并解析成Bson对象

总结

我们平常写代码的时候,最好都能够针对每个方法、接口甚至是更细的粒度加上埋点,也可以设置成debug级别,这样利用log4j/logback等日志框架动态更新级别,可以随时查看耗时,从而更能够针对性的优化,对于本文说的这个场景,我们首先看看是不是代码的逻辑有问题,然后看看是不是数据库的问题,比如说没建索引、数据量过大等,再去想办法针对性的优化,而不要上来就撸代码。

MongoDB安全介绍及配置身份认证

概述

MongoDB安全主要包括以下4个方面

1、物理隔离

系统不论设计的多么完善,在实施过程中,总会存在一些漏洞。如果能够把不安全的使用方与MongoDB数据库做物理上的隔离,即通过任何手段都不能连接到数据库,这是最安全的防护。但,通常这是不现实的。一些重要的数据可能会保存下来,放置到物理隔离的机房中

2、网络隔离

许多公司的开发机处于内网环境中。即使数据库存在漏洞,外部环境也没有机会利用,因为根本无法访问内网

3、防火墙隔离

可以利用防火墙配置IP白名单,只允许某些IP访问数据库,也可以从一定程度上增加MongoDB的安全性

4、用户名密码鉴权

相对于以上3种方式,用户名密码鉴权机制是最常见的MongoDB安全措施。如果密码设置的比较简单,或者连接环境不是加密环境,很可能被第三方获取到用户名和密码,从而造成MongoDB数据库的危险

权限认证

mongodb存储所有的用户信息在admin数据库的集合system.users中,保存用户名、密码和数据库信息。mongodb默认不启用权限认证,只要能连接到该服务器,就可连接到mongod。若要启用安全认证,需要更改配置文件参数authorization,也可以简写为auth。

未分类
  
然后,重启mongod。查看日志文件,发现权限认证已经开启

未分类
 
但是,不使用用户名和密码依然可以连接到数据库。这是因为,我们还没有创建用户。在用户创建,并且开启权限认证之后,如果不使用用户名和密码将不能够连接到数据库

角色管理

在进行用户管理之前,首先要先了解角色管理

MongoDB支持基于角色的访问控制(RBAC)来管理对MongoDB系统的访问。一个用户可以被授权一个或者多个:ref:角色 以决定该用户对数据库资源和操作的访问权限。在权限以外,用户是无法访问系统的

数据库角色在创建用户中的role参数中设置。角色分为内建角色和自定义角色

内建角色

MongoDB内建角色包括以下几类

1、数据库用户角色

read:允许用户读取指定数据库
readWrite:允许用户读写指定数据库

2、数据库管理员角色

dbAdmin:允许用户进行索引创建、删除,查看统计或访问system.profile,但没有角色和用户管理的权限
userAdmin:提供了在当前数据库中创建和修改角色和用户的能力
dbOwner: 提供对数据库执行任何管理操作的能力。这个角色组合了readWrite、dbAdmin和userAdmin角色授予的特权。

  
3、集群管理角色

clusterAdmin : 提供最强大的集群管理访问。组合clusterManager、clusterMonitor和hostManager角色的能力。还提供了dropDatabase操作
clusterManager : 在集群上提供管理和监视操作。可以访问配置和本地数据库,这些数据库分别用于分片和复制
clusterMonitor : 提供对监控工具的只读访问,例如MongoDB云管理器和Ops管理器监控代理。
hostManager : 提供监视和管理服务器的能力。

  
4、备份恢复角色

backup : 提供备份数据所需的能力,使用MongoDB云管理器备份代理、Ops管理器备份代理或使用mongodump
restore : 提供使用mongorestore恢复数据所需的能力

  
5、所有数据库角色

readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限 
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限 
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限 
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。 

  
6、超级用户角色

root:提供对readWriteAnyDatabase、dbAdminAnyDatabase、userAdminAnyDatabase、clusterAdmin、restore和backup的所有资源的访问

  
7、内部角色

__system : 提供对数据库中任何对象的任何操作的特权

自定义角色

  • 除了使用内建的角色之外,MongoDB还支持使用db.createRole()方法来自定义角色

  • [注意]只能在admin数据库中创建角色,否则会失败

  • role: 自定义角色的名称

  • privileges: 权限操作 

  • roles:继承的角色。如果没有继承的角色,可以设置为空数组  

use admin
db.createRole(
   {
     role: "myClusterwideAdmin",
     privileges: [
       { resource: { cluster: true }, actions: [ "addShard" ] },
       { resource: { db: "config", collection: "" }, actions: [ "find", "update", "insert", "remove" ] },
       { resource: { db: "users", collection: "usersCollection" }, actions: [ "update", "insert", "remove" ] },
       { resource: { db: "", collection: "" }, actions: [ "find" ] }
     ],
     roles: [
       { role: "read", db: "admin" }
     ]
   },
   { w: "majority" , wtimeout: 5000 }
)

未分类

用户管理

创建用户

  • 使用createUser命令来创建用户

  • user: 用户名 pwd: 密码

  • customData: 对用户名密码的说明(可选项)

  • roles: {role:继承自什么角色类型,db:数据库名称}

db.createUser({user: "...",pwd: "...",customDate:"...",roles:[{role: "...",db: "..."}]})

1、创建管理员用户

MongoDB没有默认管理员账号,所以要先添加管理员账号。切换到admin数据库,添加的账号才是管理员账号

在admin数据库中,添加一个用户并赋予userAdminAnyDatabase角色

db.createUser({user: "admin",pwd: "123456",roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]})

  
未分类

2、重新登录数据库,并验证权限

如果auth()方法返回0则代表授权失败,返回1代表授权成功

db.auth()

未分类
  
3、添加普通用户

一旦经过认证的用户管理员,可以使用db.createUser()去创建额外的用户。 可以分配mongodb内置的角色或用户自定义的角色给用户

未分类

[注意]需要在admin数据库下进行认证,否则认证不成功

未分类

由于该用户只有读权限,所以会写入数据失败

未分类

4、创建超级用户

未分类

查看用户

db.system.users.find()

未分类

删除用户

db.dropUser()

未分类

添加用户权限

db.grantRolesToUser()

  
给在db1数据库中只读的x用户,添加写权限

未分类

修改密码

db.changeUserPassword()

未分类

Mongodb索引详解

1、参考

MongoDB权威指南(第2版) (https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00HLX035Q/ref=sr_1_1?ie=UTF8&qid=1501597098&sr=8-1&keywords=mongodb)

Mongodb Docs (https://docs.mongodb.com/manual/)

2、前言

建立索引对于任何需要提高查询速度的数据库来说都非常重要,那么索引究竟是一个什么?首先来看看下面是《区块链:技术驱动金融》 (https://www.amazon.cn/dp/B01KGYHBEM/ref=cngwdyfloorv2_recs_0?pf_rd_p=05f2b7d6-37ec-49bf-8fcf-5d2fec23a061&pf_rd_s=desktop-2&pf_rd_t=36701&pf_rd_i=desktop&pf_rd_m=A1AJ19PSB66TGU&pf_rd_r=16P2J6RAT3GC8PN1T4BJ&pf_rd_r=16P2J6RAT3GC8PN1T4BJ&pf_rd_p=05f2b7d6-37ec-49bf-8fcf-5d2fec23a061)这本书的前两章的目录。

第1章密码学及加密货币概述----------1
1.1密码学哈希函数----------4
1.2哈希指针及数据结构----------14
1.3数字签名----------19
1.4公钥即身份----------24
1.5两种简单的加密货币----------26
第2章比特币如何做到去中心化----------35
2.1中心化与去中心化----------37
2.2分布式共识----------39
2.3使用区块链达成没有身份的共识----------44
2.4奖励机制与工作量证明----------51
2.5总结----------59

通过目录,我们能很快很清楚的知道这本书写了什么,而我们也能很快从中查找到我们感兴趣的内容在哪一页,如果没有目录,我们将会一篇一篇的去翻阅我们想了解的内容,而索引可以比作数据库的目录。

3、效率

explain()是官方提供的一个用于返回当前查询过程信息的一个方法,通过这个命令,我们可以知道查询的过程,以便于我们进行优化,explain()支持这些操作的过程查询:

  • aggregate()
  • count()
  • distinct()
  • find()
  • group()
  • remove()
  • update()

explain()方法接受三种可选字符串作为参数和两种布尔值,官方是这样来介绍的:

  • 可选的。指定说明输出的详细程度模式。该模式会影响explain()返回信息的数量和行为。可能的模式有:”queryPlanner”, “executionStats”,和”allPlansExecution”。

  • 默认模式是”queryPlanner”。

  • 为了向后兼容早期版本 cursor.explain(),MongoDB解释true为 “allPlansExecution”和false “queryPlanner”。

  • aggregate()忽略verbosity参数并在queryPlanner模式下执行。

首先我们插入100000条数据,可以通过下面代码来循环插入:

for(var i = 0; i < 100000; i++) {
  db.test.insert({
    id: i,
    username: 'user' + i
  });
}

然后我们通过explain()方法看看查询含有username:user108键值的文档过程:

db.test.find({username: 'user108'}).explain(true);

返回的结果大概为:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "blog.test",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "username" : {
                "$eq" : "user4"
            }
        },
        "winningPlan" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "username" : {
                    "$eq" : "user4"
                }
            },
            "direction" : "forward"
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 48,
        "totalKeysExamined" : 0,
        "totalDocsExamined" : 100000,
        "executionStages" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "username" : {
                    "$eq" : "user4"
                }
            },
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 37,
            "works" : 100002,
            "advanced" : 1,
            "needTime" : 100000,
            "needYield" : 0,
            "saveState" : 782,
            "restoreState" : 782,
            "isEOF" : 1,
            "invalidates" : 0,
            "direction" : "forward",
            "docsExamined" : 100000
        },
        "allPlansExecution" : [ ]
    },
    "serverInfo" : {
        "host" : "YizhoudeMacBook-Pro.local",
        "port" : 27017,
        "version" : "3.4.6",
        "gitVersion" : "c55eb86ef46ee7aede3b1e2a5d184a7df4bfb5b5"
    },
    "ok" : 1
}

在上面返回的过程中,我们只需要关注executionStats对象里面的值中的:

  • totalDocsExamined: 文档扫描总数
  • executionTimeMillis: 执行时间(毫秒)
  • nReturned: 返回的文档数量

一般通过这三个值就可以判断文档执行需不需要优化,比如上面返回的信息中表示这次查询文档扫描总数100000,执行时间48毫秒,返回的文档数量为1。

通过上面的返回信息其实我们可以看出,find()方法其实是扫描了整个集合来查询我们需要的,即使找到了我们需要的文档,但还会继续往下查询,因为find()方法是查询所有符合条件的文档,这里其实是浪费了服务器的资源。

我们已知username是唯一值,那么我们可以findOne,或者通过limit(1)来限制返回的数量,限制了返回的数量后,Mongodb内部查询是查询到符合的第一条文档就停止,比如我们要查询user5,那么查找的文档总数将是查询到第一条符合的文档数止到第一条文档的总和,这样看上去是没有问题了,但是一旦我们查询的是user99995,那么扫描的文档数量将是99995,这样的方式属于治标不治本。

4、建立索引

Mongodb建立索引是通过createIndex()方法建立,参数为一个对象,包含了需要建立索引的键:

db.test.createIndex({username: 1});

建立的索引其实相当于保存在Mongodb内部的一个单独的文档,这个文档存放了所有username的值和对应储存的物理位置,大概结构为(下面数据是比喻,并不代表真实性):

username Index

['user1', 0x00000001],
['user2', 0x00000002],
['user3', 0x00000003],
['user4', 0x00000004],
['user5', 0x00000005],
........
['user100000', 0x00100000]

5、索引选项

db.collection.createIndex(key,options);

我们可以通过多个选项对索引的文档进行限制,其中很有用的一个就是unique,使用方法如下:

db.test.createIndex({username: 1},{unique: 1});

通过上面的设置username键的值在整个集合中必须是唯一的,如果你试图插入两个username键值相等的文档,那么将会报错,比如下面这样:

db.test.createIndex({username: 1},{unique: 1});

db.test.insert({username: 'user200'});

db.test.insert({username: 'user200'});
----Error!!

它同样适用于复合索引,在对复合索引使用唯一值选项后,如果你试图插入两个及以上索引键的值都一样的两个文档,那么将报错,如果是两个文档的其中一个值不一样同样可以插入,比如下面代码所示:

db.test.createIndex({username: 1, age: 1},{unique: 1});

db.test.insert({username: 'user200', age:18});

db.test.insert({username: 'user200', age:20});

以上两个方式都是可以正常插入的,因为插入的这两个文档中的age不一样,但是如果像下面这样就会报错了:

db.test.createIndex({username: 1, age: 1},{unique: 1});

db.test.insert({username: 'user200', age:18});

db.test.insert({username: 'user200', age:18});
----Error!!

当然除了唯一值选项的设置之外,还有很多选项的设置,如果大家有兴趣可以到官方文档查看。

6、使用索引

建立了索引后,我们不需要特别的方式去查询,我们可以像普通的查询方式一样的去查询:

db.test.find({username: 'user9999'});

在查询的时候Mongodb会自动的查找我们是否为username键建立索引,如果有则扫描username的索引文档,找到相应的值后,然后在根据相应的物理地址去扫描对应的文档,如果没有则扫描test集合的所有文档。

建立了索引后,我们来看看查找username: ‘user9999’键值对的文档需要的时间以及查询上的效率:

db.test.find({username: 'user9999'}).explain(true);

返回的过程信息(只拿出executionStats属性):

"executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 0,
        "totalKeysExamined" : 1,
        "totalDocsExamined" : 1,
        "executionStages" : {
            "stage" : "FETCH",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            "works" : 2,
            "advanced" : 1,
            "needTime" : 0,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 1,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 1,
                "executionTimeMillisEstimate" : 0,
                "works" : 2,
                "advanced" : 1,
                "needTime" : 0,
                "needYield" : 0,
                "saveState" : 0,
                "restoreState" : 0,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "username" : 1
                },
                "indexName" : "username_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "username" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "username" : [
                        "["user9999", "user9999"]"
                    ]
                },
                "keysExamined" : 1,
                "seeks" : 1,
                "dupsTested" : 0,
                "dupsDropped" : 0,
                "seenInvalidated" : 0
            }
        },
        "allPlansExecution" : [ ]
    }

通过上面的返回信息中,我们可以看到

  • totalDocsExamined: 1 //扫描的文档数量为1
  • executionTimeMillis: 0 //执行的时间小于0毫秒
  • nReturned: 1 //返回的文档数为1

7、复合索引

符合索引指的是一个索引文档中存在两个值及以上的值,比如下面这样:

db.test.createIndex({username: 1, id: 1});

通过上面的语句可以创建符合索引,当我们每次查询这两个键的时候,都会从索引文档中查询,如果你经常会以两个键值对查询文档,那么符合索引非常适合你。

复合索引的第一个值可以单独查询,但是第二个值无法单独查询,什么意思呢,你可以理解成一个索引文档以第一个索引键命名,这样就很清楚了,当我们单独查询username的时候会找到这个索引文档,但是当我们单独查询id的时候无法找到这个索引文档。

那么根据上面的理论可以得出,只要第一个值匹配到索引文档,那么在复合索引中不管你跟的是复合索引中添加的哪个值都可以进行索引,比如下面的代码都可以进行复合索引:

db.test.createIndex({username: 1, id: 1, age: 1, adreess: 1});

db.test.find({username: 'user400'});

db.test.find({username: 'user400', age: 18});

db.test.find({username: 'user400', adreess: 'xxxx'});

8、对象索引

Mongodb可以支持对象索引,比如下面这样:

var a = {
    b: 1,
    c: 2
}

db.test.createIndex({a: 1});

或者对某个子键索引

db.test.createIndex({a.b: 1});

需要注意的是上面两种方法的索引效果截然不同,第一个建立索引是建立一个对象索引,对象中的所有值都会提高查询效率。而第二个建立的索引是建立一个子键索引,只对子键提高查询效率。

9、数组索引

Mongodb支持对数组索引,比如像下面这样:

var a = [
    {
        b: 1,
        c: 2
    },
    {
        b: 3,
        c: 4
    }
];

db.test.createIndex({a: 1});

或者对某个子键索引

db.test.createIndex({a.b: 1});

上面两种方式也是不同的,第一种是建立一个数组索引,并且Mongodb会对数组的每一个成员建立索引,这于对象是不一样的。而第二种是建立一个数组子成员的子键索引。

Mongodb只允许复合索引中出现一个数组,如果出现了一个以上的数组将是非法的,应当尽可能的不去使用整个数组索引。

10、获取索引

我们可以通过getIndexes()方法来获取当前集合所建立的所有索引信息,默认会返回一个_id索引,这个索引是Mongodb自动建立的。

getIndexes()使用方法:

db.test.getIndexes();

返回的大概信息:

[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "blog.test"
    },
    {
        "v" : 2,
        "key" : {
            "username" : 1
        },
        "name" : "username_1",
        "ns" : "blog.test"
    }
]

返回的信息当中,各个键值表示的是:

  • v:表示索引版本
  • key: 表示索引的键,值为表示正序倒序 1 或者 -1
  • name: 索引的标识符
  • ns: 作用于的集合

11、删除索引

Mongodb提供了dropIndex()来删除索引,它接受一个字符串参数,这个参数是索引的name值。删除索引可以根据getIndexes()方法查询到的name的值来删除,比如:

db.test.dropIndex('username_1');

12、注意事项

索引建立后,每次添加、修改、更新、删除数据,Mongodb都会更新索引文档,这也带来了一个问题,就是每当我们操作数据的时候,会比以前慢一点,因为操作数据的同时,Mongodb还会自动更新索引文档,为了不影响效率,一个集合最多只能存在64个索引。

开启MongoDB数据库密码验证

昨天我的mongodb被黑了,早先我就疑惑过怎么这东西开启服务就能用了,后来找了各方资料,直到MongoDB默认是没有鉴权的,出于侥幸心理和懒惰和忙…知道昨天我的数据库被黑了,黑客删光了我的数据,只留下了一条要我交0.2比特币来赎回我的数据的信息(如下图),我饮恨的同时,哭着给我的MongoDB加上了鉴权。

未分类

在创建用户之前,还得改变MongoDB的启动方式,在末尾加上–auth,如:mongod –dbpath ../db –auth

用户属性

要为MongoDB增加鉴权,首先需要创建用户,通过用户名密码及相应权限来告诉MongoDB我是否能动你,所以每个用户有三个属性,如下:

{
  "user":"username",
  "pwd":"password",
  "roles": ["readWrite"]
}

创建用户管理员

当然,在为你数据库创建用户之前,你需要创建用户管理员,用户管理员只能在admin表创建,roles为userAdminAnyDatabase,步骤如下:

> use admin
switched to db admin
> db.createUser({user:"admin",pwd:"123456",roles:["userAdminAnyDatabase"]})
Successfully added user: { "user" : "admin", "roles" : [ "userAdminAnyDatabase" ] }
> db.auth("admin","123456")     //立即使用此账号登录
1      //返回1即为成功

创建数据库用户

使用管理员登入,切换到你的数据库,如果还不存在,MongoDB为自动为你创建:

> use imageDB       //切换至imageDB库,不存在则创建imageDB库
switched to db imageDB
> db.createUser({user:"user",pwd:"123456",roles:["readWrite"]})      //为imageDB创建用户,并赋予读写权限
Successfully added user: { "user" : "user", "roles" : [ "readWrite" ] }
> db.auth("user","123456")     //使用刚创建的用户登录
1   //返回1即为成功

连接数据库(mongoose为例)

带鉴权的连接,文档 http://mongoosejs.com/docs/connections.html

mongoose.connect('mongodb://username:password@host:port/database?options...');
var mongoose = require('mongoose'),
    DB_URL = 'mongodb://localhost:27017/image-uploader',

    DB_URL_AUTH = 'mongodb://YOURUSERNAME:YOURPASSWORD@localhost:27017/image-uploader';//鉴权链接

mongoose.connect(DB_URL_AUTH);

/**
  * 连接成功
  */
mongoose.connection.on('connected', function () {    
    console.log('Mongoose connection open to ' + DB_URL);  
});    

/**
 * 连接异常
 */
mongoose.connection.on('error',function (err) {    
    console.log('Mongoose connection error: ' + err);  
});    

/**
 * 连接断开
 */
mongoose.connection.on('disconnected', function () {    
    console.log('Mongoose connection disconnected');  
});    

module.exports = mongoose;

至此,再也不怕数据库被黑了。

使用Nodejs对Mongodb简单的增删改查

首先电脑上要装有Node、Mongodb

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

/*创建数据库连接*/
var db = mongoose.createConnection('localhost','mytest');

/*创建Schema*/
var mySchema = new Schema({
    name : String,
    age : Number
});

/*创建Model*/
var dbModel = db.model('test1',mySchema);

/*需要插入的数据*/
var lisiData = {
    name : '李四',
    age : 28
};

/*首先实例化一个对象*/
var person = new dbModel(lisiData);

/*调用对象的save方法进行保存 方法接收一个回调函数
*回调函数第一个参数为错误信息,如果没有错误为空,第二个是成功返回的信息
*/
person.save(function(err,_d){
    console.log(_d);
})

控制台打印内容:

未分类

接下来我们可以看到数据库多了一条数据:

未分类

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

/*创建数据库连接*/
var db = mongoose.createConnection('localhost','mytest');

/*创建Schema*/
var mySchema = new Schema({
    name : String,
    age : Number
});

/*创建Model*/
var dbModel = db.model('test1',mySchema);

/*查询name为李四的数据*/
dbModel.find({name:'李四'},function(err,_d){
    if(err){
        console.log(err);
    }else{
        console.log(_d);
    }
})

如果查询成功 会返回一个数组 如下图所示:

未分类

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

/*创建数据库连接*/
var db = mongoose.createConnection('localhost','mytest');

/*创建Schema*/
var mySchema = new Schema({
    name : String,
    age : Number
});

/*创建Model*/
var dbModel = db.model('test1',mySchema);

/*查询name为李四的数据 并将其name字段值更新为王五*/
dbModel.update({name:'李四'},{name:'王五'},function(err,_d){
    if(err){
        console.log(err);
    }else{
        console.log(_d);
    }
})

如果更新成功 控制台显示如下图所示:

未分类

咱们再看一下数据库是不是确实更新了:

未分类

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

/*创建数据库连接*/
var db = mongoose.createConnection('localhost','mytest');

/*创建Schema*/
var mySchema = new Schema({
    name : String,
    age : Number
});

/*创建Model*/
var dbModel = db.model('test1',mySchema);

/*删除name为王五的数据*/
dbModel.remove({name:'王五'},function(err,_d){
    if(err){
        console.log(err);
    }else{
        console.log(_d);
    }
})

搭建mongodb 3.4分片副本集集群

mongodb是最常用的nodql数据库,在数据库排名中已经上升到了前六。这篇文章介绍如何搭建高可用的mongodb(分片+副本)集群。

在搭建集群之前,需要首先了解几个概念:路由,分片、副本集、配置服务器等。

相关概念

先来看一张图:

未分类

从图中可以看到有四个组件:mongos、config server、shard、replica set。

  • mongos,数据库集群请求的入口,所有的请求都通过mongos进行协调,不需要在应用程序添加一个路由选择器,mongos自己就是一个请求分发中心,它负责把对应的数据请求请求转发到对应的shard服务器上。在生产环境通常有多mongos作为请求的入口,防止其中一个挂掉所有的mongodb请求都没有办法操作。

  • config server,顾名思义为配置服务器,存储所有数据库元信息(路由、分片)的配置。mongos本身没有物理存储分片服务器和数据路由信息,只是缓存在内存里,配置服务器则实际存储这些数据。mongos第一次启动或者关掉重启就会从 config server 加载配置信息,以后如果配置服务器信息变化会通知到所有的 mongos 更新自己的状态,这样 mongos 就能继续准确路由。在生产环境通常有多个 config server 配置服务器,因为它存储了分片路由的元数据,防止数据丢失!

  • shard,分片(sharding)是指将数据库拆分,将其分散在不同的机器上的过程。将数据分散到不同的机器上,不需要功能强大的服务器就可以存储更多的数据和处理更大的负载。基本思想就是将集合切成小块,这些块分散到若干片里,每个片只负责总数据的一部分,最后通过一个均衡器来对各个分片进行均衡(数据迁移)。

  • replica set,中文翻译副本集,其实就是shard的备份,防止shard挂掉之后数据丢失。复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。

仲裁者(Arbiter),是复制集中的一个MongoDB实例,它并不保存数据。仲裁节点使用最小的资源并且不要求硬件设备,不能将Arbiter部署在同一个数据集节点中,可以部署在其他应用服务器或者监视服务器中,也可部署在单独的虚拟机中。为了确保复制集中有奇数的投票成员(包括primary),需要添加仲裁节点做为投票,否则primary不能运行时不会自动切换primary。

简单了解之后,我们可以这样总结一下,应用请求mongos来操作mongodb的增删改查,配置服务器存储数据库元信息,并且和mongos做同步,数据最终存入在shard(分片)上,为了防止数据丢失同步在副本集中存储了一份,仲裁在数据存储到分片的时候决定存储到哪个节点。

环境准备

  • 系统系统 centos6.5
  • 三台服务器:192.168.0.75/84/86
  • 安装包: mongodb-linux-x86_64-3.4.6.tgz

服务器规划

未分类

端口分配:

mongos:20000
config:21000
shard1:27001
shard2:27002
shard3:27003

集群搭建

1、安装mongodb

#解压
tar -xzvf mongodb-linux-x86_64-3.4.6.tgz -C /usr/local/
#改名
mv mongodb-linux-x86_64-3.4.6 mongodb

分别在每台机器建立conf、mongos、config、shard1、shard2、shard3六个目录,因为mongos不存储数据,只需要建立日志文件目录即可。

mkdir -p /usr/local/mongodb/conf
mkdir -p /usr/local/mongodb/mongos/log
mkdir -p /usr/local/mongodb/config/data
mkdir -p /usr/local/mongodb/config/log
mkdir -p /usr/local/mongodb/shard1/data
mkdir -p /usr/local/mongodb/shard1/log
mkdir -p /usr/local/mongodb/shard2/data
mkdir -p /usr/local/mongodb/shard2/log
mkdir -p /usr/local/mongodb/shard3/data
mkdir -p /usr/local/mongodb/shard3/log

配置环境变量

vim /etc/profile
# 内容
export MONGODB_HOME=/usr/local/mongodb
export PATH=$MONGODB_HOME/bin:$PATH
# 使立即生效
source /etc/profile

2、config server配置服务器

mongodb3.4以后要求配置服务器也创建副本集,不然集群搭建不成功。

添加配置文件

vi /usr/local/mongodb/conf/config.conf

## 配置文件内容
pidfilepath = /usr/local/mongodb/config/log/configsrv.pid
dbpath = /usr/local/mongodb/config/data
logpath = /usr/local/mongodb/config/log/congigsrv.log
logappend = true

bind_ip = 0.0.0.0
port = 21000
fork = true

#declare this is a config db of a cluster;
configsvr = true

#副本集名称
replSet=configs

#设置最大连接数
maxConns=20000

启动三台服务器的config server

mongod -f /usr/local/mongodb/conf/config.conf

登录任意一台配置服务器,初始化配置副本集

#连接
mongo --port 21000
#config变量
config = {
...    _id : "configs",
...     members : [
...         {_id : 0, host : "192.168.0.75:21000" },
...         {_id : 1, host : "192.168.0.84:21000" },
...         {_id : 2, host : "192.168.0.86:21000" }
...     ]
... }

#初始化副本集
rs.initiate(config)

其中,”_id” : “configs”应与配置文件中配置的 replicaction.replSetName 一致,”members” 中的 “host” 为三个节点的 ip 和 port

3、配置分片副本集(三台机器)

设置第一个分片副本集

配置文件

vi /usr/local/mongodb/conf/shard1.conf

#配置文件内容
#——————————————–
pidfilepath = /usr/local/mongodb/shard1/log/shard1.pid
dbpath = /usr/local/mongodb/shard1/data
logpath = /usr/local/mongodb/shard1/log/shard1.log
logappend = true

bind_ip = 0.0.0.0
port = 27001
fork = true

#打开web监控
httpinterface=true
rest=true

#副本集名称
replSet=shard1

#declare this is a shard db of a cluster;
shardsvr = true

#设置最大连接数
maxConns=20000

启动三台服务器的shard1 server

mongod -f /usr/local/mongodb/conf/shard1.conf

登陆任意一台服务器,初始化副本集

mongo --port 27001
#使用admin数据库
use admin
#定义副本集配置,第三个节点的 "arbiterOnly":true 代表其为仲裁节点。
config = {
...    _id : "shard1",
...     members : [
...         {_id : 0, host : "192.168.0.75:27001" },
...         {_id : 1, host : "192.168.0.84:27001" },
...         {_id : 2, host : "192.168.0.86:27001” , arbiterOnly: true }
...     ]
... }
#初始化副本集配置
rs.initiate(config);

设置第二个分片副本集

配置文件

vi /usr/local/mongodb/conf/shard2.conf

#配置文件内容
#——————————————–
pidfilepath = /usr/local/mongodb/shard2/log/shard2.pid
dbpath = /usr/local/mongodb/shard2/data
logpath = /usr/local/mongodb/shard2/log/shard2.log
logappend = true

bind_ip = 0.0.0.0
port = 27002
fork = true

#打开web监控
httpinterface=true
rest=true

#副本集名称
replSet=shard2

#declare this is a shard db of a cluster;
shardsvr = true

#设置最大连接数
maxConns=20000

启动三台服务器的shard2 server

mongod -f /usr/local/mongodb/conf/shard2.conf

登陆任意一台服务器,初始化副本集

mongo --port 27002
#使用admin数据库
use admin
#定义副本集配置
config = {
...    _id : "shard2",
...     members : [
...         {_id : 0, host : "192.168.0.75:27002"  , arbiterOnly: true },
...         {_id : 1, host : "192.168.0.84:27002" },
...         {_id : 2, host : "192.168.0.86:27002" }
...     ]
... }

#初始化副本集配置
rs.initiate(config);

设置第三个分片副本集

配置文件

vi /usr/local/mongodb/conf/shard3.conf

#配置文件内容
#——————————————–
pidfilepath = /usr/local/mongodb/shard3/log/shard3.pid
dbpath = /usr/local/mongodb/shard3/data
logpath = /usr/local/mongodb/shard3/log/shard3.log
logappend = true

bind_ip = 0.0.0.0
port = 27003
fork = true

#打开web监控
httpinterface=true
rest=true

#副本集名称
replSet=shard3

#declare this is a shard db of a cluster;
shardsvr = true

#设置最大连接数
maxConns=20000

启动三台服务器的shard3 server

mongod -f /usr/local/mongodb/conf/shard3.conf

登陆任意一台服务器,初始化副本集

mongo --port 27003
#使用admin数据库
use admin
#定义副本集配置
config = {
...    _id : "shard3",
...     members : [
...         {_id : 0, host : "192.168.0.75:27003" },
...         {_id : 1, host : "192.168.0.84:27003" , arbiterOnly: true},
...         {_id : 2, host : "192.168.0.86:27003" }
...     ]
... }

#初始化副本集配置
rs.initiate(config);

4、配置路由服务器 mongos

先启动配置服务器和分片服务器,后启动路由实例启动路由实例:(三台机器)

vi /usr/local/mongodb/conf/mongos.conf

#内容
pidfilepath = /usr/local/mongodb/mongos/log/mongos.pid
logpath = /usr/local/mongodb/mongos/log/mongos.log
logappend = true

bind_ip = 0.0.0.0
port = 20000
fork = true

#监听的配置服务器,只能有1个或者3个 configs为配置服务器的副本集名字
configdb = configs/192.168.0.75:21000,192.168.0.84:21000,192.168.0.86:21000

#设置最大连接数
maxConns=20000

启动三台服务器的mongos server

mongod -f /usr/local/mongodb/conf/mongos.conf

5、启用分片

目前搭建了mongodb配置服务器、路由服务器,各个分片服务器,不过应用程序连接到mongos路由服务器并不能使用分片机制,还需要在程序里设置分片配置,让分片生效。

登陆任意一台mongos

mongo --port 20000
#使用admin数据库
user  admin
#串联路由服务器与分配副本集
sh.addShard("shard1/192.168.0.75:27001,192.168.0.84:27001,192.168.0.86:27001")
sh.addShard("shard2/192.168.0.75:27002,192.168.0.84:27002,192.168.0.86:27002")
sh.addShard("shard3/192.168.0.75:27003,192.168.0.84:27003,192.168.0.86:27003")
#查看集群状态
sh.status()

6、测试

目前配置服务、路由服务、分片服务、副本集服务都已经串联起来了,但我们的目的是希望插入数据,数据能够自动分片。连接在mongos上,准备让指定的数据库、指定的集合分片生效。

#指定testdb分片生效
db.runCommand( { enablesharding :"testdb"});
#指定数据库里需要分片的集合和片键
db.runCommand( { shardcollection : "testdb.table1",key : {id: 1} } )

我们设置testdb的 table1 表需要分片,根据 id 自动分片到 shard1 ,shard2,shard3 上面去。要这样设置是因为不是所有mongodb 的数据库和表 都需要分片!

测试分片配置结果

mongo  127.0.0.1:20000
#使用testdb
use  testdb;
#插入测试数据
for (var i = 1; i <= 100000; i++)
db.table1.save({id:i,"test1":"testval1"});
#查看分片情况如下,部分无关信息省掉了
db.table1.stats();

{
        "sharded" : true,
        "ns" : "testdb.table1",
        "count" : 100000,
        "numExtents" : 13,
        "size" : 5600000,
        "storageSize" : 22372352,
        "totalIndexSize" : 6213760,
        "indexSizes" : {
                "_id_" : 3335808,
                "id_1" : 2877952
        },
        "avgObjSize" : 56,
        "nindexes" : 2,
        "nchunks" : 3,
        "shards" : {
                "shard1" : {
                        "ns" : "testdb.table1",
                        "count" : 42183,
                        "size" : 0,
                        ...
                        "ok" : 1
                },
                "shard2" : {
                        "ns" : "testdb.table1",
                        "count" : 38937,
                        "size" : 2180472,
                        ...
                        "ok" : 1
                },
                "shard3" : {
                        "ns" : "testdb.table1",
                        "count" :18880,
                        "size" : 3419528,
                        ...
                        "ok" : 1
                }
        },
        "ok" : 1
}

可以看到数据分到3个分片,各自分片数量为: shard1 “count” : 42183,shard2 “count” : 38937,shard3 “count” : 18880。已经成功了!

后期运维

启动关闭

mongodb的启动顺序是,先启动配置服务器,在启动分片,最后启动mongos.

mongod -f /usr/local/mongodb/conf/config.conf
mongod -f /usr/local/mongodb/conf/shard1.conf
mongod -f /usr/local/mongodb/conf/shard2.conf
mongod -f /usr/local/mongodb/conf/shard3.conf
mongod -f /usr/local/mongodb/conf/mongos.conf

关闭时,直接killall杀掉所有进程

killall mongod
killall mongos