Redis 命令详解及原理
Redis 命令详解及原理
一、Redis 是什么
Redis(Remote Dictionary Service)是一种基于内存的键值对存储数据库,常被称作“远程字典服务”。它支持多种数据结构,如字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(ZSet)等,并提供丰富的操作命令。Redis 以其高性能、丰富的数据结构和持久化能力,广泛应用于缓存、消息队列、排行榜、分布式锁等场景。
核心特点:
- 内存存储:所有数据保存在内存中,读写速度极快(纳秒级),但断电可能丢失数据(需配合持久化)。
- 键值模型:通过 key 索引 value,支持多种 value 类型。
- 请求-响应模式:客户端发送命令,服务端处理并返回结果。
二、Redis 的基本操作与数据类型
1. 启动与连接
# 启动 Redis 服务器(指定配置文件)
redis-server /path/to/redis.conf
# 连接 Redis 客户端
redis-cli -h 127.0.0.1 -p 6379 -a password
常用配置项(redis.conf):
daemonize yes:后台运行requirepass yourpassword:设置密码bind 0.0.0.0:允许远程连接(生产环境谨慎)
2. 数据类型及命令详解
2.1 String(字符串)
String 是 Redis 最基础的数据类型,值可以是字符串、整数或浮点数,最大容量 512MB。底层采用 SDS(Simple Dynamic String)实现,支持动态扩容。
常用命令:
| 命令 | 说明 | 示例 |
|---|---|---|
| SET key value | 设置键值 | SET name "Alice" |
| GET key | 获取值 | GET name |
| INCR key | 原子自增1(值必须为整数) | INCR counter |
| INCRBY key increment | 原子增加指定值 | INCRBY counter 5 |
| DECR / DECRBY | 原子自减 | DECR counter |
| SETNX key value | 仅当 key 不存在时设置(分布式锁常用) | SETNX lock "uuid" |
| MSET / MGET | 批量设置/获取 | MSET a 1 b 2 |
| GETSET key newvalue | 设置新值并返回旧值 | GETSET name "Bob" |
| APPEND key value | 追加字符串 | APPEND name " Smith" |
| STRLEN key | 获取字符串长度 | STRLEN name |
| SETEX key seconds value | 设置键并指定过期时间(秒) | SETEX token 3600 "abc123" |
| BITCOUNT key [start end] | 统计字符串中比特位为1的个数 | BITCOUNT sign:2025-03 |
应用场景:
- 缓存对象(JSON 序列化)
- 计数器(PV、点赞数)
- 分布式锁(SETNX + 过期时间)
- 位图统计(签到、在线状态)
示例:位图签到
# 用户 1001 在 2025-03-24 签到(第 24 天,偏移 23)
SETBIT sign:1001:2025-03 23 1
# 查询当天是否签到
GETBIT sign:1001:2025-03 23
# 统计本月签到天数
BITCOUNT sign:1001:2025-03
2.2 List(列表)
List 是双向链表,支持从头部(左)或尾部(右)插入和弹出元素,适合实现队列、栈、消息队列等。
常用命令:
| 命令 | 说明 | 示例 |
|---|---|---|
| LPUSH key value [value...] | 从左侧插入一个或多个元素 | LPUSH queue "task1" "task2" |
| RPUSH key value [value...] | 从右侧插入 | RPUSH queue "task3" |
| LPOP key | 从左侧弹出一个元素 | LPOP queue |
| RPOP key | 从右侧弹出一个元素 | RPOP queue |
| LRANGE key start stop | 获取指定范围的元素(0 -1 表示全部) | LRANGE queue 0 -1 |
| LLEN key | 获取列表长度 | LLEN queue |
| LTRIM key start stop | 保留指定范围内的元素 | LTRIM queue 0 49(保留前50个) |
| BLPOP / BRPOP | 阻塞式弹出,队列为空时等待 | BLPOP queue 10(等待10秒) |
应用场景:
- 消息队列(LPUSH + BRPOP)
- 最新动态(LPUSH + LTRIM 保留最近N条)
- 栈(LPUSH + LPOP)
示例:实现固定长度的最新消息列表
# 插入新消息
LPUSH latest_news "新闻3"
# 只保留最近5条
LTRIM latest_news 0 4
2.3 Hash(哈希)
Hash 类似于 Java 的 HashMap,用于存储字段-值映射,适合存储对象属性。
常用命令:
| 命令 | 说明 | 示例 |
|---|---|---|
| HSET key field value | 设置单个字段 | HSET user:1001 name "Alice" |
| HMSET key field value [field value...] | 设置多个字段(已弃用,可用 HSET 代替) | HSET user:1001 age 25 city "Beijing" |
| HGET key field | 获取单个字段 | HGET user:1001 name |
| HGETALL key | 获取所有字段和值 | HGETALL user:1001 |
| HINCRBY key field increment | 对整数字段自增 | HINCRBY user:1001 age 1 |
| HDEL key field [field...] | 删除一个或多个字段 | HDEL user:1001 city |
| HEXISTS key field | 判断字段是否存在 | HEXISTS user:1001 name |
| HLEN key | 获取字段数量 | HLEN user:1001 |
应用场景:
- 存储对象(如用户信息、商品详情)
- 购物车(每个用户一个 Hash,商品 ID 为 field,数量为 value)
示例:购物车
# 用户 1001 添加商品 2001 数量 2
HSET cart:1001 2001 2
# 增加商品数量
HINCRBY cart:1001 2001 1
# 获取购物车所有商品
HGETALL cart:1001
2.4 Set(集合)
Set 是无序且元素唯一的集合,底层使用字典或整数数组实现。支持集合间的交、并、差运算。
常用命令:
| 命令 | 说明 | 示例 |
|---|---|---|
| SADD key member [member...] | 添加一个或多个成员 | SADD tags "java" "redis" |
| SREM key member [member...] | 移除成员 | SREM tags "redis" |
| SMEMBERS key | 获取所有成员 | SMEMBERS tags |
| SISMEMBER key member | 判断成员是否存在 | SISMEMBER tags "java" |
| SCARD key | 获取成员数量 | SCARD tags |
| SPOP key [count] | 随机弹出指定数量成员 | SPOP lucky 1 |
| SRANDMEMBER key [count] | 随机获取指定数量成员(不删除) | SRANDMEMBER tags 2 |
| SINTER key [key...] | 求交集 | SINTER set1 set2 |
| SUNION key [key...] | 求并集 | SUNION set1 set2 |
| SDIFF key [key...] | 求差集 | SDIFF set1 set2 |
应用场景:
- 标签系统(用户标签、文章标签)
- 抽奖(SPOP 随机抽取不重复)
- 共同好友(SINTER)
- 推荐系统(SDIFF 计算未关注的人)
示例:共同关注
# 用户 A 关注的人
SADD user:1:follow "user2" "user3" "user4"
# 用户 B 关注的人
SADD user:2:follow "user3" "user4" "user5"
# 共同关注
SINTER user:1:follow user:2:follow
2.5 ZSet(有序集合)
ZSet 为每个元素关联一个 double 类型的分数(score),元素按分数排序且唯一。底层使用跳表(skiplist)和字典实现,支持范围查询。
常用命令:
| 命令 | 说明 | 示例 |
|---|---|---|
| ZADD key score member [score member...] | 添加元素(分数可重复) | ZADD leaderboard 100 "Alice" 98 "Bob" |
| ZRANGE key start stop [WITHSCORES] | 按分数升序获取指定范围元素 | ZRANGE leaderboard 0 2 WITHSCORES |
| ZREVRANGE key start stop [WITHSCORES] | 按分数降序获取 | ZREVRANGE leaderboard 0 2 |
| ZRANK key member | 获取元素排名(升序,从0开始) | ZRANK leaderboard "Alice" |
| ZREVRANK key member | 获取元素排名(降序) | ZREVRANK leaderboard "Alice" |
| ZSCORE key member | 获取元素分数 | ZSCORE leaderboard "Alice" |
| ZINCRBY key increment member | 增加元素的分数 | ZINCRBY leaderboard 10 "Alice" |
| ZREM key member [member...] | 删除元素 | ZREM leaderboard "Bob" |
| ZCOUNT key min max | 统计分数在区间内的元素数量 | ZCOUNT leaderboard 90 100 |
| ZREMRANGEBYRANK key start stop | 按排名范围删除 | ZREMRANGEBYRANK leaderboard 0 0 |
| ZREMRANGEBYSCORE key min max | 按分数范围删除 | ZREMRANGEBYSCORE leaderboard 0 60 |
应用场景:
- 排行榜(热门文章、游戏排名)
- 延时队列(分数为执行时间戳)
- 限流(滑动窗口)
示例:排行榜
# 记录文章热度
ZADD article:hot 100 "article_1" 200 "article_2"
# 获取前三名(降序)
ZREVRANGE article:hot 0 2 WITHSCORES
三、Redis 底层数据结构
理解 Redis 的底层实现有助于掌握性能特性。Redis 对每种数据类型采用多种编码方式,根据数据量大小自动选择最优实现。
1. 全局字典(dict)
Redis 的所有 key-value 对存储在一个全局字典中(哈希表),采用链地址法解决冲突,并支持渐进式 rehash 避免阻塞。
2. String 底层:SDS(Simple Dynamic String)
SDS 是 Redis 自定义的字符串结构,相较于 C 字符串具有以下优点:
- 常数时间获取长度
- 预分配空间减少扩容次数
- 二进制安全(可存储任意字节)
- 兼容部分 C 字符串函数
SDS 结构示例(Redis 7.0 后简化):
struct sdshdr {
uint8_t len; // 已使用长度
uint8_t alloc; // 分配的总长度
unsigned char flags;
char buf[]; // 柔性数组,存储实际数据
};
编码方式:
- int:如果字符串可转换为整数(如 "123"),直接存储整数值。
- embstr:长度 ≤ 44 字节时使用,一次分配内存(SDS 头 + 数据),内存连续,适合短字符串。
- raw:长度 > 44 字节时使用,分两次分配内存。
3. List 底层:quicklist
Redis 3.2 后 List 使用 quicklist 实现,它是由多个压缩链表(ziplist)节点组成的双向链表。每个 ziplist 存储一批元素,平衡了内存和访问性能。
在 Redis 7.0 后,ziplist 被 listpack 取代,listpack 解决了 ziplist 级联更新问题,更节省空间。
4. Hash 底层:ziplist / dict
- 当元素数量少且每个元素长度较小时,使用 ziplist(或 listpack)紧凑存储。
- 超过阈值(默认 512 个元素或任一字段长度超过 64 字节)时,转换为哈希表(dict)。
5. Set 底层:intset / dict
- 当所有元素都是整数且数量 ≤ 512 时,使用 intset(整数集合,有序紧凑存储)。
- 否则使用 dict(字典),值全部指向 NULL,只利用 key 的唯一性。
6. ZSet 底层:ziplist / dict + skiplist
- 当元素数量 ≤ 128 且每个成员长度 ≤ 64 字节时,使用 ziplist(listpack)按分数顺序存储。
- 超过阈值时,使用跳表(skiplist)+ 字典(dict)的组合:跳表按分数排序,字典提供 O(1) 的成员查找。
跳表(skiplist) 是一种多层链表,平均 O(log N) 查找,适合范围查询。
7. 编码查看
使用 OBJECT ENCODING key 命令可以查看 key 的底层编码。
> SET msg "hello"
> OBJECT ENCODING msg
"embstr"
> SET big "x"*500
> OBJECT ENCODING big
"raw"
四、Redis 高级特性
1. 过期策略
Redis 可以为任意 key 设置过期时间(秒或毫秒),支持两种删除策略:
- 惰性删除:访问时检查是否过期,过期则删除。
- 定期删除:每秒随机抽取部分 key 检查并删除过期 key,避免内存堆积。
2. 分布式锁
使用 SETNX + 过期时间可实现分布式锁(注意原子性),推荐使用 SET key value EX seconds NX 命令(Redis 2.6.12 后支持)。
# 加锁(返回 OK 表示成功,nil 表示失败)
SET lock_key "uuid" EX 10 NX
# 解锁(需要校验 value,使用 Lua 脚本保证原子性)
EVAL "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock_key "uuid"
3. 发布订阅
Redis 支持发布订阅模式,用于实时消息广播。
# 订阅频道
SUBSCRIBE channel1
# 发布消息
PUBLISH channel1 "hello"
4. 管道(Pipeline)
Pipeline 可将多个命令打包一次性发送,减少网络往返时间,提高吞吐量。
# 使用 redis-cli 的 --pipe 模式或客户端库实现
echo -e "set a 1\nset b 2\nincr a\n" | redis-cli --pipe
5. Lua 脚本
Redis 支持执行 Lua 脚本,保证原子性,常用于复杂逻辑。
# 执行脚本
EVAL "return redis.call('set',KEYS[1],ARGV[1])" 1 mykey "hello"
五、Redis 应用案例
1. 计数器与限流
利用 INCR 和过期时间实现时间窗口限流(如每秒最多 10 次请求)。
# 请求限流:1 秒内最多 10 次
FUNCTION limit(ip)
local current = redis.call('incr', ip)
if current == 1 then
redis.call('expire', ip, 1)
end
if current > 10 then
return 0
end
return 1
END
2. 延迟队列
使用 ZSet 存储任务,score 为执行时间戳,定期轮询到期任务。
# 添加任务
ZADD delay_queue 1735574400 "task:send_email:1001"
# 获取到期任务
ZRANGEBYSCORE delay_queue 0 1735574400 WITHSCORES LIMIT 0 10
3. 排行榜
ZSet 天然适合排行榜,支持实时更新排名。
# 更新分数
ZINCRBY game_rank 10 "player_123"
# 获取前三名
ZREVRANGE game_rank 0 2 WITHSCORES
4. 位图签到
使用 BIT 命令高效存储签到记录,节省内存。
# 用户 1001 在 2025-03-24 签到(偏移 23)
SETBIT sign:1001:2025-03 23 1
# 查询本月签到数
BITCOUNT sign:1001:2025-03

浙公网安备 33010602011771号