橡皮擦擦

PHP Redis GeoHash实现查询附近的人

PHP

最近要做一个IM搜索附近的人的需求。最终选择支持Geo的Redis,而关于GeoHash就不多说明了,其实一开始想看看Google S2的,但是Golang的包只完成了40%想想还是算了。

关于GeoHash与Google S2的算法介绍可以查看这篇文章点击查看

自3.2.0起Redis开始支持Geo系列函数,相关文档可以点击查看

有两点需要注意的地方:

  1. Redis Geo没有提供更新与删除的方法,可以使用ZREM移除旧数据。
  2. Reids Geo没有提供分页方法,如果数据量过多,在Client进行分页的话可能会出现问题。此问题本文用到了STOREDIST参数解决。

georadius方法不能直接查询分页。只能查count数量,如果数据量小在Client做分页也没关系。本文用到了storedist参数。
将查询到的集合作为有序集合的分数插入一个临时键内,进行新建列表并查询完成分页。

/**
 * 更新用户定位信息
 */
public function refreshLocation()
{
    $redis = new \Redis();

    $redis->connect(C('redis.host'),C('redis.port'));

    // 密码
    if (!empty(C('redis.password'))) {
        $redis->auth(C('redis.password'));
    }

    // 选择库
    $redis->select(C('redis.select'));

    // 移除旧数据信息
    $redis->rawCommand('ZREM','IM-GeoHash',$_SESSION['user_id']);

    // 记录当前位置
    $set = $redis->rawCommand('GEOADD', 'IM-GeoHash',$this->param['lng'],$this->param['lat'],$_SESSION['user_id']);

    if ($set === 0){
        $this->ajaxReturnData("位置信息更新异常");
    }

    $this->ajaxReturnData([]);
}

/**
 * 获取附近的人列表
 */
public function geo()
{
    $redis = new \Redis();

    $redis->connect(C('redis.host'),C('redis.port'));

    // 密码
    if (!empty(C('redis.password'))) {
        $redis->auth(C('redis.password'));
    }

    // 选择库
    $redis->select(C('redis.select'));

    // 获取自身经纬度数据
    $me = $redis->rawCommand('GEOPOS','IM-GeoHash',$_SESSION['user_id']);

    if (empty($me[0])){
        $this->ajaxReturnData([]);
    }

    // 定义键名
    $key = 'Geo_'.$_SESSION['user_id'];

    // 获取记录将返回结果距离中心点的距离保存到指定键
    $redis->rawCommand('GEORADIUS','IM-GeoHash',$me[0][0],$me[0][3],200,'km','STOREDIST',$key);

    $limit_s = ($this->param['page'] - 1) * $this->param['limit'];

    $limit_e = ($limit_s + $this->param['limit']) - 1;

    $range = $redis->zrange($key,$limit_s,$limit_e,true);

    $count = $redis->zcard($key);

    if ($count === 0){
        $this->ajaxReturnData([]);
    }

    $pageCount = ceil($count/$this->param['limit']);

    $pageList = [];

    foreach($range as $key => $item){
        $pageList[] = [
            'user_id'  => $key,
            'distance' => round($item,2)
        ];
    }

    $data = [
        'list'       => $pageList,
        'count'      => $count, //记录总数
        'totalPages' => $pageCount,//总页数
        'pageSize'   => $this->param['limit'], //每页多少条
    ];

    $this->ajaxReturnData($data);
}

此代码只是样例,实际使用中可定义临时键的时间,查询时先查临时表,不存在的话再建立与查询。

点我评论
打赏本文
二维码


41

文章

6

分类