转自:http://quentinxxz.iteye.com/blog/2149440
一、正常情况下,不应该有这种需求
首先,大家应该有个概念,标题中的这个问题,在大多情况下是一个伪命题,不应该被提出来。要知道,对于一般较大数据量的数据库,全表查询,这种操作一般情况下是不应该出现的,在做正常查询的时候,如果是范围查询,你至少应该要加上limit。
说一下,我的应用场景:用于全量建立搜索引擎的索引。这就是一种需要用到全表扫描的非一般情况。对于全表扫描的结果,我们没有排序要求。
二、情况说明
既然有如此大的数据量,那存储所占空间基本都是上T的了。所以肯定是使用了mongodb集群,而且配置了分片的分布式环境。
公司的服务器,性能比较好,应该是24核,96g内存的。所以读者们使用不同机器,测出来的用时,跟我这的结果可能不一定相符。
三、第一种方法,利用chunk信息作划分。
原理:我们知道,在分片环境下,mongodb使用chunk块来组织数据。同一个chunk块的数据肯定存在于同一个分片上。假设,我们以 “_id”作分片时所用的片键。Mongodb为了保证范围查找的效率,一定会将一定范围内的_id值的document方在同一个chunk中。而且通过mongodb自身提供的方法,我们可以很方便的获取,每一个chunk的中maxKey与minKey。[minKey,maxKey) 这个范围内的数据肯定在同一个chunk内,也并定在同一个分片中。
做法:1、先获取所有chunk信息,得到他们的maxKey与minKey。
2、多线程执行,将这些chunk信息,分发给这些执行线程。
3、各线程,根据当前的chunk与 maxKey与minKey信息,做范围查找。
4、最后将结果汇总。
这样做的好处在于:
1、每次对一个chunk做的范围查找,肯定是只在一个分片(意味着同一块硬盘)中进行的,不会分散到多个分片。这样很高效。
2、可以方便的利用多线程,提高效率。
这种方法我没试过,公司的前辈尝试过,据说,最终用时3小时以内。是最为理想的效果。
四、改用散列片键后的全表扫描方法
用上面的方法,有一个前题,就是分片策略采用的是mongodb默认的升序片键的方法。这样才保证,升序的_id,会按排序范围分布在chunk块中。这样这策略,存在一个明显的问题,就是会造成所以新增的doucument的写入肯定都会命中到具有当前最大_id的chunk上。造成写入的分发的不平衡。
前文中,说过,全表扫描,应该是一个正常情况下不被允许的情况。所以数据库策略的制定也不应该以考虑全表扫描的效率为优先,当前情况下,就应以写入效为优先考虑。公司正在使用的片键策略,是片键策略散列片键(hashed shard key),这样的话,写入请况会被很好地分发到多个分片上,但是不利用进行范围查找。上面用的全表扫描方法就没法再用了。
做法:1、获得全局的最大id maxID与全局的最小id minID。
2、设置一个stepSize ,比如5000。将[minID,maxID]按5000为一个chunk(我们定义的一个逻辑chunk)作切分,那么第一个块范围[minID,minID+5000),
3、各线程,根据分配到的chunk块的maxID与minID信息,做范围查找。
4、最后将结果汇总。
这样做法的问题:
1、对一个chunk进行查询,会命中多个分片进行查询,查询效率大幅降低。
2、 如果_id分布稀疏,查询变得更快。因为以5000为stepSize, 其中可能有不少_id是不存在的。测试同学帮我搭线下测试数据时,2000w条数据,由于计算错误,_id的范围分布剧然从10亿起,到130亿止。导致的线程几乎一起在空跑chunk。所以_id分布稀疏的情况下,这种查询方式完全不适用。
3、 _id分布不均。可能某个chunk中几乎5000个满载,有些chunk只有很少几个_id有效。那么就会导致计算资源分布不均。
最后的结果不理想,我一开始,由于涉及一些联表join操作,用时16多个小时。后来各种调整,加线程,去掉一些操作,差不多仍需10小时。
五、改用散列片键后的较高效全表扫描方法
上面的那种方法,当然是不理想的。光查数据就需要16个小时,加上后续处理,肯定更久,这样我们的搜索引擎索引建立,就不可能当前完成了。但一直苦于没有更好的方法。
最终这个问题,还是被我的头解决了。 先说一下,最终效果吧。20w条/秒,约3小时完成。
其实,最终的方法很简单,但必须放弃多线程(多cursor),至多一个分片一个线程。
做法:使用单线程扫描,不加适合可能影响排序的条件。
这样做的目的是使用mongodb中的自然排序。扫描时,必然是依次命中某一个分片读取,不会带来磁盘震荡问题。