Redis 的 GEO 特性在 Redis 3.2 版本中推出, 这个功能可以将用户给定的地理位置信息储存起来, 并对这些信息进行操作。来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。geo 的数据类型为 zset。
GEO 的数据结构总共有六个常用命令:geoadd、geopos、geodist、georadius、georadiusbymember、gethash
官方文档:https://www.redis.net.cn/order/3685.html
# 语法:geoadd key longitude latitude member
# 将给定的空间元素(纬度、经度、名字)添加到指定的键里面。
# 这些数据会以有序集he的形式被储存在键里面,从而使得 georadius 和 georadiusbymember 这样的命令可以在之后通过位置查询取得这些元素。
# geoadd 命令以标准的 x,y 格式接受参数,所以用户必须先输入经度,然后再输入纬度。
# geoadd 能够记录的坐标是有限的,非常接近两极的区域无法被索引。
# 有效的经度介于 -180-180 度之间,有效的纬度介于 -85.05112878 度至 85.05112878 度之间。当用户尝试输入一个超出范围的经度或者纬度时,geoadd 命令将返回一个错误。
# 测试:百度搜索经纬度查询,模拟真实数据
127.0.0.1:6379> geoadd china:city 116.23 40.22 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.48 31.40 shanghai 113.88 22.55 shenzhen 120.21 30.20 hangzhou
(integer) 3
127.0.0.1:6379> geoadd china:city 106.54 29.40 chongqing 108.93 34.23 xian 114.02 30.58 wuhan
(integer) 3
# 语法:geopos key member [member...]
# 从 key 里返回所有给定位置元素的位置(经度和纬度)
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.23000055551528931"
2) "40.2200010338739844"
127.0.0.1:6379> geopos china:city shanghai chongqing
1) 1) "121.48000091314315796"
2) "31.40000025319353938"
2) 1) "106.54000014066696167"
2) "29.39999880018641676"
# 语法:geodist key member1 member2 [unit]
# 返回两个给定位置之间的距离,如果两个位置之间的其中一个不存在,那么命令返回空值。
# 指定单位的参数unit必须是以下单位的其中一个
# m 表示单位为米
# km 表示单位为千米
# mi 表示单位为英里
# ft 表示单位为英尺
# 如果用户没有显式地指定单位参数,那么 geodist 默认使用米作为单位。
# geodist 命令在计算距离时会假设地球为完美的球形,在极限情况下,这一假设最大会造成 0.5% 的误差。
127.0.0.1:6379> geodist china:city beijing shanghai
"1088785.4302"
127.0.0.1:6379> geodist china:city beijing shanghai km
"1088.7854"
127.0.0.1:6379> geodist china:city chongqing beijing km
"1491.6716"
# 语法:georadius key longitude latitude radius m|km|ft|mi [withcoord][withdist] [withhash][asc|desc][count count]
# 以给定的经纬度为中心, 找出某一半径内的元素
# 在 china:city 中寻找坐标 100 30 半径为 1000km 的城市
127.0.0.1:6379> georadius china:city 100 30 1000 km
1) "chongqing"
2) "xian"
# withdist 返回位置名称和中心距离
127.0.0.1:6379> georadius china:city 100 30 1000 km withdist
1) 1) "chongqing"
2) "635.2850"
2) 1) "xian"
2) "963.3171"
# withcoord 返回位置名称和经纬度
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord
1) 1) "chongqing"
2) 1) "106.54000014066696167"
2) "29.39999880018641676"
2) 1) "xian"
2) 1) "108.92999857664108276"
2) "34.23000121926852302"
# withdist withcoord 返回位置名称 距离 和经纬度 count 限定寻找个数
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count 1
1) 1) "chongqing"
2) "635.2850"
3) 1) "106.54000014066696167"
2) "29.39999880018641676"
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count 2
1) 1) "chongqing"
2) "635.2850"
3) 1) "106.54000014066696167"
2) "29.39999880018641676"
2) 1) "xian"
2) "963.3171"
3) 1) "108.92999857664108276"
2) "34.23000121926852302"
# 语法:georadiusbymember key member radius m|km|ft|mi [withcoord][withdist] [withhash][asc|desc][count count]
# 找出位于指定范围内的元素,中心点是由给定的位置元素决定
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.1:6379> georadiusbymember china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
# 语法:geohash key member [member...]
# Redis 使用 geohash 将二维经纬度转换为一维字符串,字符串越长表示位置更精确,两个字符串越相似表示距离越近。
127.0.0.1:6379> geohash china:city beijing chongqing
1) "wx4sucu47r0"
2) "wm5z22h53v0"
127.0.0.1:6379> geohash china:city beijing shanghai
1) "wx4sucu47r0"
2) "wtw6sk5n300"
GEO 没有提供删除成员的命令,但是因为 GEO 的底层实现是 zset,所以可以借用 zrem 命令实现对地理位置信息的删除。
# 查看全部的元素
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "wuhan"
5) "hangzhou"
6) "shanghai"
7) "beijing"
# 移除元素
127.0.0.1:6379> zrem china:city beijing
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "wuhan"
5) "hangzhou"
6) "shanghai"
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。HyperLogLog 则是一种算法,它提供了不精确的去重计数方案。
举个例子:假如我要统计网页的 UV(浏览用户数量,一天内同一个用户多次访问只能算一次),传统的解决方案是使用 Set 来保存用户 id,然后统计 Set 中的元素数量来获取页面 UV。但这种方案只能承载少量用户,一旦用户数量大起来就需要消耗大量的空间来存储用户 id。我的目的是统计用户数量而不是保存用户,这简直是个吃力不讨好的方案!而使用 Redis 的 HyperLogLog 最多需要 12k 就可以统计大量的用户数,尽管它大概有 0.81% 的错误率,但对于统计 UV 这种不需要很精确的数据是可以忽略不计的。
比如数据集 A {1,3,5,7,5,7,8},那么这个数据集的基数集为 {1, 3, 5 ,7, 8},基数(不重复元素)为 5。基数估计就是在误差可接受的范围内,快速计算基数。
# 语法:[PFADD key element [element ...]
# 添加指定元素到 HyperLogLog 中。
# 创建第一组元素 mykey
127.0.0.1:6379> pfadd mykey a b c d e f g h i j
(integer) 1
127.0.0.1:6379> pfcount mykey # 统计第一组元素 mykey 的基数数量
(integer) 10
# 语法:[PFCOUNT key [key ...]
# 返回给定 HyperLogLog 的基数估算值。
127.0.0.1:6379> pfadd mykey a b c d e f g h i j
(integer) 1
127.0.0.1:6379> pfcount mykey
(integer) 10
# 语法:[PFMERGE destkey sourcekey [sourcekey ...]
# 将多个 HyperLogLog 合并为一个 HyperLogLog,并集计算
127.0.0.1:6379> pfadd mykey a b c d e f g h i j
(integer) 1
127.0.0.1:6379> pfcount mykey
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j k l m n
(integer) 1
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # 两组合并
OK
127.0.0.1:6379> pfcount mykey3
(integer) 14
在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如需要记录用户一年的打卡情况,打卡了是 1, 没有打卡是 0,如果使用普通的 key-value 存储,则要记录 365 条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这种数据结构。
Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1;如果要记录 365 天的打卡情况,使用 Bitmap表示的形式大概如下:0101000111000111...........................,这样有什么好处呢?当然就是节约内存了,365 天相当于 365 bit,又 1 字节 = 8 bit , 所以相当于使用 46 个字节即可。
BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态,其中的 key 就是对应元素本身,实际上底层也是通过对字符串的操作来实现。Redis 从 2.2 版本之后新增了 setbit,getbit, bitcount 等几个 bitmap 相关命令。
# 语法:SETBIT key offset value : 设置 key 的第 offset 位为value (1或0)
# 假设使用 bitmap 来记录上述事例中一周的打卡记录如下所示:
# 周一:1,周二:0,周三:0,周四:1,周五:1,周六:0,周天:0 (1 为打卡,0 为不打卡)
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
# 语法:GETBIT key offset 获取offset设置的值,未设置过默认返回0
127.0.0.1:6379> getbit sign 3 # 查看周四是否打卡
(integer) 1
127.0.0.1:6379> getbit sign 6 # 查看周日是否打卡
(integer) 0
# 语法:bitcount key [start, end] 统计 key 上位为1的个数
# 统计这周打卡的记录,可以看到只有3天是打卡的状态
127.0.0.1:6379> bitcount sign
(integer) 3