Redis 4.x系列(五):Redis 数据类型之Hash、HyperLogLog、Geo

  Hash:表示字符串字段和字符串值之间的映射关系,因此 Hash 对于存储对象是一种完美的数据类型。

  HyperLogLog:在需要唯一计数的数据处理场景中使用,用于统计元数的个数,而不需获取数据的内容,性能高消耗内存低。

  Geo:用于存储和查询与地理位置相关的位标(GPS经纬度),提供的 API 非常方便地计算位标距离和获取距离范围内的成员。

Hash

Hash 主要用于存储对象数据,但也可能存储多种类型元素。为了与 Redis 的键进行区分,使用字段(field)来表示 hash 值对象所关联的键,并且值和字段都必须是字符串类型。

具有少量字段的 Hash(少量指大约一百个)以一种占用空间非常小的方式存储,因此可以在一个小的 Redis 实例中存储数百万个对象。但对放入 Hash 中的字段数是没有实际限制(除了可用内存)。小 Hash (指具有小值的少数元数)以特殊方式编码存储,存储可以更高效。每个 Hash 最多可存 2 的 32 次方 减 1(超过40亿)个字段-值对。

备注: 以一个 Java 后台的理解, Redis 的 Hash 非常类似于 Java 的 HashMap;有键(字段),值是对象,但也可以是其它类型。

与 List 类型类似,使用 Hash 类型不需要在添加前先初始化一个空的 Hash, hsethmset会自动实现这一点;当 Hash 为空时,Redis 负责将其删除。

内部编码

Redis 在内部使用两种编码来存储 Hash 对象

  • ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries选项配置的值(默认 512),且所有元素的大小(值)都小于配置中 hash-max-ziplist-value选项配置的值(默认为 64 字节),内部采用此编码。ziplist使用更加紧凑的结构实现多个元素的连续存储,在节省内存方面比hashtable更优。
  • hashtable:当哈希类型无法满足 ziplist 的条件时,Redis 会使用 hashtable作为哈希的内部实现。

常用命令

  • hset:设置单个字段的值,可用于修改现有字段的值,字段不存在时则变成添加新的字段。
  • hget:从一个 Hash 中获取某个字段对应的值。
  • hsetnx:仅在字段不存在的情况下才设置值。可防止 hset 和 hmset 的默认覆盖行为。
  • hmset:创建字段并设置属性。
  • hmget:从一个 Hash 中获取多个字段对应的值。
  • hdel:从 Hash 中删除字段。
  • hexists:判断一个 Hash 中是否存在某个字段。
  • hlen key:获取键下有多少个字段(field 的个数)。
  • hkeys key:获取键下的所有字段,返回所有的 field
  • hvals key:获取键下所有值(Value)。
  • hgetall:获取一个 Hash 中的所有字段和值。在大 Hash 中使用此命令会阻塞服务器,不建议对大 Hash 使用此命令,可以使用 HSCAN 命令来增量地获取所有字段和值。
  • hscan:HSCAN 是 Redis 中 SCAN 命令的一种(SCAN、HSCAN、SSCAN、ZSCAN),该命令会增量迭代遍历元素而不会造成服务器阻塞。HSCAN 命令是一种基于指针的迭代器,在使用此命令时需要指定一个游标(从 0 开始),当命令执行结束后会返回一个元素列表以及一个新的游标,这个游标用于下一次迭代,当返回的游标为 0 时,表示整个遍历完成。
  • hstrlen key field:获取值(value)的字节长度,一个中文占两个字节。
  • hincrby hincrbyfloat:对键-字段的值执行自增。

hmset

创建字段并设置属性

语法:hmset key field value [field value ...]
示例:

1
2
127.0.0.1:6379> hmset people name "Kitty" age "22" address "ShenZhen" birthday "2004-05-10"
OK

hmget

获取多个字段对应的值, 不存在的字段返回(nil)

语法:hmget key field [field ...]
示例:

1
2
3
4
5
6
7
127.0.0.1:6379> hmget people age address
1) "20"
2) "ShenZhen"
127.0.0.1:6379> hmget people name
1) "Kitty"
127.0.0.1:6379> hmget people nickname
1) (nil)

hget

获取单个字段对应的值, 不存在的字段返回(nil)

语法:hget key field
示例:

1
2
127.0.0.1:6379> hget people name
"Kitty"

hexists

查看字段是不存在,存在返回 1,不存在返回 0

语法:hexists key field
示例:

1
2
3
4
127.0.0.1:6379> hexists people name
(integer) 1
127.0.0.1:6379> hexists people name1
(integer) 0

hgetall

获取一个 Hash 中所有字段和值

语法:hgetall people
示例:

1
2
3
4
5
6
7
8
9
127.0.0.1:6379> hgetall people
1) "age"
2) "20"
3) "address"
4) "ShenZhen"
5) "birthday"
6) "2004-05-10"
7) "name"
8) "Kitty"

hscan

增量地迭代遍历元素,不会阻塞服务器。

语法:hscan key cursor [MATCH pattern] [COUNT count]
count 默认值是 10,但此参数是做参考,Redis 并不保证返回元素数是就是 count 个。
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> hscan people 0 count 8
1) "0"
2) 1) "age"
2) "20"
3) "address"
4) "ShenZhen"
5) "birthday"
6) "2004-05-10"
7) "name"
8) "Kitty"
9) "nickname"
10) "Rom_rr"
127.0.0.1:6379> hscan people 0 match "name"
1) "0"
2) 1) "name"
2) "Kitty"

hset

给已存在的的字段设置新的值; 或字段不存在时添加新的字段

语法:hset key field value
示例:

1
2
3
4
5
6
7
8
127.0.0.1:6379> hset people email kitty@163.com
(integer) 1
127.0.0.1:6379> hget people email
"kitty@163.com"
127.0.0.1:6379> hset people email kitty_dingdang@163.com
(integer) 0
127.0.0.1:6379> hget people email
"kitty_dingdang@163.com"

hsetnx

仅在字段不存在时设置值。因为 hset 和 hmset 会覆盖现有字段,而 hsetnx 可防止此覆盖行为。

语法:hsetnx key field value
示例:

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> hget people name
"Kitty"
127.0.0.1:6379> hsetnx people name "Rom"
(integer) 0
127.0.0.1:6379> hget people nickname
(nil)
127.0.0.1:6379> hsetnx people nickname "Rom_rr"
(integer) 1
127.0.0.1:6379> hget people nickname
"Rom_rr"

hdel

从 Hash 中删除字段

语法:hdel key field
示例:

1
2
3
4
127.0.0.1:6379> hdel people email
(integer) 1
127.0.0.1:6379> hget people email
(nil)

HyperLogLog

HyperLogLog 类据类型主要用于需要对唯一计数进行统计的场景,若不需要获取数据集的内容,只是想要得到不同值的个数,可以使用HyperLogLog(HLL)数据类型,相比其它集合类型,特别是在大数据量(上千万)的时候, HLL类型性能更优并且消耗更少的内存。

Redis中的HLL虽然在技术上是一种不同的数据结构,但是被编码为 Redis 字符串。HLLSet 数据类型非常相似,使用 PFADD 添加新元素到计数中,并且元素是唯一性的,不会添加已存在的元素(重复),使用PFCOUNT来获取唯一元素的数量。

HLL相关的所有命令都是以PF开头,用来向 HLL 数据结构的发明者 Philippe Flajolet 致敬。Redis 中的 HLL 的优势是能够使用固定数量的内存(每个 HyperLogLog 类 型的键只需占用 12KB 内存,却可以计算最多 2 的 64 次方 个不同元素的基数)和常数时间复杂度(每个键 O(1) 进行唯一计数)。不过 HLL 返回的计数可不准确(标准差小于1%),因此决定是否使用 HLL 需要进行权衡。

HLL实际上是被当做字符串存储的,因此作为一个键值对,可以很容易地被持久化至外部或从外部持久化中恢复。

内部存储

Redis 内部存储 HLL 对象的两种方式:

  • **稀疏(Sparse)**:对于那些长度小于配置中 hll-sparse-max-bytes选项设置的值(默认为 3000)的 HLL 对象,采用此编码。此方式的存储效率更高,但可能消耗更多的 CPU 资源。
  • 稠密(Dense):当稀疏不能适用时使用此编码。

基本命令

  • PFADD:添加元素到计数中,成功返回1,失败返回0,重复元素不添加。
  • PFCOUNT:对计数中的元数进行计数统计,返回元素个数。
  • PFMERGE:合并多个计数中的元数到一个新的计数中。

PFADD

添加元素到计数中

语法:pfadd key element [element ...]
示例:

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> pfadd fruit "apple" "orange" "branana"
(integer) 1
127.0.0.1:6379> pfadd fruit "Pear"
(integer) 1
127.0.0.1:6379> pfadd fruit "Lemon"
(integer) 1
127.0.0.1:6379> pfadd fruit "Peach"
(integer) 1
127.0.0.1:6379> pfadd fruit "Peach"
(integer) 0

PFCOUNT

对计数中的元数进行计数统计,返回元素个数

语法:pfcount key [key ...]
示例:

1
2
127.0.0.1:6379> pfcount fruit
(integer) 6

PFMERGE

合并多个计数中的元数到一个新的计数中。

语法:pfmerge destkey sourcekey [sourcekey ...]
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
127.0.0.1:6379> pfadd fruit "apple" "orange" "branana"
(integer) 1
127.0.0.1:6379> pfadd fruit "Pear"
(integer) 1
127.0.0.1:6379> pfadd fruit "Lemon"
(integer) 1
127.0.0.1:6379> pfadd fruit "Peach"
(integer) 1
127.0.0.1:6379> pfcount fruit
(integer) 6
127.0.0.1:6379> pfadd fruit "Peach"
(integer) 0
127.0.0.1:6379> pfadd shop "apple" "Pear" "branana" "grape" "pineapple"
(integer) 1
127.0.0.1:6379> pfcount shop
(integer) 5
127.0.0.1:6379> pfmerge fruit_count fruit shop
OK
127.0.0.1:6379> pfcount fruit_count
(integer) 8

Geo

随着以智能手机为代表的移动智能终端的普及, 基于地理位置的服务(LBS)变得越来越受欢迎, 提供这类服务的系统也越来越多的使用到了地理位置的坐标数据(GPS经纬度),需要根据坐标数据计算距离,获取范围内的成员等业务。Redis 的 Geo 数据类型为基于地理位置相关场景提供了很好的支持。

Geo 相关的 API 用于支持存储和查询这些地理位置相关场景中的位标(GPS经纬度)。

常用命令

  • geoadd:将元素添加到 Geo 集合中。
  • geopos:从 Geo 集合中获取指定成员的坐标。
  • georadius:获取距离范围内的成员。
  • geodist:计算两个成员之间的距离。

内部存储

当通过 geoadd 设置坐标时,这些坐标会被转换成一个 52 位的 GEOHASHGEOHASH是一个被广泛接受的地理坐标编码系统(Geo-encoding system)。所在存储在 Geo 中的坐标和 GEOPOS 命令返回的坐标之间可能存在经微的差别。
GEOHASH 实现是基于一种 52 位整数的表示(实现啊低于 1 米的精度)。当需要一个标准的 GEOHASH 字符串时,可以使用 GEOHASH 命令来获取一个长度为 11 的字符串。

Geo 集合实际上被存储为一个有序集合(Sorted Set), 因此有序集合支持的所有命令都可以用于 Geo 数据类型。例如,可以使用zremGeo中移除成员,也可以使用 zrange 来获取 Geo 集合的所有成员。

georadius命令,在性能方面,georadius 的时间复杂度为 O(N+log(M)), 其中 N为由中心点和半径所决定的圆形区域的外接矩形中成员的个数。因此,若想获得最优性能,在查询时将关径参数设置的尽可能的小,以覆盖尽可能少的点。

geoadd

将元素添加到 Geo 集合中

语法:geoadd key longitude latitude member [longitude latitude member ...]
示例:

1
2
3
4
5
6
127.0.0.1:6379> geoadd offical 113.9409770034 22.6781577868 "Star City"
(integer) 1
127.0.0.1:6379> geoadd offical 113.9409770034 22.6793061050 "Garden"
(integer) 1
127.0.0.1:6379> geoadd offical 113.9410413765 22.6781181894 "ShiYan People Hospital" 113.9388312362 22.6789299328 "ShiYan Cinema"
(integer) 2

geopos

从 Geo 集合中获取指定成员的坐标

语法:**geopos key member [member ...]**
示例:

1
2
3
127.0.0.1:6379> geopos offical "Garden"
1) 1) "113.94097656011581421"
2) "22.67930731281483503"

georadius

传入经纬度,获取距离此位置范围内的成员。

语法:**georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]**
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> georadius offical 113.94097656011581421 22.67930731281483503 5 km
1) "ShiYan Cinema"
2) "Star City"
3) "ShiYan People Hospital"
4) "Garden"
127.0.0.1:6379> georadius offical 113.94097656011581421 22.67930731281483503 1 km
1) "ShiYan Cinema"
2) "Star City"
3) "ShiYan People Hospital"
4) "Garden"
127.0.0.1:6379> georadius offical 113.9317940447 22.67930731281483503 1 km
1) "ShiYan Cinema"
2) "Star City"
3) "ShiYan People Hospital"
4) "Garden"
127.0.0.1:6379> georadius offical 113.9317940447 22.6836188546 1 km
1) "ShiYan Cinema"

georadiusbymember

此命令与 georadius 相似,都可以用来找出位于指定范围内的成员,但 georadiusbymember命令的中心点是由 Geo 集合中的成员决定的;而不是像 georadius 那使通过传入经纬度来决定的。

语法:**georadiusbymember key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]**
示例:

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> georadiusbymember offical "Garden" 1 km
1) "ShiYan Cinema"
2) "Star City"
3) "ShiYan People Hospital"
4) "Garden"
127.0.0.1:6379> georadiusbymember offical "Garden" 20 m
1) "Garden"
127.0.0.1:6379> georadiusbymember offical "Garden" 200 m
1) "Star City"
2) "ShiYan People Hospital"
3) "Garden"

georadiusgeoradiusbymember 命令中, 可以使用 WITHCOORD 选项来返回成员的经纬度,WITHDIST选项得到距离,ASC|DESC选项控制返回结果的升序或降序;还可以通过STORE | STOREDIST将返回的结果存到的 Redis 中的另一个 Geo 集合中。

geodist

计算两个成员之间的距离

语法:**geodist key member1 member2 [unit]**
示例:

1
2
3
4
127.0.0.1:6379> geodist offical "Garden" "Star City"
"127.9952"
127.0.0.1:6379> geodist offical "Garden" "Star City" km
"0.1280"

GEOHASH

获取成员位置坐标的 GEOHASH 字符串

语法:**geohash key member [member ...]**
示例:

1
2
127.0.0.1:6379> geohash offical Garden
1) "ws110b9k910"

Redis 4.x系列(五):Redis 数据类型之Hash、HyperLogLog、Geo

http://blog.gxitsky.com/2018/10/02/Redis-5-datatype-3-hash-hyperloglog-geo/

作者

光星

发布于

2018-10-02

更新于

2022-08-14

许可协议

评论