Redis基础
约 6033 字大约 20 分钟
2025-01-15
概述
Redis 简介
Redis(Remote Dictionary Server) 是一个开源的内存存储系统,常用于构建高性能、高可扩展性的应用程序。它支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等,并提供了丰富的操作命令,使开发人员能够快速、灵活地处理数据。
Redis 是完全开源的,遵守 BSD 协议,是一个基于内存,高性能的 key-value 数据库。
区分其他 key-value 存储
- 内存存储:Redis 是基于内存的存储系统,数据存储在内存中,因此读写速度非常快。相比之下,传统的关系型数据库通常将数据存储在磁盘上,读写速度较慢。
- 持久化:Redis 支持多种持久化方式,如 RDB 持久化和 AOF 持久化,可以将数据定期保存到磁盘中,以防止数据丢失。而传统的关系型数据库通常具有 ACID 特性,保证数据的一致性和持久性。
- 数据结构:除了简单的 key-value 存储外,Redis 还支持多种复杂的数据结构,如列表、集合、有序集合和哈希表等。这些数据结构可以用于更灵活地存储和处理数据,适用于不同的应用场景。
- 缓存:Redis 常被用作缓存服务器,用于加速对数据库或其他后端存储系统的访问。通过缓存热点数据,可以减少对后端存储的查询次数,提高系统性能。
Windows 环境

当前文件夹,进入 cmd 命令行窗口,在命令行输入以下命令
这样就启动了 redis 服务
redis-server.exe redis.windows.conf关闭 redis 的快捷键是 ctrl + c

在确保 redis 启动的条件下,还是在当前文件夹,进入 cmd 窗口,输入以下命令,进入 redis 客户端,使用 ping 命令验证
redis-cli.exe
修改配置,修改密码

打开该文件:注意,requirepass 前面不能有空格,否则会报错

重新进入客户端,搭配-a 加上密码进入客户端

Linux 环境
- 安装命令
# wget http://download.redis.io/releases/redis-6.0.8.tar.gz
# tar -xzvf redis-6.0.8.tar.gz
# cd redis-6.0.8
# make执行完 make 命令后,redis-6.0.8 的 src 目录下会出现编译后的 redis 服务程序 redis-server,还有用于测试的客户端程序 redis-cli:
- 启动 Redis
# cd src
# ./redis-server注意这种方式启动 redis 使用的是默认配置。也可以通过启动参数告诉 redis 使用指定配置文件使用下面命令启动。
- 进入 redis 客户端
启动 redis 服务进程后,就可以使用测试客户端程序 redis-cli 和 redis 服务交互了。 比如:
# cd src
# ./redis-cli
redis> set a bb
OK
redis> get a
"bb"配置修改
可以通过 redis.conf 文件修改配置
| 配置项 | 说明 |
|---|---|
| daemonize no | Redis 默认不是以守护进程的方式运行,可以通过该配置项修改,使用 yes 启用守护进程(Windows 不支持守护线程的配置为 no ) |
| port 6379 | 指定 Redis 监听端口,默认端口为 6379 |
| bind 127.0.0.1 | 绑定的主机地址 |
| requirepass foobared | 设置 Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 AUTH 密码 命令提供密码,默认关闭 |
| appendonly no | 指定是否在每次更新操作后进行日志记录,Redis 在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。 |
# 默认端口6379
port 6379
# 绑定ip,如果是内网可以直接绑定 127.0.0.1, 或者忽略, 0.0.0.0是外网
bind 0.0.0.0
# 守护进程启动
daemonize yes
# 密码 abcd123
requirepass abcd123数据类型
基本数据类型
Redis 支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及 zset(sorted set:有序集合)
- 字符串(String):最基本的数据类型,可以存储任意二进制数据,以及普通的文本字符串。
- 哈希(Hash):包含键值对的无序散列,适合保存对象信息,每个键值对存储了字段和值之间的映射关系。
- 列表(List):按照插入顺序排序的字符串元素列表,支持在列表的两端进行插入、删除、获取等操作,可实现栈和队列的功能。
- 集合(Set):无序且不可重复的字符串元素集合,支持集合的交集、并集、差集等操作,以及添加、删除、随机获取元素。
- 有序集合(Sorted Set):与集合类似,但每个元素都关联着一个分数(score)用于排序,可以按分数范围或成员值获取元素。
注意:Redis 的数据类型,是指value 的数据类型,而 Key 普遍都是字符串类型
字符串
特点:
- string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。
- string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象。
- string 类型的值最大能存储 512MB。
用途:字符串常用于缓存、计数器、分布式锁等场景。可以用于存储简单的键值对数据,或者用作复杂的数据结构的序列化存储。
实战:将字符串用于存储较大的值时,要注意数据大小的限制。合理利用字符串操作函数,如使用 incr 自增计数器,或使用 append 追加字符串值。
举例
redis 127.0.0.1:6379> SET aaaa "菜鸟教程" OK redis 127.0.0.1:6379> GET aaaa "菜鸟教程"
哈希
特性:哈希类型是一个键值对的无序散列表,适合存储对象的多个属性。哈希可以嵌套多层,支持单独的字段的读取、更新和删除操作
用途:哈希常用于存储和操作对象的属性,如用户信息、文章信息等。可以减少键名的冗余,提高数据结构的可读性和可维护性
实战:使用哈希可以更好地组织复杂的数据结构,充分利用字段级别的读取和更新操作。可以使用 HSET 和 HMSET 分别设置单个和多个字段的值
举例
使用了 Redis HMSET, HGET 命令,HMSET 设置了两个 field=>value 对, HGET 获取对应 field 对应的 value。
redis 127.0.0.1:6379> HMSET aa field1 "Hello" field2 "World" "OK" redis 127.0.0.1:6379> HGET aa field1 "Hello" redis 127.0.0.1:6379> HGET aa field2 "World"
列表
特性:列表是一个按照插入顺序排序的字符串元素集合,支持在列表的两端(头部为左边,尾部为右边)进行插入、删除和获取元素。元素可以重复,列表的长度可以动态变化。
用途:列表常用于实现消息队列、任务队列、操作日志、聊天消息记录等。可以实现先进先出(FIFO)或后进先出(LIFO)的数据结构。
实战:使用列表操作函数,如 LPUSH、RPUSH、LPOP、RPOP 可以方便地操作列表。在读取大型列表时,要注意时间复杂度和性能问题。
举例:
redis 127.0.0.1:6379> lpush aa redis (integer) 1 redis 127.0.0.1:6379> lpush aa mongodb (integer) 2 redis 127.0.0.1:6379> lpush aa rabbitmq (integer) 3 redis 127.0.0.1:6379> lrange aa 0 10 1) "rabbitmq" 2) "mongodb" 3) "redis" redis 127.0.0.1:6379>
集合
特性:集合是一个无序且不可重复的字符串元素集合,支持集合的交集、并集、差集等操作,以及添加、删除、随机获取元素等。
用途:集合常用于去重、关注列表、标签、好友列表等场景。由于集合元素是无序的,可以快速进行成员检测和集合操作。
最佳实战:使用集合的交集、并集、差集操作可以方便地进行数据筛选和计算。使用 SADD、SREM 进行成员添加和删除。
举例:
redis 127.0.0.1:6379> sadd runoob redis (integer) 1 redis 127.0.0.1:6379> sadd runoob mongodb (integer) 1 redis 127.0.0.1:6379> sadd runoob rabbitmq (integer) 1 redis 127.0.0.1:6379> sadd runoob rabbitmq (integer) 0 redis 127.0.0.1:6379> smembers runoob 1) "redis" 2) "rabbitmq" 3) "mongodb"
有序集合
特性:有序集合是一个与每个成员关联的浮点数分数(score),可用于对成员进行排序。支持按照分数范围或成员值获取元素。
用途:有序集合常用于排行榜、计分系统、优先级队列等场景。可以按照分数进行排名和排序。
最佳实战:利用有序集合的分数,可以快速进行成员的增删改查操作。使用 ZADD、ZREM 进行成员的添加和删除,使用 ZRANGE 和 ZREVRANGE 进行范围查询。
举例:
redis 127.0.0.1:6379> zadd runoob 0 redis (integer) 1 redis 127.0.0.1:6379> zadd runoob 0 mongodb (integer) 1 redis 127.0.0.1:6379> zadd runoob 0 rabbitmq (integer) 1 redis 127.0.0.1:6379> zadd runoob 0 rabbitmq (integer) 0 redis 127.0.0.1:6379> ZRANGEBYSCORE runoob 0 1000 1) "mongodb" 2) "rabbitmq" 3) "redis"
小结
| 类型 | 简介 | 特性 | 场景 |
|---|---|---|---|
| String | 二进制安全 | 可以包含任何数据,比如 jpg 图片或者序列化的对象,一个键最大能存储 512M | --- |
| Hash | 键值对集合,即编程语言中的 Map 类型 | 适合存储对象,并且可以像数据库中 update 一个属性一样只修改某一项属性值(Memcached 中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 |
| List | 链表(双向链表) | 增删快,提供了操作某一段元素的 API | 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列 |
| Set | 哈希表实现,元素不重复 | 1、添加、删除,查找的复杂度都是 O(1) 2、为集合提供了求交集、并集、差集等操作 | 1、共同好友 2、利用唯一性,统计访问网站的所有独立 ip 3、好友推荐时,根据 tag 求交集,大于某个阈值就可以推荐 |
| Sorted Set | 将 Set 中的元素增加一个权重参数 score,元素按 score 有序排列 | 数据插入集合时,已经进行天然排序 | 1、排行榜 2、带权重的消息队列 |
连接 Redis 服务
# 基本语法
# redis-clis
#远程服务连接redis命令
$ redis-cli -h host -p port -a password命令集合
字符串—keys 命令
- SET key value:设置指定 key 的值为给定的 value。
- GET key:获取指定 key 的值。
- DEL key:删除指定 key。
- INCR key:将指定 key 的值增加 1。
- DECR key:将指定 key 的值减少 1。
- APPEND key value:将指定 value 追加到指定 key 的值末尾。
- STRLEN key:返回指定 key 值的字符串长度。
- SETEX key seconds value:设置指定 key 的值,并设置过期时间(秒)。
- SETNX key value:只在指定 key 不存在时,设置 key 的值为给定的 value。
- GETSET key value:设置指定 key 的值,并返回旧值。
- MSET key1 value1 key2 value2 …:设置多个 key-value 对。
- MGET key1 key2 …:获取多个 key 的值。
- STRLEN key:返回指定 key 值的字符串长度。
哈希—hash 命令
- HSET key field value:设置哈希类型中指定 key 的 field 域的值为给定的 value。
- HGET key field:获取哈希类型中指定 key 的 field 域的值。
- HDEL key field1 field2 …:删除哈希类型中指定 key 的一个或多个 field 域。
- HEXISTS key field:检查哈希类型中指定 key 是否存在指定的 field 域。
- HGETALL key:获取哈希类型中指定 key 的所有 field 和 value。
- HKEYS key:获取哈希类型中指定 key 的所有 field。
- HVALS key:获取哈希类型中指定 key 的所有 value。
- HINCRBY key field increment:将哈希类型中指定 key 的 field 域的值增加指定的 increment 值。
- HMSET key field1 value1 field2 value2 …:设置哈希类型中指定 key 的多个 field 和 value。
- HMGET key field1 field2 …:获取哈希类型中指定 key 的多个 field 的值。
- HLEN key:获取哈希类型中指定 key 的 field 数量。
- HSTRLEN key field:获取哈希类型中指定 key 的 field 域的字符串长度。
列表—list 命令
- LPUSH key value1 [value2 …]:将一个或多个值插入到列表的左侧(头部)。
- RPUSH key value1 [value2 …]:将一个或多个值插入到列表的右侧(尾部)。
- LPOP key:获取并删除列表左侧的第一个元素。
- RPOP key:获取并删除列表右侧的第一个元素。
- LLEN key:获取列表的长度。
- LRANGE key start stop:获取指定范围内的元素,包括 start 和 stop,索引从 0 开始。
- LINDEX key index:获取列表中指定索引位置的元素。
- LSET key index value:设置列表中指定索引位置的元素值。
- LREM key count value:从列表中删除指定个数的元素。
- LTRIM key start stop:修剪(按照索引范围)列表,只保留指定范围内的元素。
- RPOPLPUSH source destination:从源列表右侧弹出最后一个元素,并将其插入目标列表的左侧。
- BLPOP key1 [key2 …] timeout:从左侧阻塞地弹出一个或多个列表的元素,如果列表是空的,则会阻塞直到有元素可用或者超过超时时间。
- BRPOP key1 [key2 …] timeout:从右侧阻塞地弹出一个或多个列表的元素,如果列表是空的,则会阻塞直到有元素可用或者超过超时时间。
集合—set 命令
- SADD key member1 [member2 …]:向集合中添加一个或多个成员。
- SMEMBERS key:获取集合中的所有成员。
- SISMEMBER key member:判断指定成员是否存在于集合中。
- SCARD key:获取集合中的成员数量。
- SREM key member1 [member2 …]:移除集合中的一个或多个成员。
- SRANDMEMBER key [count]:从集合中随机获取一个或多个成员。
- SPOP key [count]:随机移除并返回集合中的一个或多个成员。
- SMOVE source destination member:将指定成员从一个集合移动到另一个集合。
- SUNION key1 [key2 …]:获取多个集合的并集。
- SINTER key1 [key2 …]:获取多个集合的交集。
- SDIFF key1 [key2 …]:获取多个集合的差集(属于第一个集合,但不属于其他集合)。
- SDIFFSTORE destination key1 [key2 …]:计算多个集合的差集,并将结果存储在指定的目标集合中。
有序集合—Sorted Set 命令
- ZADD key score1 member1 [score2 member2 …]:向有序集合中添加一个或多个成员,并指定对应的分数。
- ZRANGE key start stop [WITHSCORES]:按照分数从低到高的顺序,获取有序集合中指定范围内的成员。
- ZREVRANGE key start stop [WITHSCORES]:按照分数从高到低的顺序,获取有序集合中指定范围内的成员。
- ZRANK key member:获取有序集合中指定成员的排名(按照分数从低到高排序)。
- ZREVRANK key member:获取有序集合中指定成员的排名(按照分数从高到低排序)。
- ZSCORE key member:获取有序集合中指定成员的分数。
- ZCOUNT key min max:获取有序集合中分数范围内的成员数量。
- ZREM key member1 [member2 …]:从有序集合中移除一个或多个成员。
- ZINCRBY key increment member:将有序集合中指定成员的分数增加指定的增量值。
- ZUNIONSTORE destination numkeys key1 [key2 …] [WEIGHTS weight1 [weight2 …]] [AGGREGATE SUM|MIN|MAX]:计算多个有序集合的并集,并将结果存储在指定的目标有序集合中。
- ZINTERSTORE destination numkeys key1 [key2 …] [WEIGHTS weight1 [weight2 …]] [AGGREGATE SUM|MIN|MAX]:计算多个有序集合的交集,并将结果存储在指定的目标有序集合中。
通用操作
- SET key value:设置指定键的值。
- GET key:获取指定键的值。
- DEL key1 [key2 …]:删除一个或多个键。
- EXISTS key:检查指定键是否存在。
- KEYS pattern:查找与指定模式匹配的键集合。
- EXPIRE key seconds:设置键的过期时间,单位为秒。
- TTL key:获取键的剩余过期时间,单位为秒。
- INCR key:将键的值加一。
- DECR key:将键的值减一。
- INCRBY key increment:将键的值增加指定的增量。
- DECRBY key decrement:将键的值减少指定的增量。
- APPEND key value:将指定字符串追加到键的值末尾。
- HSET key field value:将哈希表中指定字段的值设置为指定值。
- HGET key field:获取哈希表中指定字段的值。
- HMSET key field1 value1 [field2 value2 …]:设置哈希表中多个字段的值。
- HMGET key field1 [field2 …]:获取哈希表中多个字段的值。
- HGETALL key:获取哈希表中所有字段和值。
- RPUSHX key value:将值插入到列表的右侧(尾部),仅当列表存在时。
- LPUSHX key value:将值插入到列表的左侧(头部),仅当列表存在时。
- PUBLISH channel message:将消息发布到指定的频道。
高级数据结构
跳表
Redis 的基本数据类型 Zset的底层实现就用到了跳表,跳表的平均复杂度为 O(logN)
Zset 结构体里面维护了两个数据结构:
- 跳表,支持高效的范围查询
- 哈希表:支持高效的单点查询
跳表结构设计
链表在查找元素的时候,因为需要逐一查找,所以查询效率非常低,时间复杂度是 O(N)
于是就出现了跳表。跳表是在链表基础上改进过来的,实现了一种「多层」的有序链表,可以快速定位数据
跳表的粗略样子如下:

图中头节点有 L0~L2 三个头指针,分别指向了不同层级的节点,然后每个层级的节点都通过指针连接起来:
- L0 层级共有 5 个节点,分别是节点 1、2、3、4、5;
- L1 层级共有 3 个节点,分别是节点 2、3、5;
- L2 层级只有 1 个节点,也就是节点 3 。
如果我们要在链表中查找节点 4 这个元素,只能从头开始遍历链表,需要查找 4 次;
而使用了跳表后,只需要查找 2 次就能定位到节点 4,因为可以在头节点直接从 L2 层级跳到节点 3,然后再往前遍历找到节点 4。
引出一个问题:跳表节点是如何实现多层级的?看看 Redis 设计跳表节点的数据结构:
typedef struct zskiplistNode {
// Zset 对象的元素值
sds ele;
// 元素权重值
double score;
// 后退指针
struct zskiplistNode *backward;
// 节点的level数组,保存每层上的前进指针和跨度
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned long span;
} level[];
} zskiplistNode;如上所示,每一个 zset 元素都保存了【元素】、【元素的权重】、【后退指针】、【层级数组】
- 元素:使用 sds 数据类型
- 元素权重:使用 double 类型
- 后向指针:指向了前一个节点,目的是为了方便从跳表的尾节点开始访问节点,高效支持倒序查找
- 层级数组:数组的每一个元素,都保存了元素的前进指针(指向下一个节点)和跨度
什么是跨度?
跨度,实际上代表了这个节点在跳表中的排位
问题:如何计算某个节点的跨度?
从头节点点到该结点的查询路径上,将沿途访问过的所有层的跨度累加起来,得到的结果就是目标节点在跳表中的排位。
各个节点的跨度如下图所示

问题:上图展示了 zskiplistNode 跳表节点,但是哪个跳表节点是头节点,我们并不能看出来,所以下面引出【跳表】结构体
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;跳表 zskiplist 结构体有:
- 跳表的头尾节点,便于在 O(1)时间复杂度内访问跳表的头节点和尾节点;
- 跳表的长度,便于在 O(1)时间复杂度获取跳表节点的数量;
- 跳表的最大层数,便于在 O(1)时间复杂度获取跳表中层高最大的那个节点的层数量;
跳表节点查询过程
查找一个跳表节点的过程时,首先,跳表会从头节点的最高层开始,遍历每一层。
在遍历某一层的跳表节点时,会用跳表节点的 SDS 类型的元素值 和 元素权重 来进行判断,共有两个判断条件:
- 如果当前节点的权重【小于】要查找的权重时,跳表就会访问该层的下一个节点。
- 如果当前节点的权重【等于】要查找的权重时,并且当前节点的 SDS 类型数据「小于」要查找的数据时,跳表就会访问该层的下一个节点。
如果上面两个条件都不满足,或者下一个节点为空时,跳表就会使用目前遍历到的节点的 level 数组里的下一层指针,然后沿着下一层指针继续查找,这就相当于跳到了下一层接着查找。
举例,如图为三层级的跳表

如果要查找「元素:abcd,权重:4」的节点,查找的过程是这样的:
- 先从头节点的最高层开始,L2 指向了「元素:abc,权重:3」节点,这个节点的权重比要查找节点的小,所以要访问该层上的下一个节点;
- 但是该层的下一个节点是空节点( leve[2]指向的是空节点),于是就会跳到「元素:abc,权重:3」节点的下一层去找,也就是 leve[1];
- 「元素:abc,权重:3」节点的 leve[1] 的下一个指针指向了「元素:abcde,权重:4」的节点,然后将其和要查找的节点比较。虽然「元素:abcde,权重:4」的节点的权重和要查找的权重相同,但是当前节点的 SDS 类型数据「大于」要查找的数据,所以会继续跳到「元素:abc,权重:3」节点的下一层去找,也就是 leve[0];
- 「元素:abc,权重:3」节点的 leve[0] 的下一个指针指向了「元素:abcd,权重:4」的节点,该节点正是要查找的节点,查询结束
跳表小结
- 跳表是在基于链表基础建立的,是一种多层的有序链表,时间复杂度为 O(logN)
- 每个跳表节点都包含了节点元素、元素权重、后退指针、以及层级数组(实现链表的多层),其中层级数组包含了在每一层中节点的前进指针、以及跨度(对下一个节点)。跨度代表了节点在跳表中的排位
- 查找跳表中节点时,从第一层开始访问,根据【节点元素权重】和【SDS 类型的元素】进行比较,慢慢的跳到下一层 1
- zset 什么选用跳表当作数据结构,不选用平衡树?
- 支持高效的范围查询:
- 平衡树:找到指定范围的小值之后,还需要以中序遍历继续寻找不超过大值的节点
- 跳表:只需要在找到小值之后,对第 1 层链表进行若干步的遍历就可以实现
- 内存占用小:
- 平衡树每个节点包含 2 个指针(分别指向左右子树),
- 而跳表每个节点包含的指针数目平均为 1/(1-p),具体取决于参数 p 的大小。如果像 Redis 里的实现一样,取 p=1/4,那么平均每个节点包含 1.33 个指针,比平衡树更有优势
- 支持高效的范围查询:
版权所有
版权归属:haipeng-lin