• 1. MongoDB探讨刘惠庭
  • 2. 目录BSON类型比较 索引 底层存储格式格式及实现 游标 日志 复制 分片
  • 3. BSON类型比较上次分享遗留的第一个问题: ------- 不同类型之间如何比较,以下是从小到大的排序 ------- 这是官方的说法,但与我的实际测试不相符 Null Numbers (ints, longs, doubles) Symbol, String Object, document Array BinaryData ObjectID Boolean Date, Timestamp Regular Expression
  • 4. BSON类型比较注:Sort({x:1})表示按照x的升序排列
  • 5. 目录BSON类型比较 索引 底层存储格式格式及实现 游标 日志 复制 分片
  • 6. 索引 虽然MongoDB的索引在存储结构上都是一样的,但是根据不同的应用层需求,还是分成了唯一索引(unique)、稀疏索引(sparse)、多值索引(multikey)等几种类型。 创建索引用ensureIndex()方法,例如: >db.people.ensureIndex({“username” :1,”date”:-1}) 注:1表示升序,-1表示降序   对某个键创建键的索引会加速对该键的查询,然而对其他查询可能没有帮助,即便是查询包含了该索引的键。 上次分享留下的问题:文档的键不要求统一,那么有的文档没有索引键,会出现什么情况? 如果没有对应的键,索引会将其作为null存储。所以,如果对某个键建立了唯一索引,但插入了多个缺少该索引键的文档,则由于文档包含null值而导致插入失败,只有第一个插入成功。如果我们不希望这些值为空的行参与到我们的索引中,这时候可以采用松散索引,松散索引只会让指定字段不为空的行参与到索引创建中来。创建一个松散索引可以用下面的命令: >db.people.ensureIndex({“username”: 1}, {“sparse”: true}) 同样,当为已有的集合创建唯一索引,可能有些值已经有重复了,创建索引会失败。解决方法:1.手动将重复值更改;2.把所有包含重复值的文档删除,dropDups选项可以保留发现的第一个文档,而删除接下来的有重复值的文档。例如: >db.people.ensureIndex({“username”:1},{“unique”:true, “dropDups”:true})
  • 7. 索引MongoDB的索引采用BTree结构,除了其地理位置索引外,数据索引本质上和MySQL 的 BTree索引没有什么差别。 那么地理位置索引是如何实现的? --------以下关于地理位置索引原理的解释引自网络 源地址:http://blog.nosqlfan.com/html/1811.html
  • 8. 索引-地理索引Mongodb在地理索引中使用geohash的方法 首先假设我们将需要索引的整个地图分成16×16的方格,如下图(左下角为坐标0,0 右上角为坐标16,16):
  • 9. 索引-地理索引单纯的[x,y]的数据是无法建立索引的,所以MongoDB在建立索引的时候,会根据相应字段的坐标计算一个可以用来做索引的hash值,这个值叫做geohash,下面我们以地图上坐标为[4,6]的点(图中红叉位置)为例。我们第一步将整个地图分成等大小的四块,如下图:
  • 10. 索引-地理索引划分成四块后我们可以定义这四块的值,如下(左下为00,左上为01,右下为10,右上为11): 01110010这样[4,6]点的geohash值目前为 00 然后再将四个小块每一块进行切割,如下:
  • 11. 索引-地理索引这时[4,6]点位于右上区域,右上的值为11,这样[4,6]点的geohash值变为:0011 继续往下做两次切分:
  • 12. 索引-地理索引最终得到[4,6]点的geohash值为:00110100 这样我们用这个值来做索引,则地图上点相近的点就可以转化成有相同前缀的geohash值了。 我们可以看到,这个geohash值的精确度是与划分地图的次数成正比的,上例对地图划分了四次。而MongoDB默认是进行26次划分,这个值在建立索引时是可控的。具体建立二维地理位置索引的命令如下: db.map.ensureIndex({point : "2d"}, {min : 0, max : 16, bits : 4})其中的bits参数就是划分几次,默认为26次。
  • 13. 索引-地理索引Geohash的缺陷 在划分边界上的点,距离很近,但是geohase值相差会很大,如下图所示。 在搜索时,除了根据geohash前缀查找附近的点,还需要对搜索点周围的点进行判断,处理上述所说的边界情况。 引自wikipedia: http://en.wikipedia.org/wiki/Geohash#Limitations One limitation of the Geohash algorithm is in attempting to utilize it to find points in proximity to each other based on a common prefix. Edge case locations close to each other but on opposite sides of the Equator or a meridian can result in Geohash codes with no common prefix.[1] Secondly a geohash essentially defines a bounding box within which a location lies, therefore two locations may be spatially very close but have different geohashes. In order to be useful to proximity searches, the surrounding eight geohashes of a geohash must be calculated and the locations matching these pulled out, therefore complicating potential usage in proximity searches. Despite those issues, there are possible workarounds, and the algorithm has been successfully used in MongoDB to implement proximity searches.[2] 注:MongoDB的地理空间索引假设索引内容是在一个平面上面的,这就意味着对于球体,不是十分精确,尤其是极地区域。
  • 14. (本页无文本内容)
  • 15. 目录BSON类型比较 索引 底层存储格式格式及实现 游标 日志 复制 分片
  • 16. 底层存储格式及实现 MongoDB的文档类似于JSON,在保留JSON基本的键/值对特性的基础上,添加了其他一些数据类型,称之为BSON(binary json)。 database->collection(like table)->document(like row), document save as BSON form in the disk. 对于每个数据库,有一个命名空间文件,用于保存每个命名空间对应的元数据(Metadata),我们通过查询这些元数据来找到对应命名空间的存储块位置。 每个数据库都有自己的独立的文件,如果你开启了direcotryperd,那你每个库的文件会单独放在一个文件夹里。 同一数据库的数据文件从16MB开始,新的数据文件比上一个文件大一倍,最大为2GB。(往后的都是2GB)
  • 17. 底层存储格式及实现 数据库文件在内部会被切分成单独的块,每个块只保存一个命名空间的数据。在mongoDB中,命名空间用于区分不同的存储类别。比如每个collection有一个独立的命名空间,每个索引也有自己的命名空间。 块与块之间用双向链表连接。 在一个块中,会保存多条记录,每条记录是BSON格式的记录与记录之间通过双向链表进行连接,索引数据也存储在数据文件中,不过索引是被组织成B-Tree结构,而不是双向链表。 每个块的头部包含了一些块的元数据,比如自己的位置,上一个和下一个块的位置以及块中第一条和最后一条记录的位置指针。剩下的部分用于存储具体的数据,具体数据之间也是通过双向链表来进行连接的。
  • 18. 底层存储格式及实现
  • 19. 底层存储格式及实现-块结构
  • 20. 底层存储格式格式及实现-Record
  • 21. 底层存储格式及实现-内存映射 文件使用MMAP进行内存映射,会将所有数据文件映射到内存中,但是只是虚拟内存,只有访问到这块数据时才会交换到物理内存。 32位机器的话,内存地址最大可以标识4GB内存,其中1GB会被内核占用,0.5GB会用于mongod进程的stack空间,只剩下大约2.5GB可用于映射数据文件,所以不建议在32位机器上配置mongoDB。 在64位机器上可以表示128TB内存,较为宽裕。 MongoDB使用内存映射,把磁盘IO操作转换成内存操作,如果是读操作,内存中的数据起到缓存作用,如果是写操作,内存还可以把随机的写操作转换成顺序的写操作,总之可以大幅提升性能。 但MongoDB不干涉内存管理,这些工作留给操作系统处理,很快会发现MongoDB对于内存可以用贪得无厌来形容,它会占用所有能用的内存。 所以部署MongoDB最好保证有足够的内存,建议使用64位机器,并将虚拟内存大小设置成unlimited. 如果通过监控发现page fault的数量增加,那么很可能就是热数据量超出了可用内存大小。当热数据量超出了可用内存量时,通常有两种解决方法:增加内存和数据分片。建议先增加内存,再考虑通过数据分片的方式解决。
  • 22. 目录BSON类型比较 索引 底层存储格式格式及实现 游标 日志 复制 分片
  • 23. 游标可以把游标看成类似一个指向结果集的指针。 Mongodb使用游标来返回查询的结果。 客户端能够使用游标对最终结果进行有效的控制。 当调用find的时候,shell并不立即执行查询数据库,而是等待真正开始要求获取结果的时候才发送查询,这样在执行之前可以给查询附加额外的选择,构成方法链。 当查询被发往服务器,shell立即获取前100个结果或者前4MB数据(两者之中较小者),这样下次调用next或者hasnext就不需要从服务区取数据。客户端用完第一组数据,shell会再次联系数据库,要求更多数据,直到游标耗尽或者结果全部返回。
  • 24. 游标游标在服务端的表现: 在服务端游标消耗内存和其他资源。游标遍历尽结果以后,或者客户端发来消息终止,数据库将会释放这些资源。 即便因为意外,用户也没有迭代完所有结果,10分钟不使用,数据库游标也会自动销毁。 如果希望游标持续时间长一些,驱动程序有一个叫immortal的函数让数据库不要让游标超时。 如果关闭了游标的超时时间,则一定要在迭代结果后将其关闭,否则它会一直在数据库中消耗服务器资源。
  • 25. 游标游标的内部实现: class Cursor : boost::noncopyable//使类和派生类不可复制 { virtual bool ok() = 0;//游标当前指向的对象是否有效 bool eof() { return !ok(); }//是否已到尾部 virtual Record* _current() = 0;//游标当前指向的记录(记录是组成数据文件的最基本单位) virtual BSONObj current() = 0;//游标当前指向的BSONObj对象 virtual DiskLoc currLoc() = 0;//游标当前指向的DiskLoc virtual bool advance() = 0; /*true=ok,将游标指向到下一条记录所在位置*/ virtual BSONObj currKey() const { return BSONObj(); } /* 标识游标是否为Tailable类型,该类型支持获取最后一条记录后,不马上关闭游标,以便持续获取后面新添加的记录*/ virtual bool tailable() { return false; } //设置游标为Tailable类型 virtual void setTailable() {} ..... }
  • 26. 固定集合与尾部游标MongoDB除了可以动态的建立普通集合,应对增长的数据自动调整大小。还支持另一种集合---固定集合。 固定集合要事先创建,而且大小固定。固定集合很像循环队列,如果空间不足,最早的文档就会被删除,为新的文档腾出空间。即固定集合在新文档插入的时候自动淘汰最早的文档。 尾部游标是一种特殊的持久游标,这类游标不会在没有结果后销毁。因为这类游标在没有结果后销毁。游标受到tail –f命令的启发,类似地会尽可能持续地获取结果输出。因为这类游标在没有结果后也不销毁,所以一旦有新文档添加到集合里面就会被取回并输入。尾部游标只能用在固定集合上。 Mongo Shell不支持尾部游标,可以用PHP代码测试。 展示例子
  • 27. 目录BSON类型比较 索引 底层存储格式格式及实现 游标 日志 复制 分片
  • 28. 日志Mongodb具有四种日志:Log, Journal, Oplog, 慢查询日志 Log 主要负责用户日志文件,和普通系统日志没有什么区别,作用也是记录系统的一些重要流程,然后持久化到log文件。启动时加上—logpath参数,如果没有带这个参数,日志信息直接输出到屏幕上。 Journal 通过”--dur”启动该模块功能,主要用于解决因为宕机时,内存中的数据未写入磁盘而造成的数据丢失。 其主要机制是通过log方式定时将操作日志(对数据库有更改的操作)记录到dbpath的命名为journal的文件夹下。数据的更新会先写入journal日志,定期集中提交(100ms一次),然后再真正数据上执行这些变更。 如果服务器安全关闭,日志会被清除。在服务器启动的时候,如果发现存在journal日志,则会执行提交。这保证了那些已经写入journal日志但在服务器崩溃前还没有提交的操作能在用户连接服务器前被执行。 两次提交之间的100m时间窗口,在未来的版本中有望被缩小。
  • 29. 日志Oplog: Oplog 当部署应用于生产的健壮的服务器时,需要对服务器进行同步备份,而Oplog的作用则主要是负责记录写服务器上所有对数据的更改,这样机器集内的读服务器通过获取Oplog就可以进行差异同步了。这个日志是一个Capped Collection(固定集合)且有大小之分,所以最好在mongod启动时配置好大小(单位:MB)。 例如: mongod - oplogSize=1024 慢查询日志: 慢查询日志记录了执行时间超过所设定时间阀值的操作语句,慢查询日志对于发现性能有问题的语句有帮助。 要想配置这个功能只需要在mongod启动时指定profile参数即可。 例如,想要将超过5秒的操作都记录,可以使用如下语句: mongod --profile=1 --slowms=5 系统运行一段时间后,可以通过查看db.system.profile这个collection来获取慢日志信息。
  • 30. 目录BSON类型比较 索引 底层存储格式格式及实现 游标 日志 复制 分片
  • 31. 复制1. 主从复制 最常见的传统主从模式,用于备份,恢复,读扩展等。 2. 副本集 副本集就是有自动故障恢复功能的主从集群。主从集群和副本集最为明显的区别是副本集没有固定的”主节点”:整个集群会选举出一个”主节点”,当其不能工作时则变更到其他节点。(有复杂的优先级、自动调权机制确保选举结果的顺利完成。)
  • 32. 复制读写分离(适用于读取密集型): 两种复制模式都可以实现读写分离。 同步: 从节点第一次启动时,会对主节点数据进行完整的同步,从节点复制主节点上面的每个文档(如果数据量大,耗费资源甚巨)。 同步完成后,从节点开始查询主节点的oplog并执行这些操作,以保证数据是最新的。 如果从节点的操作落后主节点很多,可以手动命令,或者用重新同步的参数重启从节点,实现重新的完整同步。 延时更新: 从节点可以延时更新主节点执行的操作,这种机制可以给用户无意删除重要文档或者插入垃圾数据等事故留一个恢复的时间差。
  • 33. 复制小结: 对MongoDB来说,可靠性不再过度地依赖将数据写入到磁盘的操作,更多的是通过将数据同步到其他节点的方式解决可靠性问题。 使用复制集可以提供整个集群的稳定性和安全性。
  • 34. 目录BSON类型比较 索引 底层存储格式格式及实现 游标 日志 复制 分片
  • 35. 分片(适用于写入密集型) 分片(sharding): 指将数据拆分,将其分散存储在不同机器上的过程。 MongoDB支持自动分片,可以摆脱手动分片的管理困扰。集群自动切分数据,做负载均衡。 分片需要片键,分片的效果取决于片键。 核心机制:存储服务器(Mongod)+配置服务器(Config)+路由进程(Mongos)
  • 36. 分片路由进程(mongos): 这个路由进程知道所有数据的存放位置,路由器知道数据和片的对应关系,能够转发请求到正确的片上。如果请求有了回应,路由器将其首级起来回送给客户端。 如果按照片键查询,可以准确的命中分片,如果没有片键,则在所有片上发起查询。返回结果时会做归并排序,保证结果顺序正确。
  • 37. 分片配置服务器: 配置服务器存储了集群的配置信息:数据和片的对应关系。Mongos不永久存放数据,所有需要配置服务器存放分片配置。Mongos 从配置服务器获取同步数据。
  • 38. 分片主服务器副本副本副本选举 副本集主服务器副本副本副本选举 副本集ClientConfigMongosConfigMongosMongosClientClient
  • 39. 分片貌似需要很多服务器? 配置服务器,mongos,mongod,好像需要多个服务器。 但是这些不一定都需要单独的服务器。重要的是不把所有鸡蛋都放在一个篮子里。 好比不把所有的配置服务器都放到一台机器上,不把所有的mongos放到一台机器上,不把所有的mongod放到一个机器上。但是可以把一个配置服务器,一些mongos和存储集群的一个节点放到一台机器上。
  • 40. 谢谢