Redis 字符串命令详解

string 是 Redis 最基础也最常用的类型——大约 80% 的 Redis 用法最终都落在 string 上(缓存、计数、锁)。本篇把 string 相关的常用命令完整过一遍,每个命令都给出可敲的示例。

1. 最基础的 SET / GET / DEL

# 基本 set/get
127.0.0.1:6379> SET name "小明"
OK
127.0.0.1:6379> GET name
"\xe5\xb0\x8f\xe6\x98\x8e"      # redis-cli 默认按字节显示中文
# 启动时加 --raw 可正常显示中文:redis-cli --raw
127.0.0.1:6379> GET not_exists
(nil)                            # 不存在返回 nil

# 设置已存在的 key 会覆盖旧值
127.0.0.1:6379> SET name "小红"
OK
127.0.0.1:6379> GET name
"小红"

# 删除 key
127.0.0.1:6379> DEL name
(integer) 1                      # 返回删除的数量

关于中文显示:redis-cli 默认按字节输出中文,显示成一堆 \\xNN。启动时加 --raw 参数即可正常显示。生产环境的客户端库(ioredis、jedis、go-redis)都正确处理 UTF-8,无需关心。

2. SET 命令的选项参数

SET 看起来简单,其实它支持一系列选项参数,熟练后能省掉多条命令的组合:

# SET 命令的选项参数(从 2.6.12 起统一支持)
# NX   : Not eXists,只在 key 不存在时才设置(常用做锁)
# XX   : 只在 key 已存在时才设置(和 NX 相反)
# EX s : 过期时间(秒)
# PX ms: 过期时间(毫秒)
# KEEPTTL: 保留原有 TTL(6.0 引入)

SET lock "worker1" NX EX 30       # 仅当 lock 不存在时设置,30 秒过期
SET cache:user:1 "json" EX 3600   # 缓存 1 小时
SET name "新值" XX                # 只在 name 已存在时才更新

# GETSET:设置新值并返回旧值
SET counter 100
GETSET counter 0                  # 返回 100,然后 counter 变成 0

# SETNX / SETEX / PSETEX(老语法,现在推荐用 SET ... NX/EX)
SETNX name "hello"                # 等价 SET name "hello" NX
SETEX token 60 "abc"              # 等价 SET token "abc" EX 60

其中 NX(不存在才设置)和 EX(过期秒数)是最常用的两个,组合起来 SET key value NX EX 30 一条命令就能实现"分布式锁"——既有互斥性(没人才加得上),又能自动释放(过期不会永久占用)。这是 Redis 2.6.12 之后官方推荐的加锁写法,比旧的 SETNX + EXPIRE 两条命令更安全(后者中间崩溃会留下永不过期的锁)。

3. 过期时间:EXPIRE / TTL

"过期"是 Redis 缓存的核心特性——给每个 key 设一个 TTL,到点自动删除,既能腾出内存,又能防止脏数据长期存在。

# 单独设置过期时间(秒 / 毫秒)
EXPIRE token 60          # 60 秒后过期
PEXPIRE token 60000      # 60 秒(60000 毫秒)后过期

# 设过期时间点(Unix 时间戳)
EXPIREAT cache:hot 1735689600   # 在 2025-01-01 00:00:00 过期

# 查看剩余时间(TTL: 秒, PTTL: 毫秒)
TTL token                # 返回剩余秒数;-1 表示永不过期;-2 表示 key 不存在
PTTL token               # 毫秒精度

# 取消过期(变永久)
PERSIST token            # 返回 1 表示成功

# 注意:对已有 TTL 的 key 再 SET 会清除 TTL!
SET token "v1"
EXPIRE token 60
SET token "v2"           # 此刻 TTL 没了,变成永久
# 解决:用 SET ... KEEPTTL 或先 SET 再 EXPIRE

TTL 的几个返回值要记住:正数 表示剩余秒数,-1 表示 key 存在但永不过期,-2 表示 key 不存在(或已过期被删)。一个常见坑:对已有 TTL 的 key 再执行 SET清除 TTL,key 变回永久。要么用 SET ... KEEPTTL,要么先 SETEXPIRE

Redis 怎么"自动"删过期 key?有两种机制配合:惰性删除(访问时检查,过期则删)和定期删除(后台周期性随机抽样删除)。这保证了过期 key 不会立刻占满内存,也不会无限堆积。

4. 原子计数器:INCR 家族

string 不只能存文本,也能存"数字",并提供原子自增自减操作。这是 Redis 做计数器、限流、ID 生成的核心。

# 自增/自减(必须 value 是整数,否则报错)
SET views 0
INCR views              # 1  (原子自增 1)
INCR views              # 2
DECR views              # 1  (原子自减 1)
INCRBY views 100        # 101 (自增指定整数)
DECRBY views 50         # 51
INCRBYFLOAT price 1.5   # 浮点数自增(无对应 DECR,用负数即可)

# 为什么强调"原子"?Redis 是单线程执行命令,
# 1000 个并发 INCR views 不会丢一次自增。
# 做计数器、PV、限流的核心保障。

# 限流实例:每个 IP 每分钟最多 100 次请求
# (在应用代码里,每次请求到来时执行)
INCR ratelimit:1.2.3.4              # 计数 +1
# 第一次访问时设置过期(关键!)
EXPIRE ratelimit:1.2.3.4 60 NX      # 仅在刚创建时设 60s 过期
# 如果返回值大于 100,说明超限,拒绝请求

"原子"为什么重要?假设你的服务部署在 4 台机器上,同时收到 1000 个请求,都执行"读出 views、加 1、写回"。如果用 MySQL,你得加行锁或事务,否则会丢失自增。而 Redis 单线程串行执行 INCR,所有并发请求被自动串行化,绝不会丢失一次自增。这就是为什么 Redis 是做计数器/限流的最佳选择

上面给的"固定窗口限流"实例是生产里最简单的限流算法。更精确的有滑动窗口(用 zset 实现)、令牌桶(用 list 模拟令牌)、漏桶——具体在 use-cases 篇会展开。

5. 批量操作:MSET / MGET

每次发一条 Redis 命令都要一次网络往返(RTT)。要设/取 10 个 key,发 10 次比一次发"批量命令"慢得多。Redis 提供了多个 M(multi)开头的命令:

# 批量设置/获取(减少网络往返)
MSET a 1 b 2 c 3        # 一次设多个 key
MGET a b c              # 一次取多个,返回:
# 1) "1"
# 2) "2"
# 3) "3"

# MSETNX:全部不存在时才设置(原子,要么全成功要么全失败)
MSETNX x 1 y 2          # 都不存在 -> OK
MSETNX x 1 z 3          # x 已存在 -> 全部不设置,返回 0

批量操作是性能优化的常见手段。如果你要从 100 个 key 取值,用 MGET 一次拿回远比 100 次 GET 快。更进一步还可以用 PIPELINE(管道)把多条不同命令打包,在事务篇会讲到。

6. 字符串内容操作

string 还支持把 value 当成"字符数组"或"位数组"操作:

# 字符串内容操作
SET msg "Hello"
APPEND msg " World"     # 追加,变成 "Hello World"
STRLEN msg              # 长度:11(字节数,中文一个字 3 字节)
SETRANGE msg 6 "Redis"  # 从下标 6 覆盖,变成 "Hello Redis"
GETRANGE msg 0 4        # 截取 [0,4]: "Hello"

# 位操作(Bitmap 的底层,可用于签到统计)
SETBIT sign:2024-08:1 5 1   # 把第 5 位置 1(表示第 5 天签到)
GETBIT sign:2024-08:1 5     # 读出 1
BITCOUNT sign:2024-08:1     # 这个月签到了几天

APPENDSTRLENGETRANGE 这些用于拼接/截取字符串的场景(比如拼接日志)。位操作 SETBIT / BITCOUNT 是 Bitmap 类型的底层——做"用户签到""在线状态"等大量布尔标志时极其省内存(一年签到记录只占 365 bit ≈ 46 字节)。

7. 实战:分布式锁

把前面学的组合起来,实现一个生产可用的分布式锁。这是 string 类型最经典的进阶用法,也是后端面试的高频题:

# 分布式锁经典实现
# 1. 加锁:NX + EX 保证"互斥"且"会自动释放"
SET lock:order:100 "uuid-abc" NX EX 30
# 返回 OK 表示加锁成功;(nil) 表示已被别人持有

# 2. 释放锁:必须验证 value 是自己的(防止误删别人的锁)
#    推荐用 Lua 脚本保证"判断 + 删除"原子性:
EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock:order:100 "uuid-abc"

# 3. 生产建议:
#    - value 用全局唯一 ID(如 UUID),不能写死
#    - 过期时间要大于业务最长执行时间,避免锁提前释放
#    - 长任务用"看门狗"续期(如 Redisson 框架自带)
#    - 不要用 DEL 直接删,会误删别人持有的锁

核心要点:value 必须是请求方唯一 ID(UUID),这样释放锁时才能区分"是不是自己持有的锁",避免误删别人的锁。"判断 value + 删除"必须是原子操作,所以用 Lua 脚本——Redis 单线程执行脚本期间不会被其他命令插入。

生产环境强烈建议直接用 Redisson(Java)或类似框架,它内置了"看门狗"自动续期、可重入锁、公平锁等高级特性,自己手写很难覆盖所有边界情况。

小结

本篇覆盖了 string 类型的全部常用命令:基础 SET/GET、SET 的 NX/EX 选项、过期管理 EXPIRE/TTL、原子计数器 INCR 家族、批量 MSET/MGET、字符串内容操作 APPEND/STRLEN、位操作 SETBIT,最后用 SET NX EX 组合实现了分布式锁。下一篇我们看 Redis 第二大类型——hash,适合存"对象"的数据结构。

← 上一篇 Redis 五大数据类型

下一篇 Redis 哈希命令详解

✈️💬