PHP实现附近的人、按距离排序之Redis GEO方案

【写在前面】

      获取附近的人列表,首先要有用户的位置信息,做法是客户端调用一个接口传递用户的经纬度坐标、所在区域adcode等,把这些用户位置信息入库(当然前提是用户开启定位权限)。

      有了用户位置信息库,可以通过一个用户的经纬度坐标,获取其附近的用户,实现附近的人功能,通常会按照距离正序排列,还会有男女性别的筛选。

      起初做这个功能,由于对项目时间成本的考虑,以方便省事为原则,通过用户经纬度坐标计算出4个临界点坐标,使用mysql进行条件查询,一开始还行,随着用户量增多,大概不到一万用户mysql就扛不住了,查询速度巨慢,占用内存也很高,因此要换一种实现方案。所以第一次做这个功能的朋友,就直接放弃mysql吧,这是前车之鉴。   

【实现方案】

     Redis的GEO提供对地理位置的一些操作,这里只用到​以下两个操作:

           GEOADD:将指定的地理空间位置(纬度、经度、名称)添加到指定的key中

           GEORADIUS: 以给定的经纬度为中心, 找出某一半径内的元素

1.使用composer安装Predis扩展,来更好地使用这两个操作:

composer require predis/predis

2.更新用户位置信息时使用geoadd(),将经纬度坐标存入redis,示例如下:

<?php
namespace app\index\service;
use think\Model;
use Predis\Client;
class NearbyService extends Model
{
    /**
     * 用户上传位置信息
     * @param int $user_id 用户id
     * @param string $latitude 纬度
     * @param string $longitude 经度
     * @return object
     */
    public function subSite($user_id,$latitude,$longitude){
        $key = 'geo::user';
        $client = new Client([
            'host'   => '127.0.0.1',
            'port'   => 6379,
//            'password'   => 'xxxxxx'
        ]);
        $client->geoadd($key,$longitude,$latitude,$user_id);

        //省略其它逻辑......

    }
}

3.根据一个经纬度坐标,查询附近的人时使用georadius(),示例如下:

/**
     * 获取附近的人
     * @param int $user_id 用户id
     * @param string $latitude 纬度
     * @param string $longitude 经度
     * @return object
     */
    public function nearbyList($user_id, $latitude, $longitude){
        $key = 'geo::user';
        $client = new Client([
            'host'   => '127.0.0.1',
            'port'   => 6379,
//            'password'   => 'xxxxxx'
        ]);
        $nearbyList = $client->georadius($key, $longitude, $latitude, 50000, 'm', ['withdist' => true, 'sort' => 'asc']); 
        //半径50000、单位m/km可选、距离显示、排序规则

        //省略其它逻辑......
    }

      到此实现了使用Redis GEO做附近的人功能,但也存在一些问题需要思考:

      一是针对条件查询,例如男、女的筛选,我的做法是在存入的时候,给成员标识拼上性别,但是这样会增加数据处理的复杂度,条件越多会越复杂。

//$gender 0保密 1女 2男
$client->geoadd($key,$longitude,$latitude, $user_id.'-'.$gender);

      二是出于分页的考虑,现在是每次都要把全部数据得到,然后进行数组分页,即便加一些缓存,但还是会觉得欠妥当。

附数组分页封装函数:

/**
 * 数组分页
 * @param $arr
 * @param int $page 页码
 * @param int $size 每一页的条数
 * @return array $array  分页数组
 */
function cutpage($arr, $page = 1, $size = 10)
{
    $arr = array_slice($arr, ($page - 1) * $size, $size);
    return $arr;
}
发布了109 篇原创文章 · 获赞 169 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/msllws/article/details/104103021
GEO