云MongoDB优化让LBS服务性能提升十倍

随着国内服务共享化的热潮普及,共享单车,共享雨伞,共享充电宝等各种服务如雨后春笋,随之而来的LBS服务定位问题成为了后端服务的一个挑战。MongoDB对LBS查询的支持较为友好,也是各大LBS服务商的首选数据库。腾讯云MongoDB团队在运营中发现,原生MongoDB在LBS服务场景下有较大的性能瓶颈,经腾讯云团队专业的定位分析与优化后,云MongoDB在LBS服务的综合性能上,有10倍以上的提升。 腾讯云MongoDB提供的优异综合性能,为国内各大LBS服务商,例如摩拜单车等,提供了强有力的保障。

LBS业务特点

以共享单车服务为例,LBS业务具有2个特点,分别是时间周期性和坐标分布不均匀。

一.时间周期性

高峰期与低谷期的QPS量相差明显,并且高峰期和低峰期的时间点相对固定。

二.坐标分布不均匀

坐地铁的上班族,如果留意可能会发现,在上班早高峰时,地铁周围摆满了共享单车,而下班 时段,地铁周围的共享单车数量非常少。如下图,经纬度(121,31.44)附近集中了99%以上 的坐标。此外,一些特殊事件也会造成点的分布不均匀,例如深圳湾公园在特殊家假日涌入大量的客户,同时这个地域也会投放大量的单车。部分地域单车量非常集中,而其他地域就非常稀疏。

MongoDB的LBS服务原理

MongoDB中使用2d_index 或2d_sphere_index来创建地理位置索引(geoIndex),两者差别不大,下面我们以2d_index为例来介绍。

一.2D索引的创建与使用

db.coll.createIndex({"lag":"2d"}, {"bits":int}))
通过上述命令来创建一个2d索引,索引的精度通过bits来指定,bits越大,索引的精度就越高。更大的bits带来的插入的overhead可以忽略不计
db.runCommand({
geoNear: tableName,
maxDistance: 0.0001567855942887398,
distanceMultiplier: 6378137.0,
num: 30,
near: [ 113.8679388183982, 22.58905429302385 ],
spherical: true|false})

通过上述命令来查询一个索引,其中spherical:true|false 表示应该如何理解创建的2d索引,false表示将索引理解为平面2d索引,true表示将索引理解为球面经纬度索引。这一点比较有意思,一个2d索引可以表达两种含义,而不同的含义是在查询时被理解的,而不是在索引创建时。

二.2D索引的理论 MongoDB 使用GeoHash的技术来构建2d索引(见wiki geohash 文字链 https://en.wikipedia.org/wiki/Geohash )。MongoDB使用平面四叉树划分的方式来生成GeoHashId,每条记录有一个GeoHashId,通过GeoHashId->RecordId的索引映射方式存储在Btree中

很显然的,一个2bits的精度能把平面分为4个grid,一个4bits的精度能把平面分为16个grid。 2d索引的默认精度是长宽各为26,索引把地球分为(2^26)(2^26)块,每一块的边长估算为2PI6371000/(1<<26) = 0.57 米 mongodb的官网上说的60cm的精度就是这么估算出来的 By default, a 2d index on legacy coordinate pairs uses 26 bits of precision, which isroughly equivalent to 2 feet or 60 centimeters of precision using the default range of-180 to 180

三.2D索引在Mongodb中的存储

上面我们讲到Mongodb使用平面四叉树的方式计算Geohash。事实上,平面四叉树仅存在于运算的过程中,在实际存储中并不会被使用到。

插入 对于一个经纬度坐标[x,y],MongoDb计算出该坐标在2d平面内的grid编号,该编号为是一个52bit的int64类型,该类型被用作btree的key,因此实际数据是按照 {GeoHashId->RecordValue}的方式被插入到btree中的。

查询 对于geo2D索引的查询,常用的有geoNear和geoWithin两种。geoNear查找距离某个点最近的N个点的坐标并返回,该需求可以说是构成了LBS服务的基础(陌陌,滴滴,摩拜),geoWithin是查询一个多边形内的所有点并返回。我们着重介绍使用最广泛的geoNear查询。

geoNear的查询过程,查询语句如下

db.runCommand(
{
geoNear: "places", //table Name
near: [ -73.9667, 40.78 ] , // central point
spherical: true, // treat the index as a spherical index
query: { category: "public" } // filters
maxDistance: 0.0001531 // distance in about one kilometer
}

geoNear可以理解为一个从起始点开始的不断向外扩散的环形搜索过程。如下图所示: 由于圆自身的性质,外环的任意点到圆心的距离一定大于内环任意点到圆心的距离,所以以圆 环进行扩张迭代的好处是: 1)减少需要排序比较的点的个数 2)能够尽早发现满足条件的点从而返回,避免不必要的搜索 MongoDB在实现的细节中,如果内环搜索到的点数过少,圆环每次扩张的步长会倍增

MongoDB LBS服务遇到的问题

部分大客户在使用MongoDB的geoNear功能查找附近的对象时,经常会发生慢查询较多的问题,早高峰压力是低谷时段的10-20倍,坐标不均匀的情况慢查询严重,濒临雪崩。初步分析发现,这些查询扫描了过多的点集。 如下图,查找500米范围内,距离最近的10条记录,花费了500ms,扫描了24000+的记录。类似的慢查询占据了高峰期5%左右的查询量

测试环境复现与定位 排查数据库的性能问题,主要从锁等待,IO等待,CPU消耗三封面分析。上面的截图扫描了过多的记录,直觉上是CPU或者IO消耗性的瓶颈。为了严谨起见,我们在测试环境复现后,发现慢日志中无明显的timeAcquiringMicroseconds项排除了MongoDB执行层面的锁竞争问题,并选用较大内存的机器使得数据常驻内存,发现上述用例依旧需要200ms以上的执行时间。10核的CPU资源针对截图中的case,只能支持50QPS。

为何扫描集如此大 上面我们说过,MongoDB搜索距离最近的点的过程是一个环形扩张的过程,如果内环满足条件的点不够多,每次的扩张半径都会倍增。因此在遇到内环点稀少,外环有密集点的场景时,容易陷入BadCase。如下图,我们希望找到离中心点距离最近的三个点。由于圆环扩张太快,外环做了很多的无用扫描与排序。 这样的用例很符合实际场景,早高峰车辆聚集在地铁周围,你从家出发一路向地铁,边走边找,共享单车软件上动态搜索距你最近的10辆车,附近只有三两辆,于是扩大搜索半径到地铁周围,将地铁周围的所有几千辆车都扫描计算一遍,返回距离你最近的其余的七八辆

问题的解决

问题我们已经知道了,我们对此的优化方式是控制每一圈的搜索量,为此我们为geoNear命令增加了两个参数,将其传入NearStage中。hintCorrectNum可以控制结果品质的下限,返回的前N个一定是最靠近中心点的N个点。hintScan用以控制扫描集的大小的上限。

hintScan: 已经扫描的点集大小大于hintScan后,做模糊处理。 hintCorrectNum:已经返回的结果数大于hintCorrectNum后,做模糊处理。

该优化本质上是通过牺牲品质来尽快返回结果。对于国内大部分LBS服务来说,完全的严格最近并不是必要的。且可以通过控制参数获得严格最近的效果。在搜索过程中,密集的点落到一个环内,本身距离相差也不会不大。该优化在上线后,将部分大客户的MongoDB性能上限从单机1000QPS提升了10倍到10000QPS以上。

和Redis3.2的对比

Redis3.2也加入了地理位置查询的功能,我们也将开源Redis和云数据库MongoDB进行对比。 Redis使用方式:GEORADIUS appname 120.993965 31.449034 500 m count 30 asc。在密集数据集场景下,使用腾讯云MongoDB和开源的Redis进行了性能对比。下图是在密集数据集上,在24核CPU机器上,MongoDB单实例与Redis单实例的测试对比。需要注意的是Redis本身是单线程的内存缓存数据库。MongoDB是多线程的高可用持久化的数据库,两者的使用场景有较大不同。

总结

MongoDB原生的geoNear接口是国内各大LBS应用的主流选择。原生MongoDB在点集稠密的情况下,geoNear接口效率会急剧下降,单机甚至不到1000QPS。腾讯云MongoDB团队对此进行了持续的优化,在不影响效果的前提下,geoNear的效率有10倍以上的提升,为我们的客户如摩拜提供了强力的支持,同时相比Redis3.2也有较大的性能优势。

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

1 条评论
登录 后参与评论

相关文章

来自专栏瓜大三哥

DCM 模块的Verilog HDL 调用

DCM 共由四部分组成,如图12-6 所示。其中最底层仍采用成熟的DLL 模块;其次分别为数字频率合成器(DFS,Digital Frequency Synth...

2058
来自专栏Vamei实验室

协议森林02 小喇叭开始广播 (以太网与WiFi协议)

“小喇叭开始广播啦”,如果你知道这个,你一定是老一辈的人。“小喇叭”是五十年代到八十年代的儿童广播节目。在节目一开始,都会有一段这样的播音:“小朋友,小喇叭开始...

17710
来自专栏跟着阿笨一起玩NET

C#中的串口通信

串行接口按电气标准及协议来分,包括RS-232-C、RS-422、RS485、USB等。 RS-232-C、RS-422与RS-485标准只对接口的电气特性做出...

342
来自专栏人人都是极客

开源飞控StarryPilot无人机的制作

近年来无人机应用市场日趋火热,无人机开始被应用在多个领域之中,比如航拍,植保,运输,安防等。随着应用场景的增加,对于无人机的大脑一飞控,的性能和功能要求也变得越...

541
来自专栏机器人网

技术猿 | 室外移动机器人组合的导航定位系统设计

---- 对于在室外环境工作的移动机器人通常使用惯导/卫星组合导航方式。惯性导航系统[1]具有完全自主、抗干扰强、隐蔽能力好和输出参数全面等优点,但它的鲁棒性...

2545
来自专栏LanceToBigData

TCP/IP(二)物理层详解

前言   在前面说了一下,计算机网络的大概内容,没有去深刻的去了解它,这篇文章给大家分享一下物理层!   我们知道ISO模型是七层,TCP/IP模型是五层,而t...

2195
来自专栏pythonlove

Linux防火墙iptables/netfilter(一)

防火墙大家都不陌生,或者说都听说过,现实中的防火墙是将一个区域内的火隔离开来使之不蔓延到另一个区域,计算机领域的防火墙与之功能类似,也是为了隔离危险。在如今广阔...

501
来自专栏大数据挖掘DT机器学习

【案例】电商数据挖掘:牛奶搭配销售关联算法

在数据挖掘过程中,由于数据存在分散性和偶然性,因而在底层的数据关联上很难准确挖掘出强关联规则,进而也很难为我们决策提供参考。通常的解决的方案通常是引...

3655
来自专栏Java3y

物理层

物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流,而不是指具体的传输媒体。

943
来自专栏机器人网

通俗易懂!机加工和装配自动化生产线的结构组成案例

根据制造行业及工艺上的区别,自动化生产线具有很多类型,例如自动化机械加工生产线、自动化装配生产线、自动化喷涂生产线、自动化焊接生产线、自动化电镀生产线等。其中最...

4516

扫码关注云+社区