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遍历方式思考–字典扩容方式