mmap数据hellaflush是什么意思到硬盘,是啥意思

NoSQL理论之-内存是新的硬盘,硬盘是新的磁带 | Nosql |
_数据库_运维_开发_IT学习_无忧IT学习网
一起学习!一起进步!
NoSQL理论之-内存是新的硬盘,硬盘是新的磁带
浏览: 33 views
&内存是新的硬盘,硬盘是新的磁带&此话出自图灵奖得主Jim Gray。
  一、前言
  我理解这句话的意思是,我们应该把随机IO都放到内存中去,而把像磁带一样的顺序IO留给硬盘(这里不包括SSD)。
  如果应用...
&内存是新的硬盘,硬盘是新的磁带&此话出自图灵奖得主Jim Gray。
  一、前言
  我理解这句话的意思是,我们应该把随机IO都放到内存中去,而把像磁带一样的顺序IO留给硬盘(这里不包括SSD)。
  如果应用没有达到一定的级别,可能我们看上面两句话都会觉得太geek,然而在应用数据量日益庞大,动态内容比例日益增大的今天,再忽视这个基本准则将会是一个灾难。
  今天我们谈一下这一理论在产品中的展现。
  二、实现
  问题一:宕机数据丢失
  我们先看一下几个杰出的代表,Cassandra,MongoDB,Redis。他们几乎都使用了同一种模式,就是将写操作在内存中进行,定时或按某一条件将内存中的数据直接写到磁盘上。这样做的好处是我们可以充分利用内存在随机IO上的优势,而避免了直接写磁盘带来的随机IO瓶颈:磁盘寻道时间。当然,坏处就是如果遭遇宕机等问题时,可能会丢失一些数据。
  解决宕机丢数据的问题有两个方法:
  1.实时记录操作日志
  这时通常的做法是当一个写操作到达,系统首先会往日志文件里追加一条写记录,成功后再操作内存进行写数据操作。而由于日志文件是不断追加的,因此也就保证了不会有大量的随机IO产生。
  2.Quorum NRW
  这一理论是基于集群式的,其原理是如果集群有N个结点,那么如果我们每次写操作需要至少同步到W个结点才算成功,而每次读操作只要从R个结点读数据就一定能保证其得到正确结果(如果某一结点有此数据,既成功,如果所有R个结点都无数据,则说明无此数据)。而NRW之间的关系必须满足N & R + W 。其实这一理论并不难理解,我们可以将这个不等式做一下移项:R & N & W ,我们有N个结点,写的时候最少写W个才算成功,也就是W个结点有这份数据,那么N-W就是说可能没有某一份数据的最大结点数。最多可能有N-W个结点没有某一数据,那如果我们进行数据读取操作时,读到大于N-W个结点,那么必然有一个以上的结点是有这份数据的。所以要求R & N & W。
  所以可能你已经想明白了,为了防止数据丢失,我们采用的实际是简单的冗余备份的方法。数据写到多台机器会比写单台机器的磁盘快吗?对。相对于直接的磁盘操作,跨进行内存操作可以更快。其最简单的例子就是改进的一致性hash,(关于一致性hash请看这里):
  上图摘自Amazon的Dynamo文档,key的hash值位于A,B结点间的数据,并不是只存在B结点上,而是顺着环的方向分别在C和D结点进行备份。当然这样做的好处并不完全在于上面说的冗余备份。
  当然,很多时候是上面两种解决方法同时使用以保证数据的高可用性。
  问题二:内存容量的限制
  当我们将内存当作硬盘来用的时候,我们必然会面临容量问题。这也是我们上面说到的数据会定时flush到磁盘的原因,当内存中的数据已经超出可用内存的大小,那么我们就需要将其进行落地操作,对swap的过度使用是不符合我们初衷的,也是达不到高效随机IO的效果的。这里也有两种:
  1.应用层swap
  采用这种方法的有 TokyoCabinet 和 Redis 两个产品。TokyoCabinet主要是通过mmap提高IO效率,而其mmap到的只有数据文件头部的一部分内容。一旦数据文件大于其设置的最大mmap长度(由参数xmsize控制),那剩下的部分就是纯粹的低效磁盘操作了。于是它提供了一种类似于Memcached的缓存机制,通过参数rcnum配置,将一些通过LRU机制筛选出来的热数据进行key-value式的缓存,这一部分内存是和mmap占用的内存完全独立的。同样的,Redis在2.0版本之后增加了对磁盘的支持,其机制与 TokyoCabinet 类似,也是通过数据操作来判断数据的热度,并将热数据尽量放到内存中。
  2.多版本的数据合并
  什么叫多版本的数据合并呢?我们上面讲 Bigtable,或其开源版本 Cassandra,都是通过定时将内存中的数据块flush到磁盘中,那么我们会想,如果这次是一个update操作,比如 keyA 的值从 ValueA 变成了 ValueB,那么我们在flush到磁盘的时候就得执行对老数据 ValueA 的清除工作了。而这样,是否就达不到我们希望进行顺序的磁盘IO的目的呢?没错,这样是达不到的,所以 Bigtable 类型的系统确实也并不是这样做的,在flush磁盘的时候,并不会执行合并操作,而是直接将内存数据写入磁盘。这样写是方便很多,那读的时候可能会存在一个值有多个版本的情况,这时就需要我们来进行多版本合并了。所以第二种方法就是将一段时间的写操作写成一个块(可能并非一个文件),保证内存的使用不会无限膨胀。在读取时通过读多个文件块进行数据版本合并来完成。
  那如果存储在磁盘的数据量是内存容量的很多倍,我们可能会产生许多个数据块,那么我们在获取数据版本时,是否需要全部遍历所有数据块呢?当然不用,如果你看过BigTable论文,相信你还记得它其中用到了&bloom-filter&算法。bloom-filter 算法最广泛的应用是在搜索引擎爬虫中,它用于判断一个URL是否存在于已抓取集合中,这一算法并不百分之百精准(可能将不在集合中的数据误判为在集合中,但不会出现相反的误差),但其在时间复杂度上仅是几次hash计算,而空间复杂度也非常低。Bigtable 实现中也用到了 bloom-filter 算法,用它来判断一个值是否在某一个集合中。而由于 bloom-filter 算法的特点,我们只会多读(几率很小),不会少读数据块。于是我们就实现对远远大于物理内存容量的数据的存储。
  三、结尾
  好了,就写到这里,关于中对此原理的应用还有更多理解和认识的同学,欢迎交流。
& | & & | & & | & & | & & | & & | & & | & & | & & | & & | & & | & & | & & | &
最热门文章
41175 views
10861 views
9972 views
6386 views
5945 views
4742 views
4113 views
4038 views
友情链接 |
本站进行132次查询Tokyo Tyrant与Redis的一些简单比较
 作者: ZavaKid 编辑:
&&&&&&&&【IT168&评论】之前简单的看了一下 Tokyo Tyrant(包括 Tokyo Cabint) 在 hash 存储上的一些实现,最近 Redis 又比较火热,因此,自己也尝试性的去了解了一下 Redis,并且结合 Tokyo Tyrant(以下简称 tt server),说说自己对这两种产品的看法。  目录  服务端处理模型  数据存储方式、持久化比较  复制方式比较  性能方面比较  总结  服务端处理模型  在 tt server 中,是以多线程的方式向客户端提供服务的:一个主线程负责 accept 客户端的socket,一定数目的线程(可以指定)进行读写服务,同时,也有一定数目的timer线程,专门用来负责定时的任务,比如一些定时的 Lua 脚本,同时,如果是slaver,则会有专门一个timer线程,定时负责 do slave 的工作。  而在 Redis 中,采用的则是单线程的模型来处理所有的客户端请求。  应该说这两种模型,都有各自的优点和缺点。多线程可以利用多核CPU的计算能力,但因此也会增加CAS自旋或者是锁的一些消耗,同时,如果线程过多,那么线程之间上下文的切换,也是一种消耗。  而如果是单线程,则可以完全避免锁的消耗,同时,上下文切换消耗也不需要过多的考虑(但仍需要考虑系统上还有其他的进程),这会让单个CPU的利用率比较高。  但是,单线程服务,就意味着不能利用多核。同时,服务端对客户端过来的请求是串行执行和响应的,这也在一定程度上,会影响服务端的并发能力,特别是在有些请求执行比较耗时的情况下。想象一下,就这么一个线程,可能正在拼命的执行客户端A的一个请求,而此时客户端B,C,D的请求,还仍在等着线程执行完成之后再去搭理他们。  因此,像 redis 这种单线程的服务模型,如果对一些请求的处理相对较耗时,那其 TPS 也就相应的不能提高上去,也就是说其吞吐量会提不上去;但反过来想,redis 如果能控制每次请求在执行过程是简短并且快速的,那么也许使用单线程,反而会比多线程有更好的性能,毕竟单线程少了上下文切换,以及锁或者 cas 的开销。  而 tt server 则中规中矩:一个线程负责 accept ,一定数目的线程则进行请求的处理。因此,我们在设置 tt server 的时候,也应尽量考虑好工作线程的数目,尽量让CPU数目与工作线程数目一致或者略少。原则是最好的发挥多核CPU的作用,同时又不让工作线程之间去竞争 CPU。当然,这是需要不停的去实验的。  所以,在使用 redis 的时候,应尽量不要去使用一些相对耗时的请求;同时,我想 redis 的作者,也应该会尽量优化每种请求的执行速度(至少是一些常用的请求)。  而在使用 tt server 的时候,需要仔细调整使用的工作线程数目,让每个CPU都物尽其用。  数据存储方式、持久化比较  tt server 的 hash 数据库,是使用文件的方式,然后利用 mmap 系统调用映射到内存中。  这样,就可以利用操作系统的机制,不定期地将数据 flush 到磁盘中。同时,tt server 也提供了 sync 命令,可以让客户端手动将数据 flush 到磁盘中(使用 msync 系统调用)。最后,在关闭 tt server 进程的时候,应该使用 kill -15(TERM信号),或者使用 ttserver 自带的命令:ttserver -kl pid 进行关闭。这样 ttserver 会先把数据 flush 到磁盘上,再退出进程。  同时, tt server 也提供了 ulog 的方式,对数据库的变更操作进行记录,同样,可以利用 ulog 对 ttserver 进行恢复,但 ulog 的主要目的,按照我的理解,应是用来实现 replication 的。  而 redis 则是将数据直接写在了内存中,然后利用 redis 的持久化机制,将数据写到磁盘中。  redis 提供了两种持久化机制,分别是 RDB (redis DB) 和 AOF (appending only file)。  RDB的过程是:redis 进程 fork 一个子进程,然后子进程对内存中的数据写到一个临时文件,这个时候,两个进程就利用了操作系统的 copy on write 机制,共享一份内存数据,只有当父进程(也就是 redis 进程)对原有的数据进行修改或者删除之后,操作系统才为 redis 进程重新开辟新的内存空间(以页为单位)。Redis 本身也提供了 bgsave(background save) 命令支持手动将数据持久化( save 命令是同步的,而 redis 只有一个线程在服务,结果就是影响 redis 的性能,特别是在大数据量的情况下)。  AOF的过程是:在执行每次命令之后,或者每隔1秒钟之后,Redis会有一个线程将命令以 redis 协议的格式 append 到文件中,这也就是AOF名字的由来,这些命令当然是非只读的,只读不更改数据库,没有必要记录下来。  这里会有两个问题:  1、每次命令之后写文件,还是隔1秒之后写文件,影响会有哪些?  2、这些文件总会不断的膨胀,如何对文件进行压缩呢?  对于第一个问题,也是一个权衡的问题,如果每次命令之后都进行一次写磁盘操作,那么IO的程度可想而知,肯定会影响服务器性能(使用 write 系统调用,会因为文件系统而进入 page buffer,并非立刻写磁盘,而调用 fsync ,则会将 page buffer 中的数据写入磁盘,进行 IO 操作)。而如果每隔1秒进行一次 fsync,那么在这一秒和上一秒之间,如果服务器突然断电,那很有可能这些数据就会丢失。对于这个问题,redis 默认给出的方案是每隔1秒进行一次write。对于1秒的给定,我想,也是基于性能和数据安全的权衡,在性能和数据安全方面都可以让人接受。  对于第二个问题,redis 提供了 rewrite 的机制:当 aof 过大的时候,redis可以自动的进行 rewrite (从 redis 2.4 开始)。rewrite 的过程也是 fork 一个子进程;然后打开一个临时文件,将内存中的数据写入到文件中;在此期间,主进程继续将数据写入老的 aof 文件,同时也会将数据写入到一个内存缓存中;等子进程完成之后,主进程会将缓存中的数据写入到临时文件,再将临时文件进行rename,替换掉原来的文件。这样,就实现了写 aof 过程中的rewrite。  从数据的存储方式来说,尽管 tt server 和 redis 都是在内存上面进行数据的读写,我但认为两个产品对数据存储方式的观点是不一样的。  tt server 是将磁盘上的文件当作主要的存储方式,然后使用 mmap 将文件映射到内存中。本质上,这是数据应该存储在磁盘中的观点。  而 redis ,一开始就是将数据直接存储在内存中,在之后的持久化过程中,可以理解成只是将数据的日志写入到磁盘中。本质上,这是把数据应该存储在内存中的观点。  可见,由于作者的观点不一样,也就造成了两种实现方式不一样的产品,这还是比较有意思的。  从这个层面上来讲,我更加喜欢 redis 作者的思路,很可能作者就是受到 内存是新的磁盘,磁盘是新的磁带 的启发。  redis自带实现的VM将在以后不再使用(2.4将是最后一个自带vm功能的版本),作者认为数据就应该是放在物理内存中的,没有必要要将数据交换到磁盘中,磁盘只是作为日志的一种存储方式。这也是&内存是新的硬盘&思路的体现。  复制方式比较  tt server 和 redis 都支持 master-slave 方式的通信复制。  tt server 使用了 ulog,并且 slaver 使用了 rts(replication time-stamp) 文件,对上一次的复制时间戳进行保存,实现了复制的续传。  而 redis 则是每次 slave 重新连接到 master 时,master 会将数据进行全量的复制给 slave,而不是增量式的。redis 复制的方式与使用 RDB 持久化方式原理基本相同,也是使用子进程进行内存的dump,在此期间,父进程收集改变数据库的命令,等把子进程收集的数据传输给 slave 之后,再将此期间收集到的数据也传输给 slave。  如果从 slave 数据重建的角度来看,tt server 支持断点复制的实现,应该说是比 redis 先进了一步。  性能方面比较  新浪的 Tim Yang 做了 memcacheDB、Redis、tt server 的性能测试。这是比较早期的测试,相信随着版本的升级,两者的性能都会有所提升。不过按照这个测试的结果来看,redis 在数据量不多(500W)并且value 较小的时候,性能表现是很优越的;而对于稍大一些的 value ,tt 则在写方面表现很出色,但读的性能,相对较差。相比之下,redis的读写性能,倒是比较平衡。  但觉得随着时间的迁移,这个测试的参考性可能会打折扣,如果有可能的话,希望能看到更多的测试结果。  总结  1. 从服务器模型来说,tt server 使用 acceptor + workers 的方式提供服务,能够利用多核的性能,但随着而来的是一些同步、加锁的复杂和开销;而 redis 使用了单线程提供服务,利用不了多核,但如果能够将每次服务的速度控制下来,对单个CPU的利用率,反而可以提高。如果想利用机器的多核性能,也可以在一台机器上搭建多个 redis 实例,但可能更要考虑到机器的内存限制。  2. 从数据存储的方式来说,尽管 tt server 和 redis 都是将数据存储在内存中,但我认为两个产品对&数据是如何存储&的观点是有所不同的。tt server 认为数据是存储在文件中的,只是通过内存映射,将对文件的操作转化成对内存的操作;而 redis 是直接将数据存储到内存中,之后再通过持久化等机制,将数据备份到磁盘中。虽然之前 redis 自己实现了 vm 功能,但redis 后续会取消掉自己实现的 vm 功能,按照&内存是最新的磁盘&这种思路,也就不难理解了:除了增加复杂度之外,还有一个因素,那就是 redis 不需要 vm,能存的数据大小,只能限制在物理内存的范围以内。  从这个方面来将,redis 后续的版本可能就会限制用户使用的数据库大小是要小于物理内存的,而如果使用 tt server ,则用户须让使用数据文件小于物理内存,否则,发生内存交换,是非常损性能的。  总而言之,在使用内存数据库的时候,应该有意识的对数据进行容量规划,避免出现物理内存不够而引起的内存交换。  3. tt server 和 redis 的策略都是从 slaver 配置 master ,而不是从 master 配置 slaver 关系,这样就减轻了 master 的负担,同时,master 不必知道自己有多少个 slaver ,就可以横向的扩增 slaver 。但 tt server 支持所谓的断点复制。需要考虑到的是 redis 在做 replication 的时候,是 fork 一个子进程工作的,如果有多个 replicate 的请求,redis 依然还是一个子进程在工作。这样也会对多个 slaver 产生一定的复制延时。  4. redis 在工作方式上,会 fork 子进程,因此 redis 在容量规划上,需要考虑到 redis fork 出子进程所需要的内存和 CPU,在最差的情况下:bgsave时候,父子两个进程虽然可以使用 copy on write 的好处,但如果在此期间整个表记录都被修改了,那就足足需要一倍的内存,否则,此时父进程会进行 copy ,父进程很可能没有内存可用,就需要进行内存交换,由此所带来的性能代价也是非常高的;与此同时,子进程子在 bgsave 的时候,需要对数据进行压缩,压缩是计算密集型的,因此最好不要和父进程使用同一个CPU,因为父进程使用了单线程事件处理的模型,这种模型的优点是充分利用CPU的资源,如果出现子进程与父进程抢CPU,那就得不偿失了。  5. redis 支持较多的数据结构,但在使用 sort 等时间复杂性较多的命令时,也会稍微的降低 redis 的性能,应该对这些耗时的命令进行一定的监控。
大学生分期购物销量榜
已有条评论
IT168企业级24小时热评
热点排行榜Mongodb存储特性与内部原理 - 深入一点,你会更加快乐 - ITeye博客
博客分类:
一、存储引擎(Storage)
mongodb 3.0默认存储引擎为MMAPV1,还有一个新引擎wiredTiger可选,或许可以提高一定的性能。
mongodb中有多个databases,每个database可以创建多个collections,collection是底层数据分区(partition)的单位,每个collection都有多个底层的数据文件组成。(参见下文data files存储原理)
wiredTiger引擎:3.0新增引擎,官方宣称在read、insert和复杂的update下具有更高的性能。所以后续版本,我们建议使用wiredTiger。所有的write请求都基于“文档级别”的lock,因此多个客户端可以同时更新一个colleciton中的不同文档,这种更细颗粒度的lock,可以支撑更高的读写负载和并发量。因为对于production环境,更多的CPU可以有效提升wireTiger的性能,因为它是的IO是多线程的。wiredTiger不像MMAPV1引擎那样尽可能的耗尽内存,它可以通过在配置文件中指定“cacheSizeGB”参数设定引擎使用的内存量,此内存用于缓存工作集数据(索引、namespace,未提交的write,query缓冲等)。
journal就是一个预写事务日志,来确保数据的持久性,wiredTiger每隔60秒(默认)或者待写入的数据达到2G时,mongodb将对journal文件提交一个checkpoint(检测点,将内存中的数据变更flush到磁盘中的数据文件中,并做一个标记点,表示此前的数据表示已经持久存储在了数据文件中,此后的数据变更存在于内存和journal日志)。对于write操作,首先被持久写入journal,然后在内存中保存变更数据,条件满足后提交一个新的检测点,即检测点之前的数据只是在journal中持久存储,但并没有在mongodb的数据文件中持久化,延迟持久化可以提升磁盘效率,如果在提交checkpoint之前,mongodb异常退出,此后再次启动可以根据journal日志恢复数据。journal日志默认每个100毫秒同步磁盘一次,每100M数据生成一个新的journal文件,journal默认使用了snappy压缩,检测点创建后,此前的journal日志即可清除。mongod可以禁用journal,这在一定程度上可以降低它带来的开支;对于单点mongod,关闭journal可能会在异常关闭时丢失checkpoint之间的数据(那些尚未提交到磁盘数据文件的数据);对于replica set架构,持久性的保证稍高,但仍然不能保证绝对的安全(比如replica set中所有节点几乎同时退出时)。
MMAPv1引擎:mongodb原生的存储引擎,比较简单,直接使用系统级的内存映射文件机制(memory mapped files),一直是mongodb的默认存储引擎,对于insert、read和in-place update(update不导致文档的size变大)性能较高;不过MMAPV1在lock的并发级别上,支持到collection级别,所以对于同一个collection同时只能有一个write操作执行,这一点相对于wiredTiger而言,在write并发性上就稍弱一些。对于production环境而言,较大的内存可以使此引擎更加高效,有效减少“page fault”频率,但是因为其并发级别的限制,多核CPU并不能使其受益。此引擎将不会使用到swap空间,但是对于wiredTiger而言需要一定的swap空间。(核心:对于大文件MAP操作,比较忌讳的就是在文件的中间修改数据,而且导致文件长度增长,这会涉及到索引引用的大面积调整)
为了确保数据的安全性,mongodb将所有的变更操作写入journal并间歇性的持久到磁盘上,对于实际数据文件将延迟写入,和wiredTiger一样journal也是用于数据恢复。所有的记录在磁盘上连续存储,当一个document尺寸变大时,mongodb需要重新分配一个新的记录(旧的record标记删除,新的记record在文件尾部重新分配空间),这意味着mongodb同时还需要更新此文档的索引(指向新的record的offset),与in-place update相比,将消耗更多的时间和存储开支。由此可见,如果你的mongodb的使用场景中有大量的这种update,那么或许MMAPv1引擎并不太适合,同时也反映出如果document没有索引,是无法保证document在read中的顺序(即自然顺序)。3.0之后,mongodb默认采用“Power of 2 Sized Allocations”,所以每个document对应的record将有实际数据和一些padding组成,这padding可以允许document的尺寸在update时适度的增长,以最小化重新分配record的可能性。此外重新分配空间,也会导致磁盘碎片(旧的record空间)。
Power of 2 Sized Allocations:默认情况下,MMAPv1中空间分配使用此策略,每个document的size是2的次幂,比如32、64、128、256...2MB,如果文档尺寸大于2MB,则空间为2MB的倍数(2M,4M,6M等)。这种策略有2种优势,首先那些删除或者update变大而产生的磁盘碎片空间(尺寸变大,意味着开辟新空间存储此document,旧的空间被mark为deleted)可以被其他insert重用,再者padding可以允许文档尺寸有限度的增长,而无需每次update变大都重新分配空间。此外,mongodb还提供了一个可选的“No padding Allocation”策略(即按照实际数据尺寸分配空间),如果你确信数据绝大多数情况下都是insert、in-place update,极少的delete,此策略将可以有效的节约磁盘空间,看起来数据更加紧凑,磁盘利用率也更高。
备注:mongodb 3.2+之后,默认的存储引擎为“wiredTiger”,大量优化了存储性能,建议升级到3.2+版本。
二、Capped Collections:一种特殊的collection,其尺寸大小是固定值,类似于一个可循环使用的buffer,如果空间被填满之后,新的插入将会覆盖最旧的文档,我们通常不会对Capped进行删除或者update操作,所以这种类型的collection能够支撑较高的write和read,通常情况下我们不需要对这种collection构建索引,因为insert是append(insert的数据保存是严格有序的)、read是iterator方式,几乎没有随机读;在replica set模式下,其oplog就是使用这种colleciton实现的。
Capped Collection的设计目的就是用来保存“最近的”一定尺寸的document。
db.createCollection("capped_collections",new CreateCollectionOptions()
.capped(true)
.maxDocuments(6552350)
.usePowerOf2Sizes(false).autoIndex(true));//不会涉及到更新,所以可以不用power of 2
Capped Collection在语义上,类似于“FIFO”队列,而且是有界队列。适用于数据缓存,消息类型的存储。Capped支持update,但是我们通常不建议,如果更新导致document的尺寸变大,操作将会失败,只能使用in-place update,而且还需要建立合适的索引。在capped中使用remove操作是允许的。autoIndex属性表示默认对_id字段建立索引,我们推荐这么做。在上文中我们提到了Tailable Cursor,就是为Capped而设计的,效果类似于“tail -f ”。
三、数据模型(Data Model)
上文已经描述过,mongodb是一个模式自由的NOSQL,不像其他RDBMS一样需要预先定义Schema而且所有的数据都“整齐划一”,mongodb的document是BSON格式,松散的,原则上说任何一个Colleciton都可以保存任意结构的document,甚至它们的格式千差万别,不过从应用角度考虑,包括业务数据分类和查询优化机制等,我们仍然建议每个colleciton中的document数据结构应该比较接近。
对于有些update,比如对array新增元素等,会导致document尺寸的增加,无论任何存储系统包括MYSQL、Hbase等,对于这种情况都需要额外的考虑,这归结于磁盘空间的分配是连续的(连续意味着读取性能将更高,存储文件空间通常是预分配固定尺寸,我们需要尽可能的利用磁盘IO的这种优势)。对于MMAPV1引擎,如果文档尺寸超过了原分配的空间(上文提到Power of 2 Allocate),mongodb将会重新分配新的空间来保存整个文档(旧文档空间回收,可以被后续的insert重用)。
document模型的设计与存储,需要兼顾应用的实际需要,否则可能会影响性能。mongodb支持内嵌document,即document中一个字段的值也是一个document,可以形成类似于RDBMS中的“one-to-one”、“one-to-many”,只需要对reference作为一个内嵌文档保存即可。这种情况就需要考虑mongodb存储引擎的机制了,如果你的内嵌文档(即reference文档)尺寸是动态的,比如一个user可以有多个card,因为card数量无法预估,这就会导致document的尺寸可能不断增加以至于超过“Power of 2 Allocate”,从而触发空间重新分配,带来性能开销,这种情况下,我们需要将内嵌文档单独保存到一个额外的collection中,作为一个或者多个document存储,比如把card列表保存在card collection中。“one-to-one”的情况也需要个别考虑,如果reference文档尺寸较小,可以内嵌,如果尺寸较大,建议单独存储。此外内嵌文档还有个优点就是write的原子性,如果使用reference的话,就无法保证了。
索引:提高查询性能,默认情况下_id字段会被创建唯一索引;因为索引不仅需要占用大量内存而且也会占用磁盘,所以我们需要建立有限个索引,而且最好不要建立重复索引;每个索引需要8KB的空间,同时update、insert操作会导致索引的调整,会稍微影响write的性能,索引只能使read操作收益,所以读写比高的应用可以考虑建立索引。
大集合拆分:比如一个用于存储log的collection,log分为有两种“dev”、“debug”,结果大致为{"log":"dev","content":"...."},{"log":"debug","content":"....."}。这两种日志的document个数比较接近,对于查询时,即使给log字段建立索引,这个索引也不是高效的,所以可以考虑将它们分别放在2个Collection中,比如:log_dev和log_debug。
数据生命周期管理:mongodb提供了expire机制,即可以指定文档保存的时长,过期后自动删除,即TTL特性,这个特性在很多场合将是非常有用的,比如“验证码保留15分钟有效期”、“消息保存7天”等等,mongodb会启动一个后台线程来删除那些过期的document。需要对一个日期字段创建“TTL索引”,比如插入一个文档:{"check_code":"101010",$currentDate:{"created":true}}},其中created字段默认值为系统时间Date;然后我们对created字段建立TTL索引:
collection.createIndex(new Document("created",1),new IndexOptions().expireAfter(15L,TimeUnit.MILLISECONDS));//15分钟
我们向collection中insert文档时,created的时间为系统当前时间,其中在creatd字段上建立了“TTL”索引,索引TTL为15分钟,mongodb后台线程将会扫描并检测每条document的(created时间 + 15分钟)与当前时间比较,如果发现过期,则删除索引条目(连带删除document)。
某些情况下,我们可能需要实现“在某个指定的时刻过期”,我们只需要将上述文档和索引变通改造即可,即created指定为“目标时间”,expiredAfter指定为0。
四、架构模式
Replica set:复制集,mongodb的架构方式之一 ,通常是三个对等的节点构成一个“复制集”集群,有“primary”和secondary等多中角色(稍后详细介绍),其中primary负责读写请求,secondary可以负责读请求,这有配置决定,其中secondary紧跟primary并应用write操作;如果primay失效,则集群进行“多数派”选举,选举出新的primary,即failover机制,即HA架构。复制集解决了单点故障问题,也是mongodb垂直扩展的最小部署单位,当然sharding cluster中每个shard节点也可以使用Replica set提高数据可用性。
Sharding cluster:分片集群,数据水平扩展的手段之一;replica set这种架构的缺点就是“集群数据容量”受限于单个节点的磁盘大小,如果数据量不断增加,对它进行扩容将时非常苦难的事情,所以我们需要采用Sharding模式来解决这个问题。将整个collection的数据将根据sharding key被sharding到多个mongod节点上,即每个节点持有collection的一部分数据,这个集群持有全部数据,原则上sharding可以支撑数TB的数据。
系统配置:1)建议mongodb部署在linux系统上,较高版本,选择合适的底层文件系统(ext4),开启合适的swap空间
2)无论是MMAPV1或者wiredTiger引擎,较大的内存总能带来直接收益。3)对数据存储文件关闭“atime”(文件每次access都会更改这个时间值,表示文件最近被访问的时间),可以提升文件访问效率。 4)ulimit参数调整,这个在基于网络IO或者磁盘IO操作的应用中,通常都会调整,上调系统允许打开的文件个数(ulimit -n 65535)。
五、数据文件存储原理(Data Files storage,MMAPV1引擎)
1、Data Files
mongodb的数据将会保存在底层文件系统中,比如我们dbpath设定为“/data/db”目录,我们创建一个database为“test”,collection为“sample”,然后在此collection中插入数条documents。我们查看dbpath下生成的文件列表:
-rw-------
6 17:24 test.0
-rw-------
6 17:24 test.1
-rw-------
6 17:24 test.2
-rw-------
6 17:24 test.3
-rw-------
6 17:24 test.4
-rw-------
6 17:24 test.5
-rw-------
6 17:24 test.6
-rw-------
6 17:24 test.ns
可以看到test这个数据库目前已经有6个数据文件(data files),每个文件以“database”的名字 + 序列数字组成,序列号从0开始,逐个递增,数据文件从16M开始,每次扩张一倍(16M、32M、64M、128M...),在默认情况下单个data file的最大尺寸为2G,如果设置了smallFiles属性(配置文件中)则最大限定为512M;mongodb中每个database最多支持16000个数据文件,即约32T,如果设置了smallFiles则单个database的最大数据量为8T。如果你的database中的数据文件很多,可以使用directoryPerDB配置项将每个db的数据文件放置在各自的目录中。当最后一个data file有数据写入后,mongodb将会立即预分配下一个data file,可以通过“--nopreallocate”启动命令参数来关闭此选项。
一个database中所有的collections以及索引信息会分散存储在多个数据文件中,即mongodb并没有像SQL数据库那样,每个表的数据、索引分别存储;数据分块的单位为extent(范围,区域),即一个data file中有多个extents组成,extent中可以保存collection数据或者indexes数据,一个extent只能保存同一个collection数据,不同的collections数据分布在不同的extents中,indexes数据也保存在各自的extents中;最终,一个collection有一个或者多个extents构成,最小size为8K,最大可以为2G,依次增大;它们分散在多个data files中。对于一个data file而言,可能包含多个collection的数据,即有多个不同collections的extents、index extents混合构成。每个extent包含多条documents(或者index entries),每个extent的大小可能不相等,但一个extent不会跨越2个data files。
有人肯定疑问:一个collection中有哪些extents,这种信息mongodb存在哪里?在每个database的namespace文件中,比如test.ns文件中,每个collection只保存了第一个extent的位置信息,并不保存所有的extents列表,但每个extent都维护者一个链表关系,即每个extent都在其header信息中记录了此extent的上一个、下一个extent的位置信息,这样当对此collection进行scan操作时(比如全表扫描),可以提供很大的便利性。
我们可以通过db.stats()指令查看当前database中extents的信息:
& use test
switched to db test
& db.stats();
"db" : "test",
"collections" : 3,
##collection的个数
"objects" : 1000006, ##documents总条数
"avgObjSize" : 495.9, ##record的平均大小,单位byte
"dataSize" : , ##document所占空间的总量
"storageSize" : , ##
"numExtents" : 18,
##extents个数
"indexes" : 2,
"indexSize" : ,
"fileSize" : ,
"nsSizeMB" : 16, ##namespace文件大小
"extentFreeList" : {
##尚未使用(已分配尚未使用、已删除但尚未被重用)的extent列表
"num" : 0,
"totalSize" : 0
"dataFileVersion" : {
"major" : 4,
"minor" : 22
列表信息中有几个字段简单介绍一下:
1) dataSize:documents所占的空间总量,mongodb将会为每个document分配一定空间用于保存数据,每个document所占空间包括“文档实际大小” + “padding”,对于MMAPV1引擎,mongodb默认采用了“Power of 2 Sized Allocations”策略,这也意味着通常会有padding,不过如果你的document不会被update(或者update为in-place方式,不会导致文档尺寸变大),可以在在createCollection是指定noPadding属性为true,这样dataSize的大小就是documents实际大小;当documents被删除后,将导致dataSize减小;不过如果在原有document的空间内(包括其padding空间)update(或者replace),则不会导致dataSize的变大,因为mongodb并没有分配任何新的document空间。
2)storageSize:所有collection的documents占用总空间,包括那些已经删除的documents所占的空间,为存储documents的extents所占空间总和。文档的删除或者收缩不会导致storageSize变小。
3)indexSize:所用collection的索引数据的大小,为存储indexes的extents所占空间的总和。
4)fileSize:为底层所有data files的大小总和,但不包括namespace文件。为storageSize、indexSize、以及一些尚未使用的空间等等。当删除database、collections时会导致此值变小。
此外,如果你想查看一个collection中extents的分配情况,可以使用db.&collection名称&.stats(),结构与上述类似;如果你希望更细致的了解collection中extents的全部信息,则可以使用db.&collection名称&.validate(),此方法接收一个boolean值,表示是否查看明细,这个指令会scan全部的data files,因此比较耗时:
& db.sample.validate(true);
"ns" : "test.sample",
"datasize" : ,
"nrecords" : 1000000,
"lastExtentSize" : ,
"firstExtent" : "0:5000 ns:test.sample",
"lastExtent" : "3:a05f000 ns:test.sample",
"extentCount" : 16,
"extents" : [
"loc" : "0:5000",
"xnext" : "0:49000",
"xprev" : "null",
"nsdiag" : "test.sample",
"size" : 8192,
"firstRecord" : "0:50b0",
"lastRecord" : "0:6cb0"
可以看到extents在逻辑上是链表形式,以及每个extent的数据量、以及所在data file的offset位置。具体参见【】
从上文中我们已经得知,删除document会导致磁盘碎片,有些update也会导致磁盘碎片,比如update导致文档尺寸变大,进而超过原来分配的空间;当有新的insert操作时,mongodb会检测现有的extents中是否合适的碎片空间可以被重用,如果有,则重用这些fragment,否则分配新的存储空间。磁盘碎片,对write操作有一定的性能影响,而且会导致磁盘空间浪费;如果你需要删除某个collection中大部分数据,则可以考虑将有效数据先转存到新的collection,然后直接drop()原有的collection。或者使用db.runCommand({compact: '&collection&'})。
如果你的database已经运行一段时间,数据已经有很大的磁盘碎片(storageSize与dataSize比较),可以通过mongodump将指定database的所有数据导出,然后将原有的db删除,再通过mongorestore指令将数据重新导入。(同compact,这种操作需要停机维护)
mongod中还有2个默认的database,系统级的,“admin”和“local”;它们的存储原理同上,其中“admin”用于存储“用户授权信息”,比如每个database中用户的role、权限等;“local”即为本地数据库,我们常说的oplog(replication架构中使用,类似与binlog)即保存在此数据库中。
2、Namespace文件
对于namespace文件,比如“test.ns”文件,默认大小为16M,此文件中主要用于保存“collection”、index的命名信息,比如collection的“属性”信息、每个索引的属性类型等,如果你的database中需要存储大量的collection(比如每一小时生成一个collection,在数据分析应用中),那么我们可以通过配置文件“nsSize”选项来指定。参见【】
3、journal文件
journal日志为mongodb提供了数据保障能力,它本质上与mysql binlog没有太大区别,用于当mongodb异常crash后,重启时进行数据恢复;这归结于mongodb的数据持久写入磁盘是滞后的。默认情况下,“journal”特性是开启的,特别在production环境中,我们没有理由来关闭它。(除非,数据丢失对应用而言,是无关紧要的)
一个mongodb实例中所有的databases共享journal文件。
对于write操作而言,首先写入journal日志,然后将数据在内存中修改(mmap),此后后台线程间歇性的将内存中变更的数据flush到底层的data files中,时间间隔为60秒(参见配置项“syncPeriodSecs”);write操作在journal文件中是有序的,为了提升性能,write将会首先写入journal日志的内存buffer中,当buffer数据达到100M或者每隔100毫秒,buffer中的数据将会flush到磁盘中的journal文件中;如果mongodb异常退出,将可能导致最多100M数据或者最近100ms内的数据丢失,flush磁盘的时间间隔有配置项“commitIntervalMs”决定,默认为100毫秒。mongodb之所以不能对每个write都将journal同步磁盘,这也是对性能的考虑,mysql的binlog也采用了类似的权衡方式。开启journal日志功能,将会导致write性能有所降低,可能降低5~30%,因为它直接加剧了磁盘的写入负载,我们可以将journal日志单独放置在其他磁盘驱动器中来提高写入并发能力(与data files分别使用不同的磁盘驱动器)。
如果你希望数据尽可能的不丢失,可以考虑:1)减小commitIntervalMs的值 2)每个write指定“write concern”中指定“j”参数为true
3)最佳手段就是采用“replica set”架构模式,通过数据备份方式解决,同时还需要在“write concern”中指定“w”选项,且保障级别不低于“majority”。【】最终我们需要在“写入性能”和“数据一致性”两个方面权衡,即CAP理论。
根据write并发量,journal日志文件为1G,如果指定了smallFiles配置项,则最大为128M,和data files一样journal文件也采用了“preallocated”方式,journal日志保存在dbpath下“journal”子目录中,一般会有三个journal文件,每个journal文件格式类似于“j._&序列数字&”。并不是每次buffer flush都生成一个新的journal日志,而是当前journal文件即将满时会预创建一个新的文件,journal文件中保存了write操作的记录,每条记录中包含write操作内容之外,还包含一个“lsn”(last sequence number),表示此记录的ID;此外我们会发现在journal目录下,还有一个“lsn”文件,这个文件非常小,只保存了一个数字,当write变更的数据被flush到磁盘中的data files后,也意味着这些数据已经持久化了,那么它们在“异常恢复”时也不需要了,那么其对应的journal日志将可以删除,“lsn”文件中记录的就是write持久化的最后一个journal记录的ID,此ID之前的write操作已经被持久写入data files,此ID之前的journal在“异常恢复”时则不需要关注;如果某个journal文件中最大 ID小于“lsn”,则此journal可以被删除或者重用。
参考文献:
1)/2014/01/how-big-is-your-mongodb/
2)https://docs.mongodb.org/manual/faq/storage/
3)/blog/how-to/understanding-mongodb-space-usage
4)附件中的“storage-talk-mongodb.pdf”
部分文档中可能有一些错误,本文给予纠正。
下载次数: 32
浏览: 703407 次
来自: 北京
总结的很不错,学习了。。
hbb239 写道可以使用,不过有个小Bug。你这种人,我只能 ...
QING____ 写道Rafael520 写道楼主大牛,请问下 ...
di1984HIT 写道这个代码,应该是容器自己去做吧,自己应 ...
Rafael520 写道楼主大牛,请问下 是如何有效的阅读源码 ...}

我要回帖

更多关于 hellaflush什么意思 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信