使用MongoDB记录日志数据

尽管最近两三年里对MongoDB唱衰的声音不少,从一些实际的案例以及数据测试的报告来看,确实MongoDB也还存在一些问题,但不能否认的是MongoDB提供的灵活存储机制和许多便于开发的特性,仍然让其能作为一个值得考虑的解决方案。

用MongoDB去代替一些传统的解决方案,也需要注意适合MongoDB的机制,寻求合适的方法,也能收到不错的效果。以下就是关于使用MongoDB记录日志数据的一些经验之谈。

日志数据是对系统运行情况进行分析,研究用户行为等不可缺少的重要数据。传统的方式通常将日志记录在文本文件中,存储在本地文件系统的日志文件缺陷在于引用和分析时会比较困难一些,尽管对于海量的数据如今更多采用基于Hadoop这样的技术开发解决方案,不过在规模比较小的情况下,使用MongoDB也能提供一种整体开发代价比较小和易用的方案。

这主要是因为MongoDB提供了易用的Sharding机制,以及在2.1版本之后,其Aggregation Framework的改善,也使统计和分析变得十分便利。但在实际的使用过程当中,在设计上有一定的技巧,可以让系统做得更好。

第一个问题是MongoDB对存储空间的占用比较大,在设计数据的Schema时,需要做一点权衡。一方面,json数据中的key值,程序框架能够支持的话,最好采用比较简短的描述,这可以节省存储空间。比较极端的情况下,也可以把记录的写入时间省略,因为ObjectId中是可以提取出数据产生的时间的。另一方面,由于MongoDB的锁仍然是基于Database的(更早是基于进程),所以日志可能需要考虑使用独立的Database实例,甚至独立的MongoDB进程,在这样的情况下,为了统计需求的便利,适当记录额外信息也是可以的。总之,这应当根据应用的实际需求灵活一点做出决断。

第二个需要注意的问题是Sharding Key的设计。如果采用默认的ObjectId,那么潜在的问题是因为是基于时间戳的,这会让写入的压力并不是很有效地分散在各个shard上。对于读取也是一样,如果需要频繁查询近期的数据,那么这些读取压力也会趋向于分布于单台的shard。那么,如果是一个通过Hash产生的值作为Key呢?这虽然增加了一点程序复杂性(在比较新的版本里,MongoDB已经包含了该特性),但确实比较容易解决负载的均衡问题,但弊端在于额外增加的存储空间,以及查询中如果不包含这个key,那么查询将会需要同时在所有shard上执行,也是带来性能的额外消耗。最适合的方案是,需要注意MongoDB是提供了compound shard keys的特性,因此选取一个日志数据中将常用到的自然数据,结合一个通过Hash产生的值作为一个复合的key,将同时使分布的随机性以及读取的性能得到保障。

第三个问题是注意控制日志的数据总量维护,这其实在使用纯文本文件记录日志时,我们也通常会采用rotate的方式,避免单个文件过大,以及便于定期清除太旧的日志数据。在MongoDB里,可以注意活用Capped Collection和TTL Collection的特性来限制数据总量大小。也可以通过类似rotate logfiles的方式,将日志数据存储在不同的collection当中,而MongoDB对collection的重命名以及drop操作,都是十分高效的。根据之后统计聚合的需要,也可以考虑是对database实例做rotate。

对于聚合数据以及生成报表,基于MongoDB提供的查询接口实现也是十分容易了,因为MongoDB提供了类似MapReduce的写法,相对其它使用关系数据库记录日志数据的方案,要简单和灵活一些。

总之,其实对所使用基础技术组件的特性有所了解,以及结合应用的实际需求情况,才能做出比较适合的解决方案。这里面不存在太绝对的优劣,而更重要的是对工具的掌握情况以及对业务正确的分析和设计。