Redis 核心数据结构(一)—— String 与数字

0 阅读5分钟

别被“数据结构”三个字吓到,String 就是你熟悉的字符串,只不过它能自动做加减法。

本次导航:

  • String 在 Redis 里长什么样(内部编码偷偷帮你省内存)
  • 最常用的命令:SETGETINCRMSETSETNX……
  • 实战场景:计数器、缓存 Session、分布式锁的基础
  • 几个容易踩的坑

发车前温馨提醒:

如果你还没把 Redis 跑起来,建议先看上一篇:

👉 5 分钟跑起 Redis(Docker 版)

有了一个能敲命令的 Redis,咱们就可以进行实操了。

一、String:最不起眼但用得最多的类型

String 是 Redis 最基础的数据类型。基础到什么程度呢?你随便用一个缓存,八成存的就是 String。

几个特点:

  • 二进制安全:可以存图片、序列化对象、JSON 字符串,随便什么字节流。
  • 最大容量 512 MB——够用了,别真往里塞视频就行。
  • 内部会根据值的类型和长度,自动选用三种编码之一来省内存。

内部编码

编码条件说明
int值是 64 位有符号整数直接用 long 存储,不额外分配内存
embstr字符串长度 ≤ 44 字节一次分配连续内存,读取快
raw字符串长度 > 44 字节两次分配,适合大字符串

你可以用 OBJECT ENCODING key 看一眼当前 key 用的什么编码:

SET counter 100
OBJECT ENCODING counter   # 输出 "int"

SET short "hello"
OBJECT ENCODING short     # 输出 "embstr"

SET long "这里写一个超过44个字符的字符串看看会发生什么..."
OBJECT ENCODING long      # 输出 "raw"

Redis 自己会帮你选最优解,你只需要 SETGET 就行。

二、常用命令:从存读到加减乘除

基础三件套

SET name "zhangsan"   # 存
GET name              # 取 "zhangsan"
DEL name              # 删

批量操作(少跑几次网络,省时间)

MSET a 1 b 2 c 3
MGET a b c   # 返回 ["1","2","3"]

数字专用:原子加减

SET views 0
INCR views        # 1
INCR views        # 2
INCRBY views 10   # 12
DECR views        # 11
DECRBY views 5    # 6

原子性:多个客户端同时 INCR 同一个 key,Redis 会排队一个个执行,不会出现“加了一次只加了半次”的情况。这事很重要,后面做计数器全靠它。

带过期时间的写入(缓存标配)

# 写法一:SETEX
SETEX session:1001 3600 "user_data_json"

# 写法二:SET 带 EX 参数(推荐,更统一)
SET session:1001 "user_data_json" EX 3600

# 查看剩余存活时间(秒)
TTL session:1001

不存在才写入(分布式锁的基础)

SET lock:order_001 "locked" NX
# 如果 key 已存在,返回 nil,写入失败

NX 的意思是 "Not eXists"。

从 Redis 2.6.12 开始,SET 命令已经集成了 NXEXPX 等参数,不用再分开写 SETNX + EXPIRE 了。

三、实战场景:String 能帮你干什么

场景1:计数器(文章阅读量、限流)

# 用户点开一篇文章
INCR article:10086:views

# 获取阅读量
GET article:10086:views   # 输出 "12345"

限流:比如限制每个 IP 每分钟最多访问 10 次

# 伪代码:每次请求执行下面两步
current = INCR rate_limit:192.168.1.1
if current == 1:
    EXPIRE rate_limit:192.168.1.1 60
if current > 10:
    return "请求太频繁"

场景2:缓存 Session(存用户信息)

用户登录后,把用户对象序列化成 JSON 字符串存进去,设置过期时间:

SET user:session:abc123 '{"id":1001,"name":"zhangsan","role":"admin"}' EX 7200

下次请求来了,GET 出来反序列化,不用查数据库。

场景3:分布式锁(基础版)

多个服务同时抢一个锁,谁 SET NX 成功谁就拿到:

# 抢锁(5秒自动释放,防止死锁)
SET lock:order_pay "node1" NX EX 5

# 返回 OK 表示抢到,返回 nil 表示被别人占了

释放锁的时候直接 DEL

注意:这个基础版有个小问题——可能误删别人的锁(比如锁超时了,A 刚拿到,B 删了)。生产环境要用更严谨的 Lua 脚本版本,后面会有专门的文章讲。

四、几个容易踩的坑

坑1:KEYS * 别在生产环境用

KEYS * 会遍历所有 key,数据量大的时候 Redis 直接卡住几秒。

想看有哪些 key,用 SCAN 游标迭代:

SCAN 0 MATCH user:* COUNT 100

坑2:String 存大 JSON 要谨慎

一个 key 塞几十 KB 甚至 MB 的 JSON,会拖慢整个 Redis(读写大对象耗 CPU,网络传输也慢)。

解决办法:拆成 Hash 存,或者压缩后再存。

坑3:数字不要超过 2^63-1

Redis 的 int 编码只支持 64 位有符号整数(-9223372036854775808 到 9223372036854775807)。

超过这个范围,会转成字符串存储,但 INCR 会报错。

127.0.0.1:6379> SET big 9223372036854775807
OK
127.0.0.1:6379> INCR big
(error) ERR increment or decrement would overflow

坑4:忘了设过期时间,内存爆炸

缓存场景一定要给 key 加 EXEXPIRE,否则 Redis 会变成“永不删除的数据库”,直到内存撑爆。

五、小结

String 是 Redis 的“万金油”:

  • 存文本、数字、序列化对象都行
  • 数字操作用 INCR / DECR,原子性保证不错乱
  • 批量操作用 MSET / MGET 省网络
  • 分布式锁靠 SET NX EX
  • 缓存记得设过期时间

你不需要记住所有命令,记住 SETGETINCRMSETSET NX EX 这五个,就能应付 80% 的场景。


OK,本次导航结束,下次继续。