MongoDB

基础

原理

  1. Mongodb 并发控制之乐观锁

    Mongodb不善于处理事务,但提供了findAndModify命令。该命令允许对文档进行原子性更新,并在同一次调用中返回:

    db.collection_yown.findAndModify(
        {
          query:{"name":"yown"},update:{"version":2},new:true or false
        }
    )
    

    findAndModify是有返回值的,输出中的value字段即返回修改之前的文档,使用 new:true选项返回修改后的文档。

    update是更新操作,是没有返回值的。

    findAndModify 强调操作的原子性(atomically),比如用来实现自增1的操作或者操作队列。属于 get-and-set 式的操作,一般来讲,findAndModify 比update操作稍慢,因为需要等待数据库的响应。

    另外findAndModify ,其中modify可以是update,还可以是remove

  2. findAndModify可能引起的原子性问题

    update 方法的三个参数是upsert,这个参数是个布尔类型,默认是false。当它为true的时候,update方法会首先查找与第一个参数匹配的记录,在用第二个参数更新之,如果找不到与第一个参数匹配的的记录,就插入一条

    当findAndModify()包含upsert:true选项,并且查询字段不是唯一索引时,该方法可能会在某些情况下多次插入文档。

    如下:

    db.people.findAndModify({
    
    query: { name: "Andy" },
    
    sort: { rating: 1 },
    
    update: { $inc: { score: 1 } },
    
    upsert: true
    
    })
    

    当多个客户端同时发出了这个指令,然后在服务端并行执行,而都没有找到query的匹配,可能同时执行了多个upsert操作。导致数据重复。

    如果不使用upsert,就没有这种问题。

  3. 使用$isolation来保证隔离性

    使用$isolated操作符可以保证单个写入操作修改多个文档的时候不被交错。

    $isolated其实是在整个数据库(Mongodb的手册对这点说明不清楚,也可能是在集合层面加独占锁,但是有一点文档中是说明的,不论在哪个层面加独占锁,都会导致真个数据库单线程化)加独占锁(即使是对于WiredTiger存储引擎也是),在这期间不能进行其他任何的读写操作。所以如果$isolated的操作执行的时间过长,会大大的影响系统的并发性能。

    db.foo.update(
        { status : "A" , $isolated : 1 },
        { $inc : { count : 1 } },
        { multi: true }
    )
    
    $ isolation不保证多个文档操作的原子性。
    $ isolation保证多个文档操作不会被跟其他操作交错。
    $ isolation保证此操作在进行到某一个文档的更新的时候,在不提交或者回滚之前,不会被客户端看到。也就是说不会导致这个文档的查询产生脏读。
    

对比MySQL,你究竟在什么时候更需要MongoDB(转载)

  1. 优点:

    • 更高的写负载
      MongoDB更关注高的插入速度

    • 不可靠环境保证高可用性
      设置副本集(主-从服务器设置)不仅方便而且很快,此外,使用MongoDB还可以快速、安全及自动化的实现节点(或数据中心)故障转移

    • 未来会有一个很大的规模
      数据库扩展是非常有挑战性的,当单表格大小达到5-10GB时,MySQL表格性能会毫无疑问的降低。如果你需要分片并且分割你的数据库,MongoDB将很容易实现这一点。

      容易扩展,可以支持T级别的数据量。

    • 使用基于位置的数据查询
      MongoDB支持二维空间索引,因此可以快速及精确的从指定位置获取数据。

      (唯一索引 复合索引)

    • 非结构化数据的爆发增长
      鉴于MongoDB的弱数据结构模式,添加1个新字段不会对旧表格有任何影响,整个过程会非常快速

    • 2000-3000以上读写每秒

    • 数据模型不用确定

    • 大量地理位置查询

  1. 不足:

    • 必须避免在要求高事务安全的情景下使用MongoDB
      比如一个1000万美元的交易

    • Mongodb在保存数据的时候,不是实时写入到硬盘的,所以有可能出现数据丢失的情况

    • 多表关联仅仅支持Left Outer Join

    • 16MB文档大小限制

    • 不支持中文排序

MongoDB中的一些坑

  1. 数据库级锁

    建索引导致数据库阻塞

    建索引就是一个容易引起长时间写锁的问题,MongoDB 在前台建索引时需要占用一个写锁(而且不会临时放弃),如果集合的数据量很大,建索引通常要花比较长时间,特别容易引起问题

    解决的方法很简单,MongoDB 提供了两种建索引的访问,一种是 background 方式,不需要长时间占用写锁,另一种是非 background 方式,需要长时间占用锁。使用 background 方式就可以解决问题。

    db.posts.ensureIndex({user_id: 1}, {background: 1})
    
  2. 不合理使用嵌入 embed document

    • 解决问题的方法,就是把 embed 关联更改成的普通外键关联,就是类似关系数据库的做法

    • 当关联对象的数据不固定或者经常发生变化时,一定要避免使用 embed 关联,不然会死的很惨。

  3. 不合理使用 Array 字段

    频繁的增加修改操作导致大量长时间数据库写锁,从而引发 MongoDB 数据库性能急剧下降

    解决问题的方法:

    我们把Array转移到了内存数据库 redis 中,避免了频繁更改 MongoDB 中的 User, 从而彻底解决问题。
    
    如果不使用 redis,也可以建立一个锁集合,使用外键形式关联。
    

MongoDb安装

  1. 安装

    wget --no-check-certificate http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.6.tgz
    tar -zxvf mongodb-linux-x86_64-3.0.6.tgz
    
  2. yum安装

    增加repo库配置文件

    /etc/yum.repos.d/mongodb-org-3.2.repo
    

    内容为

1
2
3
4
5
[mongodb-org-3.2]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/7Server/mongodb-org/3.2/x86_64/
gpgcheck=0
enabled=1
安装

    yum install -y mongodb-org


启动

    service mongod restart

    mongod -f /etc/mongod.conf #如果没有正常关闭数据库,则用这个命令启动

Cent OS 7上需要把mongoDB添加到systemd,否则会出现下面的错误

    systemd[1]: Failed to start SYSV: Mongo is a scalable, document-oriented database..

    # vi /usr/lib/systemd/system/mongod.service
    [Unit]
    Description=mongodb database

    [Service]
    User=mongod
    Group=mongod
    Environment="OPTIONS=--quiet -f /etc/mongod.conf"
    ExecStart=/usr/bin/mongod $OPTIONS run
    PIDFile=/var/run/mongodb/mongod.pid

    [Install]
    WantedBy=multi-user.target

建立链接

    # ln -s /usr/lib/systemd/system/mongod.service /etc/systemd/system/multi-user.target.wants/

重新加载systemctl

    # systemctl daemon-reload

配置/etc/mongod.conf

两款Mongodb的可视化工具,支持最新的Mongodb 3.2版本。

    MongoBooster

    下载地址:http://mongobooster.com/downloads

    MongoChef

    个人免费,用于商业需要有授权

    下载地址:http://3t.io/mongochef/download/platform/

操作

  1. db.help()

查看命令提示

  • show dbs

显示数据库,需要注意的是show dbs,只会显示有数据的库,没有数据的库,是不会显示的。

  1. 根据时间查询

    在FIND里面($gte/$lte/$gt/$lt)

    {
        "startTime":
        {
            $gte:"2015/07/12"
        }
    
    }
    
    {
      "_id" : ObjectId("561b2fb705924b08dc94ff8a")
    }
    
  2. 更新列名

    在UPDATE里面

    {$rename : {"groupName" : "group_name"}}
    
  3. 删除列

    在UPDATE里面

    {$unset:{"id":10}}
    

    批量更新内容

db.rank_scores.find({}).forEach(
   function(item){
        if(item.userName.indexOf("player_")>=0)
        db.rank_scores.update({"_id":item._id},{$set:{"userName":"user_"+item.userName.substr(7)}},false,true)
   }
)
  1. 连接本机

    ./mongo --port 37017
    
报错:child process failed, exited with error number 100
没有正常关闭mongodb引起的。
先删除/home/cxy/mongodb/interstellar/db/下的mongod.lock
然后以repair的模式启动
./mongod --config ../conf/interstellar.conf --repair
然后接着在启动一次
./mongod --config ../conf/interstellar.conf
现在就可以查看到mongod的进程存在了,可以正常使用了
  1. 如何正常关闭mongodb?

    先通过shell连上服务器:

    mongo
    use admin
    db.shutdownServer()
    
  2. 创建用户

    use admin
    db.createUser(
       {
         user: "root",
         pwd: "_gamedo188",
         roles: [ "root" ]
       }
    )
    
    db.createUser(
       {
         user: "sa",
         pwd: "_gamedo188",
         roles: [ { role: "__system", db: "admin" } ]
       }
    )
    
  1. 登录用户

    db.auth("sa", "_gamedo188")
    
  2. 安全验证启动

    ./mongod --auth --config ../interstellar.conf
    
  1. 查看mongodb最大连接数

    db.serverStatus().connections
    
  2. 查看一个表wechatkeys的索引

    db.getCollection('wechatkeys').getIndexes()
    
  3. 新建一个表wechatkeys的索引key,1为升序,2为降序

    db.getCollection('wechatkeys').ensureIndex({key:1})
    
  4. 绑定数据库到指定内网ip,防止外网访问攻击

    bind_ip=127.0.0.1
    
  5. 返回部分字段

    举例:

1
2
3
4
5
6
7
8
1. 返回name和age两个字段(默认会返回_id)
db.user.find({}, {"name" : 1, "age" : 1});

2. 返回name和age两个(显示排除_id字段)
db.user.find({}, {"name" : 1, "age" : 1, "_id" : 0});

3. 不返回password字段
db.user.find({}, {"password" : 0});
  1. $exists 当前不能使用索引,但 $ne 可以
1
db.players.count({fb:{$ne:null}})

备份mongo

  1. 备份

    ./mongodump -d critz -o /www/backups/mongodb/critz-2016-02-18
    

    mongodump有一个值得一提的选项是–oplog

    ==mongodump的进行过程中并不会把数据库锁死以保证整个库冻结在一个固定的时间点,这在业务上常常是不允许的。==

    所以就有了dump的最终结果中A集合是10点整的状态,而B集合则是10点零1分的状态这种情况。这样的备份即使恢复回去,可以想象得到的结果恐怕意义有限。那么上面这个oplog.bson的意义就在这里体现出来了。如果在dump数据的基础上,再重做一遍oplog中记录的所有操作,这时的数据就可以代表dump结束时那个时间点(point-in-time)的数据库状态。

  1. 压缩

    cd /www/backups/mongodb
    tar zcvf critz-2016-02-18.tar.gz critz-2016-02-18
    cp critz-2016-02-18.tar.gz /www/dotado/jetty-8888/webapps/ROOT/
    
  2. 恢复

    ./mongorestore -d critz /www/mongodb/backups/critz-2016-03-30/critz
    
  3. shell备份脚本

    #!/bin/sh
    
    # mogodb
    mogodbdirname="interstellar-`date +%y-%m-%d`"
    mogodbfilename="`date +%y-%m-%d`.tar.gz"
    cd /www/mongodb/mongodb-linux-x86_64-3.0.6/bin/
    ./mongodump -d interstellar -o /www/backups/mongodb/$mogodbdirname
    cd /www/backups/mongodb
    tar zcvf $mogodbfilename $mogodbdirname
    

实战

实用技巧

  1. sort排序动态字段

    定义排序变量,然后使用即可

1
2
3
var sortOp = {};
sortOp[xxxxxx] = -1;
players.find({}).sort(sortOp).limit(100).exec(cb);
  1. MongoDB 实现按列累加统计
1
2
// 统计总共积分是多少
db.actives5.aggregate([{$group:{_id: null,totalScore: { $sum: '$score' }}}])
  1. 统计最高最低平均
1
2
3
db.actives5.aggregate([{$group:{_id: null,maxScore: { $max: '$score' },minScore: { $min: '$score' },avgScore: { $avg: '$score' }}}])

db.actives5.aggregate([{$match:{score:{$gte:1500}}},{$group:{_id:null,totalScore: { $sum: '$score' },maxScore: { $max: '$score' },minScore: { $min: '$score' },avgScore: { $avg: '$score' }}}])
  1. 导出导入单独表
1
2
mongoexport -d xxxxxx -o actives.json -c actives
mongoimport -d xxxxxx --file actives.json -c actives
  1. 建立索引
1
db.players.ensureIndex({fb:1})
  1. 模糊查询
1
2
3
const reg = new RegExp(data.search, 'i'); //不区分大小写
options = {content : {$regex : reg}};
db.actives5.find(options)
  1. 查看db状态
1
2
3
4
5
# mongostat
insert query update delete getmore command % dirty % used flushes vsize res qr|qw ar|aw netIn netOut conn time
2 332 32 *0 0 49|0 0.8 30.0 0 6.11G 5.23G 0|0 0|0 70.3k 1.62m 324 2018-07-25T08:37:22Z
4 364 40 2 0 48|0 0.8 30.0 0 6.11G 5.23G 0|0 0|0 114k 1.92m 324 2018-07-25T08:37:23Z
6 365 35 4 0 52|0 0.8 30.0 0 6.12G 5.23G 0|0 0|0 79.9k 1.68m 324 2018-07-25T08:37:24Z
  1. 查看mongodb的日志
1
2
3
# tail -n500 -f /var/log/mongodb/mongod.log
2018-07-24T15:19:25.880+0000 I COMMAND [ftdc] serverStatus was very slow: { after basic: 40, after asserts: 150, after connections: 180, after extra_info: 220, after globalLock: 230, after locks: 240, after network: 330, after opcounters: 450, after opcountersRepl: 510, after storageEngine: 570, after tcmalloc: 1900, after wiredTiger: 2260, at end: 2550 }
2018-07-24T15:19:25.901+0000 I COMMAND [conn31541] command admin.$cmd command: isMaster { ismaster: true } keyUpdates:0 writeConflicts:0 numYields:0 reslen:178 locks:{} protocol:op_query 383ms
  1. mongodb的快速迁移
1
2
3
4
5
6
7
8
9
mongodb数据库的迁移除了常规的mongodump、mongoexport、mongorestore、mongoimport之外,可以采用直接拷贝数据库文件的方式进行。
拷贝文件需要停止数据库服务,停止后直接拷贝数据库文件目录中的数据即可。
注意事项:
1、.lock文件不要拷贝;
2、diagnostic.data的文件夹不要拷贝,如果拷贝,在新的数据库运行时会出现错误,需要修复,时间有点长。

拷贝完成,用相同的参数和数据库版本启动数据库即可。
这样拷贝数据及完成数据迁移。
采用mongodump需要重建数据库,时间长;如果迁移的机器性能比较差,重建索引有困难。
  1. 查询数据导出
  • 新建一个js文件,将查询方法写进去,如playersPage_1_1.js,文件内容如下
1
2
3
4
5
var c = db.playersPage.aggregate([{ $match: {date:{$lt:"2018-09-15"}}},{"$group" : {_id:{lp1:"$lp1"}, count:{$sum:1}}}]);
while(c.hasNext()) {
var document = c.next();
print(document._id.lp1 + "," + document.count);
}
  • 输入命令来执行
1
mongo localhost:27017/ludosocket playersPage_1_1.js > playersPage_1_1.2018-09-17.csv

这样查询的结果就会直接生成在当前文件夹下的playersPage_1_1.2018-09-17.js文件

  1. group by count 分组count

如:查询日期小于2018-09-15,根据lp1分组的玩家总数

1
db.playersPage.aggregate([{ $match: {date:{$lt:"2018-09-15"}}},{"$group" : {_id:{lp1:"$lp1"}, count:{$sum:1}}}])

如:查询日期小于2018-09-15,根据lp1和lp2组合分组的玩家总数

1
db.playersPage.aggregate([{ $match: {date:{$lt:"2018-09-15"}}},{"$group" : {_id:{lp1:"$lp1",lp2:"$lp2"}, count:{$sum:1}}}])

数据库高可用和分区解决方案-MongoDB 篇

http://udn.yyuap.com/article-8203.html

MongoDB Sharding 集群配置

参考

  1. Sharding cluster介绍

    这是一种可以水平扩展的模式,在数据量很大时特给力,实际大规模应用一般会采用这种架构去构建monodb系统。

    MongoDB Sharding技术的应用场景:

    A.如果数据集data set大小将要或者已经超过了单个MongoDB实例的容量大小。
    
    B.活动的工作集working set大小将要超过最大物理内存大小
    
    C.单个MongoDB实例无法满足频繁的写操作。
    

    如果以上三种情况没有满足,不需要部署sharding,只会增加复制度,同时在设计数据模型时,也要考虑到以后作分片的情况。

    Sharding只会在数据量比较大的情况下才会发挥作用,默认的chunk大小是64MB,只有在满足特定条件下,balancer进程才会将数据迁移到其他shard上,否则数据会一直存储到单个shard上。

    主要是从几个方面考虑这个问题:

    1、方便扩展,如果需要横向扩展,把shardkey加上,然后加资源即可。
    2、避免不同的程序驱动对rs高可用的不一致情况。
    3、虚拟化后config的资源消耗主要是磁盘io,其他cpu、内存、磁盘容量等都几乎可以忽略不计。
    4、统一资源形式,如果同时既提供rs也提供shard的话,对MongoDB不怎么熟悉的用户其实是很容易混淆。
    

    要构建一个 MongoDB Sharding Cluster,需要三种角色:

    Shard Server: mongod 实例,用于存储实际的数据块,实际生产环境中一个shard server角色可由几台机器组个一个relica set承担,防止主机单点故障
    
    Config Server: mongod 实例,存储了整个 Cluster Metadata,其中包括 chunk 信息。
    
    Route Server: mongos 实例,前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用。用于分摊客户端的请求压力。如果使用多个mongos实例,可以使用HAProxy或者LVS等代理来转发客户端请求到后端的mongos,必须要配置成client affinity模式保证来自同一个客户端的请求转发到后端相同的mongos.通常会将mongos实例部署到应用服务器上
    
  2. 实际环境架构

    分别在3台机器运行一个mongod实例(称为mongod shard11,mongod shard12,mongod shard13)组织replica set1,作为cluster的shard1
    分别在3台机器运行一个mongod实例(称为mongod shard21,mongod shard22,mongod shard23)组织replica set2,作为cluster的shard2
    每台机器运行一个mongod实例,作为3个config server
    每台机器运行一个mongos进程,用于客户端连接
    

















主机 IP端口信息
Server110.1.1.1mongod shard11:27017
mongod shard12:27018
mongod config1:20000
mongs1:30000
Server210.1.1.2mongod shard12:27017
mongod shard22:27018
mongod config2:20000
mongs2:30000
Server310.1.1.3mongod shard13:27017
mongod shard23:27018
mongod config3:20000
mongs3:30000
  1. 软件准备

    创建用户

    groupadd -g 20001 mongodb
    useradd -u 20001 -g mongodb mongodb
    passwd mongodb
    

    安装monodb软件

    su – mongodb
    tar zxvf mongodb-linux-x86_64-1.6.2.tar
    

    安装好后,目录结构如下:

    tree mongodb-linux-x86_64-1.6.2
    mongodb-linux-x86_64-1.6.2
    |– GNU-AGPL-3.0
    |– README
    |– THIRD-PARTY-NOTICES
    `– bin
        |– bsondump
        |– mongo
        |– mongod
        |– mongodump
        |– mongoexport
        |– mongofiles
        |– mongoimport
        |– mongorestore
        |– mongos
        |– mongosniff
        `– mongostat
    

    创建数据目录

    根据本例sharding架构图所示,在各台sever上创建shard数据文件目录

    Server1:
    su – monodb
    cd /home/monodb
    mkdir -p data/shard11
    mkdir -p data/shard21
    Server2:
    su – monodb
    cd /home/monodb
    mkdir -p data/shard12
    mkdir -p data/shard22
    Server3:
    su – monodb
    cd /home/monodb
    mkdir -p data/shard13
    mkdir -p data/shard23
    
  2. 配置replica sets

    配置shard1所用到的replica sets:

    Server1:

    cd /home/mongodb/mongodb-linux-x86_64-1.6.2/bin
    ./mongod –shardsvr –replSet shard1 –port 27017 –dbpath /home/mongodb/data/shard11 –oplogSize 100 –logpath /home/mongodb/data/shard11.log –logappend –fork
    

    Server2:

    cd /home/mongodb/mongodb-linux-x86_64-1.6.2/bin
    ./mongod –shardsvr –replSet shard1 –port 27017 –dbpath /home/mongodb/data/shard12 –oplogSize 100 –logpath /home/mongodb/data/shard12.log –logappend –fork
    

    Server3:

    cd /home/mongodb/mongodb-linux-x86_64-1.6.2/bin
    ./mongod –shardsvr –replSet shard1 –port 27017 –dbpath /home/mongodb/data/shard13 –oplogSize 100 –logpath /home/mongodb/data/shard13.log –logappend –fork
    

    初始化replica set

    用mongo连接其中一个mongod,执行:

    config = {_id: ‘shard1′, members: [
                              {_id: 0, host: '10.1.1.1:27017'},
                              {_id: 1, host: '10.1.1.2:27017'},
                              {_id: 2, host: '10.1.1.3:27017'}]
               }
    
    rs.initiate(config);
    
同样方法,配置shard2用到的replica sets:

server1:

    cd /home/mongodb/mongodb-linux-x86_64-1.6.2/bin
    ./mongod –shardsvr –replSet shard2 –port 27018 –dbpath /home/mongodb/data/shard21 –oplogSize 100 –logpath /home/mongodb/data/shard21.log –logappend –fork

server2:

    cd /home/mongodb/mongodb-linux-x86_64-1.6.2/bin
    ./mongod –shardsvr –replSet shard2 –port 27018 –dbpath /home/mongodb/data/shard22 –oplogSize 100 –logpath /home/mongodb/data/shard22.log –logappend –fork

server3:

    cd /home/mongodb/mongodb-linux-x86_64-1.6.2/bin
    ./mongod –shardsvr –replSet shard2 –port 27018 –dbpath /home/mongodb/data/shard23 –oplogSize 100 –logpath /home/mongodb/data/shard23.log –logappend –fork

初始化replica set

用mongo连接其中一个mongod,执行:

    config = {_id: ‘shard2′, members: [
                              {_id: 0, host: '10.1.1.1:27018'},
                              {_id: 1, host: '10.1.1.2:27018'},
                              {_id: 2, host: '10.1.1.3:27018'}]
               }

    rs.initiate(config);
  1. 配置三台config server

    Server1:

    mkdir -p /home/mongodb/data/config
    ./mongod –configsvr –dbpath /home/mongodb/data/config –port 20000 –logpath /home/mongodb/data/config.log –logappend –fork   #config server也需要dbpath
    

    Server2:

    mkdir -p /home/mongodb/data/config
    ./mongod –configsvr –dbpath /home/mongodb/data/config –port 20000 –logpath /home/mongodb/data/config.log –logappend –fork
    

    Server3:

    mkdir -p /home/mongodb/data/config
    ./mongod –configsvr –dbpath /home/mongodb/data/config –port 20000 –logpath /home/mongodb/data/config.log –logappend –fork
    
  2. 配置mongos实例(query routers)

    在server1,server2,server3上分别执行:

    ./mongos –configdb 10.1.1.1:20000,10.1.1.2:20000,10.1.1.3:20000 –port 30000 –chunkSize 5 –logpath /home/mongodb/data/mongos.log –logappend –fork
    

    mongs不需要dbpath

  3. Configuring the Shard Cluster

    连接到其中一个mongos进程,并切换到admin数据库做以下配置

    连接到mongs,并切换到admin

    ./mongo 10.1.1.1:30000/admin
    db
    Admin
    

    加入shards

    如果shard是单台服务器,用db.runCommand( { addshard : “[:]” } )这样的命令加入,如果shard是replica sets,用replicaSetName/[:port][,serverhostname2[:port],…]这样的格式表示,例如本例执行:

    db.runCommand( { addshard : "shard1/10.1.1.1:27017,10.1.1.2:27017,10.1.1.3:27017″,name:"s1″,maxsize:20480} );
    db.runCommand( { addshard : "shard2/10.1.1.1:27018,10.1.1.2:27018,10.1.1.3:27018″,name:"s2″,maxsize:20480} );
    

    注意:在添加第二个shard时,出现error:test database 已经存在的错误,这里用mongo命令连接到第二个replica set,用db.dropDatabase()命令把test数据库给删除然后就可加入

    可选参数

    Name:用于指定每个shard的名字,不指定的话系统将自动分配
    maxSize:指定各个shard可使用的最大磁盘空间,单位megabytes
    

    Listing shards

    db.runCommand( { listshards : 1 } )
    

    如果列出了以上二个你加的shards,表示shards已经配置成功

    激活数据库分片

    命令:

    db.runCommand( { enablesharding : "<dbname>" } );
    

    通过执行以上命令,可以让数据库跨shard,如果不执行这步,数据库只会存放在一个shard,一旦激活数据库分片,数据库中不同的collection将被存放在不同的shard上,但一个collection仍旧存放在同一个shard上,要使单个collection也分片,还需单独对collection作些操作

  4. Collecton分片

    要使单个collection也分片存储,需要给collection指定一个分片key,通过以下命令操作:

    db.runCommand( { shardcollection : "<namespace>",key : <shardkeypatternobject> });
    
    注:
     a. 分片的collection系统会自动创建一个索引(也可用户提前创建好)
     b. 分片的collection只能有一个在分片key上的唯一索引,其它唯一索引不被允许
    One note: a sharded collection can have only one unique index, which must exist on the shard key. No other unique indexes can exist on the collection.
    

    分片collection例子

    db.runCommand( { shardcollection : "test.c1″,key : {id: 1} } )
    for (var i = 1; i <= 200003; i++) db.c1.save({id:i,value1:"1234567890″,value2:"1234567890″,value3:"1234567890″,value4:"1234567890″});
    db.c1.stats()
    {
            “sharded” : true,
            “ns” : “test.c1″,
            “count” : 200003,
            “size” : 25600384,
            “avgObjSize” : 128,
            “storageSize” : 44509696,
            “nindexes” : 2,
            “nchunks” : 15,
            “shards” : {
                    “s1″ : {
                            “ns” : “test.c1″,
                            “count” : 141941,
                            “size” : 18168448,
                            “avgObjSize” : 128,
                            “storageSize” : 33327616,
                            “numExtents” : 8,
                            “nindexes” : 2,
                            “lastExtentSize” : 12079360,
                            “paddingFactor” : 1,
                            “flags” : 1,
                            “totalIndexSize” : 11157504,
                            “indexSizes” : {
                                    “_id_” : 5898240,
                                    “id_1″ : 5259264
                            },
                            “ok” : 1
                    },
                    “s2″ : {
                            “ns” : “test.c1″,
                            “count” : 58062,
                            “size” : 7431936,
                            “avgObjSize” : 128,
                            “storageSize” : 11182080,
                            “numExtents” : 6,
                            “nindexes” : 2,
                            “lastExtentSize” : 8388608,
                            “paddingFactor” : 1,
                            “flags” : 1,
                            “totalIndexSize” : 4579328,
                            “indexSizes” : {
                                    “_id_” : 2416640,
                                    “id_1″ : 2162688
                            },
                            “ok” : 1
                    }
            },
            “ok” : 1
    }
    

问题汇总

  1. Mongodb 占用99%CPU并且查询速度很慢原因查找

    因为程序有将近40个线程一直在不停的查询数据库并插入, 而竟然没有在数据库中建索引
    建立索引后CPU使用率瞬间降下来了,并且速度超快,抽查也顺利起来了

分析数据库正在执行的请求

执行 db.currentOp() 命令,能看到数据库当前正在执行的操作

secs_running/microsecs_running: 这个值重点关注,代表请求运行的时间,如果这个值特别大,就得注意了,看看请求是否合理
  1. DeprecationWarning: Mongoose: mpromise (mongoose’s default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html

建立连接前增加这句即可

1
2
mongoose.Promise = global.Promise; // ADD THIS
mongoose.connect('localhost:27017/mydb');
  1. WiredTiger引起的Cannot allocate memory问题

问题日志:

1
2
3
4
2018-08-28T13:59:02.505+0000 E STORAGE  [conn6943] WiredTiger (12) [1535464742:504284][15309:0x7fc22c103700], file:collection-4--4936006946892585673.wt, WT_CURSOR.insert: memory allocation of 17522688 bytes failed: Cannot allocate memory
2018-08-28T13:59:02.510+0000 I - [conn6943] Invariant failure: ret resulted in status UnknownError: 12: Cannot allocate memory at src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp 1402
2018-08-28T13:59:02.511+0000 E STORAGE [conn6831] WiredTiger (12) [1535464742:511916][15309:0x7fc1f2167700], file:collection-4--4936006946892585673.wt, WT_CURSOR.search: memory allocation of 17522688 bytes failed: Cannot allocate memory
2018-08-28T13:59:02.511+0000 I - [conn6831] Invariant failure: ret resulted in status UnknownError: 12: Cannot allocate memory at src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp 1386

解决办法:

1
2
3
4
5
6
7
配置文件里加入了 cacheSizeGB: 3

storage:
engine: wiredTiger
wiredTiger:
engineConfig:
cacheSizeGB: 3

查看WiredTiger内部缓存到底占用了多少内存的方式是,在mongo shell中之行以下命令

1
db.runCommand( { serverStatus: 1 } ).wiredTiger.cache["bytes currently in the cache"]

如果不想重启mongoDB,可以在线修改,如下

1
db.adminCommand({setParameter: 1, wiredTigerEngineRuntimeConfig: "cache_size=8G"})

Mongoose

Node.js Mongoose 参考手册

Mongoose就是一套操作MongoDB数据库的接口

  1. Schema

    数据属性模型(传统意义的表结构)

1
2
3
4
5
6
var TestSchema = new mongoose.Schema({
name : { type:String },//属性name,类型为String
age : { type:Number, default:0 },//属性age,类型为Number,默认为0
time : { type:Date, default:Date.now },
email: { type:String,default:''}
});
  1. Model

    具有数据库操作的行为,类似于管理数据库属性、行为的类

1
2
3
var db = mongoose.connect("mongodb://127.0.0.1:27017/test");
// 创建Model
var TestModel = db.model("test1", TestSchema);
  1. Entity

    由Model创建的实体,使用save方法保存数据

1
2
3
4
5
var TestEntity = new TestModel({
name : "Lenka",
age : 36,
email: "lenka@qq.com"
});
  1. ObjectId

    默认的主键_id

    ObjectId是一个12字节的 BSON 类型字符串。按照字节顺序,依次代表:

1
2
3
4
4字节:UNIX时间戳
3字节:表示运行MongoDB的机器
2字节:表示生成此_id的进程
3字节:由一个随机数开始的计数器生成的值
  1. Model 文档操作
  • 查询
1
2
3
4
5
6
7
8
9
10
11
12
13
model.find({}, callback);

model.find({},field,callback);
过滤查询,参数2: {‘name’:1, ‘age’:0} 查询文档的返回结果包含name , 不包含age.(_id默认是1)

model.find({},null,{limit:20});
过滤查询,参数3: 游标操作 limit限制返回结果数量为20个,如不足20个则返回所有.

model.findOne({}, callback);
查询找到的第一个文档

model.findById(‘obj._id’, callback);
查询找到的第一个文档,同上. 但是只接受 __id 的值查询

条件查询:

1
2
3
4
5
6
7
“$lt”	小于
“$lte” 小于等于
“$gt” 大于
“$gte” 大于等于
“$ne” 不等于
Model.find({“age”:{ “$get”:18 , “$lte”:30 } } );
查询 age 大于等于18并小于等于30的文档

或查询 OR:

1
2
3
4
5
6
7
8
9
‘$in’ 一个键对应多个值
‘$nin’ 同上取反, 一个键不对应指定值
“$or” 多个条件匹配, 可以嵌套 $in 使用
“$not” 同上取反, 查询与特定模式不匹配的文档
Model.find({“age”:{ “$in”:[20,21,22.‘haha’]} } );
查询 age等于20或21或21或’haha’的文档

Model.find({"$or" : [ {‘age’:18} , {‘name’:‘xueyou’} ] });
查询 age等于18 或 name等于’xueyou’ 的文档

类型查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
null 能匹配自身和不存在的值, 想要匹配键的值 为null, 就要通过  “$exists” 条件判定键值已经存在
"$exists" (表示是否存在的意思)

Model.find(“age” : { “$in” : [null] , “exists” : true } );
查询 age值为null的文档

Model.find({name: {$exists: true}},function(error,docs){
//查询所有存在name属性的文档
});

Model.find({telephone: {$exists: false}},function(error,docs){
//查询所有不存在telephone属性的文档
});

正则表达式:

MongoDb 使用 Prel兼容的正则表达式库来匹配正则表达式

1
2
3
4
5
find( {“name” : /joe/i } )
查询name为 joe 的文档, 并忽略大小写

find( {“name” : /joe?/i } )
查询匹配各种大小写组合

查询数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Model.find({“array”:10} );
查询 array(数组类型)键中有10的文档, array : [1,2,3,4,5,10] 会匹配到

Model.find({“array[5]”:10} );
查询 array(数组类型)键中下标5对应的值是10, array : [1,2,3,4,5,10] 会匹配到

‘$all’ 匹配数组中多个元素

Model.find({“array”:[5,10]} );
查询 匹配array数组中 既有5又有10的文档

‘$size’ 匹配数组长度

Model.find({“array”:{"$size" : 3} } );
查询 匹配array数组长度为3 的文档

‘$slice’ 查询子集合返回

Model.find({“array”:{"$slice" : 10} } );
查询 匹配array数组的前10个元素

Model.find({“array”:{"$slice" : [5,10] } } );
查询 匹配array数组的第5个到第10个元素

where

用它可以执行任意javacript语句作为查询的一部分,如果回调函数返回 true 文档就作为结果的一部分返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
find(
{"$where" : function(){
for( var x in this ){
//这个函数中的 this 就是文档
}

if(this.x !== null && this.y !== null){
return this.x + this.y === 10 ? true : false;
}else{
return true;
}
}
}
)

简化版本

1
2
find( {"$where" :  "this.x + this.y === 10" } )
find( {"$where" : " function(){ return this.x + this.y ===10; } " } )

游标:

1
2
3
limit(3)	限制返回结果的数量,
skip(3) 跳过前3个文档,返回其余的
sort( {“username”:1 , “age”:-1 } ) 排序 键对应文档的键名, 值代表排序方向, 1 升序, -1降序
  • 创建, 在集合中创建一个文档
1
Model.create(文档数据, callback))
  • 更新,参数1:查询条件, 参数2:更新对象,可以使用MondoDB的更新修改器
1
Model.update(conditions, update, function(error)

更新修改器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
‘$inc’ 增减修改器,只对数字有效.下面的实例: 找到 age=22的文档,修改文档的age值自增1

Model.update({‘age’:22}, {’$inc’:{‘age’:1} } );
执行后: age=23

‘$set’ 指定一个键的值,这个键不存在就创建它.可以是任何MondoDB支持的类型.

Model.update({‘age’:22}, {’$set’:{‘age’:‘haha’} } );
执行后: age=‘haha’

‘$unset’ 同上取反,删除一个键

Model.update({‘age’:22}, {’$unset’:{‘age’:‘haha’} } );
执行后: age键不存在

数组修改器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
‘$push’ 给一个键push一个数组成员,键不存在会创建

Model.update({‘age’:22}, {’$push’:{‘array’:10} } );
执行后: 增加一个 array 键,类型为数组, 有一个成员 10

‘$addToSet’ 向数组中添加一个元素,如果存在就不添加

Model.update({‘age’:22}, {’$addToSet’:{‘array’:10} } );
执行后: array中有10所以不会添加

‘$each’ 遍历数组, 和 $push 修改器配合可以插入多个值

Model.update({‘age’:22}, {’$push’:{‘array’:{’$each’: [1,2,3,4,5]}} } );
执行后: array : [10,1,2,3,4,5]

‘$pop’ 向数组中尾部删除一个元素

Model.update({‘age’:22}, {’$pop’:{‘array’:1} } );
执行后: array : [10,1,2,3,4] tips: 将1改成-1可以删除数组首部元素

‘$pull’ 向数组中删除指定元素

Model.update({‘age’:22}, {’$pull’:{‘array’:10} } );
执行后: array : [1,2,3,4] 匹配到array中的10后将其删除
  • 删除, 参数1:查询条件
1
Model.remove(conditions,callback);
  1. Entity 文档操作

构造函数, 其实就是model的实例

1
new TestModel( { name:‘xueyou’, age:21 } );

创建, 在集合中创建一个文档.

1
Entity.save(callback);

实战

千万数据维护

  1. 后台索引
  • 索引创建方式

https://blog.csdn.net/wll_1017/article/details/78029273

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
前台方式
缺省情况下,当为一个集合创建索引时,这个操作将阻塞其他的所有操作。即该集合上的无法正常读写,直到索引创建完毕
任意基于所有数据库申请读或写锁都将等待直到前台完成索引创建操作

后台方式
将索引创建置于到后台,适用于那些需要长时间创建索引的情形
这样子在创建索引期间,MongoDB依旧可以正常的为提供读写操作服务
等同于关系型数据库在创建索引的时候指定online,而MongoDB则是指定background
其目的都是相同的,即在索引创建期间,尽可能的以一种占用较少的资源占用方式来实现,同时又可以提供读写服务
后台创建方式的代价:索引创建时间变长

后台创建索引的示例
db.people.createIndex( { zipcode: 1}, {background: true} )
db.people.createIndex( { city: 1}, {background: true, sparse: true } )

缺省情况下background选项的值为false
  • 查看索引创建进度
1
2
3
4
5
6
7
8
db.currentOp(
{
$or: [
{ op: "command", "query.createIndexes": { $exists: true } },
{ op: "insert", ns: /\.system\.indexes\b/ }
]
}
)

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
{
"inprog" : [
{
"desc" : "conn1", //连接描述
"threadId" : "139911670933248", //线程id
"connectionId" : 1,
"client" : "127.0.0.1:37524", //ip及端口
"active" : true, //活动状态
"opid" : 5014925,
"secs_running" : 21, //已执行的时间
"microsecs_running" : NumberLong(21800738),
"op" : "command",
"ns" : "test.$cmd",
"query" : {
"createIndexes" : "inventory", //这里描述了基于inventory正在创建索引
"indexes" : [
{
"ns" : "test.inventory",
"key" : {
"item" : 1,
"unique" : true
},
"name" : "item_1_unique_true"
}
]
},
"msg" : "Index Build Index Build: 3103284/5000000 62%", //这里是完成的百分比
"progress" : {
"done" : 3103722,
"total" : 5000000
},
"numYields" : 0,
"locks" : { //当前持有的锁
"Global" : "w",
"Database" : "W",
"Collection" : "w"
},
"waitingForLock" : false,
"lockStats" : { //锁的状态信息
"Global" : {
"acquireCount" : {
"r" : NumberLong(1),
"w" : NumberLong(1)
}
},
"Database" : {
"acquireCount" : {
"W" : NumberLong(1)
}
},
"Collection" : {
"acquireCount" : {
"w" : NumberLong(1)
}
}
}
}
],
"ok" : 1
}

==经估算:9275912条数据大概要50小时完成索引的建立==

  • 终止索引的创建
1
db.killOp(opid) // opid为上面操作的参数opid值
  • 索引创建期间注意事项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
如前所述,基于后台创建索引时,其他的数据库操作能被完成。但是对于mongo shell会话或者你正在创建索引的这个连接
将不可用,直到所有创建完毕。如果需要做一些其它的操作。则需要再建立其它的连接。

在索引创建期间,即使完成了部分索引的创建,索引依旧不可用,但是一旦创建完成即可使用。

基于后台创建索引期间不能完成涉及该集合的相关管理操作
repairDatabase
db.collection.drop()
compact

意外中断索引创建
如果在后台创建索引期间,mongod实例异常终止,当mongod实例重新启动后,未完成的索引创建将作为前台进程来执行
如果索引创建失败,比如由于重复的键等,mongod将提示错误并退出
在一个索引创建失败后启动mongod,可以使用storage.indexBuildRetry or --noIndexBuildRetry跳过索引创建来启动
  • 索引创建期间性能
1
2
后台创建索引比前台慢,如果索引大于实际可用内存,则需要更长的时间来完成索引创建
所有涉及到该集合的相关操作在后台期间其执行效能会下降,应在合理的维护空挡期完成索引的创建
  1. 随机数更新字段内容

把name字段内容为Guest的改成Guest+4位随机数

1
2
3
4
5
db.robots.find({name:"Guest"}).forEach(
function(item){
db.robots.update({"_id":item._id},{$set:{name:"Guest" + Math.floor(Math.random() * 10000 + 10000)%10000}})
}
)

问题汇总

  1. 频繁保存更新Mix字段的被覆盖问题

a