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

posted @ 2026-03-24 19:12  xggx  阅读(2)  评论(0)    收藏  举报