redis数据类型要点知识及应用场景

redis是基于键值对的,它的key类型实际是object,所以一般所谓的redis数据类型其实是value的数据类型。从大的方面来分,主要有如下五种:

  1. string
  2. list
  3. hash
  4. set
  5. sorted_set

string

string类型,应该是redis里最常用的一种,它可以存字符串,可以存数值,还可以存二进制位图bitmap。
那么string类型实际到底存的是什么呢,实际上是字节数组。redis对于string类型的操作有很多的命令,使用string长度查询命令就可以验证究竟存的是字符还是字节。
验证流程是,在不同字符集编码下存储中文字符,会发现使用STRLEN结果并不是字符的数量,而是对应字符集编码下相应字符对应的字节长度。

string类型可以存储字符串,因此也提供了很多基础的常用的字符串操作的功能,例如字符串拼接append、长度查询strlen、获取特定位置字符getrange等。
在不少涉及到权限系统的设计中,经常会使用string类型存储token,并设置一定的有效期。

以上操作,以及以下操作,都可以在redis-cli中通过help进行查询,例如要查询string支持的功能,就可以使用help @string

string类型可以存储数值,所以也提供了很多对于数值的操作功能,例如加一incr、减一decr、加特定数值incrby、减特定数值decrby等。
这个功能对于某些系统某些功能的访问量、评论量、在线离线量等业务场景中,会有很好的应用。

string类型可以存储bitmap,也提供了很多对于位图的操作,例如设置位图setbit、获取位图数据getbit、位图统计bitcount、复合统计bitop等。
bitmap是基于二进制位的操作,可以使用很小的内存表示大量的数据,对于很多统计类的业务非常实用,例如周、月、天这些特定场景的数据统计。
需要注意的是,bitmap中的offset是有限制的,不能超过Integer的最大限制,否则会抛异常,存储失败。

string类型在设值的时候,可以使用单一的操作set,也可以使用多元素操作mset,不论是单一元素操作,还是多元素的操作,都有一个末尾带着nx的操作,这个操作支持nxxx操作,一个是只有key不存在时才能设值成功,一个是只有key存在时才能设值成功。
这个功能可以一定程度上保证数据的原子性,因此在分布式应用场景下经常用于分布式锁。(确切的说,是多元素操作时保证原子性,由于redis是单进程、单线程的,所以单一元素操作也不存在原子性问题)

list

list类型也是redis里很常用的类型,在list类型key中带有head和tail属性,分别指向list中第一个元素和最后一个元素,这种设计使得list类型可以模拟多种常用的数据结构。

往list中存入元素,可以选择从左或者从右追加推入数据,也可以选择linsert这种操作在特定位置插入数据,还可以使用lset这种操作替换特定位置的数据。
以上追加推入的方式,有对应的弹出数据操作,可以选择同向取出,也可以选择反向取出。
对于同向取出操作,即lpushlpop,以及rpushrpop,这种操作的结果就是数据后进先出,刚好和java中的栈这种数据结构的设计一样
而反向取出,即lpush对应rpop,或者rpush对应lpop,这种操作的结果就是先进先出,即所谓的FIFO,刚好和java中队列这种数据结构一样
linsertlset这种操作又和java中的数组结构操作一样
同时,list还支持异步阻塞,例如blpopbrpop,这种操作会阻塞客户端,直到取到相应key的数组,这种操作就可以实现一些单播订阅的业务需求。
list类型中,元素是允许重复的,并且从可以按存的顺序取,能够看出他是有序的,这个序就是存数据的顺序。
综上,redis的list类型可以代替一些java中常用的api和数据结构,可以在某种程度上更好的保证数据的可靠性。

list类型有很多操作,不限于上边所列,和string类型一样,也可以使用help @list方式查询redis自带的帮助文档,在需要的时候实际对应的学习了解。

hash

redis本身是键值结构,这里的hash指的是value本身又是一个键值对,这种结构如同java中hashmap里边又嵌套了一个hashmap。
因此,对于value的hash类型本身,就像是redis的string类型一样了,而实际上hash的大部分操作也和String类型的极其相似。
从操作命令上来说,几乎就是string相应命令前边加了一个h字符,存入数据的操作就是在原有key的基础上再加一个内层的key,只是这里标准的叫法是field。
get取数据,set存数据,以及对于数值的加减操作,都和string很像。

hash的这种结构,很适用于一个对象、多个属性,这种的数据存储。例如热门商品的部分属性存储,车联网中某辆车车况数据存储,可以减少热门数据使用时对数据库的操作,同时又能很方便高效的对数据进行修改。

set

单纯从存取数据来说,redis中list类型和set类型,与java中的list和set其实是很像的,在java中,list和set都是集合,都是容器,都能存很多其他的东西在里边,不同的是list允许重复,且是有序的,而set不允许重复。
至于说set顺序的问题,在java中set子类众多,有的无序,有的有序,所以不能一概而论。
而redis中,和java相似的是,set也是不允许重复,但是这里的set就是无序。
set不允许重复的特性,有很多应用场景,例如可以用来统计某段时间内在线用户,可以直接去除多次在线的用户。

除了set数据类型本身的特性之外,redis还为这个类型提供了一些很有用的api命令,例如sinter可以取两个set数据的交集,sdiff可以取两个set的差集,sunion可以取两个set的并集。
这里需要注意的是,sdiff实际是有方向,会以最左边一个key内容为准,所以实际的方向需要使用的时候自己控制。
另外,上述三个操作,都有一个带着store的操作,这个操作需要提供一个额外的key,会把交集、并集、差集的结果存到这个额外的key里边。

set类型还有一个非常好的操作,可以根据给定的key生成一定的随机数,命令是srandmember,这个操作,需要一个key参数,同时还需要一个数字,即生成随机数的个数。
这个数字是有一定讲究的,如果是正数,且小于set本身元素的数量,就会随机返回给定数量不重复的数据。如果是正数,且大于set本身元素数量,则会返回给定数据的数据,但是会有重复。如果是负数,且小于set本身元素数量,则结果可能重复,也可能不重复。如果是负数且大于set本身元素数量,则也会返回给定数量的数据,会有重复。
以上这个功能,就很适合类似验证码、抽奖之类的业务场景。

对于set,有一个需要注意的操作,即smembers,这个操作可以列出某个key下的所有元素,但是这是一个消耗redis吞吐量的操作,需要谨慎使用。

sorted_set

sorted_set是有序的set集合,从某种意义上来讲,sorted_set更像是对list和set的一个补充。list是有序的(存入顺序),但是不去重,而set是去重的,却又无序。
这些设计自然都是各有各的好处,各有各的应用场景,但是去重并且有序,这个需求其实也是很常见的,而sorted_set就是这样一个类型,只不过这里的有序和list不同,不是存入顺序,而是排序。

以上说法其实是一个理解,可以说sorted_set是list和set的补充,也可以说是一个综合。因为sorted_set的操作更像是综合了list和set的操作,和set一样有交集、并集、差集操作,又和list一样有阻塞取数据的操作。
但有区别的是,sorted_set中的集合操作,还带有权重和聚合的操作,可以取最大、最小值及求和,也使得它有了更多的应用场景。

sorted_set是有排序的有序类型,这里的排序需要在存数据的时候给定一个分值。这个分值可以决定相应元素的顺序,但是这个分值不是说给定了就不变的,可以进行加减操作,并且对分值进行操作之后,相应的顺序也会同时发生改变。
这种特性就很适用于一些实时的排行榜数据操作,例如歌曲排行榜、博客排行榜、销量排行榜等等。

一般来说,有排序必然就有其他额外的数据维护,也必然导致同等情况下效率会低。
而sorted_set的特别之处在于,底层存储结构是skip list跳跃表,这个结构比set的key存储了更多的内容,使得sorted_sort在效率上得到了提升(具体结构细节待补充)

推荐文章