乐趣区

关于redis:微信附近的人用redis也能实现GEO

置信微信左近的人的性能大家都应该用过

我能够很随便的通过我本人的定位能看到我左近的人,并且能看到那个人间隔我的间隔,大家有没有思考过这个是怎么实现的?

作为一个程序猿任何问题应该都有一个思考的过程,而不是间接看论断,接下来大家一步一步的思考,直到问题解决。

获取本人的地位

左近的人其实就是一种地位的比对关系,所以第一步是得获取本人的地位,个别地位都是用经纬度来示意,具体经纬度的获取得依赖客户端,作为咱们后端程序员间接接管参数就能够了,所以这一步重点是用经纬度来示意各个节点的地位,对经纬度不是很理解的敌人能够温习一下中学的地理知识。

用关系型数据库 (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 个操作命令, 晓得这些命令基本思路就进去了

  1. GEOADD:减少某个地理位置的坐标
  2. GEOPOS:获取某个地理位置的坐标
  3. GEODIST:获取两个地理位置的间隔
  4. GEORADIUS:依据给定地理位置坐标获取指定范畴内的地理位置汇合
  5. GEORADIUSBYMEMBER:依据给定地理位置获取指定范畴内的地理位置汇合
  6. 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 为以后查看左近的用户

这样就能够完满解决咱们的问题了。

退出移动版