Redis的数据类型详解和使用:key、String类型
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情
详细介绍了Redis的key和String数据类型的底层原理,以及最基本的使用方式。
1 Redis的数据类型
Redis 不是一个普通的key-value存储服务器,它实际上是一个数据结构服务器,支持不同类型的值,在Redis中值不仅限于简单的字符串,还可以包含更复杂的数据结构。
以下是Redis支持的所有数据结构:
- 二进制(Binary-safe)安全的String(字符串)。
- List:根据插入顺序排序的String元素的集合,基本上是链表。
- Set:唯一的、未排序的String元素的集合。
- Sorted set:类似于Set,但每个String元素都与一个浮点值相关联,称为分数。元素总是按它们的分数排序,因此与 Sets 不同,它可以检索一系列元素(例如,获取分数前十个或者后十个)。
- Hash:它是由与值关联的字段组成的map,字段和值都是字符串,这与 Ruby 或 Python 的Hash非常相似。
- Bit array(或者叫bit map):Bitarray本身不是一种数据结构,实际上它就是String,只不过Redis支持使用特殊命令可以像处理(bitarray)位数组一样处理字符串值,可以设置和清除单个bit位,计算所有设置为 1 的bit位,找到第一个设置或未设置的bit位等等。这使得Redis的String可被用于存储、获取、统计等操作,非常节省空间。
- HyperLogLog:这是一种概率型数据结构,用于快速估计集合的基数。
- Stream:Redis 5.0 版本新增加的数据结构,用于实现可持久化的消息队列。
2 Redis key
Redis key是String类型,也是二进制安全的,我们可以使用任何二进制序列作为键,从像“foo”这样的字符串到 JPEG 文件的内容。
关于Redis的key有以下规则和建议:
- 太长的key不是个好的选择,不仅因为消耗内存,而且在数据中查找key也需要进行多次复杂的key比较。
- 太短的键值通常也不是好的选择,如果你要用”u:1000:pwd”来代替”user:1000:password”,这本身没有什么问题,但后者更易阅读,并且由此增加的空间消耗相对于key object和value object本身来说很小。当然,没人阻止您一定要用更短的键值节省一丁点儿空间。
- 最好是坚持一种命名模式。例如:”
object-type:id:field
”就是个不错的注意,像这样:“user:1000:password
”。对多单词的字段名中可以加上一个点,就像这:“comment:1234:reply.to
”样:。 - 允许的最大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命令。
这个场景可以有很多种扩展方法:
- 通过结合使用INCR和EXPIRE命令,可以实现一个只记录用户在指定间隔时间内的访问次数的计数器。
- 客户端可以通过GETSET命令获取当前计数器的值并且重置为0。
- 通过类似于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)
以下是超时相关命令的汇总:
EXPIRE
将key的生存时间设置为秒。PEXPIRE
:将key的生存时间设置为毫秒。EXPIREAT
:将key的过期时间设置为timestamp所代表的的秒数的时间戳。PEXPIREAT
:将key的过期时间设置为timestamp所代表的的毫秒数的时间戳。PTTL
:以毫秒为单位检查key的剩余时间。
3.5 彩蛋
Redis的LOLWUT [VERSION version]
指令将会返回包含生成的计算机艺术图像的字符串,以及带有 Redis 版本的文本。本文演示版本为Redis 6.2。
LOLWUT VERSION 5
:
LOLWUT:
官方文档:http://www.redis.com.cn/commands/lolwut.html
相关文章:
- http://redis.io/topics/data-types
- http://redis.io/topics/data-types-intro
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!
- Java Executor源码解析(7)—Executors线程池工厂以及四大内置线程池
- Java中的对象内存布局以及如何计算对象大小
- Redis的数据类型详解和使用:key、String类型
- Spring Boot内嵌Tomcat原理
- Spring Boot启动源码分析【一万字】
- Redis GEO 地理位置的使用与原理解析以及Java实现GEOHash算法
- Redis的缓存穿透、缓存雪崩、缓存击穿问题的概念与解决办法
- Java Executor源码解析(5)—ThreadPoolExecutor线程池其他方法的源码
- Java Executor源码解析(3)—ThreadPoolExecutor线程池execute核心方法源码【一万字】
- Java Semaphore 信号量的源码深度解析与应用
- Java LongAccumulator原子累加器源码深度解析
- Java LongAdder原子累加器源码深度解析
- Java SynchronousQueue阻塞传输器源码深度解析【三万字】
- 再见 MMKV,自己撸一个FastKV,快的一批
- Android性能优化:全量编译提速黑科技!
- Android编译提速黑科技—Wade Plugin
- 数据结构—树、森林和二叉树的转换详解
- 数据结构—二叉树的4种遍历方式详解以及Java代码的完整演示
- 如何丝滑般地加载超大gif图?
- Java的JVM性能监控与故障处理工具详细介绍以及使用案例