Redis String深度解析:从底层到五大实战场景

引言

String 是 Redis 里最基础、也是用得最频繁的数据类型。几乎每个系统的第一个 Redis 用法,都是拿它来"缓存一个值"。但如果你对 String 的理解只停留在 SET / GET,那就太可惜了------它不仅能存文本,还能做计数器、做分布式锁、做限流、做验证码,背后还藏着精心设计的底层结构。

这篇文章我会把 Redis 的 String 类型彻底拆开:先看它"是什么"和底层结构,再逐个讲常用命令,然后讲它的三种内部编码,最后用五个真实场景演示它到底能干什么。每个知识点都配命令示例和 Java 代码。

一、String 是什么

Redis 的 String 类型是"一个 Key 对应一个 Value"的简单映射,Value 是一个字符串。但它"字符串"的概念比你想的更宽:

  • 可以是普通文本("hello")
  • 可以是数字(100,还能自增)
  • 可以是序列化后的 JSON(存对象)
  • 可以是二进制数据(如图片,最大 512MB)

一句话:**Redis 的 String 是"二进制安全"的,它不关心内容是什么,原样存取。**​ 这也是它和 Memcached 等普通 KV 缓存相比更灵活的地方。

二、底层结构:SDS(简单动态字符串)

Redis 自己实现了一种字符串结构 SDS(Simple Dynamic String,简单动态字符串) ​,而不是直接使用 C 语言的 char[]。这是 Redis 高性能、高安全性的重要基础。

SDS 相比 C 字符串的几个关键改进:

  1. O(1) 获取长度 :C 字符串要遍历到 \0 才知道长度(O(n)),SDS 用 len 字段直接记录长度,STRLEN 命令秒回。
  2. 杜绝缓冲区溢出:C 字符串拼接时如果不检查空间会溢出,SDS 在修改前会先检查并自动扩容。
  3. 减少内存重分配:SDS 采用"空间预分配"和"惰性释放"策略------扩容时多分配一些空间,缩短时先不释放,避免频繁的内存分配/回收。
  4. 二进制安全 :C 字符串遇到 \0 就认为结束,无法存二进制;SDS 以 len 为准,可以安全存任意二进制内容。

这些细节解释了为什么 Redis 的 String 操作既快又稳。

三、常用命令详解

3.1 基础读写

plaintext

复制代码
127.0.0.1:6379> SET name zhangsan
OK
127.0.0.1:6379> GET name
"zhangsan"
127.0.0.1:6379> STRLEN name        # 字符串长度
(integer) 8
127.0.0.1:6379> DEL name           # 删除
(integer) 1

3.2 批量与追加

plaintext

复制代码
127.0.0.1:6379> MSET a 1 b 2 c 3   # 批量设置
OK
127.0.0.1:6379> MGET a b c         # 批量获取
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> APPEND name "hello" # 追加
(integer) 13

3.3 数字运算(计数器核心)

plaintext

复制代码
127.0.0.1:6379> SET count 10
OK
127.0.0.1:6379> INCR count         # 原子自增 1
(integer) 11
127.0.0.1:6379> INCRBY count 5     # 自增指定值
(integer) 16
127.0.0.1:6379> DECR count         # 原子自减 1
(integer) 15
127.0.0.1:6379> DECRBY count 5     # 自减指定值
(integer) 10

INCR 系列是原子操作,即使多个客户端同时执行也不会出现"丢计数"的问题,这是做计数器的关键。

3.4 带过期时间的设置

plaintext

复制代码
127.0.0.1:6379> SETEX code 60 123456   # 设置 60 秒后过期
OK
127.0.0.1:6379> TTL code               # 查看剩余过期时间(秒)
(integer) 55
127.0.0.1:6379> SET name zhangsan EX 60 NX  # 组合参数:60秒过期 + 不存在才设置

3.5 条件设置(分布式锁基础)

plaintext

复制代码
127.0.0.1:6379> SETNX lock 1      # 不存在才设置,返回 1;存在返回 0
(integer) 1
127.0.0.1:6379> SETNX lock 2
(integer) 0                        # 已存在,设置失败

SETNX(SET if Not eXists)是分布式锁的基石,配合过期时间就能防止死锁。

3.6 子串与覆盖

plaintext

复制代码
127.0.0.1:6379> SET hello world
OK
127.0.0.1:6379> GETRANGE hello 0 2    # 取子串
"wor"
127.0.0.1:6379> SETRANGE hello 0 W    # 覆盖指定位置
(integer) 5
127.0.0.1:6379> GET hello
"World"

四、String 的三种内部编码

Redis 会根据字符串的长度和内容,自动选择最省内存的内部编码:

编码 触发条件 特点
int 值是整数,且能用 long 表示 直接存整数,最省内存
embstr 长度 ≤ 44 字节 对象和内容连续分配,一次内存分配
raw 长度 > 44 字节 对象和内容分开分配

可以用 OBJECT ENCODING 命令查看:

plaintext

复制代码
127.0.0.1:6379> SET num 100
OK
127.0.0.1:6379> OBJECT ENCODING num
"int"
127.0.0.1:6379> SET short hello
OK
127.0.0.1:6379> OBJECT ENCODING short
"embstr"

了解编码有助于理解 Redis 的内存优化思路,面试也常被问到。

五、五大实战场景

5.1 缓存对象(JSON)

把对象序列化成 JSON 存进 String,是最常见的用法:

java

复制代码
public void cacheUser(StringRedisTemplate redis, User user) {
    // 存:对象转 JSON
    redis.opsForValue().set("user:" + user.getId(),
            JSON.toJSONString(user), 30, TimeUnit.MINUTES);

    // 取:JSON 转对象
    String json = redis.opsForValue().get("user:" + user.getId());
    User cached = JSON.parseObject(json, User.class);
}

5.2 计数器(浏览量、点赞数)

java

复制代码
// 原子自增,天然线程安全
Long views = redis.opsForValue().increment("article:100:views");

相比"先读出来 +1 再写回去"的写法,INCR 一步到位且不会并发丢失。

5.3 分布式锁

java

复制代码
public boolean tryLock(StringRedisTemplate redis, String key, String value, long expireSeconds) {
    // SET key value NX EX expireSeconds:不存在才设置 + 设置过期时间
    Boolean success = redis.opsForValue()
            .setIfAbsent(key, value, expireSeconds, TimeUnit.SECONDS);
    return Boolean.TRUE.equals(success);
}

public void unlock(StringRedisTemplate redis, String key, String value) {
    // 释放锁:校验是自己的锁再删(防止误删别人的锁,正式场景建议用 Lua 保证原子性)
    if (value.equals(redis.opsForValue().get(key))) {
        redis.delete(key);
    }
}

5.4 简单限流

利用 INCR + 过期时间,实现"某个 Key 在一段时间内最多访问 N 次":

java

复制代码
public boolean isAllowed(StringRedisTemplate redis, String userId, int maxCount, long windowSeconds) {
    String key = "rate:" + userId;
    Long count = redis.opsForValue().increment(key);
    // 第一次访问时设置过期窗口
    if (count != null && count == 1) {
        redis.expire(key, windowSeconds, TimeUnit.SECONDS);
    }
    return count != null && count <= maxCount;
}

5.5 验证码 / Session 共享

java

复制代码
// 存验证码,5 分钟过期
redis.opsForValue().set("sms:" + phone, code, 5, TimeUnit.MINUTES);

// 校验
String saved = redis.opsForValue().get("sms:" + phone);
boolean ok = code.equals(saved);

六、命令速查表

命令 作用
SET / GET 设置 / 获取
SETNX 不存在才设置
SETEX 设置并指定过期时间
MSET / MGET 批量设置 / 获取
APPEND 追加
INCR / DECR / INCRBY / DECRBY 原子自增 / 自减
STRLEN 获取长度
GETRANGE / SETRANGE 取子串 / 覆盖
GETSET 设置新值并返回旧值
TTL 查看剩余过期时间

总结

Redis 的 String 类型看似简单,实则内涵丰富:

  1. 底层是 SDS ,用 len 记录长度实现 O(1) 取长、自动扩容防溢出、预分配减少重分配、二进制安全。
  2. 命令不止 SET/GET :INCR 系列做计数器、SETNX + SETEX 做分布式锁、MSET/MGET 做批量、APPEND / GETRANGE 做字符串操作。
  3. 三种内部编码:int、embstr、raw,Redis 会自动按长度选最省内存的编码。
  4. 五大场景:缓存对象、计数器、分布式锁、限流、验证码/Session,都是生产环境的高频用法。

一句话总结:**String 是 Redis 的"万金油"------一个 Key 一个值,配上原子自增、过期时间和条件设置,就能解决缓存、计数、锁、限流等一大堆问题。**​ 把它吃透,Redis 就算入门了一半。

相关推荐
利威尔xu1 小时前
Go IM 源码实战(7):断线三天重连,水位算错就漏消息
redis·后端·微服务·面试·golang
鬼手点金1 小时前
缓存作为架构约束
开发语言·前端·人工智能·python·microsoft·缓存·架构
Random Glow1 小时前
聚簇索引:为什么主键顺序决定插入性能
数据库·mysql·性能优化·innodb·索引
软件技术NINI1 小时前
基于PHP和MySQL的快递代收点管理系统的设计与实现
开发语言·javascript·数据库·mysql·毕业设计·php·毕设
云上工程笔记1 小时前
2026年大模型Token成本测算推荐清单:输入输出、缓存、并发与峰值用量
人工智能·spring boot·缓存·token
吹什么轩2 小时前
MySQL 增删改查(CRUD)完全指南:从 INSERT 到 GROUP BY
数据库·mysql
为思念酝酿的痛2 小时前
MySQL表的约束
数据库·mysql
Agent世界2 小时前
百度OCR 2开源:KV缓存从47GB压到1.77GB
百度·缓存·开源·大模型·ocr
时间售卖铺2 小时前
MySQL存储过程流程控制全解析
运维·数据库·mysql·云原生