mongodb 的使用

install:

1、ubuntu用deb安装。

2、下载压缩文件,绿色的,不用安装。   推荐此方法。也可以自己编译,编译的时间比较久。我就是自己编译的。运行时需要.conf配置文件,只需要将默认的配置文件稍微修改下就可以了,这样可以将data文件与log放在自己设定的位置,而不是系统里。

3、终端可以采用命令行终端,也可以采用gui的,有官方的gui client:MongoDB Compass。也可参考后面的管理工具介绍。

配置dbpath:

1、用deb安装的,会在 /etc 目录下 创建mongodb.conf (若没自动创建,可以自己创建),这是默认的配置文件,可以配置默认的 dbpath 路径等。

2、若是用的解压版,则即使在 /etc 目录创建 mongodb.conf 文件也无效,只有在命令行用 --dbpath /Volumes/Data/mongodb 指定。或者用 -f /path/to/mongodb.conf  指定配置文件,配置文件里设置 dbpath

 运行mongodb的方式:

./bin/mongod -f mongodb.conf

以下是我自己的 mongodb.conf 文件:

# https://docs.mongodb.com/v3.2/reference/configuration-options/#security.authorization

storage: 
    # 注意dbPath的大小写
    dbPath: "/home/hzh/hzh/soft/mongodb_dbpath" 

security: 
    authorization: "enabled"

 

退出mongodb daemon server的方式:

$ ./mongo
> use admin
> db.shutdownServer()

或者:

$ ./mongod -f ../mongodb.conf  --shutdown

或者:

$ mongo --eval "db.getSiblingDB('admin').shutdownServer()"

或者:

kill  pid_number

用户主目录 ~ 下的 ~/.mongorc.js 配置文件, 这个文件只是命令行提示字符串,没什么用,可以不要(以下是我的示例):

prompt = function() {
    var db_user = "error occured";
    try {
    var dbname = db.getName();
    var user = db.runCommand({connectionStatus : 1}).authInfo.authenticatedUsers;
    
    var user_name = "";
    if (user) {
        user.forEach(function(element) {
            if (0 == user_name.length) {
                user_name = element.user;
            } else {
                user_name = user_name + "|" + element.user;
            }
        });
    }

    var dbname_show = "notSelDb";
    if (dbname) {
        dbname_show = dbname;
    }

    var user_show = "noAuth";
    if (user_name.length > 0) {
        user_show = user_name;
    }

    db_user = dbname_show + "[" + user_show + "] > ";
    } catch (e) {
    }

    // 捕捉写入错误并打印; 同时有可能在连接断开时自动重连。
    try {
        db.runCommand({getLastError: 1});
    } catch (e) {
        print(e);
    }

    return db_user;
}

 

忘记管理密码后怎么恢复:

If you have locked yourself out then you need to do the following:

1、Stop your MongoDB instance
2、Remove the --auth and/or --keyfile options from your MongoDB config to disable authentication
3、Start the instance without authentication
4、Edit the users as needed
5、Restart the instance with authentication enabled

如果你删掉了 admin 数据库:

Deleting admin db means loosing access to all the databases.  how to solve?   Enable localhost authentication bypass(if not enabled) and restart mongod. Now create the users again. 

从上可看出,安装完mongodb后,若进而定义了用户,需要在 MongoDB config 文件里指定启动mongodb后必须认证登录才能访问数据库,这才安全。 怎么启用认证,只需要在conf配置文件里加入:

security:
    authorization: enabled

mongodb的管理工具:

1、https://github.com/Studio3T/robomongo

Robo 3T (formerly Robomongo *) is a shell-centric cross-platform MongoDB management tool. Unlike most other MongoDB admin UI tools, Robo 3T embeds the actual mongo shell in a tabbed interface with access to a shell command line as well as GUI interaction.

2、https://github.com/mrvautin/adminMongo

adminMongo is a Web based user interface (GUI) to handle all your MongoDB connections/databases needs

 

mongodb 的用户管理:

https://docs.mongodb.com/v3.0/tutorial/manage-users-and-roles/

https://docs.mongodb.com/v3.0/reference/built-in-roles/#clusterAdmin

任何database都需要一个用户来对它进行访问,如果没有任何用户对应此database,你就无法访问该database。而任何user都不可能无限制的取得database的权限,因此必须有一个权限系统,也就是role管理。role规定了user对数据库的访问权限。一个user可以被授予多个角色,因而被赋予了对mongodb的资源访问能力和能实施的行为。

The admin database includes the following roles for administering the whole system rather than just a single database. 

admin 数据库里包含一系列的角色(如: clusterAdmin, clusterManager, clusterMonitor, hostManager),专为管理整个系统而不管理单个数据库。

The admin database provides the following roles that apply to all databases in a mongod instance and are roughly equivalent to their single-database equivalents

admin 数据库里包含一系列的角色(如: readAnyDatabase, readWriteAnyDatabase, userAdminAnyDatabase, dbAdminAnyDatabase),来管理mongod实例中的所有数据库,相当于把它当成自己的数据库来管理。

注意:在哪个database下创建的user,则这个user就只属于这个database,不属于其它的任何database,在其它database里显示不出不属于自己的user。

mongoDB 3.0 用户权限系统:

MongoDB 3.0 安全权限访问控制,在添加用户上面3.0版本和之前的版本有很大的区别,这里就说明下3.0的添加用户的方法。

环境、测试:

      在安装MongoDB之后,先在conf文件里关闭auth认证,进入查看数据库,只有一个local库,admin库是不存在的(也有可能存在):

root@zhoujinyi:/usr/local/mongo4# mongo --port=27020
MongoDB shell version: 3.0.4
connecting to: 127.0.0.1:27020/test
2015-06-29T09:31:08.673-0400 I CONTROL  [initandlisten] 
> show dbs;
local  0.078GB

现在需要创建一个帐号,该账号需要有grant权限,即:账号管理的授权权限。注意一点,帐号是跟着数据库走的,所以在某数据库里授权,必须也在该库里验证(auth)(即登录)。

> use admin
switched to db admin
> db.createUser(
...   {
...     user: "dba",
...     pwd: "dba",
...     roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
...   }
... )
Successfully added user: {
    "user" : "dba",
    "roles" : [
        {
            "role" : "userAdminAnyDatabase",
            "db" : "admin"
        }
    ]
}

上面加粗的就是执行的命令:

user:用户名

pwd:密码

roles:指定用户的角色,可以用一个空数组给新用户设定空角色;在roles字段,可以指定内置角色和用户定义的角色。role里的角色可以选:

Built-In Roles(内置角色):
    1. 数据库用户角色:read、readWrite;
    2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;
    3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;
    4. 备份恢复角色:backup、restore;
    5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
    6. 超级用户角色:root  
    // 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)
    7. 内部角色:__system

具体角色: 

Read:允许用户读取指定数据库
readWrite:允许用户读写指定数据库
dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
root:只在admin数据库中可用。超级账号,超级权限

刚建立了 userAdminAnyDatabase 角色,用来管理用户,可以通过这个角色来创建、删除用户。验证:需要开启auth参数。

root@zhoujinyi:/usr/local/mongo4# mongo --port=27020
MongoDB shell version: 3.0.4
connecting to: 127.0.0.1:27020/test
> show dbs;               ####没有认证,导致没权限。
2015-06-29T10:02:16.634-0400 E QUERY    Error: listDatabases failed:{
    "ok" : 0,
    "errmsg" : "not authorized on admin to execute command { listDatabases: 1.0 }",
    "code" : 13
}
    at Error (<</span>anonymous>)
    at Mongo.getDBs (src/mongo/shell/mongo.js:47:15)
    at shellHelper.show (src/mongo/shell/utils.js:630:33)
    at shellHelper (src/mongo/shell/utils.js:524:36)
    at (shellhelp2):1:1 at src/mongo/shell/mongo.js:47
> use admin        #认证,因为在admin下面添加的帐号,所以要到admin下面认证。
switched to db admin
> db.auth('dba','dba')               # db.logout() 是登出当前认证了的用户 (##注意,每个用户都是只属于某个数据库的,没切换到哪个数据库,你无法对属于该数据库的用户进行登录与登出)
1
> show dbs;
admin  0.078GB
local  0.078GB
> use test        #在test库里创建帐号
switched to db test
> db.createUser(
...     {
...       user: "zjyr",
...       pwd: "zjyr",
...       roles: [
...          { role: "read", db: "test" }    #只读帐号
...       ]
...     }
... )
Successfully added user: {
    "user" : "zjyr",
    "roles" : [
        {
            "role" : "read",
            "db" : "test"
        }
    ]
}
> db.createUser(
...     {
...       user: "zjy",
...       pwd: "zjy",
...       roles: [
...          { role: "readWrite", db: "test" }   #读写帐号
...       ]
...     }
... )
Successfully added user: {
    "user" : "zjy",
    "roles" : [
        {
            "role" : "readWrite",                #读写账号
            "db" : "test"
        }
    ]
}
> show users;                                    #查看当前库下的用户
{
    "_id" : "test.zjyr",
    "user" : "zjyr",
    "db" : "test",
    "roles" : [
        {
            "role" : "read",
            "db" : "test"
        }
    ]
}
{
    "_id" : "test.zjy",
    "user" : "zjy",
    "db" : "test",
    "roles" : [
        {
            "role" : "readWrite",
            "db" : "test"
        }
    ]
}

上面创建了2个帐号,现在验证下:验证前提需要一个集合(collection) 

> db.abc.insert({"a":1,"b":2})              #插入失败,没有权限,userAdminAnyDatabase 权限只是针对用户管理的,对其他是没有权限的。
WriteResult({
    "writeError" : {
        "code" : 13,
        "errmsg" : "not authorized on test to execute command { insert: "abc", documents: [ { _id: ObjectId('55915185d629831d887ce2cb'), a: 1.0, b: 2.0 } ], ordered: true }"
    }
})
> 
bye
root@zhoujinyi:/usr/local/mongo4# mongo --port=27020
MongoDB shell version: 3.0.4
connecting to: 127.0.0.1:27020/test
> use test
switched to db test
> db.auth('zjy','zjy')       #用创建的readWrite帐号进行写入
1
> db.abc.insert({"a":1,"b":2})
WriteResult({ "nInserted" : 1 })
> db.abc.insert({"a":11,"b":22})
WriteResult({ "nInserted" : 1 })
> db.abc.insert({"a":111,"b":222})
WriteResult({ "nInserted" : 1 })
> db.abc.find()
{ "_id" : ObjectId("559151a1b78649ebd8316853"), "a" : 1, "b" : 2 }
{ "_id" : ObjectId("559151cab78649ebd8316854"), "a" : 11, "b" : 22 }
{ "_id" : ObjectId("559151ceb78649ebd8316855"), "a" : 111, "b" : 222 }
> db.auth('zjyr','zjyr')       #切换到只有read权限的帐号
1
> db.abc.insert({"a":1111,"b":2222})  #不能写入
WriteResult({
    "writeError" : {
        "code" : 13,
        "errmsg" : "not authorized on test to execute command { insert: "abc", documents: [ { _id: ObjectId('559151ebb78649ebd8316856'), a: 1111.0, b: 2222.0 } ], ordered: true }"
    }
})
> db.abc.find()        #可以查看
{ "_id" : ObjectId("559151a1b78649ebd8316853"), "a" : 1, "b" : 2 }
{ "_id" : ObjectId("559151cab78649ebd8316854"), "a" : 11, "b" : 22 }
{ "_id" : ObjectId("559151ceb78649ebd8316855"), "a" : 111, "b" : 222 }

有没有一个超级权限?不仅可以授权,而且也可以对集合进行任意操作?答案是肯定的,只是不建议使用。那就是role角色设置成root

> db.auth('dba','dba')
1
> db.createUser(
...  {
...    user: "zhoujinyi",
...    pwd: "zhoujinyi",
...    roles: [
...       { role: "root", db: "admin" }      #超级root帐号
...    ]
...  }
... )
Successfully added user: {
    "user" : "zhoujinyi",
    "roles" : [
        {
            "role" : "root",
            "db" : "admin"
        }
    ]
}
> 
> show users;              #查看当前库下的用户
{
    "_id" : "admin.dba",
    "user" : "dba",
    "db" : "admin",
    "roles" : [
        {
            "role" : "userAdminAnyDatabase",
            "db" : "admin"
        }
    ]
}
{
    "_id" : "admin.zhoujinyi",
    "user" : "zhoujinyi",
    "db" : "admin",
    "roles" : [
        {
            "role" : "root",
            "db" : "admin"
        }
    ]
}
> use admin
switched to db admin
> db.auth('zhoujinyi','zhoujinyi')
1
> use test
switched to db test
> db.abc.insert({"a":1,"b":2})
WriteResult({ "nInserted" : 1 })
> db.abc.insert({"a":1111,"b":2222})          #权限都有
WriteResult({ "nInserted" : 1 })
> db.abc.find()
{ "_id" : ObjectId("5591539bb78649ebd8316857"), "a" : 1, "b" : 2 }
{ "_id" : ObjectId("559153a0b78649ebd8316858"), "a" : 1111, "b" : 2222 }
> db.abc.remove({})
WriteResult({ "nRemoved" : 2 })

因为帐号都是在当前需要授权的数据库下授权的,那要是不在当前数据库下会怎么样?

 
> db
admin
> db.createUser(
...  {
...    user: "dxy",
...    pwd: "dxy",
...    roles: [
...       { role: "readWrite", db: "test" },     #在当前库下创建其他库的帐号,在admin库下创建test、abc库的帐号
...       { role: "readWrite", db: "abc" }         
...    ]
...  }
... )
Successfully added user: {
    "user" : "dxy",
    "roles" : [
        {
            "role" : "readWrite",
            "db" : "test"
        },
        {
            "role" : "readWrite",
            "db" : "abc"
        }
    ]
}
> 
> show users;
{
    "_id" : "admin.dba",
    "user" : "dba",
    "db" : "admin",
    "roles" : [
        {
            "role" : "userAdminAnyDatabase",
            "db" : "admin"
        }
    ]
}
{
    "_id" : "admin.zhoujinyi",
    "user" : "zhoujinyi",
    "db" : "admin",
    "roles" : [
        {
            "role" : "root",
            "db" : "admin"
        }
    ]
}
{
    "_id" : "admin.dxy",
    "user" : "dxy",
    "db" : "admin",
    "roles" : [
        {
            "role" : "readWrite",
            "db" : "test"
        },
        {
            "role" : "readWrite",
            "db" : "abc"
        }
    ]
}
> use test
switched to db test
> db.auth('dxy','dxy')          #在admin下创建的帐号,不能直接在其他库验证,
Error: 18 Authentication failed.
0
> use admin
switched to db admin            #只能在帐号创建库下认证,再去其他库进行操作。
> db.auth('dxy','dxy')
1
> use test
switched to db test
> db.abc.insert({"a":1111,"b":2222})
WriteResult({ "nInserted" : 1 })
> use abc
switched to db abc
> db.abc.insert({"a":1111,"b":2222})
WriteResult({ "nInserted" : 1 })

上面更加进一步说明数据库的用户帐号(user)是跟着数据库走的,哪个数据库里创建的用户必须到哪个数据库下去认证登录登出。

创建了这么多帐号,怎么查看所有帐号

>  use admin
switched to db admin
> db.auth('dba','dba')
1
> db.system.users.find().pretty()
{
    "_id" : "admin.dba",
    "user" : "dba",
    "db" : "admin",
    "credentials" : {
        "SCRAM-SHA-1" : {
            "iterationCount" : 10000,
            "salt" : "KfDUzCOIUo7WVjFr64ZOcQ==",
            "storedKey" : "t4sPsKG2dXnZztVYj5EgdUzT9sc=",
            "serverKey" : "2vCGiq9NIc1zKqeEL6VvO4rP26A="
        }
    },
    "roles" : [
        {
            "role" : "userAdminAnyDatabase",
            "db" : "admin"
        }
    ]
}
{
    "_id" : "test.zjyr",
    "user" : "zjyr",
    "db" : "test",
    "credentials" : {
        "SCRAM-SHA-1" : {
            "iterationCount" : 10000,
            "salt" : "h1gOW3J7wzJuTqgmmQgJKQ==",
            "storedKey" : "7lkoANdxM2py0qiDBzFaZYPp1cM=",
            "serverKey" : "Qyu6IRNyaKLUvqJ2CAa/tQYY36c="
        }
    },
    "roles" : [
        {
            "role" : "read",
            "db" : "test"
        }
    ]
}
{
    "_id" : "test.zjy",
    "user" : "zjy",
    "db" : "test",
    "credentials" : {
        "SCRAM-SHA-1" : {
            "iterationCount" : 10000,
            "salt" : "afwaKuTYPWwbDBduQ4Hm7g==",
            "storedKey" : "ebb2LYLn4hiOVlZqgrAKBdStfn8=",
            "serverKey" : "LG2qWwuuV+FNMmr9lWs+Rb3DIhQ="
        }
    },
    "roles" : [
        {
            "role" : "readWrite",
            "db" : "test"
        }
    ]
}
{
    "_id" : "admin.zhoujinyi",
    "user" : "zhoujinyi",
    "db" : "admin",
    "credentials" : {
        "SCRAM-SHA-1" : {
            "iterationCount" : 10000,
            "salt" : "pE2cSOYtBOYevk8tqrwbSQ==",
            "storedKey" : "TwMxdnlB5Eiaqg4tNh9ByNuUp9A=",
            "serverKey" : "Mofr9ohVlFfR6/md4LMRkOhXouc="
        }
    },
    "roles" : [
        {
            "role" : "root",
            "db" : "admin"
        }
    ]
}
{
    "_id" : "admin.dxy",
    "user" : "dxy",
    "db" : "admin",
    "credentials" : {
        "SCRAM-SHA-1" : {
            "iterationCount" : 10000,
            "salt" : "XD6smcWX4tdg/ZJPoLxxRg==",
            "storedKey" : "F4uiayykHDp/r9krAKZjdr+gqjM=",
            "serverKey" : "Kf51IU9J3RIrB8CFn5Z5hEKMSkw="
        }
    },
    "roles" : [
        {
            "role" : "readWrite",
            "db" : "test"
        },
        {
            "role" : "readWrite",
            "db" : "abc"
        }
    ]
}
> db.system.users.find().count()
5

备份还原使用那个角色的帐号?之前创建的帐号zjy:test库读写权限;zjyr:test库读权限

 
root@zhoujinyi:~# mongodump --port=27020 -uzjyr -pzjyr --db=test -o backup   #只要读权限就可以备份
2015-06-29T11:20:04.864-0400    writing test.abc to backup/test/abc.bson
2015-06-29T11:20:04.865-0400    writing test.abc metadata to backup/test/abc.metadata.json
2015-06-29T11:20:04.866-0400    done dumping test.abc
2015-06-29T11:20:04.867-0400    writing test.system.indexes to backup/test/system.indexes.bson


root@zhoujinyi:~# mongorestore --port=27020 -uzjy -pzjy --db=test backup/test/  #读写权限可以进行还原
2015-06-29T11:20:26.607-0400    building a list of collections to restore from backup/test/ dir
2015-06-29T11:20:26.609-0400    reading metadata file from backup/test/abc.metadata.json
2015-06-29T11:20:26.609-0400    restoring test.abc from file backup/test/abc.bson
2015-06-29T11:20:26.611-0400    error: E11000 duplicate key error index: test.abc.$_id_ dup key: { : ObjectId('559154efb78649ebd831685a') }
2015-06-29T11:20:26.611-0400    restoring indexes for collection test.abc from metadata
2015-06-29T11:20:26.612-0400    finished restoring test.abc
2015-06-29T11:20:26.612-0400    done

 

mongodb 的性能简介:

使用mongodb最多的是用于查询,确实,作为nosql数据库,查询的效率确实高于关系型数据库,这个无可厚非,但是我们还是有必要用到关系型数据库,sql server,  MySQL 或者 Oracle。毕竟关系型数据库存在了这么多年,还是比较稳定和安全的。我现在做的是所有的数据都存了两份,一份是放在mysql里面,一份是放在mongodb里面,但是放在mysql里面的数据只是插入,没有用于查询,查询一般都在mongodb里面查询,除非特殊的情况,我才会去mysql里面查询数据,一般这样的数据就是属于比较敏感的数据,还是查询mysql比较靠谱,但是有的数据,我就没有存在mysql里面了,直接存在mongodb里面,就比如,向用户的评论这些东西,没必要存在数据库。

使用mongodb,我用的比较多的还是他的集合查询,mongodb的查询配合linq,简直不要太爽了,有兴趣的朋友可以去看一下,相关的文章。

写到这里,我还想谈一下,我设计数据库的思路。我一般设计数据库,不管什么表都有一个ID,主键ID, 这个ID是自增长的,我设计的一个方针就是,尽量避免表与表之间的join查询,如果每个表搞个ID,又是主键,查询起来还是比较节省性能的。join查询,表与表之间的关联,又耗性能不说,还把数据库设计的相当之复杂,增加了开发的难度。

mongodb 的索引的失效机制大概是这样的:

 为集合创建一个indexes(索引) 

db.testCollection.ensureIndex( { "Date": 1 }, { expireAfterSeconds: 10 } )  

然后确保每次插入数据的时候有该列,mongodb将会自动为你删除该列 

db.testCollection.insert({"Date" : new Date(),"name":"zs","age":18})  

 

mongodb 的缓存大小(working set size):

mongodb 是不能配置缓存大小的,一直吃内存。因为MongoDB并不干涉内存管理工作,而是把这些工作留给操作系统的虚拟内存管理器去处理,这样做的好处是简化了MongoDB的工作,但坏处是你没有方法很方便的控制MongoDB占多大内存,幸运的是虚拟内存管理器的存在让我们多数时候并不需要关心这个问题。

以下是详情:

但凡初次接触MongoDB的人,无不惊讶于它对内存的贪得无厌,至于个中缘由,我先讲讲Linux是如何管理内存的,再说说MongoDB是如何使用内存的,答案自然就清楚了。

 

据说带着问题学习更有效,那就先看一个MongoDB服务器的top命令结果:

 

shell> top -p $(pidof mongod)

Mem:  32872124k total, 30065320k used,  2806804k free,   245020k buffers

Swap:  2097144k total,      100k used,  2097044k free, 26482048k cached

 

VIRT  RES  SHR %MEM

1892g  21g  21g 69.6

这台MongoDB服务器有没有性能问题?大家可以一边思考一边继续阅读。

 

先讲讲Linux是如何管理内存的

在Linux里(别的系统也差不多),内存有物理内存和虚拟内存之说,物理内存是什么自然无需解释,虚拟内存实际是物理内存的抽象,多数情况下,出于方便性的考虑,程序访问的都是虚拟内存地址,然后操作系统会通过Page Table机制把它翻译成物理内存地址,详细说明可以参考Understanding Memory和Understanding Virtual Memory,至于程序是如何使用虚拟内存的,可以参考Playing with Virtual Memory,这里就不多费口舌了。

 

很多人会把虚拟内存和Swap混为一谈,实际上Swap只是虚拟内存引申出的一种技术而已:操作系统一旦物理内存不足,为了腾出内存空间存放新内容,就会把当前物理内存中的内容放到交换分区里,稍后用到的时候再取回来,需要注意的是,Swap的使用可能会带来性能问题,偶尔为之无需紧张,糟糕的是物理内存和交换分区频繁的发生数据交换,这被称之为Swap颠簸,一旦发生这种情况,先要明确是什么原因造成的,如果是内存不足就好办了,加内存就可以解决,不过有的时候即使内存充足也可能会出现这种问题,比如MySQL就有可能出现这样的情况,一个可选的解决方法是限制使用Swap:

 

shell> sysctl -w vm.swappiness=0

查看内存情况最常用的是free命令:

 

shell> free -m

            total       used       free     shared    buffers     cached

Mem:         32101      29377       2723          0        239      25880

-/+ buffers/cache:       3258      28842

Swap:         2047          0       2047

新手看到used一栏数值偏大,free一栏数值偏小,往往会认为内存要用光了。其实并非如此,之所以这样是因为每当我们操作文件的时候,Linux都会尽可能的把文件缓存到内存里,这样下次访问的时候,就可以直接从内存中取结果,所以cached一栏的数值非常的大,不过不用担心,这部分内存是可回收的,操作系统的虚拟内存管理器会按照LRU算法淘汰冷数据。还有一个buffers,也是可回收的,不过它是保留给块设备使用的。

 

知道了原理,我们就可以推算出系统可用的内存是free + buffers + cached:

 

shell> echo $((2723 + 239 + 25880))

28842

至于系统实际使用的内存是used – buffers – cached:

 

shell> echo $((29377 - 239 - 25880))

3258

除了free命令,还可以使用sar命令:

 

shell> sar -r

kbmemfree kbmemused  %memused kbbuffers  kbcached

 3224392  29647732     90.19    246116  26070160

 

shell> sar -W

pswpin/s pswpout/s

   0.00      0.00

希望你没有被%memused吓到,如果不幸言中,重读本文。

 

再说说MongoDB是如何使用内存的

目前,MongoDB使用的是内存映射存储引擎,它会把数据文件映射到内存中,如果是读操作,内存中的数据起到缓存的作用,如果是写操作,内存还可以把随机的写操作转换成顺序的写操作,总之可以大幅度提升性能。MongoDB并不干涉内存管理工作,而是把这些工作留给操作系统的虚拟内存管理器去处理,这样做的好处是简化了MongoDB的工作,但坏处是你没有方法很方便的控制MongoDB占多大内存,幸运的是虚拟内存管理器的存在让我们多数时候并不需要关心这个问题。

 

MongoDB的内存使用机制让它在缓存重建方面更有优势,简而言之:如果重启进程,那么缓存依然有效,如果重启系统,那么可以通过拷贝数据文件到/dev/null的方式来重建缓存,更详细的描述请参考:Cache Reheating – Not to be Ignored。

 

有时候,即便MongoDB使用的是64位操作系统,也可能会遭遇OOM问题,出现这种情况,多半是因为限制了内存的大小所致,可以这样查看当前值:

 

shell> ulimit -a | grep memory

多数操作系统缺省都是把它设置成unlimited的,如果你的操作系统不是,可以这样修改:

 

shell> ulimit -m unlimited

shell> ulimit -v unlimited

注:ulimit的使用是有上下文的,最好放在MongoDB的启动脚本里。

 

有时候,MongoDB连接数过多的话,会拖累性能,可以通过serverStatus查询连接数:

 

mongo> db.serverStatus().connections

每个连接都是一个线程,需要一个Stack,Linux下缺省的Stack设置一般比较大:

 

shell> ulimit -a | grep stack

stack size              (kbytes, -s) 10240

至于MongoDB实际使用的Stack大小,可以用如下命令确认(单位:K):

 

shell> cat /proc/$(pidof mongod)/limits | grep stack | awk -F 'size' '{print int($NF)/1024}'

如果Stack过大(比如:10240K)的话没有意义,简单对照命令结果中的Size和Rss:

 

shell> cat /proc/$(pidof mongod)/smaps | grep 10240 -A 10

所有连接消耗的内存加起来会相当惊人,推荐把Stack设置小一点,比如说1024:

 

shell> ulimit -s 1024

注:从MongoDB1.8.3开始,MongoDB会在启动时自动设置Stack。

 

有时候,出于某些原因,你可能想释放掉MongoDB占用的内存,不过前面说了,内存管理工作是由虚拟内存管理器控制的,幸好可以使用MongoDB内置的closeAllDatabases命令达到目的:

 

mongo> use admin

mongo> db.runCommand({closeAllDatabases:1})

另外,通过调整内核参数drop_caches也可以释放缓存:

 

shell> sysctl -w vm.drop_caches=1

平时可以通过mongo命令行来监控MongoDB的内存使用情况,如下所示:

 

mongo> db.serverStatus().mem:

{

   "resident" : 22346,

   "virtual" : 1938524,

   "mapped" : 962283

}

还可以通过mongostat命令来监控MongoDB的内存使用情况,如下所示:

 

shell> mongostat

mapped  vsize    res faults

 940g  1893g  21.9g      0

其中内存相关字段的含义是:

 

mapped:映射到内存的数据大小

visze:占用的虚拟内存大小

res:占用的物理内存大小

注:如果操作不能在内存中完成,结果faults列的数值不会是0,视大小可能有性能问题。

 

在上面的结果中,vsize是mapped的两倍,而mapped等于数据文件的大小,所以说vsize是数据文件的两倍,之所以会这样,是因为本例中,MongoDB开启了journal,需要在内存里多映射一次数据文件,如果关闭journal,则vsize和mapped大致相当。

 

如果想验证这一点,可以在开启或关闭journal后,通过pmap命令来观察文件映射情况:

 

shell> pmap $(pidof mongod)

到底MongoDB配备多大内存合适?宽泛点来说,多多益善,如果要确切点来说,这实际取决于你的数据及索引的大小,内存如果能够装下全部数据加索引是最佳情况,不过很多时候,数据都会比内存大,比如本文所涉及的MongoDB实例:

 

mongo> db.stats()

{

   "dataSize" : 1004862191980,

   "indexSize" : 1335929664

}

本例中索引只有1G多,内存完全能装下,而数据文件则达到了1T,估计很难找到这么大内存,此时保证内存能装下热数据即可,至于热数据是多少,取决于具体的应用,你也可以通过观察faults的大小来判断当前内存是否能够装下热数据,如果faults持续变大,就说明当前内存已经不能满足热数据的大小了。如此一来内存大小就明确了:内存 > 索引 + 热数据,最好有点富余,毕竟操作系统本身正常运转也需要消耗一部分内存。

 

关于MongoDB与内存的话题,大家还可以参考官方文档中的相关介绍。

 

 

mongodb 日期的存储与查找:

日期的存储:

要求,必须将日期类型存为 ISODate 类型,不能存为 string 类型,不然无法进行时间比较。

1、在mongodb的shell可以这样存:

db.collection.insert({date: ISODate("2021-12-11T16:31:14.137951")})

在 pymongo里可以这样存:

db['collection'].insert({'f2': 'fff', 'date': datetime.datetime.now()})

注意,python的datetime.datetime打印出来是 2020-12-11T17:04:06.559106 这个样子,它和javascript的 ISODate 不完全一样,IOSDate样子为 2020-12-11T17:04:06.559Z

但是,pymongo可以自动将python的datetime.datetime转换为ISODate类型,而string类型是无法转换的。

ISODate类型在mongodb里看到的样子必须是:

{ "_id" : ObjectId("5fd33053e7b4562e42b56be4"), "date" : ISODate("2020-12-11T16:31:14.138Z") }
{ "_id" : ObjectId("5fd33282e7b4562e42b56be5"), "date" : ISODate("2021-12-11T16:31:14.138Z") }
{ "_id" : ObjectId("5fd33702924f4e1c91ba0b08"), "f2" : "fff", "date" : ISODate("2020-12-11T17:08:18.549Z") }

下面的样子是String类型:

{ "_id" : ObjectId("5fd33282e7b4562e42b56be5"), "date" : "2021-12-11T16:31:14.138Z" }

2、在python里mongodb的查找:

from_date = datetime.datetime(2010, 12, 31, 12, 30, 30, 125000)
to_date = datetime.datetime(2021, 3, 31, 12, 30, 30, 125000)

for post in self.db['collection'].find({"date": {"$gte": from_date, "$lt": to_date}}):
    print(post)

 

 

pymongo upsert操作(update or insert)操作,即document存在则更新,不存在则插入到collection

db['c'].update({'field1': 'value1'}, {'$set': {'field1': 'value1', 'field2': 'v2'}}, upsert=True)

或者整体插入或替换/更新:

# 其中 item 为一个dict 类型,且包含一个叫 xueqiu_id 的key
db['c'].update({'xueqiu_id': item['xueqiu_id']}, {'$set': dict(item)}, upsert=True)

注意upsert肯定比insert耗时,因为它有一个查找的动作,当表比较大的时候尤其如此。

 

 

pymongo 的查找操作,判断field是否包含在某array里,及field是数组的情况

find 返回的是 cursor,而不是bool。

# 如果newID是数组,而UserIDS可能是数组,则下面的是查找UserIDS数组里是否有元素在newid中,有则选出来
db.mycollection.find({'UserIDS': { "$in": newID}})
#如果newID不是数组,而UserIDS可能是数组,则下面的结果是查找UserIDS数组是否包含newID,有则选出来:
db.mycollection.find({'UserIDS': newID})

下面是英文解释:

db.myfollower.find({follower_id : 4})

is different from the following:

db.myfollower.find({"follower_id" : {"$in" : [4]}})

 

The first query tests that any value of follower_id is 4. MongoDB automatically translate from a 1-to-1 match to a 1-to-N match if the field follower_id is an array. The second query tests that any value of follower_id is any of the values of $in. In that last query you are doing a N-to-N match. The $in operator does a x-to-N query, where x=1 if you have one value in follower_id, or N if you have N value in follower_id.

Let's look at an example. Here is the dataset:

{
    "_id" : ObjectId("52e7e35f4c735353309cf077"),
    "userid" : 12,
    "follower_id" : 4
}
{
    "_id" : ObjectId("52e7e3784c735353309cf078"),
    "userid" : 12,
    "follower_id" : [ 1, 2, 3, 4, 5, 6 ]
}

And here are the queries and the matching results:

> db.followers.find({"follower_id":4})
{ "_id" : ObjectId("52e7e35f4c735353309cf077"), "userid" : 12, "follower_id" : 4 }
{ "_id" : ObjectId("52e7e3784c735353309cf078"), "userid" : 12, "follower_id" : [  1,  2,  3,  4,  5,  6 ] }

> db.followers.find({"follower_id":5})
{ "_id" : ObjectId("52e7e3784c735353309cf078"), "userid" : 12, "follower_id" : [  1,  2,  3,  4,  5,  6 ] }

> db.followers.find({"follower_id":{$in:[4]}})
{ "_id" : ObjectId("52e7e35f4c735353309cf077"), "userid" : 12, "follower_id" : 4 }
{ "_id" : ObjectId("52e7e3784c735353309cf078"), "userid" : 12, "follower_id" : [  1,  2,  3,  4,  5,  6 ] }

> db.followers.find({"follower_id":{$in:[4, 5]}})
{ "_id" : ObjectId("52e7e35f4c735353309cf077"), "userid" : 12, "follower_id" : 4 }
{ "_id" : ObjectId("52e7e3784c735353309cf078"), "userid" : 12, "follower_id" : [  1,  2,  3,  4,  5,  6 ] }

> db.followers.find({"follower_id":{$in:[5, 6]}})
{ "_id" : ObjectId("52e7e3784c735353309cf078"), "userid" : 12, "follower_id" : [  1,  2,  3,  4,  5,  6 ] }

 

 

mongodb的一对多关系的创建与查询:

mongodb的关系建立可以采用embed、reference以及dbref三种方式,embed不容易管理,因为直接把内容嵌入到另一个document,而reference一般采用_id来引用,这种方式原则上与dbref差不多,但是也有一个不同点,reference不能引用不同的collection,而dbref可以引用同一个db下的不同collection,功能强大一些。一般使用dbref来完成关系的建立,示例如下。

示例假设一个user只能有一个地址,而一个地址可以属于多个user共有(如办公室的人员等),这种一对多的关系是一种从属关系,user拥有address,address属于user。先创建一对多的关系:

db.address.insert({_id: 'add1', detail: 'detail1'})
db.address.insert({_id: 'add2', detail: 'detail2'})
db.address_aux.insert({_id: 'add1', detail: 'detail2'})
db.user.insert({_id: 'hzh', address: {$ref: 'address', $id: 'add1'}}) 
db.user.insert({_id: 'hzh2', address: {$ref: 'address', $id: 'add1'}}) 
db.user.insert({_id: 'hzh3', address: {$ref: 'address_aux', $id: 'add1'}})

 

查询:

1、查住址:

知道一个user,查他的住址:

var user = db.user.findOne({_id: "hzh"})
var dbRef = user.address
db[dbRef.$ref].findOne({"_id": dbRef.$id})
db[dbRef.$ref].find({"_id": dbRef.$id})

 

2、查user:

已知某个地址,不关心(即不指定)collection,查询住在该处的所有user:

db.user.find({'address.$id': 'add1'})

已知某个地址,指定collection,查询住在该处的所有user:

db.user.find({'address': DBRef("address", 'add1')})
db.user.find({'address.$ref': 'address', 'address.$id': 'add1'})
db.user.find({'address': {$ref: "address", $id: 'add1'}})
db.user.find({'address': DBRef($ref="address", $id='add1')})

已经知道一堆地址,查询住在这些地方的所有user:

# 先生成一堆地址,具体使用过程中你已经知道了一堆地址
#
其中第一个 {} 表示过滤条件,第二个{_id:1}表示获取哪些fields,这里1表示获取_id,设为大与1的整数效果一样,如果设为0,则表示不需要_id这一项。map也是一个过滤装置,且将结果转换为数组的函数。由于这个map的存在,前面的_id:1}就不需要了,因此有两种写法 var aa = db.address.find({}, {_id:1}).map(function(item){ return item._id; }) # 第二种写法,下面这两行只能算一种写法 var aa = db.address.find({}).map(function(item){ return item._id; }) var aa = db.address.find().map(function(item){ return item._id; }) # 查找这一堆地址里包含有哪些user。 第一种方法: 不指定collection的情况如下 db.user.find({'address.$id': {$in: aa}}) # 也是第一种方法,指定collection db.user.find({'address.$ref': 'address', 'address.$id': {$in: aa}})

# 查找这一堆地址里包含有哪些user的第二种方法:
# 对每个user采用轮询方法,判断其address的id是否属于这堆地址。 不推荐

 

 

mogodb 的备份与恢复,导入与导出:

mongodump 与 mongorestore是配套的,是使用bson文件作为中间载体,能保持数据的正确格式,一般我们选这两个进行导入导出。

mongoimport和mongoexport也是配套的,是使用csv或者json作为中间载体,一般不用他们来进行数据的导入导出,而是用来做数据库数据格式的转换器,将数据导出到csv或json,方便别的程序使用,别的程序再将数据保存为csv或json,输入到mongodb。

下载套件的地址都在同一个地方:

https://docs.mongodb.com/database-tools/mongodump/

使用方法:

 

# 备份某个数据库可以不要-c选项
mongodump --host <hostname:port> -d <database_name> -c <collection_name> -u <user_name> -p <passwd> -o <directory_backup>
# 也可以使用uri选项
mongodump --uri="mongodb://mongodb0.example.com:27017" ...
# 备份完之后目录结构是:directory_backup/database_name.     其中database_name目录下面包含所有的collection的备份文件 collection_name.bson 及 collection_name.metadata.json


# resotre from directory_backup/database_name/,   其中 -d database_name 中的 database_name 是导入到数据库后在数据库中的名字
mongorestore -d <database_name> <directory_backup/database_name>

 

 

mongodb 的数据库和collection的重命名,copy等:

db的重命名和copy都必须借助外部工具,不能在shell里完成,因为db.copyDatabase,db.cloneDatabase()等命令已经废弃了。使用 mongodump和mongorestore来完成,先mongodump,再mongorestore,在restore里指定-d参数即可完成复制,复制以后可以drop掉原来的db,就完成了数据库重命名。

collection的重命名可以使用shell命令:db.collection.renameCollection(), collection的复制还是得借助mongodump和mongorestore来完成,原理与复制db一样,在restore里指定-d参数即可完成复制。

 

 

mongodb数据的最大值、最小值及排序:

db.collection.find().sort({age:-1}).limit(1)     // for MAX
db.collection.find().sort({age:+1}).limit(1)     // for MIN

 

 

数据库的id问题:

使用数据库(包括mongodb)时,尽量为每个表设计一个递增的数字id,而不是使用自己乱设置的随机数字id,更不推荐用字符id。因为这种递增的数字id有个方便,就是可以排序,获取某条数据后的数据,如果使用随机或字符id,就很麻烦。

mongodb设置某个field不可重复的方法,是设置一个index,让index来保证field的唯一性:

Unique Index on a Single Field:

db.collection.createIndex( { "user_id": 1 }, { unique: true } )

其中的1表示For an ascending index on a field, specify a value of 1; for descending index, specify a value of -1。需要说明的是,index是互斥的,重复创建相同的index会被报错,使用try catch (在pymongo里)可以将其忽略。

查看index:

db.collection.getIndexes({})

另外,可以在collection被创建之前(即collection根本不存在),先为它的某field设置一个index,当这个collection创建之后,这个index会自动应用到该collection。而且在collection创建之前,为它创建的index也是可以查询到的。但是只能创建一个这样的index,为一个不存在的field创建2个index会报错,提示 duplicate key error。创建之后,这个不存在的collection就会被创建,不过内容为空。

对于pymongo这样创建index:

https://pymongo.readthedocs.io/en/stable/api/pymongo/collection.html#pymongo.collection.Collection.create_index

示例:

db.get_collection('address').create_index([('field_name', pymongo.DESCENDING)], name='your_index_name', unique=True)

当然还可以创建联合index,就是同时设置多条如: 

db.get_collection('address').create_index([('field_name', pymongo.DESCENDING), ('field_name2', pymongo.DESCENDING)], name='your_index_name', unique=True)

请注意,create_index 是创建一条index,而 create_indexex 是创建多条index,一般没必要同时创建多条,一条一条创建就可以了。

index的删除:

db.get_collection('address').drop_index('index_name')

index的查询,不准确方法,不应该用来判断index是否存在:

db.get_collection('address').create_index([('detail3', pymongo.DESCENDING), ('detail4', pymongo.DESCENDING)], name='u_name2', unique=True)
aa = db.get_collection('address').list_indexes()
for a in aa:
    print(a)
    print(a.get('key').keys())

  

此外,还可以设置复合唯一性,就是某几项不能完全一样,可以部分一样:

https://docs.mongodb.com/manual/core/index-unique/

 

 

mongodb作为工作(任务)队列的互斥数据源,驱动任务的运行:

https://stackoverflow.com/questions/32728670/mutex-with-mongodb

https://stackoverflow.com/questions/9274777/mongodb-as-a-queue-service

I am using mongodb as a queue service for email sending. Soon it will work in following way:

  1. When a new message comes I store it in the mongodb.
  2. A background job then loads the message from mongodb via the atomic operation findAndModify and sets the flag Processing to true, so it does not process same message twice (because my background job runs multiple threads in parallel).
  3. Once the email has been sent I remove the document from mongodb.
  4. You can also keep count of the failures for each message and remove it after 3 failed attempts.

In general I use mongodb as a queue service only for one reason: because I need to send emails by specified schedule (each message contains information on what time it should be sent).

If you do not have any schedule and need to process message immediately, I suggest that you look into existing queue services, because they probably handle all cases that you may not see without a deeper understanding of message queues.

Update

When background job crashes during message processing you could do following:

  1. Move this message to another, message queue errors collection or..

  2. Increase processing attempts counter in a message and again assign status "New", to try process it again. Just make sure that background job is idempotent (can process same message multiple times and not corrupt data) and transactional (when job fails you must undone changes that was made. if any). When job fails after 5 attempts (config value) perform #1.

  3. Once bug with message processing was fixed you could process it again once more by assigning "New" status and moving to the message queue, or just delete this message. It depends on business processes actually.

这种方式最大的缺点可能是吞吐量没有message queue大。

findAndModify与update的区别:

findAndModify返回document而update不返回。

如果你先取出一个document再update它,你分了两步完成了一件事,第一步和第二步之间数据可能会有其它变动,你无法保证;如果你先取出一个update再取出它,也是一样的。但是如果你使用findAndModify,它是一个原子操作,你可以想像成一步完成,如果期间有另外一个对当前document的请求,会被阻塞,直到findAndModify释放它持有的lock。

findAndModify的文档:

https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/

最重要的是:

find_and_modify is deprecated, use find_one_and_delete, find_one_and_replace, or find_one_and_update

 

posted @ 2017-08-07 17:29  微信公众号--共鸣圈  阅读(684)  评论(0编辑  收藏  举报