使用Redis如何实现查询附近的人?(redis查找)
mhr18 2024-10-22 12:39 17 浏览 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 底层的实现。
相关推荐
- Redis合集-使用benchmark性能测试
-
采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...
- Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好
-
最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...
- redission YYDS(redission官网)
-
每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...
- 从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道
-
2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...
- SpringBoot系列——实战11:接口幂等性的形而上思...
-
欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...
- 如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享
-
如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...
- Java面试题: 项目开发中的有哪些成长?该如何回答
-
在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...
- 互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?
-
你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...
- 每日一题 |10W QPS高并发限流方案设计(含真实代码)
-
面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...
- Java面试题:服务雪崩如何解决?90%人栽了
-
服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...
- 面试题官:高并发经验有吗,并发量多少,如何回复?
-
一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...
- 瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪
-
“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...
- 其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。
-
其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...
- PHP技能评测(php等级考试)
-
公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...
- 你的简历在HR眼里是青铜还是王者?
-
你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)