☰
Redis 的原理和使用(2)
2026/10/20 2:16:57 网站建设 项目流程

二、常见数据类型

Redis 对外提供 string、hash、list、set、zset 五种数据结构。

理解每种结构的特点与常见命令,是使用 Redis 的基础。

正式展开之前,先补充三块预备知识:全局命令、内部编码与单线程模型。

2.1 预备知识

(1)全局命令

无论哪种数据类型,都可以使用一组通用命令管理键,常见几个命令的语法与复杂度汇总在下表:

命令

作用

复杂度

KEYS pattern

按模式匹配全部键,会阻塞服务,生产环境谨慎使用。

O(n)

EXISTS key

判断键是否存在。

O(1)

DEL key [key ...]

删除一个或多个键,返回删除个数。

O(1)

EXPIRE key seconds

为键设置过期时间,到期后由 Redis 淘汰。

O(1)

TTL key

返回剩余过期时间,-1 表示未设置过期时间,-2 表示键不存在。

O(1)

TYPE key

返回键对应的数据类型。

O(1)

过期与淘汰的配合可以用一条时间轴理解:键在 n 秒内可以正常读取,n 秒过后被 Redis 淘汰,再次读取会得到不存在的提示:

EXPIRE 与 TTL 还提供毫秒级版本 PEXPIRE 与 PTTL,用于精度要求更高的场景。

以命令示例感受一下执行效果:

redis> SET mykey "Hello" "OK" redis> EXPIRE mykey 10 (integer) 1 redis> TTL mykey (integer) 10

(2)内部编码

对外是五种数据结构,对内每种结构还有多种底层实现,Redis 会根据数据规模与形态动态选择。

两个好处:一是改进内部编码时对外命令无感知,二是不同编码可以在各自场景发挥优势。五种结构与内部编码的对应关系见表:

数据结构

内部编码

string

int、embstr、raw

hash

ziplist、hashtable

list

ziplist、linkedlist

set

intset、hashtable

zset

ziplist、skiplist

内部编码可以通过 object encoding 命令查看:

127.0.0.1:6379> set hello world OK 127.0.0.1:6379> object encoding hello "embstr" 127.0.0.1:6379> rpush mylist e1 e2 e3 (integer) 3 127.0.0.1:6379> object encoding mylist "ziplist"

单线程能跑到每秒万级别,原因可以归结为三点:

  • 纯内存访问。内存响应时长大约 100 纳秒,这是高吞吐的重要基础。

  • 非阻塞 IO。Redis 使用 epoll 作为 IO 多路复用实现,把连接、读写、关闭都注册为事件,不在网络 IO 上浪费过多时间。

  • 单线程避免了线程切换与竞态消耗,也让数据结构和算法实现更简单。

单线程也有代价:单条命令执行时间过长,会拖住后面所有命令的响应。

因此 Redis 面向的是快速执行的场景,耗时操作需要谨慎设计。

2.2 String 字符串

字符串是最基础的类型。

两点留意:Redis 中所有键的类型都是字符串,其他数据结构建立在字符串之上;

字符串的值可以是普通字符串、JSON / XML 文本、数字,甚至图片、音频等二进制流,单个值上限 512 MB。

由于内部按二进制流保存,Redis 不处理编码问题,客户端用什么编码写入,就按什么编码存储。

(1)常用命令

字符串命令按功能可以分成几组,下表摘录高频命令:

命令

作用

SET key value

设置值,键已存在时覆盖并清除原有过期时间。

GET key

获取值。

MSET / MGET

批量设置 / 批量获取,一次往返完成多个键的操作。

INCR / DECR

值自增 / 自减 1,返回新值,可延伸出 INCRBY、DECRBY。

SETNX key value

键不存在时才设置成功,返回 1;已存在返回 0。

SETEX key seconds value

设置值的同时指定过期时间,等于 SET + EXPIRE。

批量命令的价值可以从数据上直观感受。

假设网络往返耗时 1 毫秒、命令执行耗时 0.1 毫秒,1000 次 get 与一次 mget 1000 个键的时间差距明显:

操作

时间

1000 次 get

1000 × 1 + 1000 × 0.1 = 1100 毫秒

1 次 mget 1000 个键

1 × 1 + 1000 × 0.1 = 101 毫秒

批量操作能显著提升吞吐,但单次批量携带的键数量也要控制,避免单条命令执行时间过长拖累整个服务。

(2)内部编码

字符串有三种内部编码,Redis 根据值的类型和长度动态选择:

  • int:值是整数时使用,节省内存。

  • embstr:值长度不超过 44 字节时使用,连续分配内存,读取更快。

  • raw:长度较长的字符串使用。

127.0.0.1:6379> set number 10086 OK 127.0.0.1:6379> object encoding number "int" 127.0.0.1:6379> set hello world OK 127.0.0.1:6379> object encoding hello "embstr" 127.0.0.1:6379> set long_str "a very very long string ..." OK 127.0.0.1:6379> object encoding long_str "raw"

(3)使用场景

字符串类型覆盖了缓存、计数、会话、验证码等常见需求:

  • 缓存:以 uid 为键存放用户信息,命中缓存直接返回,未命中再回源存储层并回写缓存。这套读写流程可以用下图概括:

  • 计数:视频播放数、评论数等用 INCR 系列命令完成自增

  • 会话:分布式 Web 服务把 Session 分散在各服务器上,用户刷新时可能被均衡到另一台机器导致重新登录

  • 短信验证码:限制用户每分钟获取验证码不超过 5 次,用字符串配合过期时间实现频控。

  • 分布式锁:利用 SETNX 的“仅当键不存在时写入”特性,作为锁的最简实现。

2.3 Hash 哈希

哈希类型指值本身又是一个键值对结构,形如 key = { field1: value1, field2: value2, ... }。

主流语言中它被称为哈希、字典或映射。

(1)常用命令

命令

作用

HSET key field value

设置指定字段的值,字段不存在时新建。

HGET key field

获取指定字段的值。

HMSET / HMGET

批量设置 / 批量获取多个字段。

HGETALL key

获取全部字段与值。

HINCRBY key field increment

字段值按指定步长自增。

HSETNX key field value

字段不存在时才设置成功。

127.0.0.1:6379> hset user:1 name james (integer) 1 127.0.0.1:6379> hset user:1 age 28 (integer) 1 127.0.0.1:6379> hgetall user:1 1) "name" 2) "james" 3) "age" 4) "28"

(2)内部编码

哈希的内部编码有两种:ziplist 与 hashtable。

一旦某个 value 超过 64 字节,或者字段个数超过 512,编码会转换为 hashtable:

127.0.0.1:6379> object encoding user:1 "ziplist" 127.0.0.1:6379> hset user:2 big_field "a value longer than 64 bytes ..." (integer) 1 127.0.0.1:6379> object encoding user:2 "hashtable"

(3)使用场景

哈希适合存放对象类数据,例如用户信息、商品信息。

使用哈希也要注意控制 ziplist 与 hashtable 之间的转换,转换过程会带来额外的内存消耗。

2.4 List 列表

列表类型用来存储多个有序的字符串,元素允许重复。

列表按插入顺序排列,两端操作效率高:lpush 从左侧插入,rpush 从右侧插入,lpop / rpop 从两端弹出。

(1)常用命令

命令

作用

LPUSH / RPUSH

从左侧 / 右侧插入一个或多个元素。

LPOP / RPOP

从左侧 / 右侧弹出元素。

LRANGE key start stop

按索引范围获取元素,支持正数与倒数下标。

LINDEX key index

获取指定索引位置的元素。

LLEN key

返回列表长度。

LREM key count value

删除指定数量的匹配元素。

BRPOP key [key ...] timeout

阻塞式弹出,队列为空时等待指定时间。

列表的获取与删除是两个不同操作:lindex 只读不改,列表长度不变;lrem 会真正删除元素,长度随之变化:

(2)内部编码

列表同样有两种编码。

元素较少且没有大元素时用 ziplist;元素个数超过 512,或者某个元素长度超过 64 字节,转换为 linkedlist:

127.0.0.1:6379> rpush listkey e1 e2 e3 (integer) 3 127.0.0.1:6379> object encoding listkey "ziplist" 127.0.0.1:6379> rpush listkey e1 e2 e3 ... e512 e513 (integer) 513 127.0.0.1:6379> object encoding listkey "linkedlist"

(3)使用场景

列表的经典用法是消息队列。

生产者用 lpush 从左侧插入,消费者用 brpop 阻塞式从右侧争抢,多个消费者客户端分摊消费,兼顾负载均衡与高可用:

队列还可以按频道拆分:消费者通过 brpop 监听不同的键,实现订阅不同频道的效果,频道之间互不干扰:

微博这类业务按时间倒序展示动态,列表天然有序且支持按索引分页,配合哈希保存每条的属性,可以拼出完整的分页列表。

2.5 Set 集合

集合类型存储无序、不重复的字符串。集合提供了丰富的集合间运算,适合标签、社交类业务。

(1)常用命令

命令

作用

SADD key member [member ...]

添加一个或多个元素,已存在的元素被忽略。

SREM key member [member ...]

删除一个或多个元素,返回实际删除个数。

SMEMBERS key

返回集合全部元素。

SISMEMBER key member

判断元素是否在集合中。

SCARD key

返回元素个数。

SPOP key [count]

随机弹出元素,适合抽奖场景。

SINTER / SUNION / SDIFF

求交集 / 并集 / 差集。

redis> SADD myset "one" "two" "three" (integer) 3 redis> SREM myset "one" (integer) 1 redis> SMEMBERS myset 1) "three" 2) "two"

(2)内部编码

集合有两种编码:元素都是整数且个数小于 set-max-intset-entries( 512)时使用 intset,节省内存;不满足条件时使用 hashtable:

127.0.0.1:6379> sadd setkey 1 2 3 4 (integer) 4 127.0.0.1:6379> object encoding setkey "intset" 127.0.0.1:6379> sadd setkey "hello" (integer) 1 127.0.0.1:6379> object encoding setkey "hashtable"

(3)使用场景

交集、并集、差集的概念可以用集合间的圆圈关系表示:

典型场景包括:

  • 标签系统:给文章打标签,用集合保存每个标签下的文章 id,支持按标签检索。

  • 社交关系:共同好友用 SINTER 求交集,关注关系用集合存粉丝 / 关注列表。

  • 抽奖:SPOP 随机弹出参与者,一次一个或多个。

2.6 Zset 有序集合

有序集合在集合“元素不重复”的基础上,为每个元素关联一个分数,元素按分数排序。用户排行榜就是它的典型应用:

列表、集合、有序集合三者容易混淆,异同点可以对比记忆:

数据结构

是否允许重复元素

是否有序

有序依据

应用场景

列表

是

是

索引下标

时间轴、消息队列

集合

否

否

无

标签、社交

有序集合

否

是

分数

排行榜、社交

(1)常用命令

命令

作用

ZADD key score member

添加元素并设置分数,元素已存在则更新分数。

ZRANGE key start stop

按分数从小到大的顺序返回指定索引范围元素。

ZREVRANGE key start stop

按分数从大到小的顺序返回元素。

ZSCORE key member

获取元素分数。

ZRANK / ZREVRANK

获取元素排名(正序 / 倒序)。

ZINCRBY key increment member

元素分数按步长自增。

(2)集合间操作

有序集合也支持交集、并集等运算,合并过程中按元素聚合分数,聚合方式有 sum、max、min,还可为各集合设置权重:

ZINTERSTORE dest 2 user:ranking:1 user:ranking:2 # 合并后各元素分数按 sum 方式聚合 # mike 168(91 + 77),martin 875(625 + 250),tom 1139(888 + 251)

(3)内部编码

有序集合的编码同样是 ziplist 与 skiplist 的组合:元素个数少于 128 且成员长度不超过 64 字节时用 ziplist,超过后转换为 skiplist:

127.0.0.1:6379> object encoding myzset "ziplist" 127.0.0.1:6379> zadd myzset 1.0 a 2.0 b ... 129 个元素 (integer) 129 127.0.0.1:6379> object encoding myzset "skiplist"

(4)使用场景

有序集合的应用集中在需要按分数排序与排名计算的场景:

  • 游戏排行:玩家分数作为 score,ZREVRANGE 取出前 N 名。

  • 热度榜单:按点击、销量等指标排序,ZINCRBY 实时更新分数。

  • 时间排序:把时间戳作为分数,实现按时间范围检索的数据结构。

2.7 键遍历

全库键遍历用 KEYS 会阻塞服务,Redis 提供了渐进式遍历命令 SCAN。

它每次返回一部分键,通过游标继续迭代,多个客户端可以分头并行扫描,过程中不会阻塞主线程:

127.0.0.1:6379> scan 0 1) "1024" # 下一次遍历的游标 2) 1) "key:1" 2) "key:12" ... 127.0.0.1:6379> scan 1024 1) "0" # 游标归零,遍历结束 2) 1) "key:24"

SCAN 也提供 MATCH 模式与 COUNT 提示参数,用于按模式过滤和调整单次返回数量。

数据库管理方面,Redis 默认有 16 个逻辑库(0-15),用 SELECT 切换,用 FLUSHDB 清空当前库、FLUSHALL 清空全部库。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询