如何使用Redis和地理位置搜索找到处于同一位置的两个用户?

25

我希望实现一个服务,能够根据用户的地理坐标实时检测两个用户是否在同一位置。

为了实现实时性和可扩展性,我似乎应该选择一个分布式内存数据存储系统,如Redis。我已经研究过使用地理哈希技术,但问题是接近的点不一定总是共享相同的哈希前缀。而且,由于我只想找出两个用户是否足够接近,站在彼此旁边,所以地理哈希可能过于复杂。

当然,简单的解决方案就是测试一对地理坐标是否在彼此附近。但据我所知,Redis和其他内存数据存储系统并没有提供空间索引来支持那种查询。

那么,最佳实现方法是什么?


1
尽管我很喜欢Redis,但我认为最好的选择是使用其他工具进行此查找。有几个很好的工具支持地理空间索引,包括Elasticsearch、MongoDB和PostgreSQL(带有PostGIS)。即使MySQL也支持GIS扩展。在这种特定的用例中,所有这些都比Redis更好。 - Carl Zulauf
1
当所有提到的解决方案被正确配置和索引时,它们都可以提供几乎即时的查找。Elasticsearch非常快速,并且具有内置的集群支持,使其能够轻松扩展到惊人的负载。 - Carl Zulauf
是的,但这些适用于实时系统吗?如果需要写入磁盘并索引条目,写入速度有多快?这就是我在寻找内存解决方案以获得快速写入的原因。 - Simian
我需要一个可以找到距离小于1公里的点的邻近搜索。我想检测两个用户是否在同一位置。ElasticSearch不支持距离小于1公里的邻近搜索。请参见原帖。 - Simian
1
它支持小于1公里的邻近搜索。语法有点复杂。您需要使用0.2千米来表示200米。虽然我没有尝试过100米以下的任何内容。 - Carl Zulauf
显示剩余3条评论
7个回答

18

这个功能已经内置于Redis 3.2+.

但对于旧版本仍存在问题。我采用了Yin Qiwen的答案,并为Node创建了一个模块,您可以通过检查代码来了解它如何使用Redis。他的说明非常完美,我能够遵循它们获得很好的结果。 https://github.com/arjunmehta/node-georedis

本质上,相同的算法用于原生命令。

它非常快速,并避免任何种类的交集/ haversine类型操作。 Yin Qiwen方法中最酷的事情(我认为)是算法中最具计算强度的部分可以分布到客户端(而不是全部在DB或服务器上发生)。

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

我还用自己的话重新表达了Yin Qiwen在GIS堆栈交换中的文章。

抱歉我放了这么多链接。 :P


2
很抱歉,我本意是要点赞这个答案,但不小心按错了,现在 StackOverflow 不允许我点赞了。 - user2924127

15

链接已损坏。 - gansub

7

正如较新的答案中所提到的,Redis v3.2+ 包括地理功能:https://dev59.com/gWIk5IYBdhLWcg3wn_ZF#31359397 - Itamar Haber

5

5
我知道这并没有回答你的问题,但我认为这不是正确的工具。
PostgreSQL + PostGIS可以表现得非常好。您可以配置PostgreSQL以在内存中运行尽可能多的数据库。
PostGIS使用(我想)rtree索引,因此执行您感兴趣的查找类型非常快。
使用触发websocket请求的后端将允许您实时执行。每当后端接收到一个人的GPS坐标时,执行空间查找,并通过websocket通知适用客户端。

3
Ubuntu 14.04存在大量依赖问题,情况很糟糕。 - sarat

1
Tarantool数据库将数据保存在内存中,通过事务日志将它们推送到磁盘,具有RTree类型的空间索引(不仅限于二维)以及许多对该索引进行操作的好方法(包含、重叠、距离等)。
我在商业项目中使用它来存储和查询描述三维空间中对象的记录。

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一样是真正的分布式数据库。因此,我认为将其与后者进行比较是不公平的,就像您链接的论文中所做的那样。 - Roland
1
是的,Tarantool有内存限制,但它是否与用户相关仍取决于用户自己。此外,我不理解“不是真正的分布式数据库”的论点 - 它确实是。谈到链接的论文 - 科学比较具有所需的属性,即它们的作者已经做了大量工作,而我们大多数人都没有做过。我坚信问题的提问者可以自行决定科学家测试的功能集是否对他有帮助,无需称任何内容为“不公平”。 - Wojciech Kaczmarek
抱歉,我并不是有意冒犯,如果那样的话请接受我的道歉。我所说的分布式是指像分片、超大数据集等等这些东西。据我所知,Tarantool 受限于一台机器可用的内存数量。因此你无法拥有巨大的数据库,只能将相同的数据复制到多台机器上。如果我说错了,请纠正我。 - Roland
1
好的,我会尝试回答你的问题:(a) 分布式 - 它以主从模式进行复制,因此我认为它算是“面向HA”的复制,没有分片。(b) 内存限制,你说得对,但同时这可能不是原帖作者关心的问题。在我们公司,我们使用它来保存数百万个在三维空间中移动的对象的数据,实际上只使用了不到6G的内存,所以这不是什么大问题。请注意,我们的情况似乎比使用它来处理地理空间(2D)数据更复杂 - 当然这取决于所需对象的数量(我们仍然有数百万个)。 - Wojciech Kaczmarek
1
抱歉回复晚了,没看到您的评论。有数千万个对象,它们是带有一些数据的3D盒子。它们具有空间3D索引和两个附加索引(一个在整数上,另一个在字符串上)。报告的内存使用情况包括数据和索引。 - Wojciech Kaczmarek
显示剩余2条评论

0
我想分享一段Redis地理版本的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进行隧道传输:http://redis.paluch.biz/docs/api/snapshots/3.3.Beta1/com/lambdaworks/redis/RedisGeoConnection.html - mp911de
@mp911de 感谢您指出这一点。我最初需要时无法找到它。 - Trinsit Wasinudomrod

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接