对于很多 LBS 应用来说,让用户寻找周围的好友 可能 都是一个必不可少的功能,下面 我们就以这个功能为例:
对于以上的问题, 目前解决方案有很多种, 比如:
a. 基于 MySQL 数据库 b. 采用 GeoHash 索引,基于 MySQL c. MySQL 空间存储(MySQL Spatial Extensions) d. 使用 MongoDB 存储地理位置信息 e. 使用 PostgreSQL 存储地理位置信息
关于 a、b、c, 这篇文章 已经很好的说明了, 这里就不一一赘述, 下面我们主要是基于方案 d 深入探讨一些东西。
MongoDB 原生支持地理位置索引,可以直接用于位置距离计算和查询。查询结果默认将会由近到远排序,而且查询结果也包含目标点对象、距离目标点的距离等信息。
而且 geoNear 是 MongoDB 原生支持的查询函数,所以性能上也做到了高度的优化,完全可以应付生产环境的压力。
2d index:
使用 2d index 能够将数据作为 2 维平面上的点存储起来, 在 MongoDB 2.2 以前 推荐使用 2d index 索引。 现在 MongodDB 2.6 了, 推荐使用 2dspere index
2dsphere index:
2dsphere index 支持球体的查询和计算,同时它支持数据存储为GeoJSON 和传统坐标。
先说说 测试环境:
Mac Pro(处理器 Intel Core i5、2.4 GHz、2 核、16G 内存) + Mongo 2.6 + Rails4.1.4
model 代码:
class User
field :location, type: Array
index({ location: "2d"}, { background: true })
# 或者 index({ location: "2dsphere"}, { background: true })
class << self
def nearby(coordinate, max_distance=5)
# 5公里内, 符合条件的记录, 默认取100个。同时会按照距离的远近 进行排序。
self.geo_near(coordinate).max_distance(max_distance.fdiv 6371).spherical.distance_multiplier(6371000)
end
end
end
使用 命令1
的 查询时间:
User.nearby([117.490219, 40.962954]).count
# 5公里内, 符合条件的记录, 默认取100个。同时会按照距离的远近 进行排序。
# 距离 存在 attributes["geo_near_distance"] 中, example:User.nearby([117.490219, 40.962954]).first["geo_near_distance"]
通过测试发现, 使用 2d index 在数据量 变大的过程中, 查询时间 会变的 非常慢, 而使用 2d sphere index 基本可以控制在 0.5s 左右。这里 留下一个问题: 为什么会是这样的?
使用命令2
:
User.where(:location => {"$within" => {"$centerSphere" => [[116.490219, 42.962954], (5.fdiv(6371) )]}}).count
# 5公里内, 符合条件的记录、默认会选出所有符合条件的结果。
# 缺点是 需要自己进行排序, 且需要自己计算 geo_near_distance。
命令2
因为不需要 对 符合条件的结果 进行排序, 所以 查询时间 相比 命令1
的 查询时间 大大减少。
备注: 每 1w 条 数据的插入时间是 8s 左右。
MongoDB 查询地理位置默认有 3 种距离单位: 米 (meters) 平面单位 (flat units,可以理解为经纬度的 “一度”) 弧度 (radians)
2d 索引能同时支持$center 和$centerSphere, 2dsphere 索引支持$centerSphere。 关于距离单位,$center 默认是度,$centerSphere 默认距离是弧度。
关于 PostgreSQL 和 Rails 的结合, 可以参考 我同事@windstill的文章, 这里就不具体描述了
测试环境介绍:
Mac Pro(处理器 Intel Core i5、2.4 GHz、2 核、16G 内存) + PostgreSQL 9.3.5 + PostGis2.1.3(PostgreSQL 的扩展) + Rails4.1.4
备注:postgis完整实现了opengis 的 Simple Features标准之中的空间对象模型和函数
测试命令
User.select("users.*, st_distance(location, 'point(116.458104 39.966293)') as distance").where("st_dwithin(location, 'point(116.458104 39.966293)', 10000)").order("distance")
# 查找10公里 内结果, 并按照距离进行排序
测试结果:
关于地理位置的计算, 其实 Mysql、MongoDB、PostgreSQL 都支持,只不过 MongoDB 和 PostgreSQL 支持的更好一些。 而且通过 测试我们可以发现 MongoDB 在数据量 变大的时候, 查询的瓶颈 会变的越来越大。反过来看 PostgreSQL, 它的查询时间基本是随着 数据量的增长, 而线性增长的。
所以 如果你的应用 数据量不大 或者说在百万级别的话 可以考虑用 MongoDB。但是如果数据量是千万级别或者更高的话, 推荐使用 PostgreSQL。
当然了 由于我们这个项目业务既 需要 事务,又需要 Geo 计算, 所以选择 PostgreSQL 作为我们的主数据库。
后期 会在使用 PostgreSQL 使用一段 时间后, 深入的总结一些关于它的东东...