MongoDB是一款为web应用程序和互联网基础设施设计的数据库管理系统。没错MongoDB就是数据库,是NoSQL类型的数据库。java
(1)MongoDB提出的是文档、集合的概念,使用BSON(类JSON)做为其数据模型结构,其结构是面向对象的而不是二维表,存储一个用户在MongoDB中是这样子的。mysql
{ username:'123', password:'123' }
使用这样的数据模型,使得MongoDB能在生产环境中提供高读写的能力,吞吐量较于mysql等SQL数据库大大加强。linux
(2)易伸缩,自动故障转移。易伸缩指的是提供了分片能力,能对数据集进行分片,数据的存储压力分摊给多台服务器。自动故障转移是副本集的概念,MongoDB能检测主节点是否存活,当失活时能自动提高从节点为主节点,达到故障转移。ios
(3)数据模型由于是面向对象的,因此能够表示丰富的、有层级的数据结构,好比博客系统中能把“评论”直接怼到“文章“的文档中,而没必要像myqsl同样建立三张表来描述这样的关系。git
(1)文档数据类型
SQL类型的数据库是正规化的,能够经过主键或者外键的约束保证数据的完整性与惟一性,因此SQL类型的数据库经常使用于对数据完整性较高的系统。MongoDB在这一方面是不如SQL类型的数据库,且MongoDB没有固定的Schema,正由于MongoDB少了一些这样的约束条件,可让数据的存储数据结构更灵活,存储速度更加快。web
(2)即时查询能力
MongoDB保留了关系型数据库即时查询的能力,保留了索引(底层是基于B tree)的能力。这一点汲取了关系型数据库的优势,相比于同类型的NoSQL redis 并无上述的能力。redis
(3)复制能力
MongoDB自身提供了副本集能将数据分布在多台机器上实现冗余,目的是能够提供自动故障转移、扩展读能力。spring
(4)速度与持久性sql
MongoDB的驱动实现一个写入语义 fire and forget ,即经过驱动调用写入时,能够当即获得返回获得成功的结果(即便是报错),这样让写入的速度更加快,固然会有必定的不安全性,彻底依赖网络。mongodb
MongoDB提供了Journaling日志的概念,实际上像mysql的bin-log日志,当须要插入的时候会先往日志里面写入记录,再完成实际的数据操做,这样若是出现停电,进程忽然中断的状况,能够保障数据不会错误,能够经过修复功能读取Journaling日志进行修复。
(5)数据扩展
MongoDB使用分片技术对数据进行扩展,MongoDB能自动分片、自动转移分片里面的数据块,让每个服务器里面存储的数据都是同样大小。
MongoDB核心服务器主要是经过mongod程序启动的,并且在启动时不需对MongoDB使用的内存进行配置,由于其设计哲学是内存管理最好是交给操做系统,缺乏内存配置是MongoDB的设计亮点,另外,还可经过mongos路由服务器使用分片功能。
MongoDB的主要客户端是能够交互的js shell 经过mongo启动,使用js shell能使用js直接与MongoDB进行交流,像使用sql语句查询mysql数据同样使用js语法查询MongoDB的数据,另外还提供了各类语言的驱动包,方便各类语言的接入。
mongodump和mongorestore,备份和恢复数据库的标准工具。输出BSON格式,迁移数据库。
mongoexport和mongoimport,用来导入导出JSON、CSV和TSV数据,数据须要支持多格式时有用。mongoimport还能用与大数据集的初始导入,可是在导入前顺便还要注意一下,为了能充分利用好mongoDB一般须要对数据模型作一些调整。
mongosniff,网络嗅探工具,用来观察发送到数据库的操做。基本就是把网络上传输的BSON转换为易于人们阅读的shell语句。
所以,能够总结获得,MongoDB结合键值存储和关系数据库的最好特性。由于简单,因此数据极快,并且相对容易伸缩还提供复杂查询机制的数据库。MongoDB须要跑在64位的服务器上面,且最好单独部署,由于是数据库,因此也须要对其进行热备、冷备处理。
由于本篇文章不是API手册,全部这里对shell的使用也是基础的介绍什么功能能够用什么语句,主要是为了展现使用MongoDB shell的方便性,若是须要知道具体的MongoDB shell语法能够查阅官方文档。
use dba
建立数据库并非必须的操做,数据库与集合只有在第一次插入文档时才会被建立,与对数据的动态处理方式是一致的。简化并加速开发过程,并且有利于动态分配命名空间。若是担忧数据库或集合被意外建立,能够开启严格模式。
db.users.insert({username:"smith"}) db.users.save({username:"smith"})
区别: 若新增的数据中存在主键 ,insert() 会提示错误,而save() 则更改原来的内容为新内容。如:
已存在数据:{_id : 1, " name " : " n1 " },再次进行插入操做时,insert({_id : 1, " name " : " n2 " }) 会报主键重复的错误提示,save({ _id : 1, " name " : " n2 " }) 会把 n1 修改成 n2 。
相同点: 若新增的数据中没有主键时,会增长一条记录。
已存在数据:{ _id : 1, " name " : " n1 " },再次进行插入操做时,insert({ " name " : " n2 " }) 插入的数据由于没有主键,因此会增长一条数据,save({ " name " : " n2 " }) 增长一条数据。
db.users.find() db.users.count()
db.users.update({username:"smith"},{$set:{country:"Canada"}}) //把用户名为smith的用户的国家改为Canada
db.users.update({username:"smith"},{$unset:{country:1}}) //把用户名为smith的用户的国家字段给移除
db.users.update({username:"jones"},{$set:{favorites:{movies:["casablance","rocky"]}}}) //这里主要体现多值修改,在favorties字段中添加多个值
db.users.update({"favorites.movies":"casablance"},{$addToSet:{favorites.movies:"the maltese"}},false,true) //多项更新
db.foo.remove() //删除全部数据 db.foo.remove({favorties.cities:"cheyene"}) //根据条件进行删除 db.drop() //删除整个集合
db.numbers.ensureIndex({num:1}) //建立一个升序索引 db.numbers.getIndexes() //获取所有索引
show dbs //查询全部数据库 show collections //显示全部表 db.stats() //显示数据库状态信息 db.numbers.stats() //显示集合表状态信息 db,shutdownServer() //中止数据库 db.help() //获取数据库操做命令 db.foo.help() //获取表操做命令 tab 键 //能自动帮咱们补全命令
以上的命令只是简单实例,假设若是你以前没有学习过任何数据库语法,同时开始学sql查询语法和MongoDB 查询语法,你会发现哪个更简单呢?若是你使用的是java驱动去操做MongoDB,你会发现任何的查询都像Hibernate提供出来的查询方式同样,只要构建好一个查询条件对象,便能轻松查询(接下来会给出示例),博主以前熟悉ES6,因此入手MongoDB js shell完成没问题,也正由于这样简洁,完善的查询机制,深深的爱上了MongoDB。
使用java驱动连接MongoDB是一件很是简单的事情,简单的引用,简单的作增删改查。在使用完java驱动后我才发现spring 对MongoDB 的封装还不如官方自身提供出来的东西好用,下面简单的展现一下使用。
<dependency> <groupId>org.mongodbgroupId> <artifactId>mongodb-driver-syncartifactId> <version>3.8.0-beta3version> dependency>
MongoClient client = MongoClients.create(“mongodb://10.201.76.94:27017”);
public long count() { MongoClient client = this.getClient(); MongoCollection collections= client.getDatabase("mongodb_db_name").getCollection("mongodb_collection_name"); return collections.count(); }
public List find(Document params,Bson sort,int skip,int limit) { MongoClient client = this.getClient(); MongoCollection collections= client.getDatabase("mongodb_db_name").getCollection("mongodb_collection_name"); List list = new ArrayList(Integer.valueOf(config.getPro("sync_limit"))); collections.find(params).sort(sort).skip(skip).limit(limit).forEach(new Block() { @Override public void apply(Document document) { list.add(document); } }); return list; }
这里只举例了简单的连接与简单的MongoDB操做,可见其操做的容易性。使用驱动时是基于TCP套接字与MongoDB进行通讯的,若是查询结果较多,刚好没法所有放进第一服务器中,将会向服务器发送一个getmore指令获取下一批查询结果。
插入数据到服务器时间,不会等待服务器的响应,驱动会假设写入是成功的,实际是使用客户端生成对象id,可是该行为能够经过配置配置,能够经过安全模式开启,安全模式能够校验服务器端插入的错误。
要清楚了解MongoDB的基本数据单元。在关系型数据库中有带列和行的数据表。而MongoDB数据的基本单元是BSON文档,在键值中有指向不定类型值的键,MongoDB拥有即时查询,但不支持联结操做,简单的键值存储只能根据单个键来获取值,不支持事务,但支持多种原子更新操做。
如读写比是怎样的,须要何种查询,数据是如何更新的,会不会存在什么并发问题,数据结构化的程度是要求高仍是低。系统自己的需求决定mysql仍是MongoDB。
内嵌与引用 :当子对象老是出如今父对象的上下文中时,使用内嵌文档;不然将子对象单独存一个集合。
一对多的关系
:在“多”的集合关系中添加id指向依赖的id。
多对多
:在其中一种对应关系中使用对象数组指向另一个对象。
树
:具化路径,在树中的每一个节点都包含一个path字段,该字段具体保存了每一个节点祖先的id。
动态属性
:能够为不一样的动态属性添加索引,若是须要将属性圈在一个范围,那么能够经过key-value的方式,而后在统一的key上面加索引。
关于事务
:若是须要事务支持,那么只能选择另外一种数据库,或者提供补偿性事务来解决事务的问题。
在关于schema 的设计中要注意一些原则,好比:
(1)关注数据库的概念
数据库是集合的逻辑与物理分组,MongoDB没有提供建立数据库的语法,只有在插入集合时,数据库才开始创建。建立数据库后会在磁盘分配一组数据文件,全部集合、索引和数据库的其余元数据都保存在这些文件中,查阅数据库使用磁盘状态可经过。
db.stats()
(2)关注集合概念
集合是结构上或概念上类似得文档的容器,集合的名称能够包含数字、字母或 . 符号,但必须以字母或数字开头,彻底。
限定集合名不能超过128个字符,实际上 . 符号在集合中颇有用,能提供某种虚拟命名空间,这是一种组织上的原则,和其余集合是一视同仁的。在集合中可使用。
system.namespaces //查询当前数据库中定义的全部命名空间 system.indexes //存储当前数据库的全部索引定义
(3)关注文档
其次是键值,在MongoDB里面全部的字符串都是UTF-8类型。数字类型包括double、int、long。日期类型都是UTC格式,因此在MongoDB里面看到的时间会比北京时间慢8小时。整个文档大小会限制在16m之内,由于这样能够防止建立难看的数据类型,且小文档能够提高性能,批量插入文档理想数字范围是10~200,大小不能超过16MB。
(1)索引能显著减小获取文档的所需工做量,具体的对比能够经过 .explain()方法进行对比
(2)解析查询时MongoDB经过最优计划选择一个索引进行查询,当没有最适合索引时,会先不一样的使用各个索引进行查询,最终选出一个最优索引作查询
(3)若是有一个a-b的复合索引,那么仅针对a的索引是冗余的
(4)复合索引里的键的顺序是很重要的
(1)单键索引
(2)复合索引
(3)惟一性索引
(4)稀疏索引
如索引的字段会出现null的值,或是大量文档都不包含被索引的键。
若是数据集很大时,构建索引将会花费很长的时间,且会影响程序性能,可经过
db.currentOp() //查看索引的构建时间
当使用 mongorestore 时会从新构建索引。当曾经执行过大规模的删除时,可以使用
db.values.reIndex()
对索引进行压缩,重建。
(1)查阅慢查询日志
grep -E '([0-9])+ms' mongod.log //使用grep 命令 识别命令信息 db.setProfillingLevel(2) //使用解刨器,将记录每次的读写到日志 db.setProfillingLevel(1) //只记录慢(100ms)操做
(2)分析慢查询
db.values.find({}).sort({close:-1}).limit(1).explain() scanOrder 字段代表没有使用索引
cursor当没有索引时,用的是BasicCursor,当使用索引时使用的是BtreeCursor
n 表示须要返回的结果集
nscanned表示须要遍历的文档数 indexBounds 表示索引边界
注意新版本的MongoDB 的explain方法是须要参数的,否则只显示普通的信息。
本节一样主要简单呈现MongoDB副本集搭建的简易性,与副本集的强壮性,监控容易性
提供主从复制能力,热备能力,故障转移能力
rs.initiate() rs.add("localhost:40001") rs.add("localhost:40002",{arbiterOnly:true})
db.isMasrter() rs.status()
实际上MongoDB对副本集的操做跟mysql主从操做是差很少的,先看一下mysql的主从数据流动过程
主binlog -> 从relay.log -> 从bin.log -> 从数据库
而MongoDB主要依赖的日志文件是oplog
主oplog -> 从oplog
写操做先被记录下来,添加到主节点的oplog里。与此同时,全部从结点复制oplog。首先,查看本身oplog里最后一条的时间戳;其次,查询主节点oplog里全部大于此时间戳的条目;最后,把那些条目添加到本身的oplog里并应用到本身的库里。从节点使用长轮询当即应用来自主结点oplog的新条目。
当遇到如下状况,从节点会中止复制
local数据库保存了全部副本集元素据和oplog日志
可使用如下命令查看复制状况
db.oplog.rs.findOne()
每一个副本集成员每秒钟ping一次其余全部成员,能够经过rs.status()看到节点上次的心跳检测时间戳和健康情况。
这个点不必过多描述,可是有一个特殊场景,若是从节点和仲裁节点都被杀了,只剩下主节点,他会把本身降级成为从节点。
若是主节点的数据尚未写到从库,那么数据不能算提交,当该主节点变成从节点时,便会触发回滚,那些没写到从库的数据将会被删除,能够经过rollback子目录中的BSON文件恢复回滚的内容。
(1)使用单节点连接
只能连接到主节点,若是连接到从节点的话,会被拒绝写入操做,可是若是没有使用安全模式,由于mongo的fire and forget 特性,会把拒绝写入的异常给吃掉。
(2)使用副本集方式连接
能根据写入的状况自动进行故障转移,可是当副本集进行新的选举时,仍是会出现故障,若是不使用安全模式,依旧会出现写不进去,但现实成功的状况。
(3)写关注
可使用写关注来关注数据是否已经被写入MongoDB的库中,使用写关注会消耗性能,须要在速度和持久性之间作出权衡。
分片是数据库切分的一个概念实现,这里也是简单总结为何要使用分片以及分片的原理,操做。
当数据量过大,索引和工做数据集占用的内存就会愈来愈多,因此须要经过分片负载来解决这个问题
(1)分片组件
分片:每一个分片都是一个副本集
mongos路由器:是一个路由器,将读写请求指引到合适的分片上
配置服务器config:持久化分片集群的元数据,包括:全局集群配置;每一个数据库、集合和特定范围数据位置;一份变动记录,保存了数据在分片之间进行迁移的历史信息。配置服务器之间不是副本集形式存在,mongos向配置服务器提交信息时是两阶段提交,保证配置服务器之间的一致性。
(2)分片的核心操做
分片一个集合:分片是根据一个属性的范围进行划分的,MongoDB使用所谓的分片键让每一个文档在这些范围里找到本身的位置
块:是位于一个分片中的一段连续的分片键范围,能够理解为若干个块组成分片,分片组成MongoDB的所有数据
(3)拆分与迁移
块的拆分:初始化时只有一个块,达到最大块尺寸64MB或100000个文档就会触发块的拆分。把原来的范围一分为二,这样就有了两个块,每一个块都有相同数量的文档。
迁移:当分片中的数据大小不一时会产生迁移的动做,好比分片A的数据比较多,会将分片A里面的一些块转移到分片B里面去。分片集群经过在分片中移动块来实现均衡,是由名为均衡器的软件进程管理的,任务是确保数据在各个分片中保持均匀分布,当集群中拥有块最多的分片与拥有块最少分片的块差大于8时,均衡器就会发起一次均衡处理。
启动两个副本集、三个配置服务器、一个mongos进程
配置分片
sh.help() //查看分片相关帮助 sh.addShard() //添加分片 db,getSiblingDB("config").shards.find() //查看分片列表 sh.status() //分片详情 sh.enableSharding("cloud-docs") //开启一个数据库上的分片 db.getSiblingDB("config").databases,find() //查看数据库列表 sh.shardCollection("cloud-docs.spreadsheets",{username:1,_id:1}) //使用一个分片键定义一个分片集合spreadsheets,根据用户名进行切分 sh.getSiiblingDB("config").collections.findOne() //查看集合列表 db.chunks.count() //查看块的个数 db.chunks.findOne() //查看块的信息 db.changelog.count(}what:"split"|) //查看块切分日志 db.changelog.find({what:"moveChunk.commit"}).count() //查看日志迁移记录
(1)分片查询类型
针对性查询:查询包含分片键
全局查询或分散/汇集查:查询不包含分片键
查询过程:经过分片键将查询路由给指定分片,一旦到了某个分片上,由分片自行决定使用哪一个索引来执行该查询
(2)索引
每一个分片都维护了本身的索引,当在分片集合上声明索引时,每一个分片都会为它那部分集合构建独立的索引,每一个分片上的分片集合都应该拥有相同的索引。
分片集合只容许在_id字段和分片键上添加惟一性索引,其余地方不行,由于这须要在分片间进行通讯,实施起来很复杂。
当建立分片时,会根据分片键建立一个索引。
(1)分片键是不可修改的、分片键的选择很是重要
(2)低效的分片键
分布性差:如使用BSON对象ID,那么会致使全部最新插入的文档都会落到某个很小的连续范围,没法分散插入
缺少局部性:升序分片键有明确的方向,彻底随机的分片键则根本没有方向。前者没法分散插入,后者插入分散,如使用MD5做为分片键
(3)理想的分片键
将插入数据均匀分布到各个分片上
保证CRUD操做可以利用局部性 有足够的粒度进行块拆分
知足这些要求的分片键一般由两个字段组成,第一个是粗粒度的,第二个粒度较细
(1)部署拓扑
根据不一样的数据中心划分
(2)最低要求
(3)配置的注意事项
须要估计集群大小,可以使用如下命令对现有集合进行分片处理
sh.splitAt("cloud-docs.spreadsheets",{"username":"chen","_id":ObjectId("")}) //手动拆分块 sh.moveChunk("cloud-docs.spreadsheets",{username:"chen"},"shardB") //手动将某分块移至分片B db.runCommand({removeshard:"shard-1/arete:30100,arete:30101"}) //删除分片 db.runCommand({moveprimary:"test",to:"shard-0-test-rs"}); //移动主分片
(4)备份分片集群
备份分片时须要中止均衡器
db.settings.update({_id:"ba;ancer"},{$set:{stopped:true},true}); sh.setBalancerState(false); //中止均衡器,此时均衡器将进行最后一轮均衡 db.locks.find({_id:"balancer"}); sh.isBalancerRunning(); //查看均衡器状态,任何状态大于0 的状态值都说明均衡器仍在进行中
(1)部署架构
使用64位机器、32位机器会制约mongodb的内存,使其最大值为1.5GB
(2)cpu
mongodb 只有当索引和工做集均可放入内存时,才会遇到CPU瓶颈,CPU在mongodb使用中的做用是用来检索数据,若是看到CPU使用饱和的状况,能够经过查询慢查询日志,排查是否是查询的问题致使的,若是是能够经过添加索引来解决问题
mongodb写入数据时会使用到CPU,可是mongodb写入时间一次只用到一个核,若是有频繁的写入行为,能够经过分片来解决这个问题
(3)内存
大内存是mongodb的保障,若是工做集大小超过内存,将会致使性能降低,由于这将会增长数据加载入内存的动做
(4)硬盘
mongodb默认每60s会与磁盘强制同步一次,称为后台刷新,会产生I/O操做。在重启时mongodb会将磁盘里面的数据加载至内存,高速磁盘将会减小同步的时间
(5)文件系统
使用ext4 和 xfs 文件系统
禁用最后访问时间
vim /etc/fstab
(6)文件描述符
linux 默认文件描述符是1024,须要大额度的提高这个额度
(7)时钟
mongodb各个节点服务器之间使用ntp服务器
(1)绑定IP
启动时使用 - -bind_ip 命令
(2)身份验证
启动时使用 - -auth 命令
db.addUser("","",true) //建立用户,最后一个参数指定是否只读
(3)副本集身份认证
使用keyFile,注意keyFile文件的权限必须是600,否则会启动不起来
mongoimport mongoexport
(1)拓扑结构
搭建副本集至少须要两个节点,其中仲裁结点不须要有本身的服务器
(2)Journaling日志
写数据时会先写入日志,而此时的数据也不是直接写入硬盘,而是写入内存
可是Journaling日志会消耗内存,因此能够在主库上面关闭,在从库上面启动
能够单独为Journaling日志使用一块固态硬盘
在插入时,能够经过驱动确保Journaling插入后再反馈,可是会很是影响性能。
logpath 选项指定日志存储地址 -vvvvv 选项(v越多,输出越详细) db.runCommand({logrotare:1}) 开启滚动日志
(1)serverStatus
(2)top
(3)db.currentOp()
动态展现mongodb活动数据
占用当前mongodb监听端口往上1000号的端口
(1)mongodump
把数据库内容导出成BSON文件,而mongorestore能读取并还原这些文件
(2)mongorestore
把导出的BSON文件还原到数据库
(3)备份原始数据文件
能够这么作,可是,操做以前须要进行锁库处理 db.runCommand({fsync:1,lock:true})
db.$cmd.sys.unlock.findOne() 请求解锁操做,可是数据库不会马上解锁,须要使用db.currentOp()验证。
(1)修复
mongd --repair 修复全部数据库
db.runCommand({repairDatabase:1}) 修复单个数据库
修复就是根据Jourling文件读取和重写全部数据文件并重建各个索引
(2)压紧
db.spreadsheets.reIndex() //重建索引
db.runCommand({compact:"spreadsheets"})
压紧,会重写数据文件,并重建集合的所有索引,须要停机或者在从库上面运行,若是须要在主库上面运行,须要添加force参数 保证加写锁。
(1)监控磁盘状态
iostat
(2)为提高性能检查索引和查询
总的来讲,扫描尽量少的文档。
保证没有冗余的索引,冗余的索引会占用磁盘空间、消耗更多的内存,在每次写入时还需作更多工做
(3)添加内存
db.stats() //查看数据库数据占用大小状态
dataSize 数据大小 和 indexSize 索引大小,若是二者的和大于内存,那么将会影响性能。
storageSize超过dataSize 数据大小 两倍以上,就会因磁盘碎片而影响性能,须要压缩。