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

时间:2013-10-01 03:47:34

标签: redis geospatial geo geohashing

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

为了实时和扩展,我似乎应该使用像Redis这样的分布式内存数据存储。我已经研究过使用geohashing,但问题是彼此接近的点可能并不总是共享相同的哈希前缀。因为我有兴趣找到两个用户是否足够接近他们彼此相邻的位置,所以geohashing可能有点过头了。

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

实施此方法的最佳方法是什么?

7 个答案:

答案 0 :(得分:17)

此功能已融入Redis 3.2+

但对于旧版本,问题仍然存在。我接受了Yin Qiwen的回答并为Node创建了一个模块,你可以通过检查代码来看看它是如何使用Redis的。他的指示是完美的,我能够跟随他们取得好成绩。 https://github.com/arjunmehta/node-georedis

相同的算法本质上是用于本机命令的。

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

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

我也在GIS stack exchange翻译了尹启文的文章。

对不起所有关联。 :P

答案 1 :(得分:15)

通常,这可以通过GeoHash和Redis的排序集来完成。在讨论如何在redis上实现空间索引服务之前,我写了一个设计。

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

答案 2 :(得分:7)

也许你可以尝试这个:

Redis Geography Edition

你真的想尝试一下,它真的很棒。 :)

答案 3 :(得分:5)

我意识到这不能回答你的问题...但我认为这不是正确的工具。

PostgreSQL + PostGIS可以表现得非常好。您可以将PostgreSQL配置为几乎可以运行在内存中的数据库。

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

使用后端触发websocket请求将允许您执行非常实时的操作。您的后端随时收到GPS坐标;执行空间查找;并通过websockets通知适用的客户。

答案 4 :(得分:5)

此主题中其他答案所提到的Redis地理版已集成到Redis since version 3.2中(另请参阅this earlier comment)。

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

答案 5 :(得分:1)

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

我在商业项目中使用它来存储和查询描述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

答案 6 :(得分: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);
    }
}