type
status
date
slug
summary
tags
category
icon
password
第一章 数据结构与对象
redis数据库里面的每个key-value pair都是对象组成的
key总是一个string obj
value可以是字符串,list obj, hash obj, set obj, sorted set obj
简单动态字符串
127.0.0.1:6379[15]> set msg "hello world"
OK
127.0.0.1:6379[15]> rpush fruits "apple" "banana"
(integer) 2
key是一个字符串对象
value存的字符串底层是SDS实现的
sds的定义
struct sdshdr {
int len; // 已使用的字节数
int free; // 未使用的字节数
char buf[]; // 字符数组,用于保存字符串
};
与C字符串区别:
SDS 相比传统 C 字符串有以下优点:
- 获取字符串长度的时间复杂度是 O(1),而不是 O(n)
- 杜绝缓冲区溢出
- 减少字符串修改时的内存重分配次数
- 二进制安全,可以存储任何二进制数据
- 兼容部分 C 字符串函数

获取字符串的时间复杂度不同
杜绝溢出


内存重分配是个耗时的操作,
sds利用空间预分配和惰性空间释放两个策略
空间预分配:
当 SDS 字符串需要增长时,Redis 不仅会分配所需的空间,还会额外分配一些预留空间。具体规则如下:
- 当新长度小于 1MB 时:额外分配与当前长度相同的未使用空间
- 例如:如果扩展后字符串长度为 11 字节,则会额外分配 11 字节的空闲空间,总容量为 22 字节
- 当新长度大于等于 1MB 时:额外固定分配 1MB 的未使用空间
- 例如:如果扩展后字符串长度为 5MB,则会额外分配 1MB 的空闲空间,总容量为 6MB
调用sdscat(s,”World”)
等额分配

链表
listNode结构:
typedef struct listNode {
struct listNode *prev; // 前置节点
struct listNode *next; // 后置节点
void *value; // 节点的值
} listNode;
list结构:
typedef struct list {
listNode *head; // 表头节点
listNode *tail; // 表尾节点
unsigned long len; // 链表所包含的节点数量
void *(*dup)(void *ptr);// 节点值复制函数
void (*free)(void *ptr);// 节点值释放函数
int (*match)(void *ptr, void *key);// 节点值比较函数
} list;
Redis 链表的特性
- 双向链表:每个节点都有指向前后节点的指针,支持双向遍历
- 无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,链表不会形成环
- 带链表长度计数器:通过
len
属性可以 O(1) 时间复杂度获取链表节点数量
- 多态:链表节点的 value 指针可以指向不同类型的值,通过 dup、free 和 match 函数可以针对不同类型的值进行适配

字典

key 里面又有key-value 就是字典

哈希算法与索引计算
当要将一个新的键值对添加到字典中时,Redis 会执行以下步骤:
- 使用哈希函数计算键的哈希值
- 通过哈希值与 sizemask 进行位运算(按位与,&)得到索引值
- 根据索引值将包含键值对的节点放入哈希表数组的指定位置
// 计算哈希值与索引的伪代码
hash = dict->type->hashFunction(key);
index = hash & dict->ht[0].sizemask;
哈希冲突解决
Redis 使用链地址法(separate chaining)来解决哈希冲突。当两个或更多键被分配到哈希表数组的同一个索引上时,Redis 会将这些键值对以链表的形式连接起来。

渐进式rehash
当哈希表需要扩展或收缩时,Redis 不会一次性地将所有键值对从旧表迁移到新表,而是分多次、渐进式地完成。这样可以避免大字典的 rehash 操作阻塞服务器。

阶段 1:开始前的状态
如上图所示,在 rehash 开始前:
- dict 结构体中的 rehashidx 字段为 -1,表示没有在进行 rehash
- 字典只使用 ht[0] 哈希表,ht[1] 为空
- 假设当前有 3 个键值对存储在 ht[0] 中,ht[0].size = 4,负载因子为 0.75
阶段 2:rehash 开始
当负载因子超过阈值,rehash 开始:
- 为 ht[1] 分配空间:
- 扩容情况下,ht[1].size = 第一个 >= ht[0].used * 2 的 2^n (保证为 2 的幂)
- 缩容情况下,ht[1].size = 第一个 >= ht[0].used 的 2^n
- 在我们的例子中,ht[1].size = 8(因为 3 * 2 = 6,而 8 是大于 6 的最小 2 的幂)
- 设置 rehashidx = 0:
- 将 dict 的 rehashidx 设置为 0,标志着 rehash 正式开始
- rehashidx 表示当前正在迁移 ht[0] 中的哪个索引下的链表
阶段 3:渐进式迁移
在 rehash 进行期间,每次字典操作(如增删改查)都会顺带迁移一部分键值对:
- 迁移过程:
- 对 ht[0].table[rehashidx] 索引上的所有键值对,使用 ht[1] 的大小和掩码重新计算索引值
- 将这些键值对放到 ht[1] 的对应位置
- 清空 ht[0].table[rehashidx]
- rehashidx++ (将 rehashidx 加 1,表示处理下一个索引)
- 图中可视化:
- 在阶段 3 的图示中,rehashidx = 2,表示索引 0 和 1 已经完成迁移
- "name" 和 "score" 键值对已经被迁移到了 ht[1] 中
- "age" 键值对位于 ht[0] 的索引 2 上,还未被迁移
- 绿色虚线框表示已经迁移的索引,红色虚线框表示当前正在迁移的索引
- 查找过程:
- 在 rehash 期间,查找一个键时,会先在 ht[0] 中查找,如果没找到再到 ht[1] 中查找
- 这确保了在 rehash 过程中能够正确访问所有的键值对
- 新增键值对:
- 在 rehash 期间,新增的键值对会直接添加到 ht[1] 中
阶段 4:rehash 完成
当 ht[0] 中的所有键值对都迁移到 ht[1] 后:
- 释放 ht[0] 的内存空间
- 将 ht[1] 设置为 ht[0],创建新的空 ht[1]
- 将 rehashidx 重置为 -1,表示 rehash 完成
- 分摊时间复杂度:
- 避免了一次性迁移大量键值对造成的服务阻塞
- 将 O(n) 的操作分散到多次操作中进行,每次只处理一小部分
- 定时任务辅助迁移:
- 除了正常操作触发的迁移外,Redis 还会在定时任务中主动执行一定量的 rehash 工作
- 这确保了即使字典没有新的操作,rehash 也能逐步完成
- 双表并存期间的空间开销:
- 在 rehash 期间,需要同时维护两个哈希表,会占用更多内存
- 这是用空间换时间的一种策略

跳跃表
用途:
- 有序集合(Sorted Set):跳表是Redis有序集合的底层实现之一,另一种是压缩列表(ziplist)。
- 当有序集合的元素数量较少且元素值较小时,Redis使用压缩列表
- 当元素数量增长或出现较大元素时,Redis会将其转换为跳表实现
- 排行榜功能:利用有序集合可以实现游戏排行榜、积分排名等功能。
- 范围查询:跳表特别适合范围查询操作,如ZRANGEBYSCORE命令



整数集合
有序的整数集合
127.0.0.1:6379[15]> sadd numbers 1 3 5 7 9
(integer) 5
127.0.0.1:6379[15]> object encoding numbers
"intset"
结构:
typedef struct intset {
uint32_t encoding; // 编码方式
uint32_t length; // 集合包含的元素数量
int8_t contents[]; // 保存元素的数组(柔性数组成员)
} intset;

由encoding决定编码方式
升级
如果新元素添加到集合里,这个新元素比里面的都要长,那就要先升级
升级过程包括以下步骤:
- 根据新编码方式,扩展整数集合底层数组的空间
- 将现有的所有元素转换为新编码方式,并放到正确的位置上
- 将新元素添加到整数集合中


压缩列表
压缩列表(ziplist)是 Redis 中一种为节约内存而设计的特殊数据结构,主要用于存储少量数据项的列表和哈希表。
127.0.0.1:6379[15]> hmset profile "jacl" 28
OK
127.0.0.1:6379[15]> object encoding profile
"ziplist"

节点结构:


连锁更新

压缩列表的特性与优缺点
优点
- 内存效率高:通过特殊编码存储,节省了大量指针开销,并针对小整数值做了特殊优化
- 存取效率高:数据存储在连续内存空间中,有利于CPU缓存
- 实现简单:相比于其他复杂数据结构,实现较为简单
缺点
- 连锁更新问题:当一个节点的长度变化导致其prevlen字段编码方式改变时,可能会引发"连锁更新",最坏情况下的时间复杂度为O(N²)
- 不适合大量元素:随着元素增多,操作效率会降低
- 整体复制开销:任何修改操作可能需要重新分配内存并复制整个列表
对象
Redis 实现了一个对象系统,它在数据类型的底层实现和高层数据类型命令之间充当了中间层
typedef struct redisObject {
unsigned type:4; // 类型
unsigned encoding:4; // 编码
unsigned lru:LRU_BITS; // LRU时间(相对于全局lru_clock)或LFU数据
int refcount; // 引用计数
void *ptr; // 指向实际实现数据结构的指针
} robj;
Redis 支持五种主要的对象类型:
REDIS_STRING
: 字符串对象
REDIS_LIST
: 列表对象
REDIS_HASH
: 哈希对象
REDIS_SET
: 集合对象
REDIS_ZSET
: 有序集合对象
对象编码 (encoding)
每种对象类型可以由不同的底层数据结构实现,这些实现方式被称为对象的编码:
- 字符串对象:int、embstr、raw
- 列表对象:ziplist、linkedlist(3.2版本后改为quicklist)
- 哈希对象:ziplist、hashtable
- 集合对象:intset、hashtable
- 有序集合对象:ziplist、skiplist
Redis 使用引用计数来管理内存。每个对象都有一个引用计数,当计数变为0时,该对象会被释放。
这种机制支持对象共享——当创建一个新对象时,Redis 会尝试查找是否已经存在一个完全相同的对象,如果存在,则增加该对象的引用计数而不是创建新对象。
对象共享:

lru:
这个字段记录了对象最后一次被访问的时间(LRU模式)或者访问频率信息(LFU模式)。Redis 使用这个信息来实现内存回收策略,当内存超出限制时淘汰最近最少使用的对象。
对象系统的好处
- 节省内存:针对不同的使用场景选择最高效的数据结构实现
- 对象共享:通过引用计数实现对象共享,进一步节约内存
- 类型检查:确保对不同类型的键执行正确的命令
- 内存回收:通过引用计数实现内存回收机制
- 内存淘汰:通过LRU/LFU机制实现内存淘汰策略

第二章 redis实现单机数据库
数据库
typedef struct redisDb {
dict *dict; // 数据库键空间,保存所有键值对
dict *expires; // 键过期时间字典
dict *blocking_keys; // 处于阻塞状态的键
dict *ready_keys; // 可以解除阻塞的键
dict *watched_keys; // 被WATCH命令监视的键
int id; // 数据库号码
long long avg_ttl; // 数据库平均TTL统计
unsigned long expires_cursor; // 过期遍历的游标
list *defrag_later; // 推迟碎片整理的键列表
} redisDb;
默认16个数据库
select <dbid> 来切换
每个客户端都有一个
redisClient.db
指针,指向当前选择的数据库
数据库隔离,不同数据库的key空间隔离
数据库key空间例子

key的过期机制
127.0.0.1:6379[15]> EXPIRE key 5
(integer) 1
127.0.0.1:6379[15]> get key
"value"
127.0.0.1:6379[15]> get key
(nil)
127.0.0.1:6379[15]> set key value
OK
127.0.0.1:6379[15]> expire key 20
(integer) 1
127.0.0.1:6379[15]> ttl key
(integer) 17
- 过期字典
- 每个 Redis 数据库都有一个过期字典(
expires
) - 过期字典的键是数据库中的键,值是该键的过期时间(UNIX 时间戳)
- 设置过期时间
EXPIRE key seconds
:设置键的过期时间为指定的秒数PEXPIRE key milliseconds
:以毫秒为单位设置过期时间EXPIREAT key timestamp
:设置键过期的时间戳PEXPIREAT key milliseconds-timestamp
:以毫秒为单位设置过期时间戳
- 移除过期时间
PERSIST key
:移除键的过期时间,使其变为永久有效
- 查询过期时间
TTL key
:返回键的剩余生存时间(秒)PTTL key
:以毫秒为单位返回剩余生存时间
过期键清除策略
Redis 使用三种策略来删除过期的键:
- 惰性删除
- 当客户端尝试访问一个键时,Redis 检查它是否已过期
- 如果已过期,则删除该键并返回nil/null
- 这种策略不会主动删除过期键,只在访问时才检查
- 定期删除
- Redis 定期随机选择一些键检查是否过期
- 默认每 100ms 执行一次,随机选择一些数据库,然后从每个选定的数据库中随机检查一部分键
- 这是惰性删除和定时删除的折中方案
- 主从复制中的过期处理
- 从服务器不会主动删除过期键,而是等待主服务器删除
- 当主服务器删除一个过期键时,会向从服务器发送 DEL 命令
- 这确保了主从服务器的数据一致性
RDB 和 AOF 中的过期键处理
- RDB 文件
- 生成 RDB 文件时,Redis 不会保存已过期的键
- 加载 RDB 文件时:
- 如果是主服务器,会忽略已过期的键
- 如果是从服务器,会加载所有键,包括过期键
- AOF 文件
- 当键过期后,如果还没被删除(惰性或定期删除),AOF 文件不会因此产生任何影响
- 当过期键被删除时,Redis 会在 AOF 文件中追加一条 DEL 命令
- AOF 重写时会忽略已过期的键

RDB持久化
手动触发
- SAVE 命令:阻塞 Redis 服务器进程,直到 RDB 文件创建完成
- BGSAVE 命令:派生一个子进程来创建 RDB 文件,不会阻塞服务器进程

如果开启了aof,优先选择aof文件
save执行时,服务器会被阻塞;
bgsave fork了子进程;
rdb文件载入会一直处于阻塞状态
自动触发
Redis 配置文件中的
save
配置项可以设置自动触发 BGSAVE 的条件:其他自动触发情况:
- 执行
FLUSHALL
命令时(可配置)
- 执行主从复制时,如果主节点没有正在执行 BGSAVE,则自动触发
- 正常关闭 Redis 服务器
RDB 文件的结构包含以下部分:
- 文件头:包含 "REDIS" 字符串和 RDB 版本号
- 数据库部分:包含每个数据库中的键值对数据
- EOF 标记:表示文件结束
- 校验和:用于验证文件完整性的数据
RDB 持久化的优缺点
优点
- 文件紧凑:RDB 是一个紧凑的单一文件,非常适合于备份和恢复
- 恢复速度快:与 AOF 相比,RDB 文件恢复速度更快
- 性能影响小:子进程负责持久化工作,对主进程性能影响较小
- 适合灾难恢复:可以将 RDB 文件保存到远程位置用于灾难恢复
缺点
- 数据丢失风险:两次快照之间的数据可能丢失
- fork() 操作开销:在内存较大的情况下,fork() 可能导致服务短暂停顿
- 不适合实时持久化:无法做到像 AOF 那样的秒级或命令级持久化
RDB 文件的恢复
恢复 RDB 文件非常简单:
- 将 RDB 文件放置到 Redis 的工作目录中
- 启动 Redis 服务器
- Redis 会自动检测 RDB 文件并加载其中的数据

AOF
AOF 持久化的核心思想是将服务器执行的写命令以追加的方式记录到 AOF 文件,当 Redis 重启时,重新执行这些命令来恢复数据
AOF 持久化的三个步骤
- 命令追加(append):当 Redis 执行完写命令后,会将该命令追加到 AOF 缓冲区
- 文件写入(write):Redis 将 AOF 缓冲区中的内容写入 AOF 文件
- 文件同步(sync):Redis 调用 fsync() 或 fdatasync() 将 AOF 文件同步到磁盘
AOF 文件使用 Redis 命令请求协议(RESP)格式保存命令,这是一种纯文本格式,可以直接阅读和编辑。
例如,执行
SET key value
命令后,AOF 文件中会追加如下内容:这种格式的好处是:
- 具有可读性
- 与 Redis 通信协议一致,便于解析
- 支持增量追加
AOF 同步策略(fsync 策略)
Redis 提供了三种 AOF 同步策略,可以通过
appendfsync
配置项设置:- always:每次写命令执行完后都立即进行同步
- 最安全,最多只会丢失一个命令的数据
- 性能最差
- everysec(默认):每秒进行一次同步
- 在安全性和性能之间取得平衡
- 最多可能丢失 1 秒钟的数据
- no:由操作系统决定何时同步
- 性能最好
- 安全性最差,可能丢失较多数据
AOF文件的载入和数据还原:

AOF 重写的原理
AOF 重写不会读取和分析现有的 AOF 文件,而是通过读取服务器当前的数据库状态来实现的:
- Redis 创建一个新的 AOF 文件
- 遍历数据库中的所有键值对
- 用一条命令记录键的当前值,而不是之前的多条命令
- 用新的 AOF 文件替换旧的 AOF 文件
例如,如果一个键先后执行了
SET key value1
、SET key value2
、SET key value3
,重写后的 AOF 文件只会包含 SET key value3
。AOF 重写的实现
为了不阻塞主进程,AOF 重写是通过子进程来完成的:
- Redis 创建一个子进程用于 AOF 重写
- 子进程创建一个新的 AOF 文件,并写入当前数据库状态
- 主进程继续处理命令请求,并将这些命令追加到现有 AOF 文件
- 主进程同时将这些新命令存入 AOF 重写缓冲区
- 子进程完成重写后,主进程将 AOF 重写缓冲区中的内容追加到新 AOF 文件
- 主进程用新的 AOF 文件替换旧的 AOF 文件
AOF 持久化的优缺点
优点
- 数据安全性高:根据同步策略的不同,最多只丢失 1 秒钟的数据
- 适合实时性要求高的应用:可以做到秒级或命令级持久化
- 文件具有可读性:AOF 文件是纯文本格式,可以直接查看和编辑
- 自动重写机制:避免 AOF 文件过大
缺点
- 文件体积大:通常比 RDB 文件大得多
- 恢复速度慢:需要重新执行所有命令,相比 RDB 恢复较慢
- 对性能有一定影响:尤其是使用 always 同步策略时
aof-use-rdb-preamble yes|no
:是否在 AOF 文件开头使用 RDB 格式(混合持久化)
事件
Redis 的事件处理系统主要处理两类事件:
- 文件事件 (File Events):处理 Redis 服务器与客户端的网络通信
- 时间事件 (Time Events):处理需要在特定时间执行的任务,如定期清理过期键
文件事件


Redis 的文件事件处理器由四个组件组成:
- 套接字 (Socket):客户端与服务器之间的连接
- I/O 多路复用程序 (I/O Multiplexing):监听多个套接字的状态变化
- 文件事件分派器 (Event Dispatcher):接收 I/O 多路复用程序传来的套接字事件,并根据事件类型调用相应的事件处理器
- 事件处理器 (Event Handlers):处理具体事件的函数
常见的文件事件处理器包括:
- 连接应答处理器:处理客户端连接请求
- 命令请求处理器:处理客户端发送的命令请求
- 命令回复处理器:将命令执行结果返回给客户端
- 复制处理器:处理主从复制相关的网络事件
过程:
- I/O 多路复用程序监听套接字状态变化
- 当套接字变为可读或可写时,产生相应的文件事件
- 文件事件分派器接收事件并调用相应的事件处理器
- 事件处理器处理事件,如接受连接、读取命令、返回结果等
时间事件
- 定时事件:在指定时间后执行一次,然后被删除
- 周期性事件:按照一定周期重复执行
实际上,Redis 主要使用周期性事件,定时事件很少被使用。
主要的时间事件:
- serverCron:Redis 服务器的周期性函数,执行以下操作:
- 更新服务器统计信息
- 清理过期键值对
- 执行客户端超时检测
- 执行数据库大小调整
- 执行 AOF/RDB 持久化操作
- 如果是主服务器,对从服务器进行定期同步
- 如果是集群模式,执行集群操作
默认情况下,serverCron 每 100 毫秒执行一次


客户端
常见的客户端redis-cli
内部实现:
typedef struct client {
int fd; // 客户端套接字描述符
robj *name; // 客户端名字
int flags; // 客户端状态标志
sds querybuf; // 查询缓冲区
size_t querybuf_peak; // 查询缓冲区峰值
int argc; // 参数个数
robj **argv; // 参数数组
struct redisCommand *cmd; // 命令指针
list *reply; // 回复链表
unsigned long long reply_bytes; // 回复链表中的字节数
size_t sentlen; // 已发送字节数
time_t ctime; // 客户端创建时间
time_t lastinteraction; // 最后一次交互时间
// 其他字段...
} client;
输入缓冲区:
客户端的输入缓冲区用于存储客户端发送的命令。Redis 服务器通过读取这个缓冲区来获取并执行命令。
输入缓冲区特点:
- 没有大小限制,但会在超过 1GB 时关闭客户端连接
- 缓冲区过大可能导致内存占用过多
输出缓冲区:
输出缓冲区用于存储服务器返回给客户端的数据。Redis 使用了两种输出缓冲区:
- 固定大小缓冲区:用于小型回复,通常是一些状态回复
- 可变大小缓冲区:用于较大的回复,如大型列表、集合等
输出缓冲区可以通过
client-output-buffer-limit
配置项来限制大小,防止单个客户端占用过多内存。管道化(Pipelining)
管道化允许客户端一次性发送多个命令,而不必等待每个命令的响应:
事务(Transactions)
Redis 事务允许客户端一次性执行多个命令:
事务的特点:
- 命令在 EXEC 后一次性执行
- 执行过程中不会被其他客户端打断
- 没有回滚机制,即使有命令失败
服务端
服务器初始化与启动
Redis 服务器的启动过程大致包括以下步骤:
- 初始化服务器配置:设置默认配置,然后加载配置文件
- 初始化数据结构:创建命令表、客户端链表、数据库等
- 创建事件循环:初始化事件处理机制
- 创建监听套接字:绑定端口,准备接受客户端连接
- 初始化数据库:创建指定数量的数据库实例
- 加载持久化数据:如果有 AOF 或 RDB 文件,从中恢复数据
- 初始化后台任务:设置定时任务,如清理过期键、持久化等
- 进入事件循环:开始处理网络事件和时间事件
当客户端发送命令到 Redis 服务器时,服务器的处理流程如下:
- 接收请求:服务器接收客户端发送的命令请求
- 解析命令:将命令请求转换为命令对象
- 查找命令:在命令表中查找命令对应的实现函数
- 执行预备操作:权限检查、内存检查等
- 调用命令处理函数:执行具体命令
- 执行后续操作:更新统计信息、记录慢查询等
- 返回结果:将命令执行结果返回给客户端
第三章 多机数据库的实现
第四章 独立功能的一些实现
常用的操作
好的,Redis 是一个非常流行的键值(Key-Value)存储系统,它支持多种数据结构。以下是 Redis 最常见的五种基本数据类型及其常用的命令行操作(使用 redis-cli):
1. String (字符串)
- 描述: 最基本的数据类型,一个 Key 对应一个 Value。Value 不仅可以是字符串,也可以是数字(Redis 可以对其进行原子性的增加/减少操作)。最大能存储 512MB。
- 常见用途: 缓存、计数器、分布式锁、存储 Session 信息等。
- 常用命令:
- SET key value [EX seconds | PX milliseconds | KEEPTTL] [NX | XX]:设置指定 key 的值。
- EX seconds: 设置过期时间(秒)。
- PX milliseconds: 设置过期时间(毫秒)。
- NX: 只在 key 不存在时设置。
- XX: 只在 key 存在时设置。
- KEEPTTL: 保留 key 原有的 TTL。
- 示例: SET mykey "hello"
- 示例 (带过期时间): SET counter 10 EX 60
- GET key: 获取指定 key 的值。
- 示例: GET mykey
- GETSET key value: 设置 key 的新值,并返回旧值。
- 示例: GETSET mykey "world"
- MSET key value [key value ...]: 同时设置一个或多个 key-value 对。
- 示例: MSET key1 "v1" key2 "v2"
- MGET key [key ...]: 获取一个或多个 key 的值。
- 示例: MGET key1 key2 non_existing_key
- INCR key: 将 key 中储存的数字值增一(如果 key 不存在,则初始化为 0 再执行 INCR)。
- 示例: INCR page_views
- DECR key: 将 key 中储存的数字值减一。
- 示例: DECR items_left
- INCRBY key increment: 将 key 所储存的值加上指定的增量值。
- 示例: INCRBY user_score 10
- DECRBY key decrement: 将 key 所储存的值减去指定的减量值。
- 示例: DECRBY user_score 5
- STRLEN key: 返回 key 所储存的字符串值的长度。
- 示例: STRLEN mykey
- APPEND key value: 如果 key 已经存在并且是一个字符串,将 value 追加到 key 原来的值的末尾。
- 示例: APPEND mykey "!!!"
2. List (列表)
- 描述: 简单的字符串列表,按照插入顺序排序。可以在列表的头部(Lef)或尾部(Righ)添加元素。底层实际是个双向链表或压缩列表。
- 常见用途: 消息队列、栈、最新 N 个数据(如用户最近访问的文章)。
- 常用命令:
- LPUSH key element [element ...]: 将一个或多个值插入到列表头部。
- 示例: LPUSH mylist "world" "hello" (列表内容: "hello", "world")
- RPUSH key element [element ...]: 将一个或多个值插入到列表尾部。
- 示例: RPUSH mylist "!" (列表内容: "hello", "world", "!")
- LPOP key [count]: 移除并获取列表的第一个元素(可指定数量)。
- 示例: LPOP mylist (返回 "hello")
- RPOP key [count]: 移除并获取列表的最后一个元素(可指定数量)。
- 示例: RPOP mylist (返回 "!")
- LLEN key: 获取列表的长度。
- 示例: LLEN mylist
- LRANGE key start stop: 获取列表指定范围内的元素(0 是第一个,-1 是最后一个)。
- 示例: LRANGE mylist 0 -1 (获取所有元素)
- 示例: LRANGE mylist 0 1 (获取前两个元素)
- LINDEX key index: 通过索引获取列表中的元素。
- 示例: LINDEX mylist 0 (获取第一个元素)
- LSET key index element: 通过索引设置列表元素的值。
- 示例: LSET mylist 0 "new_hello"
- LREM key count element: 根据参数 count 的值,移除列表中与参数 element 相等的元素。
- count > 0: 从表头开始向表尾搜索,移除与 element 相等的元素,数量为 count。
- count < 0: 从表尾开始向表头搜索,移除与 element 相等的元素,数量为 -count。
- count = 0: 移除表中所有与 element 相等的值。
- 示例: LREM mylist 1 "world"
- LTRIM key start stop: 对一个列表进行修剪,只保留指定区间内的元素。
- 示例: LTRIM mylist 0 99 (保留最新的 100 个元素)
- BLPOP key [key ...] timeout: 阻塞式列表的弹出原语,从列表头部弹出。如果列表为空,会阻塞连接直到等待超时或发现可弹出元素为止。
- BRPOP key [key ...] timeout: 阻塞式列表的弹出原语,从列表尾部弹出。
3. Hash (哈希/字典)
- 描述: 一个键值(key-value)对集合,其中 key 是唯一的。非常适合用于存储对象。
- 常见用途: 存储用户信息(如用户ID为Key,用户信息字段如name, age, email为field-value)、存储对象。
- 常用命令:
- HSET key field value [field value ...]: 将哈希表 key 中的字段 field 的值设为 value (如果字段不存在,则创建)。
- 示例: HSET user:1000 name "Alice" age 30 email "[email protected]"
- HGET key field: 获取存储在哈希表中指定字段的值。
- 示例: HGET user:1000 name
- HMGET key field [field ...]: 获取所有给定字段的值。
- 示例: HMGET user:1000 name age non_existing_field
- HGETALL key: 获取在哈希表中指定 key 的所有字段和值。
- 示例: HGETALL user:1000
- HDEL key field [field ...]: 删除一个或多个哈希表字段。
- 示例: HDEL user:1000 email
- HLEN key: 获取哈希表中字段的数量。
- 示例: HLEN user:1000
- HEXISTS key field: 查看哈希表的指定字段是否存在。
- 示例: HEXISTS user:1000 age
- HKEYS key: 获取哈希表中的所有字段名(keys)。
- 示例: HKEYS user:1000
- HVALS key: 获取哈希表中的所有值(values)。
- 示例: HVALS user:1000
- HINCRBY key field increment: 为哈希表 key 中的指定字段的整数值加上增量 increment。
- 示例: HINCRBY user:1000 age 1
4. Set (集合)
- 描述: String 类型的无序集合。集合成员是唯一的,不允许重复。
- 常见用途: 标签(tagging)、共同好友、独立 IP 访问统计、判断用户是否点赞/收藏。
- 常用命令:
- SADD key member [member ...]: 向集合添加一个或多个成员。
- 示例: SADD myset "a" "b" "c" "a" (最终集合为 "a", "b", "c")
- SMEMBERS key: 返回集合中的所有成员。
- 示例: SMEMBERS myset
- SISMEMBER key member: 判断 member 元素是否是集合 key 的成员。
- 示例: SISMEMBER myset "b"
- SCARD key: 获取集合的成员数(基数)。
- 示例: SCARD myset
- SREM key member [member ...]: 移除集合中一个或多个成员。
- 示例: SREM myset "c"
- SPOP key [count]: 移除并返回集合中的一个或多个随机元素。
- 示例: SPOP myset
- SRANDMEMBER key [count]: 返回集合中一个或多个随机元素(不移除)。
- 示例: SRANDMEMBER myset 2
- SINTER key [key ...]: 返回给定所有集合的交集。
- 示例: SADD set1 "a" "b" "c", SADD set2 "c" "d" "e", SINTER set1 set2 (返回 "c")
- SUNION key [key ...]: 返回给定所有集合的并集。
- 示例: SUNION set1 set2 (返回 "a", "b", "c", "d", "e")
- SDIFF key [key ...]: 返回第一个集合与其他集合之间的差集。
- 示例: SDIFF set1 set2 (返回 "a", "b")
5. Sorted Set (ZSet / 有序集合)
- 描述: 和 Set 一样也是 String 类型的元素的集合, 且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数(score)。Redis 正是通过分数来为集合中的成员进行从小到大排序。成员是唯一的, 但分数(score)可以重复。
- 常见用途: 排行榜、带权重的消息队列、范围查找(如查找积分在某个范围的用户)。
- 常用命令:
- ZADD key [NX|XX] [CH] [INCR] score member [score member ...]: 向有序集合添加一个或多个成员,或者更新已存在成员的分数。
- 示例: ZADD leaderboard 100 "player1" 95 "player2" 110 "player3"
- ZRANGE key start stop [WITHSCORES]: 通过索引区间返回有序集合成指定区间内的成员(按分数从小到大)。
- 示例: ZRANGE leaderboard 0 -1 (获取所有成员,按分升序)
- 示例: ZRANGE leaderboard 0 -1 WITHSCORES (获取所有成员及分数)
- ZREVRANGE key start stop [WITHSCORES]: 返回有序集中指定区间内的成员,通过索引,分数从高到底。
- 示例: ZREVRANGE leaderboard 0 2 WITHSCORES (获取排名前三的玩家及其分数)
- ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]: 通过分数区间返回有序集合的成员(默认从小到大)。
- min 和 max 可以是 -inf 和 +inf。
- 使用 ( 表示不包含边界,例如 (90 100 表示分数大于90小于100。
- 示例: ZRANGEBYSCORE leaderboard 90 100 WITHSCORES
- 示例: ZRANGEBYSCORE leaderboard (90 +inf LIMIT 0 10 WITHSCORES (获取分数大于90的前10名)
- ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]: 返回有序集合中指定分数区间的成员,分数从高到低排序。
- ZCARD key: 获取有序集合的成员数。
- 示例: ZCARD leaderboard
- ZSCORE key member: 返回有序集中,成员的分数值。
- 示例: ZSCORE leaderboard "player1"
- ZREM key member [member ...]: 移除有序集合中的一个或多个成员。
- 示例: ZREM leaderboard "player2"
- ZCOUNT key min max: 计算在有序集合中指定区间分数的成员数。
- 示例: ZCOUNT leaderboard 90 100
- ZINCRBY key increment member: 对有序集合中指定成员的分数加上增量 increment。
- 示例: ZINCRBY leaderboard 5 "player1"
- ZRANK key member: 返回有序集合中指定成员的排名(按分数从小到大,排名从 0 开始)。
- 示例: ZRANK leaderboard "player1"
- ZREVRANK key member: 返回有序集合中指定成员的排名(按分数从大到小,排名从 0 开始)。
- 示例: ZREVRANK leaderboard "player1"
通用 Key 操作命令:
- DEL key [key ...]: 删除一个或多个 key。
- 示例: DEL mykey mylist user:1000
- EXISTS key [key ...]: 检查给定 key 是否存在。
- 示例: EXISTS mykey
- KEYS pattern: 查找所有符合给定模式 pattern 的 key (生产环境慎用,可能阻塞)。
- 示例: KEYS user:*
- TYPE key: 返回 key 所储存的值的类型。
- 示例: TYPE mykey (可能返回 "string", "list", "hash", "set", "zset")
- EXPIRE key seconds: 为给定 key 设置生存时间(秒)。
- 示例: EXPIRE mykey 3600 (1小时后过期)
- TTL key: 以秒为单位,返回给定 key 的剩余生存时间。
- 示例: TTL mykey (返回 -1 表示永不过期,-2 表示 key 不存在)
- PERSIST key: 移除给定 key 的生存时间,将这个 key 从“易失的”(带生存时间 key )转换成“持久的”(一个不带生存时间、永不过期的 key )。
- 示例: PERSIST mykey
- RENAME key newkey: 修改 key 的名称。
- 示例: RENAME mykey my_new_key
- FLUSHDB: 清空当前数据库中的所有 key (慎用!)
- FLUSHALL: 清空所有数据库中的所有 key (极度慎用!)
这些是 Redis 最核心的数据类型和常用命令,掌握它们就能应对大部分常见的 Redis 应用场景。你可以通过 redis-cli 连接到 Redis 服务器,然后直接输入这些命令进行尝试。输入 HELP @<datatype> (例如 HELP @string) 可以获取该数据类型相关的命令帮助。