learn.lianglianglee.com/专栏/Redis 核心原理与实战/20 查询附近的人——GEO.md.html
2022-05-11 18:57:05 +08:00

1295 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>20 查询附近的人——GEO.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/Redis 核心原理与实战/01 Redis 是如何执行的.md.html">01 Redis 是如何执行的.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/02 Redis 快速搭建与使用.md.html">02 Redis 快速搭建与使用.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/03 Redis 持久化——RDB.md.html">03 Redis 持久化——RDB.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/04 Redis 持久化——AOF.md.html">04 Redis 持久化——AOF.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/05 Redis 持久化——混合持久化.md.html">05 Redis 持久化——混合持久化.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/06 字符串使用与内部实现原理.md.html">06 字符串使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/07 附录:更多字符串操作命令.md.html">07 附录:更多字符串操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/08 字典使用与内部实现原理.md.html">08 字典使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/09 附录:更多字典操作命令.md.html">09 附录:更多字典操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/10 列表使用与内部实现原理.md.html">10 列表使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/11 附录:更多列表操作命令.md.html">11 附录:更多列表操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/12 集合使用与内部实现原理.md.html">12 集合使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/13 附录:更多集合操作命令.md.html">13 附录:更多集合操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/14 有序集合使用与内部实现原理.md.html">14 有序集合使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/15 附录:更多有序集合操作命令.md.html">15 附录:更多有序集合操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/16 Redis 事务深入解析.md.html">16 Redis 事务深入解析.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/17 Redis 键值过期操作.md.html">17 Redis 键值过期操作.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/18 Redis 过期策略与源码分析.md.html">18 Redis 过期策略与源码分析.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/19 Redis 管道技术——Pipeline.md.html">19 Redis 管道技术——Pipeline.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Redis 核心原理与实战/20 查询附近的人——GEO.md.html">20 查询附近的人——GEO.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/21 游标迭代器过滤器——Scan.md.html">21 游标迭代器过滤器——Scan.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/22 优秀的基数统计算法——HyperLogLog.md.html">22 优秀的基数统计算法——HyperLogLog.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/23 内存淘汰机制与算法.md.html">23 内存淘汰机制与算法.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/24 消息队列——发布订阅模式.md.html">24 消息队列——发布订阅模式.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/25 消息队列的其他实现方式.md.html">25 消息队列的其他实现方式.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/26 消息队列终极解决方案——Stream.md.html">26 消息队列终极解决方案——Stream.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/27 消息队列终极解决方案——Stream.md.html">27 消息队列终极解决方案——Stream.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/28 实战:分布式锁详解与代码.md.html">28 实战:分布式锁详解与代码.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/29 实战:布隆过滤器安装与使用及原理分析.md.html">29 实战:布隆过滤器安装与使用及原理分析.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/30 完整案例:实现延迟队列的两种方法.md.html">30 完整案例:实现延迟队列的两种方法.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/31 实战:定时任务案例.md.html">31 实战:定时任务案例.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/32 实战RediSearch 高性能的全文搜索引擎.md.html">32 实战RediSearch 高性能的全文搜索引擎.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/33 实战Redis 性能测试.md.html">33 实战Redis 性能测试.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/34 实战Redis 慢查询.md.html">34 实战Redis 慢查询.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/35 实战Redis 性能优化方案.md.html">35 实战Redis 性能优化方案.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/36 实战Redis 主从同步.md.html">36 实战Redis 主从同步.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/37 实战Redis哨兵模式.md.html">37 实战Redis哨兵模式.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/38 实战Redis 哨兵模式(下).md.html">38 实战Redis 哨兵模式(下).md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/39 实战Redis 集群模式(上).md.html">39 实战Redis 集群模式(上).md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/40 实战Redis 集群模式(下).md.html">40 实战Redis 集群模式(下).md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/41 案例Redis 问题汇总和相关解决方案.md.html">41 案例Redis 问题汇总和相关解决方案.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/42 技能学习指南.md.html">42 技能学习指南.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/43 加餐Redis 的可视化管理工具.md.html">43 加餐Redis 的可视化管理工具.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>20 查询附近的人——GEO</h1>
<p>受过高等教育的我们都知道,我们所处的任何位置都可以用经度和纬度来标识,经度的范围 -180 到 180纬度的范围为 -90 到 90。纬度以赤道为界赤道以南为负数赤道以北为正数经度以本初子午线英国格林尼治天文台为界东边为正数西边为负数。</p>
<p>Redis 在 3.2 版本中增加了 GEO 类型用于存储和查询地理位置,关于 GEO 的命令不多,主要包含以下 6 个:</p>
<ol>
<li>geoadd添加地理位置</li>
<li>geopos查询位置信息</li>
<li>geodist距离统计</li>
<li>georadius查询某位置内的其他成员信息</li>
<li>geohash查询位置的哈希值</li>
<li>zrem删除地理位置</li>
</ol>
<p>下面我们分别来看这些命令的使用。</p>
<h3>基础使用</h3>
<h4><strong>添加地理位置</strong></h4>
<p>我们先用百度地图提供的经纬度查询工具,地址:</p>
<blockquote>
<p><a href="https://api.map.baidu.com/lbsapi/getpoint/index.html">http://api.map.baidu.com/lbsapi/getpoint/index.html</a></p>
</blockquote>
<p>如下图所示:</p>
<p><img src="assets/c91f60b0-63a2-11ea-99a6-09fef5109e7b" alt="百度经纬度查询工具.png" /></p>
<p>找了以下 4 个地点,添加到 Redis 中:</p>
<ol>
<li>天安门116.404269,39.913164</li>
<li>月坛公园116.36,39.922461</li>
<li>北京欢乐谷116.499705,39.874635</li>
<li>香山公园116.193275,39.996348</li>
</ol>
<p>代码如下:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; geoadd site 116.404269 39.913164 tianan
(integer) 1
127.0.0.1:6379&gt; geoadd site 116.36 39.922461 yuetan
(integer) 1
127.0.0.1:6379&gt; geoadd site 116.499705 39.874635 huanle
(integer) 1
127.0.0.1:6379&gt; geoadd site 116.193275 39.996348 xiangshan
(integer) 1
</code></pre>
<p>相关语法:</p>
<pre><code>geoadd key longitude latitude member [longitude latitude member ...]
</code></pre>
<p>重点参数说明如下:</p>
<ul>
<li>longitude 表示经度</li>
<li>latitude 表示纬度</li>
<li>member 是为此经纬度起的名字</li>
</ul>
<p>此命令支持一次添加一个或多个位置信息。</p>
<h4><strong>查询位置信息</strong></h4>
<pre><code class="language-shell">127.0.0.1:6379&gt; geopos site tianan
1) 1) &quot;116.40541702508926392&quot;
2) &quot;39.91316289865137179&quot;
</code></pre>
<p>相关语法:</p>
<pre><code>geopos key member [member ...]
</code></pre>
<p>此命令支持查看一个或多个位置信息。</p>
<h4><strong>距离统计</strong></h4>
<pre><code class="language-shell">127.0.0.1:6379&gt; geodist site tianan yuetan km
&quot;3.9153&quot;
</code></pre>
<p>可以看出天安门距离月坛公园的直线距离大概是 3.9 km我们打开地图使用工具测试一下咱们的统计结果是否准确如下图所示</p>
<p><img src="assets/de8bb110-63a2-11ea-b997-6566d32f1735" alt="天安门到月坛公园距离统计图.png" /></p>
<p>可以看出 Redis 的统计和使用地图工具统计的距离是完全吻合的。</p>
<blockquote>
<p>注意:此命令统计的距离为两个位置的直线距离。</p>
</blockquote>
<p>相关语法:</p>
<pre><code>geodist key member1 member2 [unit]
</code></pre>
<p>unit 参数表示统计单位,它可以设置以下值:</p>
<ul>
<li>m以米为单位默认单位</li>
<li>km以千米为单位</li>
<li>mi以英里为单位</li>
<li>ft以英尺为单位。</li>
</ul>
<h4><strong>查询某位置内的其他成员信息</strong></h4>
<pre><code class="language-shell">127.0.0.1:6379&gt; georadius site 116.405419 39.913164 5 km
1) &quot;tianan&quot;
2) &quot;yuetan&quot;
</code></pre>
<p>此命令的意思是查询天安门116.405419,39.913164)附近 5 公里范围内的成员列表。</p>
<p>相关语法:</p>
<pre><code>georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]
</code></pre>
<p>可选参数说明如下。</p>
<p><strong>1. WITHCOORD</strong></p>
<p>说明:返回满足条件位置的经纬度信息。</p>
<p>示例代码:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; georadius site 116.405419 39.913164 5 km withcoord
1) 1) &quot;tianan&quot;
2) 1) &quot;116.40426903963088989&quot;
2) &quot;39.91316289865137179&quot;
2) 1) &quot;yuetan&quot;
2) 1) &quot;116.36000186204910278&quot;
2) &quot;39.92246025586381819&quot;
</code></pre>
<p><strong>2. WITHDIST</strong></p>
<p>说明:返回满足条件位置与查询位置的直线距离。</p>
<p>示例代码:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; georadius site 116.405419 39.913164 5 km withdist
1) 1) &quot;tianan&quot;
2) &quot;0.0981&quot;
2) 1) &quot;yuetan&quot;
2) &quot;4.0100&quot;
</code></pre>
<p><strong>3. WITHHASH</strong></p>
<p>说明:返回满足条件位置的哈希信息。</p>
<p>示例代码:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; georadius site 116.405419 39.913164 5 km withhash
1) 1) &quot;tianan&quot;
2) (integer) 4069885552230465
2) 1) &quot;yuetan&quot;
2) (integer) 4069879797297521
</code></pre>
<p><strong>4. COUNT count</strong></p>
<p>说明:指定返回满足条件位置的个数。</p>
<p>例如,指定返回一条满足条件的信息,代码如下:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; georadius site 116.405419 39.913164 5 km count 1
1) &quot;tianan&quot;
</code></pre>
<p><strong>5. ASC|DESC</strong></p>
<p>说明:从近到远|从远到近排序返回。</p>
<p>示例代码:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; georadius site 116.405419 39.913164 5 km desc
1) &quot;yuetan&quot;
2) &quot;tianan&quot;
127.0.0.1:6379&gt; georadius site 116.405419 39.913164 5 km asc
1) &quot;tianan&quot;
2) &quot;yuetan&quot;
</code></pre>
<p>当然以上这些可选参数也可以一起使用,例如以下代码:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; georadius site 116.405419 39.913164 5 km withdist desc
1) 1) &quot;yuetan&quot;
2) &quot;4.0100&quot;
2) 1) &quot;tianan&quot;
2) &quot;0.0981&quot;
</code></pre>
<p><strong>5. 查询哈希值</strong></p>
<pre><code class="language-shell">127.0.0.1:6379&gt; geohash site tianan
1) &quot;wx4g0cgp000&quot;
</code></pre>
<p>相关语法:</p>
<pre><code>geohash key member [member ...]
</code></pre>
<p>此命令支持查询一个或多个地址的哈希值。</p>
<p><strong>6. 删除地理位置</strong></p>
<pre><code class="language-shell">127.0.0.1:6379&gt; zrem site xiaoming
(integer) 1
</code></pre>
<p>相关语法:</p>
<pre><code>zrem key member [member ...]
</code></pre>
<p>此命令支持删除一个或多个位置信息。</p>
<h3>代码实战</h3>
<p>下面我们用 Java 代码,来实现查询附近的人,完整代码如下:</p>
<pre><code class="language-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(&quot;127.0.0.1&quot;, 6379);
Map&lt;String, GeoCoordinate&gt; map = new HashMap&lt;&gt;();
// 添加小明的位置
map.put(&quot;xiaoming&quot;, new GeoCoordinate(116.404269, 39.913164));
// 添加小红的位置
map.put(&quot;xiaohong&quot;, new GeoCoordinate(116.36, 39.922461));
// 添加小美的位置
map.put(&quot;xiaomei&quot;, new GeoCoordinate(116.499705, 39.874635));
// 添加小二
map.put(&quot;xiaoer&quot;, new GeoCoordinate(116.193275, 39.996348));
jedis.geoadd(&quot;person&quot;, map);
// 查询小明和小红的直线距离
System.out.println(&quot;小明和小红相距:&quot; + jedis.geodist(&quot;person&quot;, &quot;xiaoming&quot;,
&quot;xiaohong&quot;, GeoUnit.KM) + &quot; KM&quot;);
// 查询小明附近 5 公里的人
List&lt;GeoRadiusResponse&gt; res = jedis.georadiusByMemberReadonly(&quot;person&quot;, &quot;xiaoming&quot;,
5, GeoUnit.KM);
for (int i = 1; i &lt; res.size(); i++) {
System.out.println(&quot;小明附近的人:&quot; + res.get(i).getMemberByString());
}
}
}
</code></pre>
<p>以上程序执行的结果如下:</p>
<pre><code>小明和小红相距3.9153 KM
小明附近的人xiaohong
</code></pre>
<h3>应用场景</h3>
<p>Redis 中的 GEO 经典使用场景如下:</p>
<ol>
<li>查询附近的人、附近的地点等;</li>
<li>计算相关的距离信息。</li>
</ol>
<h3>小结</h3>
<p>GEO 是 Redis 3.2 版本中新增的功能,只有升级到 3.2+ 才能使用GEO 本质上是基于 ZSet 实现的,这点在 Redis 源码找到相关信息,我们可以 GEO 使用实现查找附近的人或者附近的地点,还可以用它来计算两个位置相隔的直线距离。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Redis 核心原理与实战/19 Redis 管道技术——Pipeline.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Redis 核心原理与实战/21 游标迭代器过滤器——Scan.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709973db08d13d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>