上一篇【第02篇】手把手安装Redis——Win/Mac/Linux三平台实战教程下一篇【第04篇】Redis配置文件那些事——从新手到老鸟必知的关键参数摘要如果说Redis是一辆超级跑车那五种核心数据类型就是它的引擎、刹车、变速箱、悬挂和轮胎——少一样都不行。本文用最接地气的方式把String、Hash、List、Set、ZSet五种类型从头到脚拆解一遍每种类型的内存模型长啥样核心命令有哪些什么场景该选哪种类型结尾还有一张五维选型速查表帮你一秒定位最佳数据类型。全程配ASCII图、代码块和对比表格建议收藏备用。一、全局总览——五分钟搞清五种类型在Redis的世界里所有数据都是 Key-Value 形式储存的。Key 永远是字符串Value 则可以是以下五种类型之一Redis 五大数据类型关系图 ┌─────────┐ │ Redis │ │ Key- │ │ Value │ └────┬────┘ │ ┌────┬────┬──────┼──────┬────┬────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ String Hash List Set ZSet Stream ... ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │V1│ │{}│ │[]│ │{}│ │{}│ └──┘ └──┘ └──┘ └──┘ └──┘ 最简单 装对象 消息队列 去重 排行榜先送你一张五秒选型速查表你要做的事选类型原因存个字符串、数字、二进制String最简单还能自增存用户信息、配置项多个字段Hash像一个迷你数据库行消息队列、最新动态列表List有序支持两端操作去重、共同好友、标签Set自动去重有交集并集排行榜、按分数排序ZSet带分数的集合天生排序二、String——最简单也最强大的瑞士军刀2.1 这是什么String 是最基础的类型一个 Key 对应一个 Value。虽然叫 String但可以存字符串、数字、二进制数据如图片 Base64、序列化对象最大能存512MB。String 内存模型 Key Value ┌────────┐ ┌──────────────┐ │ name │ ──────► │ 张三 │ └────────┘ └──────────────┘ ┌──────────┐ ┌──────────────┐ │ pv:blog │ ────► │ 1024 │ (实际存的是字符串1024) └──────────┘ └──────────────┘ ┌──────────────┐ ┌──────────────────────────────────┐ │ avatar:1001 │──►│ binary data, up to 512MB │ └──────────────┘ └──────────────────────────────────┘2.2 核心命令速查命令作用示例返回值SET key value设置值SET name RedisOKGET key获取值GET name“Redis”MSET k1 v1 k2 v2批量设置MSET a 1 b 2OKMGET k1 k2批量获取MGET a b[“1”,“2”]INCR key自增1INCR counter新值INCRBY key N自增NINCRBY counter 10新值DECR key自减1DECR counter新值APPEND key str追加字符串APPEND msg World新长度STRLEN key获取长度STRLEN name字符数GETSET key val设新值返回旧值GETSET name New旧值SETNX key val不存在才设SETNX lock 11或0SETEX key sec val设值过期时间SETEX token 300 xxxOKGETRANGE key s e子串GETRANGE name 0 2子串SETRANGE key o val覆盖指定位置SETRANGE name 0 Go新长度INCRBYFLOAT key f浮点数自增INCRBYFLOAT price 0.5新值2.3 实战缓存 计数器 分布式锁# 场景1缓存用户信息 SET user:1001{name:张三,age:28,city:北京}EX3600GET user:1001# {name:张三,age:28,city:北京}TTL user:1001# 3600 (秒)# 场景2文章点赞计数器 SET article:8848:likes0INCR article:8848:likes# 1INCR article:8848:likes# 2INCR article:8848:likes# 3DECR article:8848:likes# 2 (取消点赞)# 场景3简单的分布式锁 SETNX lock:order:2024001server-A# 返回1获取锁成功# 返回0锁已被别人持有# 业务处理完成后释放锁DEL lock:order:2024001⚠️ 注意上面的分布式锁是最简版没有过期时间、没有锁续期。生产环境请用 Redisson 或 SET … NX EX 组合。如果持有锁的进程崩了锁就永远不释放了——这个问题叫死锁。2.4 String 的底层编码String 类型在 Redis 内部有三种编码方式编码触发条件内存特点int值是整数且能用 long 表示直接用指针存数字省内存embstr字符串长度 ≤ 44字节一次申请连续内存raw字符串长度 44字节两次申请内存SDS独立存储# 查看值的编码SET int_val12345OBJECT ENCODING int_val# intSET short_strhelloOBJECT ENCODING short_str# embstrSET long_strxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx# 超过44字节OBJECT ENCODING long_str# raw三、Hash——迷你数据库行3.1 这是什么Hash 是一个 String 类型的 field-value 映射表特别适合存储对象。一个 Hash 里的 field 数量没有限制实际上受内存限制。Hash 内存模型 Key: user:1001 ┌──────────────────────────┐ │ │ │ field value │ │ ┌─────┐ ┌──────┐ │ │ │name │ ──► │张三 │ │ │ ├─────┤ ├──────┤ │ │ │age │ ──► │ 28 │ │ │ ├─────┤ ├──────┤ │ │ │city │ ──► │北京 │ │ │ ├─────┤ ├──────┤ │ │ │level│ ──► │ 15 │ │ │ └─────┘ └──────┘ │ │ │ └──────────────────────────┘ 对比 MySQL 的存储方式 MySQL: Redis Hash: ┌──────────────────┐ user:1001 │ user_info 表 │ ├── name → 张三 │ 按字段顺序存储 │ ├── age → 28 │ 需要整行读写 │ └── city → 北京 │ │ 可以单独读写某字段 └──────────────────┘3.2 核心命令速查命令作用示例返回值HSET key field val设置一个字段HSET user:1 name 张三1或0HGET key field获取一个字段HGET user:1 name“张三”HMSET key f1 v1 f2 v2批量设置字段HMSET u1 name 李四 age 25OKHMGET key f1 f2批量获取字段HMGET u1 name age[“李四”,“25”]HGETALL key获取所有字段HGETALL user:1所有fieldvalHDEL key field删除字段HDEL user:1 age1HEXISTS key field字段是否存在HEXISTS user:1 name1或0HLEN key字段数量HLEN user:13HINCRBY key field N字段值加NHINCRBY user:1 age 1新值HKEYS key获取所有字段名HKEYS user:1[“name”,“age”,“city”]HVALS key获取所有值HVALS user:1[“张三”,“28”,“北京”]HSTRLEN key field字段值长度HSTRLEN user:1 name63.3 实战用户信息 购物车# 场景1存储用户信息 HSET user:1001 name张三age28city北京emailzhangsanexample.com# 单独获取某个字段HGET user:1001 name# 张三# 批量获取HMGET user:1001 name age city# 1) 张三# 2) 28# 3) 北京# 年龄加一岁HINCRBY user:1001 age1# 29# 看看有多少个字段HLEN user:1001# 4# 场景2购物车 # 用户1001的购物车商品ID → 数量HSET cart:1001sku_0012HSET cart:1001sku_0021HSET cart:1001sku_9993# 查看购物车HGETALL cart:1001# 1) sku_001 2) 2# 3) sku_002 4) 1# 5) sku_999 6) 3# 商品数量1HINCRBY cart:1001sku_0011# 3# 移除某个商品HDEL cart:1001sku_999# 1# 购物车里还有几个东西HLEN cart:1001# 2String 也能存对象SET user:1001 {name:张三,age:28}但 Hash 的好处是可以单独读写某个字段不用把整个 JSON 序列化/反序列化。对于频繁修改某个字段的场景如更新用户等级Hash 完胜。3.4 Hash vs String 存储对象对比维度String (存JSON)Hash读整个对象一次 GET一次 HGETALL读单个字段需反序列化一次 HGETO(1)更新单个字段序列化整个对象再SET一次 HSETO(1)字段值自增不行HINCRBY内存占用对象小时差不多对象大时Hash更省过期控制整个Key过期整个Key过期不能按field设置嵌套对象天然支持不支持需要扁平化四、List——你的消息队列已经到位4.1 这是什么List 是一个有序的字符串链表可以从两端推入/弹出元素。它就像一个双端队列常被用来做消息队列、最新动态列表、分页列表。List 内存模型 Key: messages ┌─────────────────────────────────────┐ │ │ │ HEAD(左) TAIL(右)│ │ ┌──────────────────────────┐ │ │ │ msg1 → msg2 → msg3 → msg4│ │ │ └──────────────────────────┘ │ │ │ └─────────────────────────────────────┘ 操作示意图 LPUSH ──► [HEAD] ... [TAIL] ◄── RPUSH (推入) LPOP ◄── [HEAD] ... [TAIL] ──► RPOP (弹出) 索引 0 1 2 3 msg1 → msg2 → msg3 → msg44.2 核心命令速查命令作用示例返回值LPUSH key val从左边插入LPUSH list a列表长度RPUSH key val从右边插入RPUSH list z列表长度LPOP key从左边弹出LPOP list弹出元素RPOP key从右边弹出RPOP list弹出元素LRANGE key s e获取范围的元素LRANGE list 0 -1元素列表LLEN key列表长度LLEN list长度LINDEX key idx按索引取元素LINDEX list 0元素值LSET key idx val设指定索引的值LSET list 0 newOKLTRIM key s e裁剪只保留范围LTRIM list 0 99OKBLPOP key t阻塞左弹出(等t秒)BLPOP queue 0[key, val]BRPOP key t阻塞右弹出BRPOP queue 5[key, val]LINSERT k before a b在a前面插入bLINSERT list before a X新长度RPOPLPUSH s ds右弹,d左入(原子)RPOPLPUSH src dst弹出元素4.3 实战消息队列 最新动态# 场景1简单消息队列 # 生产者RPUSH task:queuesend_email:1001RPUSH task:queuecompress_image:2001RPUSH task:queuegenerate_report:3001# 消费者阻塞等待0表示永久等待BRPOP task:queue0# 1) task:queue# 2) send_email:1001# 场景2最新10条动态 # 新动态来了就往左边塞LPUSH news:latest张三 发布了文章# [1]LPUSH news:latest李四 更新了相册# [2, 1]LPUSH news:latest王五 点赞了你的评论# [3, 2, 1]# 只保留最新三条裁剪掉旧的LTRIM news:latest02# 查看最新动态LRANGE news:latest0-1# 1) 王五 点赞了你的评论# 2) 李四 更新了相册# 3) 张三 发布了文章# 场景3粉丝列表分页 # 先塞一堆粉丝IDforiin{1..50};doredis-cli RPUSH fans:1001user$i;done# 第一页每页10条LRANGE fans:100109# 第二页LRANGE fans:100110194.4 为什么 List 能当队列用基于 List 的 FIFO 队列模型 生产者 Redis List 消费者 ┌──────┐ RPUSH ┌──────────────────┐ BLPOP ┌──────┐ │ P1 │──────────► │ m1 m2 m3 m4 m5 ...│◄─────────── │ C1 │ └──────┘ RPUSH │ │ BLPOP └──────┘ ┌──────┐──────────► │ │◄─────────── ┌──────┐ │ P2 │ └──────────────────┘ │ C2 │ └──────┘ └──────┘ 优点简单、快 缺点 - 没消费者组Redis 5.0 Stream 解决了 - 不支持消息确认和重试 - 不支持消息回溯⚠️ 注意RPOPLPUSH是一个特别有用的命令把元素从源列表弹出并推入目标列表且这个操作是原子的。常用于实现可靠队列——消费者取走消息的同时把它放入处理中队列处理完了再从处理中队列删除。这样即使消费者崩了消息也不会丢。五、Set——自动去重的朋友圈5.1 这是什么Set 是一个无序的、元素不重复的字符串集合。底层是哈希表实现添加删除 O(1)还支持交集SINTER、并集SUNION、差集SDIFF。Set 内存模型 Key: tags:article:1001 ┌────────────────────┐ │ ┌──────────────┐ │ │ │ Redis ────────┼─┼── 哈希表实现 │ │ 缓存 ────────┼─┼── 无序 │ │ 性能优化 ────┼─┼── 元素唯一 │ │ 内存数据库 ──┼─┼── O(1) 增删查 │ └──────────────┘ │ └────────────────────┘5.2 核心命令速查命令作用示例返回值SADD key m1 m2添加元素SADD set a b实际添加数SREM key m删除元素SREM set a1或0SMEMBERS key查看所有元素SMEMBERS set元素列表SCARD key元素个数SCARD set数量SISMEMBER key m是否在集合中SISMEMBER set a1或0SRANDMEMBER key N随机取N个SRANDMEMBER set 3随机3个SPOP key N随机弹出N个SPOP set 2被弹出的元素SUNION k1 k2并集SUNION s1 s2并集元素SINTER k1 k2交集SINTER s1 s2交集元素SDIFF k1 k2差集SDIFF s1 s2s1有s2没有的SUNIONSTORE d s1 s2并集存到dSUNIONSTORE rst s1 s2元素数量SINTERSTORE d s1 s2交集存到dSINTERSTORE rst s1 s2元素数量SMOVE s d m移动元素SMOVE s1 s2 a1或05.3 实战标签系统 共同好友# 场景1文章标签 SADD article:1001:tagsRedis缓存数据库性能优化SADD article:1002:tagsRedis集群数据库SADD article:1003:tagsMySQL数据库索引# 文章1001有哪些标签SMEMBERS article:1001:tags# 1) Redis 2) 缓存 3) 数据库 4) 性能优化# 有8个标签吗SCARD article:1001:tags# 4 (自动去重重复添加也不会多)# 文章1001和1002的共同标签SINTER article:1001:tags article:1002:tags# 1) Redis# 2) 数据库# 推荐1001和1002的标签并集排除掉1003的SUNION article:1001:tags article:1002:tags SDIFF(上面的并集)article:1003:tags# 场景2共同好友 SADD friends:zhangsanlisiwangwuzhaoliusunqiSADD friends:lisizhangsanwangwuzhouba# 共同好友交集SINTER friends:zhangsan friends:lisi# wangwu# 你可能认识的人张三好友中李四没有的SDIFF friends:zhangsan friends:lisi# lisi zhaoliu sunqi# 场景3抽奖随机取不重复 SADD lottery:pooluser1user2user3user4user5SADD lottery:pooluser6user7user8user9user10# 随机抽取3个中奖者不重复SPOP lottery:pool3# 1) user7# 2) user2# 3) user9# 看看池子还剩多少人SCARD lottery:pool# 7六、ZSet——排行榜的终极神器6.1 这是什么ZSetSorted Set是 Set 的升级版——每个元素都有一个score分数double类型Redis 会根据 score 自动为元素排序。它是排行榜、优先级队列、延时队列的天然数据结构。ZSet 内存模型 Key: game:rank ┌────────────────────────────────┐ │ │ │ ┌─────────┐ ┌─────────────┐ │ │ │ 跳跃表 │ │ 成员 → 分数 │ │ │ │ (按分数) │ │ ┌───────────┐│ │ │ │ │ │ │ 玩家A→1200││ │ │ │ Level 3 │ │ │ 玩家B→950 ││ │ │ │ Level 2 │ │ │ 玩家C→1200││ │ ← 同一分数按字母序排 │ │ Level 1 │ │ │ 玩家D→880 ││ │ │ │ │ │ └───────────┘│ │ │ └─────────┘ │ (哈希表) │ │ │ └─────────────┘ │ └────────────────────────────────┘ 底层双重结构字典 (dict) 跳跃表 (skiplist) - 字典根据成员快速定位 O(1) - 跳跃表按分数排序和范围查询 O(log N)6.2 核心命令速查命令作用示例返回值ZADD key s m添加成员分数ZADD rank 100 alice实际添加数ZREM key m删除成员ZREM rank alice1或0ZSCORE key m获取成员分数ZSCORE rank alice100ZINCRBY key N m成员分数加NZINCRBY rank 50 alice新分数ZRANGE key s e按分数从小到大ZRANGE rank 0 -1成员列表ZREVRANGE key s e按分数从大到小ZREVRANGE rank 0 -1 WITHSCORES成员分数ZRANGEBYSCORE key min max按分数范围取ZRANGEBYSCORE rank 100 200范围内的成员ZRANK key m排名升序最小为0ZRANK rank alice排名ZREVRANK key m排名降序最大为0ZREVRANK rank alice排名ZCARD key成员个数ZCARD rank数量ZCOUNT key min max分数范围内的个数ZCOUNT rank 100 200数量ZREMRANGEBYSCORE key min max按分数范围删除ZREMRANGEBYSCORE rank 0 100删除数ZREMRANGEBYRANK key s e按排名范围删除ZREMRANGEBYRANK rank 0 10删除数ZUNIONSTORE dst N k1... w1...并集权重ZUNIONSTORE r 2 s1 s2 weights 1 2结果数6.3 实战游戏排行榜 热点文章# 场景1游戏积分排行榜 ZADD game:rank1200playerAZADD game:rank950playerBZADD game:rank1200playerC# 同分按字典序排ZADD game:rank880playerDZADD game:rank1350playerE# 查看Top 3ZREVRANGE game:rank02WITHSCORES# 1) playerE 2) 1350# 3) playerA 4) 1200# 5) playerC 6) 1200# playerB的排名第几名ZREVRANK game:rankplayerB# 4 (第4名从0开始数)# 分数在1000-1300之间的玩家ZRANGEBYSCORE game:rank10001300WITHSCORES# 3) playerA 4) 1200# 5) playerC 6) 1200# playerE 又赢了100分ZINCRBY game:rank100playerE# 1450# 场景2热门文章排行榜 # 用时间戳的加权作为分数ZADD hot:articles100Redis入门# 初始热度100ZADD hot:articles250Spring实战ZADD hot:articles88Vue3教程# 给Redis入门增加热度ZINCRBY hot:articles10Redis入门# 110# 今日最热的3篇文章ZREVRANGE hot:articles02WITHSCORES# 场景3延时队列 # 把任务加入延时队列score是执行时间戳毫秒ZADD delay:queue1716700000000task:send_sms_1001ZADD delay:queue1716700005000task:send_sms_1002ZADD delay:queue1716700010000task:send_sms_1003# 获取当前时间之前应该被执行的第一个任务ZRANGEBYSCORE delay:queue01716700003000LIMIT01# task:send_sms_1001# 执行完删除ZREM delay:queuetask:send_sms_1001七、选型决策指南——一张表搞定五种类型选型决策树 你要做什么 │ ├── 存简单的值字符串/数字 │ └──► String (SET/GET/INCR) │ ├── 存对象的多个属性 │ ├── 需要单独更新某个属性 → Hash (HSET/HGET) │ └── 整体读写居多 → String存JSON也行 │ ├── 存一个有序列表 │ ├── 做队列FIFO→ List (RPUSH/BLPOP) │ ├── 做栈 (LIFO) → List (LPUSH/LPOP) │ └── 做排行榜 → ZSet (ZADD/ZREVRANGE) │ ├── 存不重复的元素集合 │ ├── 需要集合运算交/并/差→ Set (SADD/SINTER/SUNION/SDIFF) │ ├── 需要按分数排序 → ZSet │ └── 随机取元素 → Set (SRANDMEMBER/SPOP) │ └── 既要元素唯一又要排序 └──► ZSet (ZADD/ZRANGE/ZRANK)五种类型终极对比维度StringHashListSetZSet有序性N/A无序有顺序无序按score排序唯一性N/Afield唯一可重复元素唯一元素唯一底层结构SDSziplist/hashtablequicklistdictdictskiplist时间复杂度(增删)O(1)O(1)O(1)O(1)O(logN)时间复杂度(查)O(1)O(1)O(N)按索引O(1)O(logN)范围查询GETRANGEHGETALLLRANGE无ZRANGEBYSCORE原子自增INCR/INCRBYHINCRBY无无ZINCRBY最大容量512MB2^32-1字段2^32-1元素2^32-1元素2^32-1元素集合运算无无无交集/并集/差集并集(可加权)/交集典型场景缓存/计数器对象/配置队列/栈/日志标签/好友排行榜/延时队列内存效率一般字段少时高效中等中等中等八、进阶思考底层数据结构五种类型各有面子对外接口和里子底层编码。了解底层结构有助于你理解为什么某些操作快、为什么内存占用高。Redis 数据类型的面子与里子 ┌──────────────────────────────────────────────┐ │ 面 子 (API) │ │ String │ Hash │ List │ Set │ ZSet │ └──────┬───────┬───────┬───────┬───────┬───────┘ │ │ │ │ │ ┌────▼──┐ ┌▼──────────▼──┐ ┌▼───────▼──┐ │ 里 子 │ │ 里 子 │ │ 里 子 │ └───────┘ └──────────────┘ └───────────┘ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ raw int ziplist hashtable quicklist dictskiplist embstr键值枚举表类型编码触发条件优势Stringint值是整数直接存数值零额外内存Stringembstr字符串 ≤ 44 字节一次分配内存紧凑Stringraw字符串 44 字节支持扩容Hashziplist字段少 值短连续内存节省空间Hashhashtable字段多或值长O(1)查找Listquicklist3.2 默认ziplist链表组合Setintset全整数且个数少紧凑的整数数组Sethashtable含字符串或个数多通用哈希表ZSetziplist元素少 值短连续内存紧凑ZSetskiplistdict元素多或值长高效的排序和查找# 可以查看每种类型的编码OBJECT ENCODING mykey本篇小结五种数据类型各有所长用对了事半功倍用错了事倍功半。记住一个简单的决策口诀单值用 String对象用 Hash列表用 List去重用 Set排序用 ZSet。这篇文章覆盖了五种类型 60 条命令建议收藏到浏览器书签用到时快速查阅。下一篇文章我们要把目光转向 Redis 的灵魂——配置文件 redis.conf那些让你从新手变成老鸟的关键参数。上一篇【第02篇】手把手安装Redis——Win/Mac/Linux三平台实战教程下一篇【第04篇】Redis配置文件那些事——从新手到老鸟必知的关键参数