String
- String 是最基本的 key-value 结构,key 是唯一标识,value 是具体的值,
value其实不仅是字符串, 也可以是数字(整数或浮点数)
,value 最多可以容纳的数据长度是 512M。
内部实现
-
String 类型的底层的数据结构实现主要是
int 和 SDS
(简单动态字符串)。SDS 不仅可以保存文本数据,还可以保存二进制数据
。(SDS的所有API都会以处理二进制的方式来处理 SDS 存放在 buf[] 数组里的数据)SDS 获取字符串长度的时间复杂度是 O(1)
。(len 属性记录了字符串长度)Redis 的 SDS API 是安全的,拼接字符串不会造成缓冲区溢出
。(SDS 在拼接字符串之前会检查 SDS 空间是否满足要求)
-
字符串对象的内部编码(encoding)有 3 种 :
int、raw和 embstr。
- 字符串对象保存的是
整数值
,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成 long),并将字符串对象的编码设置为int
。
- 字符串对象保存的是
一个字符串
,并且这个字符申的长度小于等于 32 字节
,字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串,并将对象的编码设置为embstr
。
- 字符串对象保存的是
一个字符串
,并且这个字符串的长度大于 32 字节
(redis 2.+版本),那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串,并将对象的编码设置为raw
- 字符串对象保存的是
-
embstr
和raw
的区别embstr
会通过一次内存分配函数来分配一块连续的内存空间来保存redisObject和SDS
,而raw
编码会通过调用两次内存分配函数来分别分配两块空间来保存redisObject和SDS
-
embstr优点
- embstr编码将创建字符串对象所需的内存分配次数从 raw 编码的两次降低为一次;
- 释放 embstr编码的字符串对象同样只需要调用一次内存释放函数;
- embstr编码的字符串对象的所有数据都保存在一块连续的内存里面可以更好的利用 CPU 缓存提升性能。
-
embstr缺点
- 如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,所以
embstr编码的字符串对象实际上是只读的
,当我们对embstr编码的字符串对象执行任何修改命令(例如append)时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令。
- 如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,所以
应用场景
缓存对象
- 使用 String 来缓存对象有两种方式:
- 直接缓存整个对象的 JSON(
SET user:1 '{"name":"xiaolin", "age":18}'
) - 采用将 key 进行分离为 user:ID:属性,采用 MSET 存储,用 MGET 获取各属性值(
MSET user:1:name xiaolin user:1:age 18 user:2:name xiaomei user:2:age 20。
)
- 直接缓存整个对象的 JSON(
常规计数
- 因为 Redis 处理命令是单线程,所以执行命令的过程是原子的。因此 String 数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。
# 初始化文章的阅读量
> SET aritcle:readcount:1001 0
OK
#阅读量+1
> INCR aritcle:readcount:1001
(integer) 1
> GET aritcle:readcount:1001
"1"
分布式锁
- SET 命令有个 NX 参数可以实现「key不存在才插入」,可以用它来实现分布式锁:
- 如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
- 如果 key 存在,则会显示插入失败,可以用来表示加锁失败。
分布式锁加上过期时间
SET lock_key unique_value NX PX 10000
共享 Session 信息
- 借助 Redis 对这些 Session 信息进行统一的存储和管理,这样无论请求发送到那台服务器,服务器都会去同一个 Redis 获取相关的 Session 信息,这样就解决了分布式系统下 Session 存储的问题。
List
- List 列表是简单的字符串列表,按照插入顺序排序,可以从头部或尾部向 List 列表添加元素。
内部实现
- List 类型的底层数据结构是由双向链表或压缩列表实现的:
- 如果列表的元素个数小于 512 个,列表每个元素的值都小于 64 字节。Redis 会使用
压缩列表
作为 List 类型的底层数据结构; - 如果列表的元素不满足上面的条件,Redis 会使用
双向链表
作为 List 类型的底层数据结构;
- 如果列表的元素个数小于 512 个,列表每个元素的值都小于 64 字节。Redis 会使用
在 Redis 3.2 版本之后 List 数据类型底层数据结构就只由 quicklist
实现了,替代了双向链表和压缩列表。
应用场景
消息队列
- 消息队列在存取消息时,必须要满足三个需求,分别是
消息保序、处理重复的消息和保证消息可靠性。
1.消息保存
List 可以使用LPUSH + RPOP
(或者反过来,RPUSH+LPOP)命令实现消息队列。
- 生产者使用 LPUSH key value[value…] 将消息插入到队列的头部,如果 key 不存在则会创建一个空的队列再插入消息。
- 消费者使用 RPOP key 依次读取队列的消息,先进先出。
缺点
- 当有消息送达时,List不会主动通知消费者有消息写入,消费者只能while(1)循环不断检查,有消息则返回结果,无消息返回空继续循环。
解决办法
BRPOP命令也称为阻塞式读取,客户端在没有读到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据
2.处理重复消息
- 每个消息都有一个全局的 ID。
- 消费者要记录已经处理过的消息的 ID。(将已处理的ID和要处理的ID对比)
但是 List 并不会为每个消息生成 ID 号,所以我们需要自行为每个消息生成一个全局唯一ID,生成之后,我们在用 LPUSH 命令把消息插入 List 时,需要在消息中包含这个全局唯一 ID。
> LPUSH mq "111000102:stock:99"
(integer) 1
3.保证消息可靠性
消费者程序在处理消息的过程出现了故障或宕机,就会导致消息没有处理完成,那么,消费者程序再次启动后,就没法再次从 List 中读取消息了。
- List 类型提供了
BRPOPLPUSH
命令,这个命令的作用是让消费者程序从一个 List 中读取消息,同时,Redis 会把这个消息再插入到另一个 List(可以叫作备份 List)留存。
4.List做消息队列缺陷
- List 不支持多个消费者消费同一条消息,因为一旦消费者拉取一条消息后,这条消息就从 List 中删除了,无法被其它消费者再次消费。
- 要实现一条消息可以被多个消费者消费,那么就要将多个消费者组成一个消费组,但是 List 类型并不支持消费组的实现。而新类型
Stream
支持消费组形式的消息读取。
原文来自https://www.xiaolincoding.com/redis/data_struct/command.html#%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF-2