首页 > MongoDB + Redis 任务队列性能瓶颈

MongoDB + Redis 任务队列性能瓶颈

问题背景: 近期在重构公司内部一个重要的任务系统,由于原来的任务系统使用了MongoDB来保存任务,客户端从MongoDB来取,至于为什么用MongoDB,是一个历史问题,也是因为如果使用到MongoDB的数组查询可以减少任务数量很多次,假设这样的情况,一个md5需要针对N种情况做任务处理,如果用到MongoDB的数组,只需要将一个md5作为一条任务,其中包含一个长度为N的待处理任务列表(只有N个子任务都处理完后整个任务才算处理完毕),这样整个任务系统的数量级就变为原来的 1/N。

细节描述: 1.当MongoDB的任务数量增多的时候,数组查询相当的慢,任务数达到5K就已经不能容忍了。 2.任务处理每个md5对应的N个子任务必须要全部完成才从MongoDB中删除 3.任务在超时后可以重置

改进方案如下: 由于原有代码的耦合,不能完全抛弃MongoDB,所以决定加一个Redis缓存。一个md5对应的N个子任务分发到N个Redis队列中(拆分子任务)。一个单独的进程从MongoDB中向Redis中将任务同步,客户端不再从MongoDB取任务。这样做的好处是抛弃了原有的MongoDB的数组查询,同步进程从MongoDB中取任务是按照任务的优先级偏移(已做索引)来取,所以速度比数组查询要快。这样客户端向Redis的N个队列中取子任务,把任务结果返回原来的MongoDB任务记录中(根据md5返回子任务)。

改进过程遇到的问题: 由于客户端向MongoDB返回时候会有一个update操作,如果N个子任务都完成,就将任务从MongoDB中删除。这样的一个问题就是,经过测试后发现MongoDB在高并发写的情况下性能很低下,整个任务系统任务处理速度最大为200/s(16核, 16G, CentOS, 内核2.6.32-358.6.3.el6.x86_64),原因大致为在频繁写情况下,MongoDB的性能会由于锁表操作急剧下降。

具体问题: (Think out of the Box)能否提出一个好的解决方案,能够保存任务状态(子任务状态),速度至少超过MongoDB的?


任务队列可以考虑 rabbitmq 另外mongodb不应该这么慢吧,没加索引?或者试试capped collection.


初步的思考了一下,仅供参考:

  1. 首先,提一下索引,相信这个你应该加了索引。
  2. 有个问题确认一下,mongodb最新版本中的锁粒度还是Database级别吧,不知道你用的哪个版本,还没到锁表(Collection)这个粒度,所以写并发大的情况下比较糟糕,不过应该性能也不至于糟到像你描述的那样啊?不解,建议考虑任务分库的可能性?
  3. 能否考虑把子任务的状态和主任务的状态分开保存。子任务的状态,可以放到redis,主任务只负责自己本身的状态,这样每个主任务更新频率降为1/N,可大大减少mongodb中主任务表的压力。
  4. 子任务完成或超时后,可否考虑后台异步单线程顺序同步mongodb的主任务状态?

个人认为题主提到的MongoDB数组查询和更新的性能问题,很可能是Schema设计上的问题。但题主并没有给出具体的设计,所以我就提出几个值得关注的点仅供参考:

  1. 索引,正如楼上所说,你应该已经为数组加上了索引。但是值得注意的是,数组字段的索引比普通字段的索引体积要大很多(具体取决于数组的大小,数组越大,索引所占的空间越大)。这样就可能会导致一个问题:索引并不(完全)在内存里! 后果是,每次查询都需要涉及到额外的IO操作,性能会急剧下降。
  2. 查询返回文档的大小。如果每次返回查询的文档数据量较大,而且客户端与mongodb并不处于同一机器上,那就会增加了网络传输所需的时间(不要小看这点时间),所以尽可能只返回所需要的字段。
  3. update-in-place. 由于schemaless的特性,mongodb会为每条文档记录预留一些空间给增加额外的字段或数据时使用,提高update的性能。但如果你文档的大小频繁地扩展(增加字段,增加数组长度等),那就会导致写的性能问题:mongodb需要把增长了的文档移动(move)到别的地方。(相当于从硬盘的一个位置移动另一个更空闲的位置)这时候的性能会大大下降。

mongodb是一个内存型的数据库,如果你的热点数据都在内存上,它的性能会非常优异,而这很大程度取决与你的Schema设计。

PS:mongodb一直标榜的Schemaless优点误导了很多人,其实这个更多是想说明mongodb是动态的schema,而并不是不需要设计schema。

【热门文章】
【热门文章】