当前位置: 移动技术网 > IT编程>数据库>MongoDB > MongoDB磁盘IO问题的3种解决方法

MongoDB磁盘IO问题的3种解决方法

2018年08月08日  | 移动技术网IT编程  | 我要评论

io概念

在数据库优化和存储规划过程中,总会提到io的一些重要概念,在这里就详细记录一下,对这个概念的熟悉程度也决定了对数据库与存储优化的理解程度,以下这些概念并非权威文档,权威程度肯定就不能说了。

读/写io,最为常见说法,读io,就是发指令,从磁盘读取某段扇区的内容。指令一般是通知磁盘开始扇区位置,然后给出需要从这个初始扇区往后读取的连续扇区个数,同时给出动作是读,还是写。磁盘收到这条指令,就会按照指令的要求,读或者写数据。控制器发出的这种指令+数据,就是一次io,读或者写。

大/小块io,指控制器的指令中给出的连续读取扇区数目的多少,如果数目很大,比如128,64等等,就应该算是大块io,如果很小,比如1, 4,8等等,就应该算是小块io,大块和小块之间,没有明确的界限。

连续/随机io,连续和随机,是指本次io给出的初始扇区地址,和上一次io的结束扇区地址,是不是完全连续的,或者相隔不多的,如果是,则本次io应该算是一个连续io,如果相差太大,则算一次随机io。连续io,因为本次初始扇区和上次结束扇区相隔很近,则磁头几乎不用换道或换道时间极短;如果相差太大,则磁头需要很长的换道时间,如果随机io很多,导致磁头不停换道,效率大大降底。

顺序/并发io,这个的意思是,磁盘控制器每一次对磁盘组发出的指令套(指完成一个事物所需要的指令或者数据),是一条还是多条。如果是一条,则控制器缓存中的io队列,只能一个一个的来,此时是顺序io;如果控制器可以同时对磁盘组中的多块磁盘,同时发出指令套,则每次就可以执行多个io,此时就是并发io模式。并发io模式提高了效率和速度。

io并发几率。单盘,io并发几率为0,因为一块磁盘同时只可以进行一次io。对于raid0,2块盘情况下,条带深度比较大的时候(条带太小不能并发io,下面会讲到),并发2个io的几率为1/2。其他情况请自行运算。

iops。一个io所用的时间=寻道时间+数据传输时间。 iops=io并发系数/(寻道时间+数据传输时间),由于寻道时间相对传输时间,大几个数量级,所以影响iops的关键因素,就是降底寻道时间,而在连续io的情况下,寻道时间很短,仅在换磁道时候需要寻道。在这个前提下,传输时间越少,iops就越高。

每秒io吞吐量。显然,每秒io吞吐量=iops乘以平均io size。 io size越大,iops越高,每秒io吞吐量就越高。设磁头每秒读写数据速度为v,v为定值。则iops=io并发系数/(寻道时间+io size/v),代入,得每秒io吞吐量=io并发系数乘io size乘v/(v乘寻道时间+io size)。我们可以看出影响每秒io吞吐量的最大因素,就是io size和寻道时间,io size越大,寻道时间越小,吞吐量越高。相比能显著影响iops的因素,只有一个,就是寻道时间。

mongodb磁盘io问题的3种解决方法

1.使用组合式的大文档

我们知道mongodb是一个文档数据库,其每一条记录都是一个json格式的文档。比如像下面的例子,每一天会生成一条这样的统计数据:

  { metric: content_count, client: 5, value: 51, date: isodate(2012-04-01 13:00) }

  { metric: content_count, client: 5, value: 49, date: isodate(2012-04-02 13:00) }

而如果采用组合式大文档的话,就可以这样将一个月的数据全部存到一条记录里:

  { metric: content_count, client: 5, month: 2012-04, 1: 51, 2: 49, ... }

通过上面两种方式存储,预先一共存储大约7gb的数据(机器只有1.7gb的内存),测试读取一年信息,这二者的读性能差别很明显:

  第一种: 1.6秒

  第二种: 0.3秒

  那么问题在哪里呢?

实际上原因是组合式的存储在读取数据的时候,可以读取更少的文档数量。而读取文档如果不能完全在内存中的话,其代价主要是被花在磁盘seek上,第一种存储方式在获取一年数据时,需要读取的文档数更多,所以磁盘seek的数量也越多。所以更慢。

实际上mongodb的知名使用者foursquare就大量采用这种方式来提升读性能。

2.采用特殊的索引结构

我们知道,mongodb和传统数据库一样,都是采用b树作为索引的数据结构。对于树形的索引来说,保存热数据使用到的索引在存储上越集中,索引浪费掉的内存也越小。所以我们对比下面两种索引结构:

  db.metrics.ensureindex({ metric: 1, client: 1, date: 1}) 与 db.metrics.ensureindex({ date: 1, metric: 1, client: 1 })

采用这两种不同的结构,在插入性能上的差别也很明显。

当采用第一种结构时,数据量在2千万以下时,能够基本保持10k/s 的插入速度,而当数据量再增大,其插入速度就会慢慢降低到2.5k/s,当数据量再增大时,其性能可能会更低。

而采用第二种结构时,插入速度能够基本稳定在10k/s。

其原因是第二种结构将date字段放在了索引的第一位,这样在构建索引时,新数据更新索引时,不是在中间去更新的,只是在索引的尾巴处进行修改。那些插入时间过早的索引在后续的插入操作中几乎不需要进行修改。而第一种情况下,由于date字段不在最前面,所以其索引更新经常是发生在树结构的中间,导致索引结构会经常进行大规模的变化。

3.预留空间

与第1点相同,这一点同样是考虑到传统机械硬盘的主要操作时间是花在磁盘seek操作上。

比如还是拿第1点中的例子来说,我们在插入数据的时候,预先将这一年的数据需要的空间都一次性插入。这能保证我们这一年12个月的数据是在一条记录中,是顺序存储在磁盘上的,那么在读取的时候,我们可能只需要一次对磁盘的顺序读操作就能够读到一年的数据,相比前面的12次读取来说,磁盘seek也只有一次。

  db.metrics.insert([

  { metric: content_count, client: 3, date: 2012-01, 0: 0, 1: 0, 2: 0, ... }

  { .................................., date:

  { .................................., date:

  { .................................., date:

  { .................................., date:

  { .................................., date:

  { .................................., date:

  { .................................., date:

  { .................................., date:

  { .................................., date:

  { .................................., date:

  { .................................., date:

  ])

结果:

  如果不采用预留空间的方式,读取一年的记录需要62ms

  如果采用预留空间的方式,读取一年的记录只需要6.6ms

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网