前言
String 是 Redis 中最基础、最常用的数据类型。它可以存储字符串、整数、浮点数,甚至是序列化后的二进制数据(如图片、JSON),单个 value 最大可存 512MB。
String 类型常用命令
# 设置与获取
SET key value
GET key
# 批量操作
MSET k1 v1 k2 v2
MGET k1 k2
# 仅当 key 不存在时设置(常用于分布式锁)
SETNX key value
# 设置过期时间(秒 / 毫秒)
SETEX key 60 value
PSETEX key 60000 value
# 追加字符串,返回追加后的长度
APPEND key value
# 获取子串,范围是闭区间 [start, end]
GETRANGE key 0 3
# 从 offset 位置开始覆盖
SETRANGE key 6 "Redis"
# 返回字符串长度
STRLEN key
# 计数(value 必须是整数或浮点数)
INCR counter
INCRBY counter 10
DECR counter
DECRBY counter 5
INCRBYFLOAT price 0.5
典型使用场景
1. 缓存对象
把对象序列化成 JSON 字符串存入 Redis,读取时反序列化:
redis.set("user:1001", JSON.toJSONString(user));
User user = JSON.parseObject(redis.get("user:1001"), User.class);
2. 计数器
利用 INCR 的原子性实现文章阅读量、点赞数、接口限流等:
INCR article:1001:views
3. 分布式锁
SET lock:order uniqueValue NX EX 30
NX 保证只有 key 不存在时才设置,EX 30 设置过期时间防止死锁,value 存唯一标识用于释放锁时校验。
4. 全局唯一 ID
利用 INCR 的原子递增特性生成自增 ID。
底层实现:SDS 动态字符串
Redis 并没有直接使用 C 语言的字符串(以 \0 结尾的字符数组),而是自己实现了 SDS(Simple Dynamic String,简单动态字符串)。
SDS 的结构
struct sdshdr {
int len; // 记录 buf 中已使用的字节数,即字符串长度
int free; // 记录 buf 中未使用的字节数
char buf[]; // 字节数组,保存实际字符串
};
SDS 相比 C 字符串的优势
- O(1) 获取长度:C 字符串要遍历整个数组才能得到长度(O(N)),SDS 直接读
len字段,复杂度 O(1),这也是STRLEN命令极快的原因。 - 杜绝缓冲区溢出:C 字符串拼接(
strcat)可能覆盖已有数据。SDS 修改前会先检查free空间,不足则自动扩容。 - 减少内存重分配次数:通过空间预分配(修改后 len 小于 1MB 时分配 double 的 free 空间,大于 1MB 时多分配 1MB)和惰性释放(缩短时不立即回收内存,而是记在 free 里,后续再用)两种策略,减少频繁的内存分配。
- 二进制安全:C 字符串遇到
\0就认为结束,不能存二进制数据。SDS 用len判断结束,可以存储图片、序列化对象等任意二进制数据。 - 兼容部分 C 函数:SDS 的
buf末尾仍保留\0,可以复用一部分 C 标准库函数。
编码方式
String 类型有三种内部编码,Redis 会自动选择:
- int:value 是整数且能用 long 表示
- embstr:字符串长度小于等于 44 字节,redisObject 与 SDS 一次性分配在连续内存中,只需一次内存分配,适合短字符串(只读场景)
- raw:长度大于 44 字节,需要两次内存分配
可以用命令查看编码:
SET num 123
OBJECT ENCODING num # 返回 int
SET str "hello"
OBJECT ENCODING str # 返回 embstr
SET str "一个超过 44 字节的长字符串……"
OBJECT ENCODING str # 返回 raw
embstr 与 raw 的分界线是 44 字节,因为一个 redisObject 头部占 16 字节,SDS 头部占 3 字节(sdshdr8 结构),加上结尾 \0 的 1 字节,正好 44 + 16 + 3 + 1 = 64 字节,即一个 CPU 缓存行能容纳的 jemalloc 分配单元。
Redis 为什么这么快
1. 纯内存操作
数据全部存储在内存中,读写速度远超磁盘数据库,这是最根本的原因。
2. 单线程模型避免上下文切换
命令执行采用单线程(6.0 之前包括网络 IO 也是单线程),没有多线程的锁竞争和上下文切换开销,也不存在死锁问题。注意:Redis 的慢只慢在 IO 层面,纯内存操作本身极快,单线程足够撑起十万级 QPS。
3. IO 多路复用
基于 epoll(Linux)/ kqueue(FreeBSD)实现非阻塞 IO 多路复用,单线程可以同时监听大量客户端连接,谁有数据就处理谁。
4. 高效的数据结构
不同类型对应不同底层编码(如 SDS、跳表、压缩列表、整数集合等),针对不同数据规模选择最合适的结构,用空间换时间。
5. 6.0 引入多线程 IO
Redis 6.0 开始,网络请求的解析和响应回写可以由多个 IO 线程并行处理,但命令执行仍然是单线程,进一步提升了吞吐量。
总结
- String 是 Redis 最常用的类型,典型场景有缓存、计数器、分布式锁、全局 ID。
- 底层基于 SDS 实现,支持 int、embstr、raw 三种编码,44 字节是 embstr 和 raw 的分界线。
- Redis 快的原因:内存操作、单线程无切换、IO 多路复用、高效数据结构。