LBS解决方案
- LBS(基于地理位置的服务)服务是现在移动互联网中比较常用的功能,例如外卖中我附近的店铺,通常是以客户位置坐标为中心,查询一定范围内的店铺信息,按照距离由近及原进行倒叙排序
方案一,直接mysql
- 经纬度范围计算就是弧度的计算,只要数学够好,算出来分分钟的事情,直接上mysql一个语句搞定:
CREATE TABLE `places` (`id` INT (11) NOT NULL AUTO_INCREMENT,`lat` DOUBLE NOT NULL DEFAULT '0',`lng` DOUBLE NOT NULL DEFAULT '0',PRIMARY KEY (`Id`),KEY `lat` (`lat`),KEY `lng` (`lng`)
) ENGINE = INNODB AUTO_INCREMENT = 9 DEFAULT CHARSET = utf8;-- 测试经纬度
INSERT INTO `zhenai_yewu`.`places` (`lat`, `lng`) VALUES ( 23.123123, 123.123);
INSERT INTO `zhenai_yewu`.`places` (`lat`, `lng`) VALUES ( 21.123123, 121.123);
INSERT INTO `zhenai_yewu`.`places` (`lat`, `lng`) VALUES ( 22.123123, 122.123);
INSERT INTO `zhenai_yewu`.`places` (`lat`, `lng`) VALUES ( 24.123123, 124.123);
INSERT INTO `zhenai_yewu`.`places` (`lat`, `lng`) VALUES ( 25.123123, 125.123);
INSERT INTO `zhenai_yewu`.`places` (`lat`, `lng`) VALUES ( 26.123123, 126.123);
INSERT INTO `zhenai_yewu`.`places` (`lat`, `lng`) VALUES ( 27.123123, 127.123);
INSERT INTO `zhenai_yewu`.`places` (`lat`, `lng`) VALUES ( 28.123123, 128.123);-- 为了查询效果,将范围设置为255555 单位是KM
SELECT id, ( 6371 * acos( cos( radians(24.123123) ) * cos( radians( lat ) ) * cos( radians
( lng ) - radians(124.123) ) + sin( radians(24.123123) ) * sin( radians( lat ) ) ) ) AS distanceFROM places HAVING distance < 255555 ORDER BY distance LIMIT 0 , 100;
-- 结果信息 id: distance
12 0
13 150.27231636470339
9 150.80702908932616
14 299.9866451782974
11 302.12576883654754
15 449.11942176001054
10 453.93347262147796
16 597.6467747050862
- sql 查询的是lat,lng对应的坐标信息,中心点的信息是[124.123, 24.123123],明显这个是通过弧度计算来算出适合范围内的经纬度范围,然后搜索对应的id,如下sql语句分析
- 即使加上了索引这种查询也必须扫全表信息,效率低下
- 优化一下:
- 另外一种算法,可以根据圆心坐标计算正方形四个点的坐标,然后查询正方形内的点。
SELECT * FROM places WHERE ((lat BETWEEN 20 AND 29) AND (lng BETWEEN 90 AND 170));
//获取如下结果:
9 23.123123 123.123
10 21.123123 121.123
11 22.123123 122.123
12 24.123123 124.123
13 25.123123 125.123
14 26.123123 126.123
15 27.123123 127.123
16 28.123123 128.123
- 这样优化后,虽然数据不完全精确(圆形变正方形),但性能提升很明显,并且可以通过给lat lng字段做索引的方式进一步加快这条SQL的查询速度。对精度有要求的应用也可以在这个结果上再进行计算,排除那些在方块范围内但不在圆形范围内的数据,已达到对精度的要求。 但是没有排序,没有距离,除非做其他运算。
方案二,Redis
- Redis3.2.0后开始支持LBS相关的命令,要实现以上功能,主要用到Redis geo相关的两个命令 GEOADD, GETRADIOUS
- 命令描述,案例:
GEOADD key longitude latitude member [longitude latitude member ...]
//命令将指定地理空间位置(金纬度信息,名称)添加到指定key中
- 有效经度值域[-180,180], 有效纬度值域[-85.05112878 , 85.05112878 ],单坐标超过上述指定范围时候,会返回错误信息,可以一次添加多个位置地点,如下案例:
新docker-redis:0>GEOADD location 117 20 beijing
"1"
新docker-redis:0>GEOADD location 120 30 shenzheng
"1"
新docker-redis:0>GEOADD location 121 31 guangzhou, 122 32 nanjing
"2"
- 添加后通过GEORADIOUS查询如下:
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
- 命令以给定的经纬度为中心,返回键值key包含的位置元素中,与中心距离不超过给定最大距离的所有位置的元素,并且可以指定单位,顺序返回单位信息如下:
m:米
km:千米
mi:英里
ft:英尺
- 给定一下选项时候,命令会额外返回信息:
- WITHDIST:返回位置元素的同时,将位置元素与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致 如下:
新docker-redis:0>GEORADIUS location 120.987 30.1234 500 km WITHDIST1) 1) "shenzheng"2) "95.9992"2) 1) "guangzhou,"2) "97.5090"3) 1) "nanjing"2) "229.9580"
- WITHCOORD :将位置元素的进度和纬度也一起返回。
新docker-redis:0>GEORADIUS location 120.987 30.1234 500 km WITHCOORD1) 1) "shenzheng"2) 1) "120.00000089406967163"2) "30.00000024997701331"2) 1) "guangzhou,"2) 1) "120.99999815225601196"2) "31.00000097648057817"3) 1) "nanjing"2) 1) "122.00000077486038208"2) "31.99999916826298119"
- WITHHASH 以52位有符号整形,返回位置元素经过原始geohash编码的有序集合分值
新docker-redis:0>GEORADIUS location 120.987 30.1234 500 km WITHHASH1) 1) "shenzheng"2) "4054115787372083"2) 1) "guangzhou,"2) "4054742425916764"3) 1) "nanjing"2) "4066587848444692"
- asc:更具中心位置按从近到远的方式返回位置元素
- desc:根据中心位置,从远到近的方式返回位置元素
- 默认情况GEORADIUS命令会返回所有匹配的位置元素,用户可以使用count 获取前面N个
新docker-redis:0>GEORADIUS location 120.987 30.1234 500 km WITHHASH asc1) 1) "shenzheng"2) "4054115787372083"2) 1) "guangzhou,"2) "4054742425916764"3) 1) "nanjing"2) "4066587848444692"新docker-redis:0>GEORADIUS location 120.987 30.1234 500 km WITHHASH desc1) 1) "nanjing"2) "4066587848444692"2) 1) "guangzhou,"2) "4054742425916764"3) 1) "shenzheng"2) "4054115787372083"新docker-redis:0>GEORADIUS location 120.987 30.1234 500 km WITHHASH desc count 11) 1) "nanjing"2) "4066587848444692"
方案三
- mongodb 原生支持地理位置索引2dSpace,可以直接用于位置距离计算和查询。 而且我们用的也比较多,能够支持地理位置查询的同事也有免息集合存储,数据格式自由,查询性能高等特点,对应我们需求只需要一个mongodb查询语句:
db。moment.find( { location : { $geoWithin :{ $centerSphere :[ [121.492183, 31.247610 ] , 1000 / 3963.2 ]} } } )
- 查询结果默认由近及远,并且geoWithin是mongodb支持的查询函数,已经在性能上做了高度的优化,完全可以生产用,现在moment的地理位置推荐用的这个
方案四
- GeoHash是一种地址编码,通过切分地图区域变成小的方块,只有切分的足够小,精度就越高。
上一篇:Redis分布式锁奥义
下一篇:Redis遍历方式思考–字典扩容方式