百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

使用Redis如何实现查询附近的人?(redis查找)

mhr18 2024-10-22 12:39 24 浏览 0 评论

查询附近的人或者是附近的商家是一个实用且常用的功能,比如微信中“附近的人”或是美团外卖中“附近商家”等,如下图所示:

那它是如何实现的呢?我们本文就一起来看。

我们本文的面试题是,使用 Redis 如何实现查询附近的人?

典型回答

在说如何实现地理位置查询之前,首先我们需要搞清楚地理位置查询的基础知识。

我们所处的任何位置都可以用经度和纬度来标识,经度的范围 -180 到 180,纬度的范围为:-90 到 90。纬度以赤道为界,赤道以南为负数,赤道以北为正数;经度以本初子午线 (英国格林尼治天文台) 为界,东边为正数,西边为负数。这样我们所处的位置才能在地球上被标注出来,这也成为了我们能够查询出两点之间距离的基础,如下图所示:

从而让查询附近的人变得简单了,我们只需要查询出附近几个点和自己的距离,再进行排序就可以实现查询附近人的功能了,然而使用 Redis 让这一切更简单了,Redis 为我们提供了专门用来存储地理位置的类型 GEO,我们使用它以及它所内置的方法就可以轻松的实现查询附近的人了。

我们可以使用 Redis 3.2 版本中新增了 GEO 类型,以及它的 georadius 命令来实现查询附近的人,例如我们可以先添加几个人的位置信息,实现命令如下:

127.0.0.1:6379> geoadd site 116.404269 39.913164 tianan
(integer) 1
127.0.0.1:6379> geoadd site 116.36 39.922461 yuetan
(integer) 1
127.0.0.1:6379> geoadd site 116.499705 39.874635 huanle
(integer) 1
127.0.0.1:6379> geoadd site 116.193275 39.996348 xiangshan
(integer) 1
复制

添加位置信息我们需要使用 geoadd 命令,它的语法为:geoadd key longitude latitude member [longitude latitude member ...] 其中:

  • longitude 表示经度;
  • latitude 表示纬度;
  • member 是为此经纬度起的名字。

此命令支持一次添加一个或多个位置信息。

我们在查询某个人(某个经纬度)附近的人,实现命令如下:

127.0.0.1:6379> georadius site 116.405419 39.913164 5 km
1) "tianan"
2) "yuetan"
复制

从上述结果中可以看出在经纬度为 116.405419,39.913164 的附近五公里范围内有两个人“tianan”和“月坛”,于是查询附近人的功能就算实现完成了。

georadius 命令的相关语法为: georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]

georadius 命令可以使用以下其中一个单位:

  • m 表示单位为米;
  • km 表示单位为千米;
  • mi 表示单位为英里;
  • ft 表示单位为英尺;

georadius 命令包含的可选参数如下。

① WITHCOORD

说明:返回满足条件位置的经纬度信息。 示例代码:

127.0.0.1:6379> georadius site 116.405419 39.913164 5 km withcoord
1) 1) "tianan"
   2) 1) "116.40426903963088989"
      2) "39.91316289865137179"
2) 1) "yuetan"
   2) 1) "116.36000186204910278"
      2) "39.92246025586381819"
复制

② WITHDIST

说明:返回满足条件位置与查询位置的直线距离。 示例代码:

127.0.0.1:6379> georadius site 116.405419 39.913164 5 km withdist
1) 1) "tianan"
   2) "0.0981"
2) 1) "yuetan"
   2) "4.0100"
复制

③ WITHHASH

说明:返回满足条件位置的哈希信息。 示例代码:

127.0.0.1:6379> georadius site 116.405419 39.913164 5 km withhash
1) 1) "tianan"
   2) (integer) 4069885552230465
2) 1) "yuetan"
   2) (integer) 4069879797297521
复制

④ COUNT count

说明:指定返回满足条件位置的个数。 例如,指定返回一条满足条件的信息,代码如下:

127.0.0.1:6379> georadius site 116.405419 39.913164 5 km count 1
1) "tianan"
复制

⑤ ASC|DESC

说明:从近到远|从远到近排序返回。 示例代码:

127.0.0.1:6379> georadius site 116.405419 39.913164 5 km desc
1) "yuetan"
2) "tianan"
127.0.0.1:6379> georadius site 116.405419 39.913164 5 km asc
1) "tianan"
2) "yuetan"
复制

当然以上这些可选参数也可以一起使用,例如以下代码:

127.0.0.1:6379> georadius site 116.405419 39.913164 5 km withdist desc
1) 1) "yuetan"
   2) "4.0100"
2) 1) "tianan"
   2) "0.0981"
复制

考点分析

查询附近的人看似是一个复杂的问题,但深入研究之后发现借助 Redis 还是很好实现的,但别高兴的太早这只是入门题,和此知识点相关的面试题还有以下这些:

  • 如何查询位置的经纬度信息?
  • 如何在代码中实现查询附近的人?
  • GEO 类型的底层是如何实现的?

知识扩展

1.经纬度获取

我们可以借助在线的坐标查询系统来获取经纬度的值,例如百度的坐标系统。

百度坐标系统的访问地址是:http://api.map.baidu.com/lbsapi/getpoint/index.html 它的缩略图如下:

我们可以在搜索栏输入位置信息,然后使用鼠标点击该区域就会出现经纬度信息,如下图所示:

2.用代码实现查询附近的人

本文我们将使用 Java 语言来实现一下“查询附近的人”,我们借助 Jedis 包来操作 Redis,实现在 pom.xml 添加 Jedis 框架的引用,配置如下:

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>
复制

具体的 Java 实现代码如下:

import redis.clients.jedis.GeoCoordinate;
import redis.clients.jedis.GeoRadiusResponse;
import redis.clients.jedis.GeoUnit;
import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GeoHashExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        Map<String, GeoCoordinate> map = new HashMap<>();
        // 添加小明的位置
        map.put("xiaoming", new GeoCoordinate(116.404269, 39.913164));
        // 添加小红的位置
        map.put("xiaohong", new GeoCoordinate(116.36, 39.922461));
        // 添加小美的位置
        map.put("xiaomei", new GeoCoordinate(116.499705, 39.874635));
        // 添加小二
        map.put("xiaoer", new GeoCoordinate(116.193275, 39.996348));
        jedis.geoadd("person", map);
        // 查询小明和小红的直线距离
        System.out.println("小明和小红相距:" + jedis.geodist("person", "xiaoming",
                "xiaohong", GeoUnit.KM) + " KM");
        // 查询小明附近 5 公里的人
        List<GeoRadiusResponse> res = jedis.georadiusByMemberReadonly("person", "xiaoming",
                5, GeoUnit.KM);
        for (int i = 1; i < res.size(); i++) {
            System.out.println("小明附近的人:" + res.get(i).getMemberByString());
        }
    }
}
复制

以上程序执行的结果如下:

小明和小红相距:3.9153 KM 小明附近的人:xiaohong

至此使用代码实现查询附近的人的功能已经实现了。

3.GEO 类型底层实现

GEO 类型相关的命令不多,主要包含了:

  • geoadd:添加地理位置;
  • geopos:查询位置信息;
  • geodist:距离统计;
  • georadius:查询某位置内的其他成员信息;
  • geohash:查询位置的哈希值。

我们很神奇的发现竟然没有删除命令,于是打开 GEO 的源码才发现 GEO 类型的底层其实是借助 ZSet(有序集合)实现的,因此我们可以使用 zrem 命令来删除地理位置信息,GEO 实现的主要源码为:

void geoaddCommand(client *c) {
    // 参数校验
    if ((c->argc - 2) % 3 != 0) {
        /* Need an odd number of arguments if we got this far... */
        addReplyError(c, "syntax error. Try GEOADD key [x1] [y1] [name1] "
                         "[x2] [y2] [name2] ... ");
        return;
    }
    // 参数提取 Redis
    int elements = (c->argc - 2) / 3;
    int argc = 2+elements*2; /* ZADD key score ele ... */
    robj **argv = zcalloc(argc*sizeof(robj*));
    argv[0] = createRawStringObject("zadd",4);
    argv[1] = c->argv[1]; /* key */
    incrRefCount(argv[1]);
    // 参数遍历+转换
    int i;
    for (i = 0; i < elements; i++) {
        double xy[2];
        // 提取经纬度
        if (extractLongLatOrReply(c, (c->argv+2)+(i*3),xy) == C_ERR) {
            for (i = 0; i < argc; i++)
                if (argv[i]) decrRefCount(argv[i]);
            zfree(argv);
            return;
        }
        // 将经纬度转换为 52 位的 geohash 作为分值 & 提取对象名称
        GeoHashBits hash;
        geohashEncodeWGS84(xy[0], xy[1], GEO_STEP_MAX, &hash);
        GeoHashFix52Bits bits = geohashAlign52Bits(hash);
        robj *score = createObject(OBJ_STRING, sdsfromlonglong(bits));
        robj *val = c->argv[2 + i * 3 + 2];
        // 设置有序集合的对象元素名称和分值
        argv[2+i*2] = score;
        argv[3+i*2] = val;
        incrRefCount(val);
    }
    replaceClientCommandVector(c,argc,argv);
    // 调用 zadd 命令,存储转化好的对象
    zaddCommand(c);
}
复制

通过上述的源码我们可以看出 Redis 内部使用 ZSet 来保存位置对象的,它使用 ZSet 的 Score 来存储经纬度对应的 52 位的 GEOHASH 值的。

总结

本文我们讲了使用 Redis 实现查询附近的人的实现方案,既使用 Redis 3.2 新增的 GEO 类型的 georadius 命令来实现,还是用 Java 代码演示了查询附近的人,并讲了 GEO 其他的几个命令的使用,以及经纬度的获取方法和 GEO 底层的实现。

相关推荐

从Oracle演进看数据库技术的发展(从oracle演进看数据库技术的发展的过程)

数据库技术发展本质上是应用需求驱动与基础架构演进的双向奔赴,如何分析其技术发展的脉络和方向?考虑到oracle数据库仍然是这个领域的王者,以其为例,管中窥豹,对其从Oracle8i到23ai版本的核...

docker安装jdk并且部署java项目,一文看懂它部署springboot项目

1.下载jdkhttps://repo.huaweicloud.com/java/jdk/11.0.2+9/https://www.oracle.com/java/technologies/javas...

PL/SQL 杂谈(六)(pl/sql for)

承接(五)。内容书写不容易,各位友友们,如果可以,麻烦点个赞,让我有动力可以写下去。这节主要谈谈关于SQL*Plus的其他任务。SQL*Plus有大量命令,但由于文章字数有限,我们只能聊一聊那...

快速解决Win远程桌面CredSSP加密数据库修正问题!

前不久我做渗透测试时,在Win10下通过Mstsc访问服务器的3389端口时,遇到了“身份验证错误,要求的函数不受支持”这个错误。这通常是由于客户端和服务器的CredSSP加密协议不匹配导致的。(一...

报表服务器SSRS的安装和Oracle database的配置

WindowsServer2022NETFramework3.5SQLServer2022(FromServerCNQSQLDB01)SQLServerManagementStudi...

网络安全面试题-SQL注入问题总结(sql注入攻击的防护方法)

一、知识储备类1.SQL与NoSQL的区别?SQL:关系型数据库NoSQL:非关系型数据库存储方式:SQL具有特定的结构表,NoSQL存储方式灵活性能:NoSQL较优于SQL数据类型:SQL适用结构化...

运维日记|postgresql-利剑出鞘-psql出手,天下我有

小编最近看了电影《利剑出鞘》,结局中,反派错拿了一把道具刀导致报复失败,所以说,选对工具很重要。在postgresql数据库中,有这样一个工具不得不提,那就是psql,掌握好psql,那么在运维管理p...

2021款14寸MacBook Pro使用体验真实感受

购买的2021款MacBookPro是14寸的加配10核心M1Pro芯片,32g内存,512g存储。已经使用两周了,说说这段时间的使用体验。1.外观和2015款的13寸对比,他俩的大小、尺寸、重量...

渗透测试岗位面试分享,多年经验(渗透测试岗位面试分享,多年经验怎么写)

综合启明星辰多年招聘渗透测试工程师的经验,发现有很多面试者喜欢说没接触过这个没接触过那个,都没接触过为什么要来面试,录取你的理由是什么?在面试中真遇到你不懂的技术问题,千万不要说没接触过,你可以说一些...

在 Spring Boot3 中如何轻松连接并操作 SQLLite 数据库?

你是不是正在为在SpringBoot3项目里连接并操作SQLLite数据库而发愁?在实际的软件开发过程中,选择轻量级且易于部署的数据库至关重要。当面对小型项目、资源受限的环境,或是对部署便捷...

PHP开源项目ADOdb曝CVSS满分SQL注入漏洞

IT之家5月5日消息,PHP开源项目ADOdb于上周发布了v5.22.9版本,该版本主要修复一项CVSS风险评分高达10分(满分)的严重安全漏洞CVE-2025-46337...

Maven常用命令(maven用法)

一、Maven常用命令及其介绍命令描述mvnclean对项目进行清理,删除target目录下编译的内容mvncompile编译项目源代码mvntest对项目进行运行测试mvnpackage打包...

oracle数据恢复—oracle执行truncate命令误删除数据的数据恢复

oracle数据库误执行truncate命令导致数据丢失是一种常见情况。通常情况下,oracle数据库误操作删除数据只需要通过备份恢复数据即可。也会碰到一些特殊情况,例如数据库备份无法使用或者还原报错...

Java单向代码执行链配合的动态代码上下文执行

Java反序列化漏洞的危害不光在于普通gadgets能够带来的命令执行,由于Java应用的使用场景以及gadgets大多都是构造出单向代码执行,一般通过利用链构造出的单向代码链能做到的能力往往有限。而...

如果可以从历史上抹去一种编程语言,你会选择哪个?

假设你获得一个程序员界的“死亡笔记”,但只能写下一种编程语言的名字,然后这门语言就会从历史中彻底抹除——没有它的发明、没有它的生态、更没有它写下的那几百万行遗产代码。你,会选择谁?是“人人喊打”的P...

取消回复欢迎 发表评论: