利用mongodb开发lbs应用实践
近期作为突击队员,与同事一起突击构建了一个简单的lbs系统。当前比较主流的做法是使用mongodb,因为其已经封装了常用的lbs基本操作(如查找附近的人),功能非常强大,对于开发周期只有一周的项目,mongodb真可谓是救世主,把最重要的需求给完成了,谢天谢地!
mongodb是比较著名的nosql db,想了解的同学不妨问问谷歌或度娘,对于专业问题本人倾向于问谷歌。本文还参考了这篇文章。我们使用的是目前最新版本的mongodb-2.4.9。
首先从mongodb提供的lbs功能讲起:mongodb支持以下几种查询:
1.区域内搜索:所谓区域内搜索,即列出附近一定范围内的所有记录,如baidu地图的“附近”。mongodb使用$geoWithin命令即可实现此类查询,其强大之处在于支持矩形区域($box)、圆形区域( $center)和多边形区域( $polygon)查询!以圆形区域查询为例,只需要给出圆心的经纬度和查询半径,就可以取到在该区域内的所有记录,不过返回结果是无序的。
2.查找附近:查找附近即将某一个位置附近的记录按照由近到远的顺序返回,如很多社交app的查找附近的人。mongodb使用$near命令,输入中心点的坐标即可返回结果,结果是按照距离由小到大排序好的。$near接口比$geoWithin多了一步排序,但对2维坐标建索引的话,二者的查询效率基本相同。
另外还有一个命令是$geoNear,可以认为是$near的升级版,除了返回记录之外还会返回距离及其他诊断信息。我们的应用需要计算距离,一开始就想使用这个命令,但是其使用很不方便。很奇怪的,该命令不能像$near和$geoWithin般可以和find命令集成,使用过程中很不灵活,无法完全满足我们的应用需求。
其次说说距离计算
MongoDB地理位置索引常用的有两种:1)2d 平面坐标索引,适用于基于平面的距离计算。2)2dsphere 几何球体索引,适用于球面几何距离运算。追求百分之百的精确,应选择2dsphere。不过,在坐标跨度不太大(如两千公里以内),这两个索引计算出的距离相差几乎可以忽略不计。关于mongodb实现的2维地理位置索引,可以参考这篇文章,写的简单易懂。
接下来说说实践中碰到的复合索引的使用。因为mongodb是个开源项目,总会有些不完善的地方,而这些问题也只有使用的时候才会碰到。
复合索引即对多个字段联合建立索引,假设在一个mongo集合(collection)的记录含有A和B两个字段,我们要查询A大于0且B大于100的记录,显然A、B的复合索引{“A”:1,”B”:1}可以有效提高这类查询的效率,mongodb也做到了。该查询用mongo的语言描述为:db.posts.find({ “A”:{$gt:0}, “B”:{$gt:100} } )。但如果我们要得到A大于0且按照B升序排序的记录,刚才建立的联合索引就不起作用了。这个查询用mongo的语言描述为:db.posts.find({ “A”:{$gt:0}} ).sort({“B”:1})。
而且即使对所取记录总数做限制也无法提高查询效率。例如我假设复合条件的记录共有2000条,我只取100条,即db.posts.find({ “A”:{$gt:0}} ).sort({“B”:1}).limit(100)。按我的理解,如果复合索引起作用,不管limit与否都应该很快。但测试下来发现,查询的时间跟符合条件的记录总数成正比,即2000条取100条要50ms,而1000条取100条就只要30ms,似乎是不管limit多少,mongo会把所有满足find条件的记录全部读到内存,再根据sort的条件进行排序。这排序恰恰是性能的瓶颈所在,如果去除排序,2000条取100条也只要10ms左右,查询A大于0且B大于100的记录也是只要100ms。
据老外的分析,这个是mogodb目前的bug,应该会在2.6版本fix掉,希望如此吧。目前也没有什么办法可以优化这个问题,只能在find的时候通过更多的限制条件,减少被排序的记录数目,但在面对实际需求时往往不能这么做。
所以,此次项目学到的经验是,开源项目的确有非常强大的功能,帮我们解决了大部分基础问题,也常常给我们很多惊喜(如mongo对地理信息查询的强大而全面的支持)。不过,所谓“天下没有免费的午餐”,使用开源项目不仅需要学习怎么用,使用过程中也会遇到一些坑,需要交点学费,所以,要时刻保持谨慎和批判的态度。