Redis 入门:核心数据类型详解
前言:为什么需要了解 Redis 数据类型?
在当今高速发展的互联网应用中,性能和效率是永恒的追求。数据库作为应用的核心组件之一,其性能直接影响到整个系统的响应速度。传统的关系型数据库(如 MySQL)在处理高并发读写、缓存、队列等场景时,往往会成为瓶颈。
这时,高性能的内存数据库 Redis 应运而生。Redis(Remote Dictionary Server)是一个开源的、使用 C 语言编写的、基于内存的键值存储系统。它不仅仅是一个简单的 key-value 存储,更是一个支持多种复杂数据结构的 NoSQL 数据库。
Redis 之所以如此强大和流行,很大程度上归功于它丰富而灵活的数据类型。理解并掌握 Redis 的核心数据类型是使用 Redis 的基石。不同的数据类型适用于不同的场景,选择合适的数据类型能够极大地提高开发效率、降低系统复杂度、优化应用性能。
本文将深入介绍 Redis 的五种核心数据类型:String、List、Set、Sorted Set (ZSet) 和 Hash。我们将详细阐述每种数据类型的特点、常用命令、应用场景以及底层的实现原理,帮助你从零开始,逐步掌握 Redis 的精髓。
1. String(字符串)
1.1 什么是 String?
String 是 Redis 中最基础的数据类型。它可以存储任何类型的数据,例如字符串、整数、浮点数甚至二进制数据(因此被称为二进制安全)。一个 String 类型的值最大可以容纳 512MB 的数据。
在 Redis 中,所有的键(Key)都是 String 类型。当你使用 SET mykey myvalue
命令时,mykey
是一个 String 类型的键,myvalue
也是一个 String 类型的值。
String 类型是其他更复杂数据类型的构建基础。例如,List、Set、Hash 中的元素(除了 Hash 的字段名)最终都是以 String 的形式存储的。
1.2 String 的特点
- 二进制安全: 可以存储图片、序列化的对象等任何形式的二进制数据。
- 简单: 是最基本、最常用的数据类型。
- 多样化操作: 除了基本的存取,还支持原子性的递增/递减操作、子串操作、位操作等。
1.3 String 的常用命令
以下是 String 类型的一些核心命令:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
:设置指定 key 的值。EX seconds
: 设置 key 的过期时间,单位秒。PX milliseconds
: 设置 key 的过期时间,单位毫秒。NX
: 只在 key 不存在时设置值(原子性的SET
if Not eXists)。XX
: 只在 key 存在时设置值(原子性的SET
if eXists)。- 示例:
SET username "redisuser"
,SET session:userid:123 "abc" EX 3600
GET key
:获取指定 key 的值。如果 key 不存在,返回 nil。- 示例:
GET username
- 示例:
DEL key [key ...]
:删除一个或多个 key。对于 String 类型,就是删除键值对。- 示例:
DEL username
- 示例:
INCR key
:将存储在 key 的值原子性地递增 1。如果 key 不存在,则在执行之前将其值初始化为 0。如果 key 的值不是整数,返回错误。- 示例:
SET counter 0
->OK
;INCR counter
->(integer) 1
;INCR counter
->(integer) 2
- 示例:
DECR key
:将存储在 key 的值原子性地递减 1。功能与INCR
类似。- 示例:
DECR counter
->(integer) 1
- 示例:
INCRBY key increment
:将存储在 key 的值原子性地递增指定的增量 increment。- 示例:
INCRBY counter 10
->(integer) 11
- 示例:
DECRBY key decrement
:将存储在 key 的值原子性地递减指定的减量 decrement。GETSET key value
:原子性地设置 key 的新值,并返回 key 的旧值。如果 key 不存在,返回 nil。- 示例:
SET oldval "hello"
;GETSET oldval "world"
->"hello"
;GET oldval
->"world"
- 示例:
STRLEN key
:获取 key 所存储 String 的长度。APPEND key value
:如果 key 已经存在并且是一个 String,则将 value 追加到 key 原来的值的末尾。如果 key 不存在,则创建一个新的 String key 并设置其值为 value。- 示例:
SET greeting "hello"
;APPEND greeting " world"
->(integer) 11
;GET greeting
->"hello world"
- 示例:
MSET key value [key value ...]
:同时设置多个键值对。原子性操作。MGET key [key ...]
:同时获取多个 key 的值。返回一个列表,列表中的元素与请求的 key 顺序一致。如果 key 不存在,对应的元素是 nil。
1.4 String 的应用场景
- 缓存: 这是最常见的用途。例如,将数据库查询结果、API 调用结果等热点数据缓存为 String,使用
GET
快速读取,大大减轻后端数据库或服务的压力。可以结合SET EX
设置过期时间。 - 计数器: 使用
INCR
/DECR
/INCRBY
/DECRBY
可以实现网站访问量、商品点赞数、用户积分等实时计数功能。由于是原子操作,在高并发环境下非常安全可靠。 - 分布式锁: 可以利用
SET key value NX EX seconds
命令来实现一个简单的分布式锁。只有在 key 不存在时才能设置成功,并且设置了过期时间防止死锁。 - Session 管理: 将用户的 session 信息序列化后存储为 String,以用户 ID 或 session ID 作为 key,方便快速查找和更新。
- 对象缓存: 将一个对象(如用户信息)序列化为 JSON 或其他格式的字符串,存储在 Redis 中。
1.5 String 的底层实现
在 Redis 内部,String 类型的值并非直接存储为 C 语言的字符串(以 \0
结尾的 char[]
)。而是使用一种称为 SDS (Simple Dynamic String) 的结构。
SDS 相对于 C 字符串的优点:
1. 获取长度快: SDS 结构本身就包含了字符串的长度信息(len
字段),获取长度是 O(1) 操作,而 C 字符串需要遍历到 \0
,是 O(N) 操作。
2. 避免缓冲区溢出: SDS 在修改字符串时,会预先检查空间是否足够,并自动进行扩容,从而避免了 C 字符串常见的缓冲区溢出问题。
3. 二进制安全: SDS 使用 len
字段来判断字符串的结束,而不是 \0
字符,因此可以安全地存储包含 \0
的二进制数据。
Redis 会根据 String 值的大小,选择不同的 SDS 结构来存储,以优化内存使用。
2. List(列表)
2.1 什么是 List?
List 是一个有序的 String 元素集合。它可以看作是一个链表,你可以从列表的头部(left)或尾部(right)添加或删除元素。
List 的特点在于它的元素是有序的,并且允许重复。你可以根据索引访问列表中的元素(尽管这通常不是 List 的主要用途,因为链表按索引访问效率不高),也可以获取指定范围内的元素。
2.2 List 的特点
- 有序: 元素按照插入顺序或指定的方向排列。
- 可重复: 列表中可以包含相同的元素。
- 双向: 支持从列表的两端进行元素的添加和删除。
- 灵活: 可以作为队列(Queue,一边进一边出)或栈(Stack,同一边进出)使用。
2.3 List 的常用命令
以下是 List 类型的一些核心命令:
LPUSH key value [value ...]
:将一个或多个值插入到列表的头部(最左边)。如果 key 不存在,则创建一个空列表再执行推入操作。- 示例:
LPUSH mylist "world"
->(integer) 1
;LPUSH mylist "hello"
->(integer) 2
;LRANGE mylist 0 -1
->1) "hello"
2) "world"
- 示例:
RPUSH key value [value ...]
:将一个或多个值插入到列表的尾部(最右边)。- 示例:
RPUSH mylist "hello"
->(integer) 1
;RPUSH mylist "world"
->(integer) 2
;LRANGE mylist 0 -1
->1) "hello"
2) "world"
- 示例:
LPOP key
:移除并返回列表的头部元素。- 示例:
LPUSH mylist "a" "b" "c"
->(integer) 3
;LPOP mylist
->"c"
;LPOP mylist
->"b"
- 示例:
RPOP key
:移除并返回列表的尾部元素。- 示例:
LPUSH mylist "a" "b" "c"
->(integer) 3
;RPOP mylist
->"a"
;RPOP mylist
->"b"
- 示例:
LRANGE key start stop
:返回列表指定范围内的元素。索引从 0 开始。-1
表示最后一个元素,-2
表示倒数第二个元素,以此类推。- 示例:
LPUSH mylist "a" "b" "c" "d"
;LRANGE mylist 0 -1
->"d", "c", "b", "a"
;LRANGE mylist 1 3
->"c", "b", "a"
- 示例:
LLEN key
:返回列表的长度。LINDEX key index
:返回列表中指定索引的元素。索引从 0 开始。- 示例:
LPUSH mylist "a" "b" "c"
;LINDEX mylist 0
->"c"
;LINDEX mylist 1
->"b"
- 示例:
LREM key count value
:根据 count 的值,移除列表中前或后 count 个匹配 value 的元素。count > 0
: 从头部开始,移除最多 count 个值为 value 的元素。count < 0
: 从尾部开始,移除最多 |count| 个值为 value 的元素。count = 0
: 移除所有值为 value 的元素。- 示例:
RPUSH mylist "a" "b" "a" "c" "a"
;LREM mylist 2 "a"
->(integer) 2
;LRANGE mylist 0 -1
->"b", "c", "a"
LTRIM key start stop
:保留列表中指定范围内的元素,移除范围外的元素。可以用来修剪列表(例如,只保留最新的 N 条记录)。- 示例:
RPUSH mylist "a" "b" "c" "d"
;LTRIM mylist 1 2
->OK
;LRANGE mylist 0 -1
->"b", "c"
- 示例:
RPOPLPUSH source destination
:原子性地移除列表 source 的尾部元素,并将该元素添加到列表 destination 的头部。常用于构建循环队列或任务队列。
2.4 List 的应用场景
- 消息队列 (Queue): 使用
LPUSH
(生产者)和RPOP
(消费者)可以实现一个简单的队列。RPOPLPUSH
可以用于实现可靠队列,将正在处理的任务转移到另一个列表,处理失败后再放回去。 - 堆栈 (Stack): 使用
LPUSH
(入栈)和LPOP
(出栈)可以实现一个简单的栈。 - 最新列表/历史记录: 记录最近的访问用户、最新评论、用户操作历史等。例如,使用
LPUSH
记录新事件,然后使用LTRIM
保持列表长度不超过某个阈值。 - 排行榜 (简单): 虽然 Sorted Set 更适合复杂的排行榜,但对于简单的按时间排序的排行榜(例如,最新发布的文章),List 也可以派上用场。
- 发布/订阅 (Pub/Sub) 的实现基础: 虽然 Redis 有专门的 Pub/Sub 功能,但 List 也可以通过 BPOP 系列命令(阻塞的 LPOP/RPOP)来实现简陋的生产者/消费者模型。例如
BLPOP key [key ...] timeout
。
2.5 List 的底层实现
Redis 的 List 在不同的版本和不同的场景下,使用两种底层数据结构来优化性能和内存:
- ZipList (压缩列表): 当 List 中的元素数量较少,且每个元素的长度较小时,Redis 会使用 ZipList 来存储。ZipList 是一块连续的内存,通过特殊的编码方式存储数据,非常节省内存。但它的缺点是修改中间元素或插入/删除操作可能需要移动大量内存,效率较低。
- LinkedList (双向链表): 当 List 中的元素数量较多或元素长度较大时,Redis 会将 ZipList 转化为双向链表。链表的好处是头部和尾部的插入/删除操作是 O(1) 的,即使是中间插入/删除也很高效(O(N) 找到位置后是 O(1))。但链表需要额外的指针开销,内存占用相对 ZipList 高。
Redis 会根据 List 的当前状态(元素数量和大小)动态选择使用 ZipList 还是 LinkedList。
3. Set(集合)
3.1 什么是 Set?
Set 是一个无序的 String 元素集合。它最重要的特点是集合中的元素都是唯一的,不允许重复。
Set 提供了一些非常方便的集合操作,如交集、并集、差集等。
3.2 Set 的特点
- 无序: 集合中的元素没有固定的顺序。
- 唯一: 集合中的每个元素都是唯一的,不允许重复。添加重复元素不会改变集合。
- 支持集合运算: 可以方便地进行交集、并集、差集等操作。
3.3 Set 的常用命令
以下是 Set 类型的一些核心命令:
SADD key member [member ...]
:将一个或多个 member 元素添加到集合 key 中,已经存在的 member 元素将被忽略。- 示例:
SADD myset "a" "b" "c"
->(integer) 3
;SADD myset "c" "d"
->(integer) 1
(因为 “c” 已存在) ;SMEMBERS myset
->"a", "b", "c", "d"
(顺序不确定)
- 示例:
SMEMBERS key
:返回集合 key 中的所有成员。注意,集合是无序的,返回的元素顺序不确定。SISMEMBER key member
:判断 member 元素是否是集合 key 的成员。是返回 1,否返回 0。- 示例:
SISMEMBER myset "a"
->(integer) 1
;SISMEMBER myset "e"
->(integer) 0
- 示例:
SCARD key
:返回集合 key 的基数(集合中元素的数量)。- 示例:
SCARD myset
->(integer) 4
- 示例:
SREM key member [member ...]
:移除集合 key 中的一个或多个 member 元素,不存在的 member 元素将被忽略。- 示例:
SREM myset "a" "d"
->(integer) 2
;SMEMBERS myset
->"b", "c"
- 示例:
SPOP key [count]
:随机移除并返回集合中的一个或多个元素。- 示例:
SADD another:set "x" "y" "z"
;SPOP another:set
->"y"
(或其他随机元素) ;SCARD another:set
->(integer) 2
- 示例:
SRANDMEMBER key [count]
:随机返回集合中的一个或多个元素,但不移除。SUNION key [key ...]
:返回所有给定集合的并集。- 示例:
SADD set1 "a" "b"
;SADD set2 "b" "c"
;SUNION set1 set2
->"a", "b", "c"
- 示例:
SINTER key [key ...]
:返回所有给定集合的交集。- 示例:
SINTER set1 set2
->"b"
- 示例:
SDIFF key [key ...]
:返回第一个集合与其他集合的差集。- 示例:
SDIFF set1 set2
->"a"
;SDIFF set2 set1
->"c"
- 示例:
SUNIONSTORE destination key [key ...]
:将所有给定集合的并集存储在指定的 destination 集合中。SINTERSTORE destination key [key ...]
:将所有给定集合的交集存储在指定的 destination 集合中。SDIFFSTORE destination key [key ...]
:将第一个集合与其他集合的差集存储在指定的 destination 集合中。
3.4 Set 的应用场景
- 社交网络:
- 粉丝/关注列表:
SADD user:1:followers user:2
,SISMEMBER user:2:following user:1
检查是否关注。 - 共同关注/共同好友:使用
SINTER
找出两个用户的共同关注或共同好友。 - 推荐可能认识的人:使用
SUNION
和SDIFF
找出与我有共同关注但不是我的粉丝的人。
- 粉丝/关注列表:
- 标签系统: 给文章、商品等打标签。例如,
SADD article:100:tags "Redis" "Database" "NoSQL"
。要查找带有某个或某几个标签的文章,可以使用SINTER
。 - 统计独立访客 (UV): 每天将访问用户的 ID 或 IP 加入一个 Set,通过
SCARD
命令可以获取当天的独立访客数。由于 Set 自动去重,非常适合这个场景。 - 权限管理: 存储用户拥有的角色或权限集合,使用
SISMEMBER
检查用户是否拥有某个权限。 - 过滤重复数据: 快速判断某个元素是否存在于一个集合中,利用
SISMEMBER
(O(1) 复杂度)。
3.5 Set 的底层实现
Redis 的 Set 在不同的场景下,使用两种底层数据结构:
- IntSet (整数集合): 当 Set 中的元素都是整数,且元素数量较少时,Redis 会使用 IntSet。IntSet 是一块连续的内存,存储有序的整数数组。查找、添加、删除都比较高效(虽然不是 O(1),但对于小集合性能很好),并且非常节省内存。
- HashTable (哈希表): 当 Set 中的元素是字符串,或者包含的整数数量较多时,Redis 会将 IntSet 转化为 HashTable。HashTable 使用哈希函数将元素映射到数组位置,查找、添加、删除的平均时间复杂度都是 O(1)。Set 的所有元素都作为哈希表的键,值则为 NULL。
和 List 一样,Redis 会根据 Set 的实际情况动态选择使用 IntSet 还是 HashTable。
4. Sorted Set (ZSet/有序集合)
4.1 什么是 Sorted Set?
Sorted Set 与 Set 类似,也是 String 元素的集合,元素也是唯一的。但与 Set 不同的是,每个元素都会关联一个浮点数分数 (score)。Sorted Set 中的元素是按照分数从小到大进行排序的。如果分数相同,则按照成员的字典序进行排序。
Sorted Set 结合了 Set 的唯一性以及 List 的有序性,并且通过分数提供了排序的能力。
4.2 Sorted Set 的特点
- 有序: 元素按照关联的分数从小到大排序。
- 唯一: 集合中的每个成员都是唯一的。
- 可关联分数: 每个成员都有一个浮点数分数,用于排序。
- 支持范围查询: 可以根据分数范围或排名范围获取元素。
4.3 Sorted Set 的常用命令
以下是 Sorted Set 类型的一些核心命令:
ZADD key [NX|XX|CH|INCR] score member [score member ...]
:将一个或多个 member 元素及其 score 值加入到有序集合 key 中。NX
: 只在 member 不存在时添加。XX
: 只在 member 存在时更新其 score。CH
: 即使成员的 score 没有改变,也返回更新的总数。INCR
: 将 member 的 score 增加给定的值。- 示例:
ZADD myzset 1 "one"
->(integer) 1
;ZADD myzset 2 "two" 3 "three"
->(integer) 2
;ZADD myzset 1.5 "one point five"
->(integer) 1
;ZRANGE myzset 0 -1 WITHSCORES
->1) "one"
2) "1"
3) "one point five"
4) "1.5"
5) "two"
6) "2"
7) "three"
8) "3"
ZRANGE key start stop [WITHSCORES]
:返回有序集合中指定排名范围内的成员。排名从 0 开始。WITHSCORES
选项可以同时返回成员和它的分数。- 示例:
ZRANGE myzset 0 1
->"one", "one point five"
;ZRANGE myzset 0 1 WITHSCORES
->"one", "1", "one point five", "1.5"
- 示例:
ZREVRANGE key start stop [WITHSCORES]
:返回有序集合中指定排名范围内的成员,但按照分数从大到小排序(即逆序)。- 示例:
ZREVRANGE myzset 0 1
->"three", "two"
- 示例:
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
:返回有序集合中指定分数范围内的成员。min
,max
: 分数范围。可以使用(
表示开区间,-inf
和+inf
表示无穷。LIMIT offset count
: 分页。- 示例:
ZRANGEBYSCORE myzset 1.5 2.5 WITHSCORES
->"one point five", "1.5", "two", "2"
;ZRANGEBYSCORE myzset (1.5 +inf
->"two", "three"
ZCOUNT key min max
:返回有序集合中分数在指定范围内的成员数量。- 示例:
ZCOUNT myzset 1.5 2.5
->(integer) 2
- 示例:
ZCARD key
:返回有序集合的基数(成员数量)。ZSCORE key member
:返回有序集合中指定 member 的分数。- 示例:
ZSCORE myzset "two"
->"2"
- 示例:
ZREM key member [member ...]
:移除有序集合中的一个或多个 member。- 示例:
ZREM myzset "one"
->(integer) 1
;ZRANGE myzset 0 -1 WITHSCORES
->"one point five", "1.5", "two", "2", "three", "3"
- 示例:
Z Rank key member
:返回有序集合中指定 member 的排名(分数由小到大,从 0 开始)。- 示例:
ZRANK myzset "two"
->(integer) 2
(因为 one point five 是排名 0,two 是排名 2)
- 示例:
ZREVRANK key member
:返回有序集合中指定 member 的排名(分数由大到小,从 0 开始)。- 示例:
ZREVRANK myzset "two"
->(integer) 1
(因为 three 是排名 0,two 是排名 1)
- 示例:
ZINCRBY key increment member
:原子性地增加 member 的 score 值。- 示例:
ZINCRBY myzset 2 "two"
->"4"
(原来的 score 是 2,增加了 2 后是 4) ;ZRANGE myzset 0 -1 WITHSCORES
->"one point five", "1.5", "three", "3", "two", "4"
(注意 two 的位置变了)
- 示例:
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
:计算给定有序集合的交集,并将结果存储在 destination 中。可以指定权重和聚合函数。ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
:计算给定有序集合的并集,并将结果存储在 destination 中。
4.4 Sorted Set 的应用场景
- 排行榜: 这是 Sorted Set 最经典的用途。例如,游戏积分排行榜、网站用户活跃度排行榜、商品销量排行榜等。分数代表用户的积分、活跃度或销量,通过
ZREVRANGE
获取排名靠前的用户。 - 带权重的任务队列: 可以将任务的优先级作为 score,使用
ZRANGEBYSCORE
获取优先级最高的任务进行处理。 - 时间序列数据索引: 将时间戳作为 score,事件内容作为 member,可以方便地查询某个时间段内的事件。
- 范围查找: 例如,在一个商品列表中,可以根据价格(score)范围查找商品。
4.5 Sorted Set 的底层实现
Redis 的 Sorted Set 使用两种数据结构组合实现:
- HashTable (哈希表): 用于存储成员到分数的映射。这样可以通过成员快速查找其分数(O(1) 平均复杂度)。哈希表的键是 Sorted Set 的成员,值是该成员对应的分数。
- SkipList (跳跃表) 或 ZipList (压缩列表):
- SkipList: 当 Sorted Set 的元素数量较多时,Redis 会使用跳跃表。跳跃表是一种概率性数据结构,它允许在 O(log N) 的平均时间复杂度下进行有序元素的插入、删除和查找。它通过多层链表结构,使得在底层链表中跳跃成为可能,从而加快查找速度。跳跃表是 Sorted Set 能够快速进行范围查询的关键。
- ZipList: 当 Sorted Set 的元素数量较少,且成员和分数占用内存较小时,Redis 会使用 ZipList 来存储。ZipList 的优点是节省内存,但其范围查询和插入/删除效率不如跳跃表。
Redis 会根据 Sorted Set 的规模动态选择使用 ZipList 还是 SkipList。
5. Hash(哈希)
5.1 什么是 Hash?
Hash 存储了一个键值对的集合,这里的键是外部的 Redis Key,而内部的值是一个类似于 Map 或字典的结构。它存储的是字段 (field) 和值 (value) 的映射关系。
Hash 特别适合用来存储对象。例如,一个用户信息对象可以存储为一个 Hash,其中 key 是用户 ID,Hash 内部的字段包括 name
、age
、city
等,对应的值则是具体的属性值。
5.2 Hash 的特点
- 字段值对: 内部存储的是多个
field -> value
的映射。 - Key 唯一: 每个 Hash 类型的 Key 只能表示一个 Hash 结构。
- 字段唯一: 同一个 Hash 结构内部,字段是唯一的。
- 适合存储对象: 可以将一个对象的多个属性存储在一个 Hash 中,相比于将每个属性存储为一个独立的 String Key 更节省内存和管理成本。
5.3 Hash 的常用命令
以下是 Hash 类型的一些核心命令:
HSET key field value [field value ...]
:设置 Hash 中指定字段的值。如果 Hash 不存在,会创建一个新的 Hash 并进行设置。如果字段已经存在,会覆盖旧值。- 示例:
HSET user:100 name "Alice" age 30 city "Beijing"
->(integer) 3
- 示例:
HGET key field
:获取 Hash 中指定字段的值。- 示例:
HGET user:100 name
->"Alice"
- 示例:
HMGET key field [field ...]
:获取 Hash 中多个字段的值。返回一个列表。- 示例:
HMGET user:100 name city
->1) "Alice"
2) "Beijing"
- 示例:
HGETALL key
:获取 Hash 中所有的字段和值。返回一个列表,列表中依次是字段、值、字段、值…- 示例:
HGETALL user:100
->1) "name"
2) "Alice"
3) "age"
4) "30"
5) "city"
6) "Beijing"
- 示例:
HDEL key field [field ...]
:删除 Hash 中一个或多个字段。- 示例:
HDEL user:100 city
->(integer) 1
- 示例:
HEXISTS key field
:判断 Hash 中指定字段是否存在。存在返回 1,不存在返回 0。- 示例:
HEXISTS user:100 age
->(integer) 1
;HEXISTS user:100 gender
->(integer) 0
- 示例:
HLEN key
:返回 Hash 中字段的数量。- 示例:
HLEN user:100
->(integer) 2
(删除了 city 之后)
- 示例:
HKEYS key
:返回 Hash 中所有的字段名。- 示例:
HKEYS user:100
->1) "name"
2) "age"
- 示例:
HVALS key
:返回 Hash 中所有的值。- 示例:
HVALS user:100
->1) "Alice"
2) "30"
- 示例:
HINCRBY key field increment
:原子性地增加 Hash 中指定字段的整数值。- 示例:
HSET product:101 price 100
;HINCRBY product:101 price 5
->(integer) 105
- 示例:
HINCRBYFLOAT key field increment
:原子性地增加 Hash 中指定字段的浮点数值。- 示例:
HSET product:101 rating 4.5
;HINCRBYFLOAT product:101 rating 0.1
->"4.6"
- 示例:
5.4 Hash 的应用场景
- 存储对象: 将用户、商品、订单等对象的多个属性存储在一个 Hash 中。相比于使用多个 String Key (
user:100:name
,user:100:age
),Hash 更节省内存(尤其是在字段数量多时)且管理更方便(例如,使用DEL user:100
一次性删除整个对象)。 - 缓存结构化数据: 缓存数据库中的一条记录,将记录的字段作为 Hash 的 field。
- 购物车: 使用用户 ID 作为 key,商品 ID 作为 field,购买数量作为 value。
- 网站配置: 存储网站的各种配置信息,例如,
HSET site:config title "My Website" keywords "Redis, Demo"
。
5.5 Hash 的底层实现
Redis 的 Hash 在不同的场景下,使用两种数据结构实现:
- ZipList (压缩列表): 当 Hash 中字段数量较少,且所有字段名和值都较小时,Redis 会使用 ZipList。ZipList 以键值对的形式顺序存储字段和值,结构紧凑,非常节省内存。
- HashTable (哈希表): 当 Hash 中字段数量较多或某个字段名/值较大时,Redis 会将 ZipList 转化为 HashTable。HashTable 使用哈希函数将字段名映射到数组位置,查找、添加、删除字段的平均时间复杂度都是 O(1)。
Redis 会根据 Hash 的实际情况动态选择使用 ZipList 还是 HashTable。
6. Redis Keys 的通用操作与 TTL
虽然本文重点介绍了数据类型,但了解如何管理 Redis 的键(Key)也是基础。所有 Redis 数据类型的操作都围绕 Key 进行。
- 键命名: Redis Key 的命名没有强制规定,但通常使用
:
进行命名空间的分隔,例如user:100:profile
,product:category:electronics
。这有助于组织和管理 Key。 - 通用命令:
KEYS pattern
: 查找所有符合给定模式的 key。注意: 在生产环境谨慎使用,尤其是在 Keys 数量庞大时,会阻塞 Redis。考虑使用SCAN
命令进行分批扫描。EXISTS key [key ...]
:检查指定 key 是否存在。DEL key [key ...]
:删除一个或多个 key。TYPE key
:返回 key 存储的值的数据类型(string, list, set, zset, hash, stream 等)。RENAME key newkey
: 重命名 key。RANDOMKEY
: 随机返回一个 key。
- 键的过期(Expiration / TTL): Redis 支持为键设置过期时间,到期后键会自动被删除。这是实现缓存淘汰的重要机制。
EXPIRE key seconds
: 设置 key 的过期时间,单位秒。PEXPIRE key milliseconds
: 设置 key 的过期时间,单位毫秒。EXPIREAT key timestamp
: 设置 key 在指定的 Unix 时间戳过期(秒)。PEXPIREAT key millisecondsTimestamp
: 设置 key 在指定的 Unix 时间戳过期(毫秒)。TTL key
: 返回 key 剩余的过期时间(秒)。-1 表示永不过期,-2 表示 key 不存在。PTTL key
: 返回 key 剩余的过期时间(毫秒)。PERSIST key
: 移除 key 的过期时间,使其永不过期。SETEX key seconds value
: 设置 key 的值,并同时设置过期时间(秒)。这是一个原子操作,相当于SET
+EXPIRE
。PSETEX key milliseconds value
: 功能同SETEX
,但过期时间单位是毫秒。
过期机制的应用非常广泛,例如缓存会话、验证码、限时活动数据等。
7. 总结与展望
本文详细介绍了 Redis 的五种核心数据类型:String、List、Set、Sorted Set 和 Hash。我们了解了它们的特点、常用命令、典型的应用场景以及简要的底层实现原理。
每种数据类型都有其独特的优势和适用范围:
- String: 适用于简单的键值存储、缓存、计数器、分布式锁等。
- List: 适用于需要有序、可重复元素的场景,特别是队列、堆栈、最新列表等。
- Set: 适用于需要存储唯一元素、进行集合运算(交、并、差)或快速判断元素是否存在的场景。
- Sorted Set: 适用于需要根据分数排序、进行范围查询的场景,特别是排行榜。
- Hash: 适用于存储结构化数据或对象,将多个字段存储在一个键下。
选择合适的数据类型是发挥 Redis 强大性能的关键。通过熟练掌握这些核心数据类型及其命令,你将能够高效地解决各种实际开发问题。
Redis 的世界远不止这五种核心数据类型。随着版本迭代,Redis 还引入了 Stream (流)、Geospatial (地理空间索引)、HyperLogLog (基数统计) 等更高级的数据类型,以及模块 (Modules) 机制,极大地扩展了 Redis 的能力。但对于初学者而言,牢牢掌握这五种核心类型,就已经为你迈入 Redis 世界奠定了坚实的基础。
实践是最好的老师。建议你在阅读本文后,立即动手在本地安装 Redis 并使用 redis-cli
工具,亲自尝试本文中介绍的各种命令,加深理解。
希望本文能帮助你开启 Redis 学习之旅!祝你使用 Redis 愉快!