如何使用 Redis 和地理邻近搜索在同一位置找到两个用户?

Posted

技术标签:

【中文标题】如何使用 Redis 和地理邻近搜索在同一位置找到两个用户?【英文标题】:How to use Redis and geo proximity search to find two users at the same location? 【发布时间】:2013-10-07 02:33:59 【问题描述】:

我想实现一个服务,给定用户的地理坐标,可以实时检测两个用户是否在同一个位置。

为了实时执行此操作并进行扩展,我似乎应该使用像 Redis 这样的分布式内存数据存储。我已经使用 geohashing 进行了研究,但问题是彼此靠近的点可能并不总是共享相同的哈希前缀。而且 geohashing 可能是矫枉过正,因为我有兴趣找出两个用户是否足够靠近,他们站在彼此旁边。

当然,简单的解决方案只是测试地理坐标对是否彼此相距很小。但是 AFAIK、Redis 和其他内存数据存储没有地理空间索引来支持这种查找。

实现这一点的最佳方法是什么?

【问题讨论】:

尽管我很喜欢 redis,但我认为最好的选择是使用其他东西进行此查找。有几个很好的工具确实支持地理空间索引,包括 elasticsearch、mongodb 和 PostgreSQL(使用 PostGIS)。甚至 mysql 也支持 GIS 扩展。在这个特定的用例中,所有这些都比 redis 更好。 但是这些工具是否足够快以进行实时查找?我想要几乎可以立即完成查找的东西。 当正确配置和索引时,所有提到的解决方案都提供近乎即时的查找。 Elasticsearch 速度非常快,并且内置了对集群的支持,使其能够轻松扩展到难以置信的负载。 是的,但是那些适合实时系统吗?如果需要写入磁盘并索引条目,写入速度有多快?这就是为什么我一直在寻找用于快速写入的内存解决方案。 它确实支持小于 1 公里的邻近搜索。语法有点难。您必须使用 0.2km 来表示 200m。不过,我没有尝试过低于 100m 的任何东西。 【参考方案1】:

我知道这并不能回答您的问题...但我认为它不是正确的工具。

PostgreSQL + PostGIS 可以执行得非常非常好。您可以将 PostgreSQL 配置为在内存中运行尽可能多的数据库。

PostGIS 使用(我认为)rtree 索引,因此执行您感兴趣的查找类型非常快。

使用触发 websocket 请求的后端将允许您执行几乎实时的操作。每当您的后端收到一个人的 GPS 坐标时;执行空间查找;并通过 websockets 通知适用的客户。

【讨论】:

Ubuntu 14.04 的大量依赖问题很糟糕【参考方案2】:

一般情况下,这可以通过 GeoHash 和 Redis 的 sorted set 来完成。之前写过一个设计,讲如何在redis上实现空间索引服务。

https://github.com/yinqiwen/ardb/wiki/Spatial-Index

【讨论】:

链接失效【参考方案3】:

此功能已融入Redis 3.2+。

但是对于旧版本,问题仍然存在。我采纳了尹启文的回答,并为Node创建了一个模块,您可以通过查看代码来了解它是如何使用Redis的。他的指示是完美的,我能够遵循它们以获得很好的结果。 https://github.com/arjunmehta/node-georedis

本机命令基本上使用相同的算法。

它非常快,并且避免了任何类型的交集/haversine 类型的操作。尹启文的方法最酷的地方(我认为)是算法中计算量最大的部分可以分发给客户端(而不是全部发生在数据库或服务器上)。

它不是 100% 精确并使用预配置的距离步长,但对于大多数应用程序,您不需要我想像的精确度。

我还在GIS stack exchange转述了尹启文的文章。

抱歉所有的链接。 :P

【讨论】:

很抱歉,我本想提高这个答案,但现在不小心按下了 *** 不允许我提高它。【参考方案4】:

也许你可以试试这个:

Redis Geography Edition

您真的很想尝试一下,它的效果很棒。 :)

【讨论】:

如较新的答案中所述,Redis v3.2+ 包括地理功能:***.com/a/31359397/3160475【参考方案5】:

该线程中其他答案提到的Redis地理版已集成到Redis since version 3.2(另见this earlier comment)中。

您可以在此处找到新命令(目前处于测试阶段):

GEOADD GEODIST GEOHASH GEOPOS GEORADIUS GEORADIUSBYMEMBER

【讨论】:

【参考方案6】:

我想分享一个 Redis Geography 版的示例 Java 代码。

public void geoadd(String objectId, BigDecimal latitude, BigDecimal longitude) 
    log.info("geoadd():   ", objectId, latitude, longitude);
    try (Jedis jedis = jedisPool.getResource()) 
        if (geoaddSha == null) 
            String script = "return redis.call('geoadd','" + GEOSET + "', ARGV[1], ARGV[2], KEYS[1])";
            geoaddSha = jedis.scriptLoad(script);
        
        log.info("geoaddSha: ", geoaddSha);
        log.info(jedis.evalsha(geoaddSha, 1, objectId, latitude.toString(), longitude.toString()).toString());
    


@SuppressWarnings("unchecked")
public List<String> georadius(BigDecimal latitude, BigDecimal longitude, int radius, Unit unit) 
    log.info("georadius():    ", latitude, longitude, radius, unit);
    try (Jedis jedis = jedisPool.getResource()) 
        if (georadiusSha == null) 
            String script = "return redis.call('georadius','" + GEOSET + "', ARGV[1], ARGV[2], ARGV[3], ARGV[4])";
            georadiusSha = jedis.scriptLoad(script);
        
        log.info("georadiusSha: ", georadiusSha);
        List<String> objectIdList = (List<String>) jedis.evalsha(georadiusSha, 0, latitude.toString(), longitude.toString(), String.valueOf(radius), unit.toString());
        log.info("objectIdList: ", objectIdList);
        return objectIdList;
    


public void remove(String objectId) 
    log.info("remove(): ", objectId);
    try (Jedis jedis = jedisPool.getResource()) 
        jedis.zrem(GEOSET, objectId);
    

【讨论】:

lettuce 支持原生 GEO 命令,无需使用 Lua 进行隧道化:redis.paluch.biz/docs/api/snapshots/3.3.Beta1/com/lambdaworks/… @mp911de 感谢您的指出。我第一次需要它时找不到它。【参考方案7】:

Tarantool 数据库将数据保存在内存中,将它们作为事务日志推送到磁盘,具有 RTree 类型的空间索引(不仅是二维的)和对此类索引的许多不错的操作(包含、重叠、距离)。

我在一个商业项目中使用它来存储和查询描述 3D 空间中对象的记录。

http://tarantool.org/doc/book/box/box_index.html

https://github.com/tarantool/tarantool/wiki/R-tree-index-quick-start-and-usage

标准客户端和示例在 Lua 中,但数据库作者开发了其他几个客户端。我在 Scala 应用程序中成功使用 Java 客户端。

数据库也非常快 - 这是与其他数据库的科学比较(撇开空间数据库的一个方面): http://airccse.org/journal/ijdms/papers/6314ijdms01.pdf

【讨论】:

Tarantool 受到可用内存的限制,而不是像 Cassandra 这样的真正分布式数据库。因此,我认为将其与您链接的论文中所做的比较后者是不公平的。 是的 Tarantool 有内存限制,但与他是否相关仍然是发帖人的决定。此外,我没有理解“不是真正的分布式数据库”的论点——它是。说到链接的论文——科学比较具有他们的作者所做的很多我们大多数人都没有做的工作的理想属性。我坚信问题的发布者可以自己决定科学家测试的一组特征是否对他有帮助 - 无需称任何“不公平”。 对不起,我无意冒犯,如果是这样的话,我很抱歉。分布式我的意思是你可以拥有分片、非常大的数据集等...... AFAIK Tarantool 受到 one 机器上可用内存量的限制。所以你不能拥有庞大的数据库。只有相同的数据在多台机器上复制。如果我错了,请纠正我。 好的,我将尝试解决您的问题:(a) 分布式 - 它以主从模式进行复制,因此它算作“HA 目的”复制,我会说,没有分片(b )内存限制,你是对的,同时这对于原始海报来说可能不是问题。在我们公司,我们使用它来保存有关在 3D 空间中移动的数百万个对象的数据,并且实际上使用的 RAM 不超过 6G,所以这没什么大不了的。另请注意,我们的案例似乎比将其用于地理空间 (2D) 数据更复杂 - 当然这取决于所需对象的数量(我们仍然有数百万) (抱歉回复晚了,没看到你的评论)数以千万计的物体,它们是带有一些数据的3D盒子。它们具有空间 3D 索引和两个附加索引(一个在整数上,第二个在字符串上)。报告的内存使用情况适用于数据和索引。

以上是关于如何使用 Redis 和地理邻近搜索在同一位置找到两个用户?的主要内容,如果未能解决你的问题,请参考以下文章

黑莓缓存反向地理编码地址信息与邻近度

IP地址搜索的地理位置帮助?

基于邻近的 PushNotification 软件。

如何使用 R 使用地理邻近度填充缺失的分类值?

我如何找到服务器的地理坐标/位置?

解析:查询类属性并按邻近度排序