Redis的数据类型详解和使用:HashSetSorted setBitmapHyperLogLog类型
Posted 刘Java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis的数据类型详解和使用:HashSetSorted setBitmapHyperLogLog类型相关的知识,希望对你有一定的参考价值。
详细介绍了Redis的Hash、Set、Sorted set、Bitmap、HyperLogLog类型的常见命令和应用方式。
1 Redis Hash
Redis Hash是一个String 类型的 field 和 value 的映射表,底层是Redis自己实现的dict字典结构,类似于JDK1.7前的 HashMap,内部采用数组+链表结构,采用链地址法解决哈希冲突。Hash结构特别适合用于存储对象,后续操作的时候,可以直接仅仅修改这个对象中的某个字段的值。元素较少时,将会采用ziplist进行存储。
Redis 中每个 hash 可以存储 2^32 – 1个键值对(超过40亿),完全可被用来存储除了对象之外的其他数据。
HSET
命令用于设置Hash的单个字段,而HMSET命令则用于设置Hash的多个字段,如果此前没有key会被创建,相同HashKey的value将会被覆盖。HGET检索单个字段,HMGET用于检索一组字段值,对于散列中不存在的每个字段,返回一个 nil 值。
127.0.0.1:6379> HSET a a aa
(integer) 1
127.0.0.1:6379> HMSET a a aaa b bb c cc
OK
127.0.0.1:6379> HGET a a
"aaa"
127.0.0.1:6379> HMGET a a b d
1) "aaa"
2) "bb"
3) (nil)
可以对Hash中的单个字段执行操作,例如 HINCRBY
:
127.0.0.1:6379> HSET inc 10 1000
(integer) 1
127.0.0.1:6379> HINCRBY inc 10 -1
(integer) 999
HGETALL
用于获取key中所有Hash键值对,HKEYS
用于获取所有的key,HVALS
用于获取所有的value:
127.0.0.1:6379> HGETALL a
1) "a"
2) "aaa"
3) "b"
4) "bb"
5) "c"
6) "cc"
127.0.0.1:6379> HKEYS a
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> HVALS a
1) "aaa"
2) "bb"
3) "cc"
2 Redis Set
Redis Set是无序且不会重复的字符串集合,类似于 Java 中的 HashSet,相当于一个特殊的Hash,存入的值被存储为key,而value都是一个null,返回数据的时候,就返回key。
Redis 中每个 Set可以存储 2^32 – 1个value(超过40亿)。
SADD
命令用于将指定的多个元素添加到key对应的List集合中(Redis 2.4之前的版本每次调用只能添加一个元素),已存在的元素将不会被再次添加,返回添加成功的元素数量。
127.0.0.1:6379> SADD name hello
(integer) 1
127.0.0.1:6379> SADD name world
(integer) 1
127.0.0.1:6379> SADD name world
(integer) 0
127.0.0.1:6379> SMEMBERS name
1) "world"
2) "hello"
SMEMBERS
用于获取所有的元素,SCARD
用于获取元素数量,SISMEMBER
用于判断某个元素是否存在:
127.0.0.1:6379> SMEMBERS name
1) "xx"
2) "world"
3) "hello"
127.0.0.1:6379> SCARD name
(integer) 3
127.0.0.1:6379> SISMEMBER name xx
(integer) 1
127.0.0.1:6379> SISMEMBER name xxx
(integer) 0
Redis Set支持集合交集、并集或差集等计算,因此可以用于快速的开发某些功能,比如求两个用户相同的关注。
- SINTER:返回多个集合的交集(在每个Set中都有出现的元素);
- SINTERSTORE destination:求多个集合的交集,并将结果存入指定的集合中;
- SDIFF:返回第一个集合与其他集合之间的差集(只有在第一个Set中出现,在其他任何Set中都没有出现的元素)。
- SDIFFSTORE destination:返回第一个集合与其他集合之间的差集,并将结果存入Set集合destination中。
- SUNION:返回多个集合的并集(去重);
- SUNIONSTORE destination:返回多个集合的并集,并将结果存入Set集合destination中。
127.0.0.1:6379> SADD a a b c
(integer) 3
127.0.0.1:6379> SADD b b c d
(integer) 3
127.0.0.1:6379> SADD c c d e
(integer) 3
127.0.0.1:6379> SINTER a b c
1) "c"
127.0.0.1:6379> SDIFF a b c
1) "a"
127.0.0.1:6379> SUNION a b c
1) "c"
2) "d"
3) "b"
4) "a"
5) "e"
可以使用 Set的 SPOP
或 SRANDMEMBER
命令随机提取元素,SPOP将会弹出元素,而SRANDMEMBER则不会:
127.0.0.1:6379> SADD a a b c d e f g
(integer) 7
127.0.0.1:6379> SRANDMEMBER a
"c"
127.0.0.1:6379> SRANDMEMBER a
"f"
127.0.0.1:6379> SRANDMEMBER a
"b"
3 Redis Sorted set
Redis Sorted Set 类似于 Redis Set,都是非重复的字符串集合。不同之处在于,Sorted Set 的每个成员都与一个double类型的score 分数相关联,实际上是一个按照分数从最小到最大排序的有序集合。同样可用于排名、排行榜等业务。
Redis 中每个 Sorted Set可以存储 2^32 – 1个score-value对(超过40亿)。
由于集合元素有序,因此可以根据score或排名(位置)非常快的获取获取范围的元素。另外,虽然有序集合的元素是唯一的,但分数(score)却可以重复。如果 A 和 B 是具有不同score的两个元素,如果 A.score 是 > B.score,则 A > B。如果 A 和 B 的score完全相同,则按照字符串按字典顺序比较A和B并排序。
Redis Sorted Set的数据结构比较复杂,由ziplist、zset(dict和skiplist)组成,可以说是最高级的 Redis 数据类型:
- 当数据较少时,Sorted Set是由一个ziplist来实现的。ziplist使用整块内存,元素之间非常紧凑,每个存入的(元素,score)对将会被存储为两个ziplist节点,元素在前,score在后。ziplist更加节省内存,但是只能进行顺序或者倒序依次查找,插入、删除元素时,涉及到其他元素位置移动,因此ziplist只适用于元素数量较少、元素较小的情况。
- 当数据多的时候,Sorted Set采用zset来实现,这个zset内部包含一个dict(类似JDK1.7的hashmap)和一个skiplist(跳跃表)数据结构。skiplist的每个结点都保存了元素和score,并且根据score来进行了排序,可用来根据score查询元素,比如score范围查找。而dict则保存了元素到分数(score)的对应关系,实际上一个skiplist就能满足需求,为什么要使用dict呢,采用dict就可以使用map的特性了,比如快速根据元素找到它的score,时间复杂度为O(1),如果只使用skiplist,则根据元素查找score则非常耗时,需要O(N)。
关于跳跃表skiplist,它也是一种有序的查找数据结构,它采用以空间换时间的方法,通过添加“索引”(消耗空间)来实现数据的快速查找,并且支排序,支持范围查找,相比于平衡二叉树实现起来更加简单,占用的内存也不会比平衡二叉树大多少。在Java中有ConcurrentSkipListMap与其对应,skiplist的具体原理我们在ConcurrentSkipListMap的文章中就讲过了,在此不再赘述!
某个Sorted Set由ziplist结构转换为zset结构的要求是:
- Sorted Set保存的元素数量小于128个。
- Sorted Set保存的所有元素的长度小于64字节。
使用ZADD
可以添加多个元素到Sorted Set,这与SADD类似,但是每一个元素必须关联一个score,score在前,即score-value 对。由于元素唯一,因为相同的value的分数将会进行替换。
使用ZRANGE
展示全部value,该命令类似于LRANGE,使用该命令时Redis并不会排序,因为每一个元素插入时,已经排好序了。使用ZREVRANGE
则可以查看倒序的结果。
127.0.0.1:6379> ZADD a 1 aa 3 bb 2 cc 7 dd 8.1 ee
(integer) 5
127.0.0.1:6379> ZRANGE a 0 -1
1) "aa"
2) "cc"
3) "bb"
4) "dd"
5) "ee"
127.0.0.1:6379> ZREVRANGE a 0 -1
1) "ee"
2) "dd"
3) "bb"
4) "cc"
5) "aa"
在最后添加WITHSCORES
,即可返回每一个value对应的socre:
127.0.0.1:6379> ZREVRANGE a 0 -1 WITHSCORES
1) "ee"
2) "8.0999999999999996"
3) "dd"
4) "7"
5) "bb"
6) "3"
7) "cc"
8) "2"
9) "aa"
10) "1"
ZSCORE
用于返回某个元素的score,ZREM
用于删除某个元素:
127.0.0.1:6379> ZADD b 1 aa 3 bb 2 cc 7 dd 8.1 ee
(integer) 5
127.0.0.1:6379> ZSCORE b cc
"2"
127.0.0.1:6379> ZREM b cc
(integer) 1
127.0.0.1:6379> ZRANGE b 0 -1
1) "aa"
2) "bb"
3) "dd"
4) "ee"
使用Sorted Set可以执行范围操作。ZRANGEBYSCORE
获取指定分数之间的元素(范围默认是为闭区间,可以通过给某个端点前增加 ( 符号来使用可选的开区间=),-inf
表示负无穷,+inf
表示正无穷。
127.0.0.1:6379> ZRANGEBYSCORE a 2 3
1) "cc"
2) "bb"
127.0.0.1:6379> ZRANGEBYSCORE a -inf 3
1) "aa"
2) "cc"
3) "bb"
127.0.0.1:6379> ZRANGEBYSCORE a -inf (3
1) "aa"
2) "cc"
ZRANGEBYSCORE
后还可以跟limit offset count
命令,用于进行分页查询,如limit 0 1
就表示返回最小的一条数据。
ZRANGEBYSCORE
可以用于实现简单的Redis版本的延时队列,我们使用ZADD添加延时消息,将score设置为一个表示关联消息的未来执行时间点的时间戳,然后使用一个线程轮询的通过ZRANGEBYSCORE命令获取score小于等于当前时间戳的最小score的消息进行消费,消费完毕再使用ZREM删除该消息。当然,还需要考虑多客户端实例(加分布式锁)、消费ack机制(需要额外的确认队列,消费失败重试)等问题,比较繁琐。
ZREMRANGEBYSCORE
类似于上的ZRANGEBYSCORE,但它会移除范围内的元素,并返回移除的数量:
127.0.0.1:6379> ZREMRANGEBYSCORE a 2 3
(integer) 2
127.0.0.1:6379> ZRANGE a 0 -1
1) "aa"
2) "dd"
3) "ee"
ZRANK
用于获取某个元素的排序索引位置(从0开始),ZREVRANK
则获取倒序顺序:
127.0.0.1:6379> ZRANK a ee
(integer) 2
127.0.0.1:6379> ZRANK a aa
(integer) 0
127.0.0.1:6379> ZREVRANK a aa
(integer) 2
4 Bitmap
Bitmap不是实际的数据类型,而是在 String 类型上定义的一组面向位(bit)的操作。由于String的最大长度为 512 MB,因此一个String可以设置多达 2^32 个不同的位。
位操作分为两种:一种是恒定时间的单个位操作,例如将某个位设置为 1 或 0,或者获取某个位的值,另一种是对位组的操作,例如计算给定位范围内设置为1的位的数量。
Bitmap的最大优点之一是它们在存储信息时通常可以极大地节省空间。例如,在不同用户由自增用户ID表示的系统中,仅使用 512 MB 内存就可以记住 40 亿用户的单个位信息。例如,记录用户是否想要接收实时通讯,1表示是,0表示否,或者记录用户是否签到、是否登陆、是否活跃等各种布尔状态信息。
使用 SETBIT
命令设置某个偏移量的位的值(0或1),返回原始值,使用 GETBIT
检索某个偏移量的位的值,偏移量offset从0开始,小于等于2^32:
127.0.0.1:6379> SETBIT a 0 1
(integer) 0
127.0.0.1:6379> SETBIT a 3 1
(integer) 0
127.0.0.1:6379> GETBIT a 0
(integer) 1
在SETBIT设置值时,底层字符串长度是可变的,长度为目前最大的位,如果设置的位超出当前字符串长度,该命令会自动扩大字符串。而如果通过GEIBIT获取的索引位置时超出范围的位(查找存储在目标键中的字符串长度之外的位)总是被认为是0。
BITOP
命令用于对多个BitMap的key执行按位运算,支持AND, OR, XOR 和NOT,并将结果存储在destkey中:
BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP NOT destkey srckey
BITCOUNT
命令执行计数操作,返回设置为 1 的位的数量。
127.0.0.1:6379> SETBIT a 1 1
(integer) 0
127.0.0.1:6379> SETBIT a 2 1
(integer) 0
127.0.0.1:6379> SETBIT a 3 1
(integer) 0
127.0.0.1:6379> SETBIT b 2 1
(integer) 0
127.0.0.1:6379> SETBIT b 3 1
(integer) 0
127.0.0.1:6379> SETBIT b 4 1
(integer) 0
127.0.0.1:6379> BITOP AND c a b
(integer) 1
127.0.0.1:6379> BITCOUNT c
(integer) 2
127.0.0.1:6379> BITOP OR c a b
(integer) 1
127.0.0.1:6379> BITCOUNT c
(integer) 4
BITPOS
命令查找指定值为 0 或 1 的第一位的索引:
127.0.0.1:6379> BITPOS c 0
(integer) 0
127.0.0.1:6379> BITPOS c 1
(integer) 1
5 HyperLogLog
HyperLogLog 是Redis 在 2.8.9版本开始添加的一种用于计算集合基数的概率型数据结构。所谓基数,就是指一个集合中不重复的元素的个数,基数是一个估计值而不是一个准确值,因此有一定范围的误差,但可以接受。
基数的应用非常多,比如,在MySQL中也有基数(Cardinality)的概念,索引基数是数据列所包含的不同值的数量,索引的基数相对于总数据量的占比(索引选择性)越高,那么说明索引不重复的概率就越大,索引效率就越高,否则说明索引效率越低,比如十万行数据的性别字段,就不适用与建立索引(大部分情况,但有例外),因为性别只有男/女,两种,基数与总数的占比过低,索引作用不大!
通常,计算基数需要使用的内存量与要计算的元素数量成正比,因为需要记住过去已经计算过的元素,以避免对它们进行多次计数。但是,有一组算法可以用内存换取精度:以带有标准误差的估计基数值,在 Redis 实现的情况下,该标准误差小于 1%(注:mysql中索引基数计算的也是一个估算值)。
Redis的HyperLogLog算法的神奇之处在于不再需要使用与元素数量成正比的内存量,而是可以使用恒定的内存!在最坏的情况下(大量元素)内存占用为12kB,如果元素更少,则HyperLogLog花费的内存更少。
Redis 中的 HLL (HyperLogLog)虽然在技术上是一种不同的数据结构,但被编码为 Redis String类型,因此可以调用GET来序列化 HLL,并调用 SET 将其反序列化回服务器(同理,Redis的Bitmap也是以使用SET和GET命令)。
使用PFADD
命令向某个key添加元素,但实际上并没有真正将元素添加到 HLL 中,因为数据结构中仅包含没有实际元素的状态信息。使用PFCOUNT
命令对添加的元素进行基数近似值统计:
127.0.0.1:6379> PFADD a xx
(integer) 1
127.0.0.1:6379> PFADD a yy
(integer) 1
127.0.0.1:6379> PFADD a xxx
(integer) 1
127.0.0.1:6379> PFADD a xx
(integer) 0
127.0.0.1:6379> PFADD a zz cc dd
(integer) 1
127.0.0.1:6379> PFCOUNT a
(integer) 6
HyperLogLog可以用来做很多事,比如统计用户在搜索框中执行的不同查询的次数,或者计算某个页面每天不同的访客数,这种结果不需要精确值。
相关文章:
- https://redis.io/topics/data-types
- https://redis.io/topics/data-types-intro
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!
以上是关于Redis的数据类型详解和使用:HashSetSorted setBitmapHyperLogLog类型的主要内容,如果未能解决你的问题,请参考以下文章