Redis 是一个高性能的key-value数据库,其最大优点就是,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。同时Redis还提供了Java开发,C/C++,C#,PHP开发,JavaScript,Perl,Object-C,Python开发,Ruby,Erlang等客户端,使用起来很方便。
而今天小编要和大家聊的就是Redis 的 GEO 特性,这个特性将在 Redis 3.2 版本释出,可以将用户给定的地理位置信息储存起来, 并对这些信息进行操作。下面我们就来看看Redis GEO 的相关命令及这些命令相关功能的实现。
一、添加和获取位置
要进行地理位置的操作,我们需要先通过执行 GEOADD 命令将具体的地理位置记录起来:
GEOADD location-set longitude latitude name [longitude latitude name ...]
GEOADD 命令每次可以添加一个或多个经纬度地理位置。 其中 location-set 为储存地理位置的集合, 而 longitude 、 latitude 和 name 则分别为地理位置的经度、纬度、名字。
下面我们来通过例子,看下具体如何通过 GEOADD 命令,记录地理位置。将清远、广州、佛山、东莞、深圳等数个广东省的市添加到位置集合 Guangdong-cities 里面:
redis> GEOADD Guangdong-cities 113.2099647 23.593675 Qingyuan
1 -- 成功添加一个位置
redis> GEOADD Guangdong-cities 113.2278442 23.1255978 Guangzhou 113.106308 23.0088312 Foshan 113.7943267 22.9761989 Dongguan 114.0538788 22.5551603 Shenzhen
4 -- 成功添加四个位置
在将位置记录到位置集合之后, 我们可以使用 GEOPOS 命令, 输入位置的名字并取得位置的具体经纬度:
GEOPOS location-set name [name ...]
比如说, 如果我们想要获取清远、广州和佛山的经纬度, 那么可以执行以下代码:
redis> GEOPOS Guangdong-cities Qingyuan Guangzhou Foshan
1) 1) "113.20996731519699" -- 清远的经度
2) "23.593675019671288" -- 清远的纬度
2) 1) "113.22784155607224" -- 广州的经度
2) "23.125598202060807" -- 广州的纬度
3) 1) "113.10631066560745" -- 佛山的经度
2) "23.008831202413539" -- 佛山的纬度
二、计算两个位置之间的距离
在拥有了地理数据之后, 我们就可以基于这些数据进行各种各样的操作。 针对地理位置信息的其中一个最简单的操作, 就是计算两个位置之间的距离。
在 Redis 里面, 计算两个位置之间的距离可以通过 GEODIST 命令来实现:
GEODIST location-set location-x location-y [unit]
在调用这个命令时, 用户需要给定想要计算差距的地点 location-x 和 location-y , 以及储存这两个地点的地理位置集合。
可选参数 unit 用于指定计算距离时的单位, 它的值可以是以下单位的其中一个:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
如果用户没有指定 unit 参数, 那么 GEODIST 默认使用米为单位。
而对于上面的例子,我们如何利用代码计算出清远和广州之间的距离:
redis> GEODIST Guangdong-cities Qingyuan Guangzhou
"52094.433840356309" -- 两地相聚 52094 米
上面的计算结果使用了米来表示清远和广州两地的距离, 不过在表示比较长的距离时, 我们更习惯采用公里(km)作为单位。 通过显式地给定 km (千米)作为单位, 我们可以让 GEODIST 显示两个地点之间相距的公里数:
redis> GEODIST Guangdong-cities Qingyuan Guangzhou km
"52.094433840356309" -- 两地相聚 52 公里
三、获取指定范围内的元素
除了计算两地的距离之外, 另一个常见的地理位置操作就是找出特定范围之内的其他存在的地点。 比如找出地点 x 范围 100 米之内的所有地点, 找出地点 y 范围 50 公里之内的所有地点等等。
Redis 提供了 GEORADIUS 和 GEORADIUSBYMEMBER 两个命令来实现查找特定范围内地点的功能, 它们的作用一样, 只是指定中心点的方式不同: GEORADIUS 使用用户给定的经纬度作为计算范围时的中心点, 而 GEORADIUSBYMEMBER 则使用储存在位置集合里面的某个地点作为中心点。 以下是这两个命令的基本格式:
GEORADIUS location-set longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [ASC|DESC]
[COUNT count]
GEORADIUSBYMEMBER location-set location radius m|km|ft|mi [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count]
这两个命令的各个参数的意义如下:
m|km|ft|mi 指定的是计算范围时的单位;
如果给定了可选的 WITHCOORD , 那么命令在返回匹配的位置时会将位置的经纬度一并返回;
如果给定了可选的 WITHDIST , 那么命令在返回匹配的位置时会将位置与中心点之间的距离一并返回;
在默认情况下, GEORADIUS 和 GEORADIUSBYMEMBER 的结果是未排序的, ASC 可以让查找结果根据距离从近到远排序, 而 DESC 则可以让查找结果根据从远到近排序;
COUNT 参数指定要返回的结果数量。
还是通过上面的例子来看, 我们可以使用 GEORADIUSBYMEMBER 去找出位于广州 50 公里、 100 公里以及 150 公里以内的城市:
redis> GEORADIUSBYMEMBER Guangdong-cities Guangzhou 50 km
1) "Foshan"
2) "Guangzhou"
redis> GEORADIUSBYMEMBER Guangdong-cities Guangzhou 100 km
1) "Foshan"
2) "Guangzhou"
3) "Dongguan"
4) "Qingyuan"
redis> GEORADIUSBYMEMBER Guangdong-cities Guangzhou 150 km
1) "Foshan"
2) "Guangzhou"
3) "Dongguan"
4) "Qingyuan"
5) "Shenzhen"
四、查找附近的人
了解了基本的信息后,我们来看看其在具体实践中的应用。比如“查找附近的人”这一功能,如何通过Redis实现呢?
def pin(user, longitude, latitude):
"""
记录用户的地理位置。
"""
GEOADD('user-location-set', longitude, latitude, user)
def find_nearby(user, n):
"""
返回指定用户附近 n 公里的所有其他用户。
"""
return GEORADIUSBYMEMBER('user-location-set', user, n, unit='km')
五、摇一摇
相信大家都用过微信的“摇一摇”功能,那“摇一摇”功能时怎么实现的呢:
RANDOM_RADIUS = 1 # 随机查找的范围为 1 公里
def find_random(user):
获取范围内的所有其他用户
get_all_near_users = find_nearby(user, RANDOM_RADIUS)
将查找的结果从 Python 列表转换为 Python 集合
user_set = set(get_all_near_users)
然后调用 pop() 方法,从集合里面随机地移除并返回一个元素
return user_set.pop()
六、效率优化
虽然通过上面的介绍,可看出find_random() 函数可以实现“摇一摇”功能, 但这个函数的效率并不高: 因为程序每次执行这个函数的时候都需要重新执行 find_nearby() 函数以查找用户附近的位置, 然而大部分用户的位置并不经常改变, 并且 GEORADIUSBYMEMBER 命令的执行代价并不低, 因此每次执行 find_random() 都重新执行 find_nearby() 是一种非常低效的做法。
为了优化 find_random() 的效率, 我们可以为 find_random() 的结果创建缓存: 把每个执行“摇一摇”的用户的 find_nearby() 结果储存到一个 Redis 集合里面, 并设置一个过期时间(比如3 分钟), 然后通过对集合使用 SRANDMEMBER 来随机地获取用户。 这样用户在指定过期时间内执行的所有“摇一摇”操作都只会引起一次 GEORADIUSBYMEMBER , 这将极大地提高 find_random() 的执行效率。
另外, 如果用户密集地聚集在一起, 那么通过使用 GEORADIUSBYMEMBER 命令提供的 COUNT 参数可以有效地减少指定范围内的用户数量, 这可以提高 find_nearby() 的效率, 从而提高 find_random() 的效率。
总结
以上就是redis 3.2版本GEO特性的一些相关命令和相关功能的实现,希望对大家使用redis进行相关开发有所帮助。如果有新的上文没体现出来的命令或功能,也欢迎大家分享。
推荐学习:《redis视频教程》 http://www.maiziedu.com/course/php/337-5915/
有疑问加站长微信联系(非本文作者)