redis数据结构之字符串

189 阅读7分钟

你以为Redis的字符串就是"Hello World"?Too young too simple!今天咱们要掀开这位"字符串精分患者"的三重人格面具,准备好瓜子饮料,老司机带你飙车Redis字符串的魔幻底层世界!(温馨提示:阅读本文可能会引起颅内高潮,请系好安全带)

一、你以为的字符串 vs Redis的字符串

普通青年眼里的字符串:"不就是一段文本嘛"
Redis工程师眼里的字符串:"这货是个变形金刚啊!"

举个栗子:当你执行SET age 18时,你以为存的是"18"?错!Redis偷偷把它变成整数存着。但是当你APPEND age "岁"的瞬间——见证奇迹的时刻!这个"18"会瞬间黑化成raw编码的字符串,像极了被老板临时加需求时的程序员。

(此时不妨打开redis-cli,输入OBJECT ENCODING key,你会看到三种精分现场:int、embstr、raw)

1.1 APPEND age "岁" 解释

简单来说:你把一个数字硬生生改造成了"混血字符串"!

来,咱们把这段代码扒光了看:
1️⃣ 原始状态:SET age 18 → Redis偷懒存成int编码(内存里就是个光溜溜的整数)
2️⃣ 作死操作:APPEND age "岁" → Redis突然发现你要搞事情!
3️⃣ 暗黑变身:把原本的18从int强制转成字符串"18",再拼接"岁" → 变成"18岁"
4️⃣ 精分现场:编码瞬间从int叛变为raw编码(此时OBJECT ENCODING age会显示"raw")

举个栗子
这就好比你在Excel里写了个数字18,突然在后面手打了一个"岁"字,Excel惊恐地大喊:"卧槽这单元格不纯了!"然后把整个单元格格式从"数字"改成"文本"(还偷偷给你标个绿色三角警告)


灵魂图解版

[ 原内存布局 ]  
|  int编码特区  |  
|      18      | ← 岁月静好的美男子  

[ APPEND攻击后 ]  
| SDS头(len=3) | 18岁 | ← 突然长出三头六臂的狂战士

程序员の经典翻车场景

  • 你以为的计数器:INCR age → 美滋滋+1
  • 手滑后的灾难:APPEND age "岁" → 再INCR age时Redis怒吼:"Error! value is not an integer"
    (此时你的表情 → 就像试图给榴莲削皮的猫)

终极生存指南
✅ 用GET age查看值时:返回的是"18岁"(人畜无害)
✅ 用TYPE age查看类型时:仍然是string(深藏不露)
✅ 用OBJECT ENCODING age查看编码时:暴露了raw的真面目(当场社死)

所以APPEND的精髓是:让Redis字符串完成从"纯情少年"到"社会老油条"的黑化仪式!


二、解剖SDS:字符串界的瑞士军刀

Redis私藏的秘密武器叫SDS(Simple Dynamic String) ,这货简直就是字符串界的哆啦A梦!看看它的解剖结构:

struct sdshdr {
    int len;    // 已用长度
    int free;   // 剩余空间
    char buf[]; // 柔性数组
};

你以为的优势:

  1. O(1)时间复杂度获取长度(再也不用strlen()扫大街了!)
  2. 杜绝缓冲区溢出(告别"Segmentation fault"的恐怖片)

想不到的骚操作:

  • 空间预分配:当SDS需要扩容时,会多分配一倍空间,就像吃货点外卖总要多加两个菜
  • 惰性空间释放:缩短字符串时不立即回收内存,而是记在小本本(free字段)上,像极了舍不得删前女友照片的男生

三、三大精分现场:编码的千层套路

1. int编码——扮猪吃老虎

  • 触发条件:纯数字且≤long能表示的范围(别杠,64位系统是9223372036854775807)
  • 实战场景:计数器(INCR/DECR)、限流器、分布式ID生成
  • 骚操作预警:SETNX lock:order 1 做分布式锁?小心!超过数字范围会突然变成raw编码哦

2. embstr编码——精致小仙女

  • 诞生条件:≤39字节的短字符串(Redis5之后是44字节,版本不同可能不同)
  • 内存布局:和redisObject头挨着头存放,像热恋中的情侣
  • 死亡现场:对embstr做修改操作,立即黑化成raw编码,堪比卸妆后的女朋友

3. raw编码——狂野男孩

  • 适用场景:大文本、频繁修改的字符串
  • 内存管理:采用经典SDS结构,适合放飞自我
  • 冷知识:即使1MB的大字符串,修改时也只需要O(1)时间复杂度(当然网络传输时间另算)


四、实战中的骚操作指南

1. 位图黑魔法

  • SETBIT user:checkin 10086 1 看似字符串,实则是SDS实现的位数组
  • 适合场景:用户签到统计(1亿用户每日签到仅需12MB!)

2. JSON存储的陷阱

  • 把整个JSON对象存成字符串?小心!修改时要反序列化->修改->序列化,不如用Hash结构
  • 但如果是读多写少的热点数据...真香!

3. 分布式锁的暗战

  • SET lock:order UUID NX EX 30 用字符串实现,但要注意value要唯一(别用简单数字!)

4. 对象存储的奇技淫巧

  • 把图片序列化成base64存字符串?Redis:我劝你善良!不如用更省内存的Hash+字段拆分

五、来自灵魂的拷问

❓ 为什么大字符串推荐用hash分桶存储?
(剧透:和Redis的持久化机制、内存碎片有关)

5.1、血泪案例:500KB的JSON之死

某程序员把用户画像存成SET user:123 {...500KB JSON...},结果:

  1. 读取时:GET操作像拉屎便秘,网络传输卡成PPT
  2. 修改时:反序列化+修改+序列化的三连击,CPU直接螺旋升天
  3. 持久化时:bgsave瞬间内存翻倍,OOM杀手闪现收人头

5.2、分桶存储の四大求生法则

5.2.1.内存碎片の终结者

  • 大字符串:需要连续内存空间(就像非要找能停航母的停车位)
  • Hash分桶:拆成HSET user:123 profile_base "{...}" profile_extra "{...}"(像把衣柜拆成多个抽屉)
  • 底层玄机:jemalloc分配内存时,64字节为单位更高效(碎肉 vs 肉排)

5.2.2.持久化の闪电侠

# RDB持久化时(想象搬运工场景)
大字符串 → "扛着整个席梦思床垫过窄门"
Hash分桶 → "分批搬运床垫弹簧"
  • COW(写时复制) :修改大字符串会复制整个对象,而Hash只复制被修改的字段

5.3.网络IOの瘦身教练

# 需要读取部分数据时
GET user:123 → 被迫传输500KB(就像下载小电影必须看完整部)
HGET user:123 profile_base → 只传100KB(精准拉进度条)

5.4.过期时间の微观管理

  • 大字符串:只能整个key设置过期(像超市所有商品同一天过期)
  • Hash分桶:不同字段可伪过期(虽然不能真过期,但能标记为无效)

六、终极生存指南

  1. 短小精悍用embstr:像保存验证码、配置项这种小可爱
  2. 频繁修改用raw:像实时变化的股市数据这种躁动症患者
  3. 纯数字用int:计数器场景省内存还快,但小心操作超出范围
  4. 超大文本要谨慎:超过10KB的字符串考虑其他存储方案

现在你终于知道,Redis的字符串不是青铜而是王者!下次面试被问到"Redis字符串底层实现"时,请优雅地甩出SDS结构,再漫不经心地说说编码转换的临界值,最后用位图案例镇场——深藏功与名!

(偷偷告诉你,Redis作者Salvatore Sanfilippo曾经说过:"我用SDS就是为了让你们这些面试者有的可聊!")