共计 3221 个字符,预计需要花费 9 分钟才能阅读完成。
置信微信左近的人的性能大家都应该用过
我能够很随便的通过我本人的定位能看到我左近的人,并且能看到那个人间隔我的间隔,大家有没有思考过这个是怎么实现的?
作为一个程序猿任何问题应该都有一个思考的过程,而不是间接看论断,接下来大家一步一步的思考,直到问题解决。
获取本人的地位
左近的人其实就是一种地位的比对关系,所以第一步是得获取本人的地位,个别地位都是用经纬度来示意,具体经纬度的获取得依赖客户端,作为咱们后端程序员间接接管参数就能够了,所以这一步重点是用经纬度来示意各个节点的地位,对经纬度不是很理解的敌人能够温习一下中学的地理知识。
用关系型数据库 (mysql) 的形式解决问题
咱们先把问题简化,如果我左近的人都是不动的,也就是说他们的地位是固定的,依照咱们传统的思路,就是把每个人的经纬度存起来,而后遍历这些经纬度,咱们能够通过某种办法获取我和各个经纬度之间的间隔,而后把绝对于我间隔在 5km 以内的用户展现进去就能够了
具体实现如下
把每个人的经纬度存起来,存储如下
user_id | longitude(经度) | latitude(纬度) |
---|---|---|
1 | 116.39 | 39.91 |
2 | 121.48 | 31.4 |
3 | 117.30 | 39.71 |
… | … | … |
遍历数据,和本人比照,取得每个人和本人的间隔
把数据库的所有记录都遍历一遍,把每一条记录的经纬度和本人的经纬度做个比照,就能获取到各个记录离本人的间隔。
如何依据两个经纬度,获取到这两个点之间的间隔我在网上招了个办法,大家能够参考下
/**
* 求两个已知经纬度之间的间隔, 单位为米
*
* @param lng1 $ ,lng2 经度
* @param lat1 $ ,lat2 纬度
* @return float 间隔,单位米
* @author www.Alixixi.com
*/
function getdistance($lng1, $lat1, $lng2, $lat2) {
// 将角度转为狐度
$radLat1 = deg2rad($lat1); //deg2rad()函数将角度转换为弧度
$radLat2 = deg2rad($lat2);
$radLng1 = deg2rad($lng1);
$radLng2 = deg2rad($lng2);
$a = $radLat1 - $radLat2;
$b = $radLng1 - $radLng2;
$s = 2 * asin(sqrt(pow(sin($a / 2), 2) + cos($radLat1) * cos($radLat2) * pow(sin($b / 2), 2))) * 6378.137 * 1000;
return $s;
}
筛选出间隔和本人在 5km 以内的数据就是咱们想得到的后果
把上次算进去的间隔一一比照,在 5km 以内的数据就是咱们须要的左近的人的数据。
用关系型数据库 (mysql) 存在的问题
其实用 mysql 的形式外表上看着是能够解决问题的,其实不然
- 首先遍历数据就是遍历所有的数据,而且是在一个须要及时返回后果的接口中,这样做是十分不迷信的,用户量十分多的话基本不事实
- 遍历完了之后还得持续计算间隔,这个数量级也是十分大的
- 间隔那些都弄完了还得再筛选一遍在左近的,又是一遍所有数据的遍历
- 如果合乎左近的人的要求是须要依照间隔从近到远来排序,又得遍历计算
上述形式如果用户量比拟小其实是能够实现的,然而当初挪动互联网公司个别用户体量都很大,全表遍历的形式根本都能够 pass 掉,所以接下来咱们来看一种新的计划,用 redis geo 的形式来实现
redis geo 介绍
首先咱们须要留神的是,redis geo 是 3.2 版本才有的,所以须要用这个性能的敌人记得更新 redis 的版本
其实 redis geo 只有 6 个操作命令, 晓得这些命令基本思路就进去了
- GEOADD:减少某个地理位置的坐标
- GEOPOS:获取某个地理位置的坐标
- GEODIST:获取两个地理位置的间隔
- GEORADIUS:依据给定地理位置坐标获取指定范畴内的地理位置汇合
- GEORADIUSBYMEMBER:依据给定地理位置获取指定范畴内的地理位置汇合
- GEOHASH:获取某个地理位置的 geohash 值
对于下面的命令,咱们间接看例子吧, 不便大家更深刻的了解
redis> GEOADD nearbyPeople 13.36 38.11 "user_1" 15.08 37.50 "user_2"
(integer) 2
对于下面例子来说 相当于 nearbyPeople 是一个总的 key,user_1 和 user_2 是相当于 nearbyPeople 外面的两个元素以及他们对应的经纬度
其实上述例子就是说把 user_1 和 user_2 的经纬度存在了 nearbyPeople 这个 key 中
redis> GEOPOS nearbyPeople user_1 user_2
1) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "15.08726745843887329"
2) "37.50266842333162032"
这个就比较简单了,就是获取 nearbyPeople 中的元素 user_1 和 user_2 这两个元素的经纬度,当然如果之前没有 geoadd 绝对应元素的经纬度的话,会返回 nil
redis> GEODIST nearbyPeople user_1 user_2
"166274.1516"
redis> GEODIST nearbyPeople user_1 user_2 km
"166.2742"
redis> GEODIST nearbyPeople user_1 user_2 mi
"103.3182"
获取 nearbyPeople 中 user_1 和 user_2 这两个节点之间的间隔,间隔单位能够指定,如下所示
- m:米,默认单位。
- km:千米。
- mi:英里。
- ft:英尺。
GEORADIUS 这个比拟重要,也是比拟外围的一个办法,参数也比拟多,咱们来具体参照文档说一说
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
参数阐明:
- m:米,默认单位。
- km:千米。
- mi:英里。
- ft:英尺。
- WITHDIST: 在返回地位元素的同时,将地位元素与核心之间的间隔也一并返回。
- WITHCOORD: 将地位元素的经度和维度也一并返回。
- WITHHASH: 以 52 位有符号整数的模式,返回地位元素通过原始 geohash 编码的有序汇合分值。这个选项次要用于底层利用或者调试,理论中的作用并不大。
- COUNT 限定返回的记录数。
- ASC: 查找后果依据间隔从近到远排序。
- DESC: 查找后果依据从远到近排序。
redis>GEORADIUS nearbyPeople 15 37 200 km WITHDIST
1) 1) "user_1"
2) "190.4424"
2) 1) "user_2"
2) "56.4413"
上述命令也就是说把 nearbyPeople 中的 间隔经纬度(15,37)200km 以内的元素都找进去,而且带上间隔
GEORADIUSBYMEMBER 其实和 GEORADIUS 作用都一样,惟一的区别在于
GEORADIUS 是以某个经纬度为基准点
GEORADIUSBYMEMBER 是以某个元素为基准点
用 redis geo 的形式解决问题
其实上述命令相熟了的同学这个问题就很好解决了
首先咱们能够在后盾把每个人的地位定时刷新到以 nearbyPeople 为 key 的 geo 对象中。
reids> GEOADD nearbyPeople 13.36 38.11 "user_1" 15.08 37.50 "user_2" .......
因为查看左近的人的地位信息也在 nearBy 中,所以显然用 GEORADIUSBYMEMBER 比拟适合
GEORADIUSBYMEMBER nearbyPeople user_n 5 km WITHDIST //user_n 为以后查看左近的用户
这样就能够完满解决咱们的问题了。