Redis的数据类型详解和使用:key、String类型

语言: CN / TW / HK

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情

详细介绍了Redis的key和String数据类型的底层原理,以及最基本的使用方式。

1 Redis的数据类型

Redis 不是一个普通的key-value存储服务器,它实际上是一个数据结构服务器,支持不同类型的值,在Redis中值不仅限于简单的字符串,还可以包含更复杂的数据结构。

以下是Redis支持的所有数据结构:

  1. 二进制(Binary-safe)安全的String(字符串)。
  2. List:根据插入顺序排序的String元素的集合,基本上是链表。
  3. Set:唯一的、未排序的String元素的集合。
  4. Sorted set:类似于Set,但每个String元素都与一个浮点值相关联,称为分数。元素总是按它们的分数排序,因此与 Sets 不同,它可以检索一系列元素(例如,获取分数前十个或者后十个)。
  5. Hash:它是由与值关联的字段组成的map,字段和值都是字符串,这与 Ruby 或 Python 的Hash非常相似。
  6. Bit array(或者叫bit map):Bitarray本身不是一种数据结构,实际上它就是String,只不过Redis支持使用特殊命令可以像处理(bitarray)位数组一样处理字符串值,可以设置和清除单个bit位,计算所有设置为 1 的bit位,找到第一个设置或未设置的bit位等等。这使得Redis的String可被用于存储、获取、统计等操作,非常节省空间。
  7. HyperLogLog:这是一种概率型数据结构,用于快速估计集合的基数。
  8. Stream:Redis 5.0 版本新增加的数据结构,用于实现可持久化的消息队列。

2 Redis key

Redis key是String类型,也是二进制安全的,我们可以使用任何二进制序列作为键,从像“foo”这样的字符串到 JPEG 文件的内容。

关于Redis的key有以下规则和建议:

  1. 太长的key不是个好的选择,不仅因为消耗内存,而且在数据中查找key也需要进行多次复杂的key比较。
  2. 太短的键值通常也不是好的选择,如果你要用”u:1000:pwd”来代替”user:1000:password”,这本身没有什么问题,但后者更易阅读,并且由此增加的空间消耗相对于key object和value object本身来说很小。当然,没人阻止您一定要用更短的键值节省一丁点儿空间。
  3. 最好是坚持一种命名模式。例如:”object-type:id:field”就是个不错的注意,像这样:“user:1000:password”。对多单词的字段名中可以加上一个点,就像这:“comment:1234:reply.to”样:。
  4. 允许的最大key的大小为 512 MB

keys命令用于返回服务指定模式的key列表,该命令是阻塞式的,阻塞期间导致Redis服务不可用,可以使用scan命令,该命令无阻塞的提取出指定模式的 key 列表,但有一定重复的概率,可以在客户端执行去重。

2.1 二进制安全与SDS

在Redis的学习中,将会看到非常多的binary safe的描述,比如key、String等等,那么什么是二进制安全呢?可以简单的这么理解:只关心二进制化的字符串,不关心具体格式,只会严格的按照二进制的数据存取,不会妄图已某种特殊格式解析数据,即二进制数据在写入时是怎么样的,读取时就是怎么样。

C语言的字符串默认是以'\0'结尾的,也就是说你保存的字符串内存在'\0',c语言自会识别前面的数据,后面的就会被忽略掉,所以说是不安全的。比如str=“redis cluster”这个字符串,在C中直接读取的结果就是“redis”,而strlen(str)计算字符串长度的结果也是4,这样就跟原始数据不是相等的,因此就是二进制不安全的。

Redis虽然是采用C语言来开发的,但是Redis内部保存的字符串数据结构是自己实现的,并不是沿用C语言的字符串数据结构。

redis采用SDS(simple dynamic string)来实现简单动态字符串(sds.h/sds.c),同时保证了redis保存的数据是二进制安全的,结构如下:

c struct sdshdr { int len; int free; char buf[]; };

实际上的字符串底层是一个char数组(和Java的ArrayList类似),与此同时还保存了len和free两个属性。

len表示该字符串的实际内容所占长度,free表示分配给该字符串的全部空间-字符串实际内容长度,也就是free表示该字符串的空闲空间长度,所以你对该字符串取值时是通过len属性判断实际内容的长度,然后取的值。

拼接字符串时是追加到free空间内中的。所以redis对字符串的求长度和更新内容等操作比C语言要快很多,因为求长度只需要返回该字符串的len属性值,C语言想要遍历整个字符串才会知道长度。拼接字符串“一般”也不需要在重新分配空间,拼接的字符串直接放在free内存中就可以了。

因为有了对字符串长度定义len,所以在处理字符串时候不会以零值字节(\0)为字符串结尾标志,能够获取到完整的输入的数据,即保证二进制。

3 Redis String

String是最基本的 Redis 值类型,也是Memcached中的唯一值类型。String是最基本的 Redis 值类型,也是Memcached中的唯一值类型。由于 Redis key是字符串,所以当我们也使用字符串类型作为值时,我们将一个字符串映射到另一个字符串。String值的最大长度为 512 MB。

Redis的String是二进制安全的,这意味着 Redis String可以包含任何类型的数据,例如 JPEG 图像或序列化的 Ruby 对象,为了提高网站运行速度,可以使用String类型缓存一些静态文件,如图片文件、CSS文件等。

我们可以使用 redis-cli 命令来对 Redis 进行命令行的操作。最基本的操作就是SET和GET命令:

shell 127.0.0.1:6379> set xx yyy OK 127.0.0.1:6379> get xx "yyy"

使用SET命令时,如果此前已存在相同key的缓存,则会执行value的替换:

shell 127.0.0.1:6379> set xx yyy OK 127.0.0.1:6379> get xx "yyy"

可以使用SETNX来避免上面这种情况,SETNX 是“SET if Not eXists(如果不存在,则 SET)”的简写。只在key 不存在的情况下,将键 key 的值设置为 value 。若key 已经存在,则 SETNX 命令不做任何动作,也不会覆盖原值。

shell 127.0.0.1:6379> GET a (nil) 127.0.0.1:6379> SETNX a aa (integer) 1 127.0.0.1:6379> SETNX a bb (integer) 0 127.0.0.1:6379> GET a "aa"

另一个有趣的命令就是操作是GETSET命令,它为指定key设置新值并且返回原值。这有什么用处呢?例如:你的系统每当有新用户访问时就用INCR命令操作一个Redis key。如果你希望每小时对这个信息收集一次。你就可以在每个小时的开始GETSET这个key并给其赋值0并读取原值。

shell 127.0.0.1:6379> get num "100" 127.0.0.1:6379> GETSET num 0 "100" 127.0.0.1:6379> get num "0"

3.1 数值原子操作

即使Redis 的基本值是字符串类型,也可以对其进行一些特殊操作,比如进行原子递增(前提是整数类型的字符串)。

shell 127.0.0.1:6379> set num 100 OK 127.0.0.1:6379> incr num (integer) 101 127.0.0.1:6379> incr num (integer) 102 127.0.0.1:6379> get num "102"

INCR命令将字符串值解析为10进制的64位有符号整型数据,将其加一,最后将获得的值设置为新值。还有其他类似的命令,如 INCRBY——增加指定的值、DECR——自减1 和 DECRBY——减去指定的值。在内部,它实际上是相同的命令,以稍微不同的方式执行。

shell 127.0.0.1:6379> INCRBY num 20 (integer) 122 127.0.0.1:6379> decr num (integer) 121 127.0.0.1:6379> decr num (integer) 120 127.0.0.1:6379> DECRBY num 20 (integer) 100

如果key 不是整数数值类型的字符串,使用增减操作将返回一个异常:

shell 127.0.0.1:6379> set flo 11.1 OK 127.0.0.1:6379> INCR flo (error) ERR value is not an integer or out of range

如果指定的key不存在,那么在执行增减操作之前,会先将它的值设定为0:

shell 127.0.0.1:6379> DECR numm (integer) -1

即使多个客户端对同一个key发出增减操作的命令,也决不会导致竞争的情况。例如如下情况永远不可能发生:“客户端1和客户端2同时读出10,他们俩都对其加到11,然后将新值设置为11”。最终的值一定是12。

由于INCR、INCRBY、DECR、DECRBY等数值操作都是原子性的,因此可以用来做很多有用的事情,最常用的使用场景是计数器。

使用思路是:每次有相关操作的时候,就向Redis服务器发送一个incr命令。例如这样一个场景:我们有一个web应用,我们想记录每个用户每天访问这个网站的次数。web应用只需要通过拼接用户id和代表当天时间的字符串作为key,该每次用户访问这个页面的时候对这个key执行一下incr命令。

这个场景可以有很多种扩展方法:

  1. 通过结合使用INCR和EXPIRE命令,可以实现一个只记录用户在指定间隔时间内的访问次数的计数器。
  2. 客户端可以通过GETSET命令获取当前计数器的值并且重置为0。
  3. 通过类似于DECR或者INCRBY等原子递增/递减的命令,可以根据用户的操作来增加或者减少某些值,比如在线游戏,需要对用户的游戏分数进行实时控制,分数可能增加也可能减少。

另外对于电商领域,也有一种应用就是秒杀业务。

3.2 批量操作

Redis支持在单个命令中设置或检索多个key的值的能力,这对于对于减少延迟也很有用。

Redis提供了 MSET 和 MGET 命令:

shell 127.0.0.1:6379> MSET a aa b bb c cc d dd OK 127.0.0.1:6379> MGET a b d 1) "aa" 2) "bb" 3) "dd"

当使用 MGET 时,Redis 返回一个值数组。

3.3 key通用操作

有些命令不是针对特定的值类型,可以与任何类型的key一起使用。

例如,使用EXISTS命令判断key对应的值是否存在,将会返回1或0。也可以传入多个key,将会返回存在的key的总和:

shell 127.0.0.1:6379> keys * 1) "b" 2) "c" 3) "d" 4) "a" 5) "num" 127.0.0.1:6379> EXISTS a (integer) 1 127.0.0.1:6379> EXISTS a b (integer) 2 127.0.0.1:6379> EXISTS a b e (integer) 2 127.0.0.1:6379> EXISTS e (integer) 0

使用DEL命令可以删除key对应的值,将会返回1或0,标识值是被成功删除(值存在)或者没被删除(key对应的值本就不存在)。也可以传入多个key,将会返回成功删除的key的总和:

shell 127.0.0.1:6379> DEL a c (integer) 2 127.0.0.1:6379> DEL a c (integer) 0 127.0.0.1:6379> DEL b (integer) 1 127.0.0.1:6379> DEL a (integer) 0

TYPE命令可以返回key对应的值的存储类型:

shell 127.0.0.1:6379> KEYS * 1) "d" 2) "num" 127.0.0.1:6379> TYPE num string 127.0.0.1:6379> TYPE d string 127.0.0.1:6379> DEL num d (integer) 2 127.0.0.1:6379> TYPE num none 127.0.0.1:6379> TYPE d none

可以对任何值类型的key设置超时时间,当这个时间到达后key-value会被删除。精度可以使用毫秒或秒,但过期时间的分辨率始终为 1 毫秒。

通常使用EXPIRE来设置超时时间,默认单位是秒,也可以再次调用这个命令来改变超时时间:

shell 127.0.0.1:6379> set a aa OK 127.0.0.1:6379> set b bb OK 127.0.0.1:6379> EXPIRE a 8 (integer) 1 127.0.0.1:6379> GET a (nil) 127.0.0.1:6379> GET b "bb"

有关过期的信息被复制并保存在磁盘上,实际上Redis会保存key的过期时间点,当Redis 服务器停止时,时间实际上已经过去了,重启时该过期key将被删除。

使在key过期之前用PERSIST命令可以去除key的超时时间:

shell 127.0.0.1:6379> SET a aa OK 127.0.0.1:6379> EXPIRE a 8 (integer) 1 127.0.0.1:6379> PERSIST a (integer) 1 127.0.0.1:6379> GET a "aa"

我们也可以在设置key-value的时候设置超时时间,使用TTL查看key的剩余时间:

shell 127.0.0.1:6379> SET a aa ex 8 OK 127.0.0.1:6379> TTL a (integer) 4 127.0.0.1:6379> TTL a (integer) 2 127.0.0.1:6379> GET a (nil)

以下是超时相关命令的汇总:

  1. EXPIRE 将key的生存时间设置为秒。
  2. PEXPIRE:将key的生存时间设置为毫秒。
  3. EXPIREAT:将key的过期时间设置为timestamp所代表的的秒数的时间戳。
  4. PEXPIREAT:将key的过期时间设置为timestamp所代表的的毫秒数的时间戳。
  5. PTTL:以毫秒为单位检查key的剩余时间。

3.5 彩蛋

Redis的LOLWUT [VERSION version]指令将会返回包含生成的计算机艺术图像的字符串,以及带有 Redis 版本的文本。本文演示版本为Redis 6.2。

LOLWUT VERSION 5

在这里插入图片描述

LOLWUT: 在这里插入图片描述

官方文档:https://www.redis.com.cn/commands/lolwut.html

相关文章:

  1. https://redis.io/topics/data-types
  2. https://redis.io/topics/data-types-intro

如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!