认识Redis
Redis是在分布式系统中发挥作用的,如果只在单机程序中,直接通过变量存储数据的方式比使用Redis更佳。
Redis 本质上是一个驻留内存的键值数据库,它采用客户端-服务器架构,并通过标准网络协议提供服务。这使得运行在任意网络位置的应用进程,都能以极低的延迟读写 Redis 服务器内存中的数据,从而实现高效的分布式数据共享与状态管理。
Redis的性能非常好,所有的数据在内存中操作,读写的速度极快,可以轻松应对每秒数十万的请求,可以轻松处理高并发场景;并且提供丰富的数据结构,这些数据结构还支持原子操作。但Redis虽然支持持久化,但其持久化是异步的,并且存储的成本极高,内存比硬盘的价格昂贵太多,只支持简单的查询,没有SQL。
因此我们需要查询效率又高但又希望数据可以持久化时,我们就需要将Redis和MySQL结合起来使用,但这样的编码难度又会大大提升(数据修改会涉及到MySQL和Redis间的数据同步)。
了解分布式系统
单机架构
单机架构是软件系统中最基础、最简单的架构形式。它指整个应用程序及其所有组件都部署在一台物理机或虚拟机上的架构模式。在互联网早期和许多中小型项目中,这是最常见的架构。
数据库和应用分离模式
如果业务进一步增长,用户量和数据量水涨船高,一台主机难以应付的时候,就需要引入更多的主机,更多的硬件资源 。(单台主机的硬件资源是有上限的,包括CPU、内存、磁盘、网络)
服务器每次收到一个请求就会消耗一定的资源,如果同一时间接收到大量的请求就可能会导致服务器资源不足,这段就会导致请求处理的时间变长甚至出错。
主要的两种解决方案
1.开源 增加更多的硬件资源,但单个主机上面的增加资源的能力有限(取决于主板扩展能力) 这就需要引入多台主机,需要对机器做软件上的调整和适配,这就是分布式系统。但系统的复杂程度会大大提高。
2.节流 软件优化(通过性能测试找出什么地方出现性能瓶颈然后对症下药 对程序员要求较高)

关于图中的负载均衡
用户请求会经过负载均衡器,然后被分发到对应的应用服务器/网关服务器(单独的一个服务器),再通过应用服务器中的Web服务访问存储服务器中的数据库获得相应的数据。
关于数据库的读写分离

实际的应用场景中,读的频率是比写的频率更高的。核心思路是将数据库的读操作(如 SELECT)和写操作(如 INSERT/UPDATE/DELETE)拆分到不同的数据库实例上执行:通常搭建一个主库(Master)专门处理写请求,同时部署若干从库(Slave)通过主从复制机制同步主库数据,所有读请求则分发到从库执行。这种架构既可以通过增加从库数量横向扩展读能力,缓解单库的读压力,又能避免读写操作在同一实例上的资源竞争,提升整体响应效率;同时主库仅聚焦写操作,也降低了因大量读请求导致写操作阻塞的风险。
主服务器一般是一个,从服务器可以有多个,同时数据库通过负载均衡的方式,让应用服务器进行访问。
引入缓存
由于数据库基于磁盘存储,其访问速度受限于物理I/O,在面对高并发查询时容易成为性能瓶颈。因此,我们引入Redis作为缓存层,通过将"热数据"(被频繁访问的数据)存储在内存中,实现数据的"冷热分离"。这能极大提升数据读取速度,有效缓解数据库压力。
分库分表
引入分布式系统不光光是去应对更高的请求量,同时也会需要应对更大的数据量,可能会出现一台服务器存不下数据的情况,那么就会使用多台主机来存储数据库(分库分表)。

微服务架构

之前的应用服务器中做了很多的业务处理,这就会导致服务器的代码越来越复杂,为了方便后期维护,就可以将一个复杂的服务器拆分成更多功能单一、体积更小的服务器(服务器的种类和数量增加了)。
微服务的代价
1.系统性能下降 拆出来的更多的服务,多个功能之间要更依赖网络通信,网络通信的速度很可能比访问硬盘更慢。
2.运维复杂程度指数级上涨 因为微服务涉及到涉及容器编排、服务网格、配置中心、API网关等一系列复杂的基础设施,运维成本上升。
Redis的核心特性
1.在内存中存储数据
2.可编程 针对Redis的操作,可以直接通过简单的交互式命令进行操作,也可以通过一些脚本的方式进行一些批量操作。
3.可扩展 在Redis原有的功能基础上进行扩展,Redis会提供一组API(C/C++、Rust语言支持)Redis自身已经提供了许多的数据结构,如果我们还需要其他自定义的数据结构就可以通过扩展来支持
4.持久化 Redis把数据存储在内存上,但内存上的数据是易失的,因此数据需要存储到硬盘上,这相当于将数据备份,方便重启后进行数据恢复
5.集群化 Redis作为一个分布式系统的中间件,支持水平扩展(类似于分库分表)。通过引入多个主机部署多个Redis节点,每个Redis存储数据的一部分。
6.高可用 当主节点出现故障时,系统能够自动、快速、无数据丢失(或最小化数据丢失)地切换到备用节点,保持服务不间断。
问题:为什么Redis快速?
1.Redis的数据存储在内存中,相比访问硬盘中存储的数据库要快得多
2.Redis的核心功能都是比较简单的逻辑,相比关系型数据库要简单很多,无需考虑外键等等
3.Redis 采用经典的 Reactor 模式,其核心是一个单线程的事件循环,这个线程负责处理所有客户端的连接、请求和响应。
4.Redis使用的是单线程模型(即使高版本Redis引入多线程),这样的单线程减少了不必要的线程之间的竞争开销
Redis的使用场景
1.把Redis充当数据库使用 对实时性要求较高、较低的延迟和更高的吞吐率。 Redis存储的是全量数据,不能有丢失情况
2.充当缓存角色 Redis中存储的是部分热点数据,提高访问时候的效率,但全量数据存储在类似MySQL中,如果Redis数据丢失还能从MySQL中加载回来
3.session存储 服务端创建会话数据(Session)并存储在服务器内存或Redis集群中,同时生成唯一的Session ID;客户端浏览器通过Cookie保存这个Session ID。后续请求时,浏览器自动携带Cookie,服务端根据Session ID查找对应的会话数据完成身份验证。
4.充当消息队列的功能 具有业务解耦、非实时业务削峰填平等特性
Redis的客户端 redis是一个客户端-服务器结构的程序
1.自带的命令话客户端 redis-cli
2.图形化界面的客户端 (桌面程序、web程序)
3.基于redis的api自行开发客户端
问题:如果多个客户端"并发"发起对同一个key的加一操作(key对应的value类型为int),那么是否会引起线程安全的问题?
不会,redis服务器实际上是单线程模型,保证了当前收到的请求是穿行执行的。多个请求同时到达redis服务器也是要先在队列中排队等待redis服务器一个一个的去除里面的命令再去执行。但这也会有弊端,不能出现某个操作占用时间过长从而阻塞其他指令运行。
Redis中的string是直接按照二进制数据的方式存储数据不会做任何的编码转换,因此不单单可以存储文本数据,还可以存储JSON、xml、甚至是图片、音频、视频(但这可能会占据很大空间 不推荐)
Redis的通用命令
set命令
SET key value
set 将key-value存入进去 key必须是字符串,value可以是字符串、哈希表、列表、集合、有序集合(无需像C++中添加引号)

get命令
GET key
get 根据key将value取出来(如果key不存在会返回nil)

keys命令
KEYS pattern
通过一些特殊符号(通配符)来描述key的模样,从而匹配符合条件的key
?可以匹配任何字符
*可以匹配零个或者多个任意字符
\]方括号中的字母均可被匹配,但只能匹配其中的一此(或关系) \[\^\]除去该符号匹配不了其他均可匹配(并关系) \[起始字符-终止字符\]匹配范围内的所有字符 问题:为什么不建议在生产环境中使用keys \*命令 Redis 的 KEYS 命令时间复杂度为 O(N)(其中 N 为 Redis 实例中所有键的总数),因此该命令在生产环境中通常被严格禁止使用。原因在于 Redis 基于单线程模型处理命令请求,执行 KEYS \* 这类遍历全量键的操作时,会占用服务器全部处理资源且执行时间随键数量增加而显著变长,导致 Redis 主线程被长时间阻塞,无法及时响应其他客户端的请求(如读写操作),进而引发整个服务的响应超时甚至不可用。此外,KEYS 命令不支持分页,即使非通配符匹配也会一次性遍历所有键,进一步放大了阻塞风险,这也是生产环境中禁用该命令的核心原因。 因为redis经常作为缓存挡在mysql的前面,替mysql负重前行,如果redis被keys \*命令阻塞住了,其他差选redis的操作就会超时,此时这些请求就会直接查询数据库,如果一大堆的请求同时到达mysql,mysql就会容易挂,这将会导致系统瘫痪。 ### exits命令 > EXITS key \[key ....
通用命令exits 判断某个key是否存在
返回值:key存在的个数(针对多个key)
时间复杂度:O(n) 检查几个key n就是几,因此是常数级别,即O(1)
Redis组织这些key是按照哈希表的方式来组织的。
使用类似EXITS这种命令去查询的时候,与直接操作内存相比效率是更多低下且成本更高,因为客户端和服务器进行通信的时候需要消耗网络资源,这中间涉及到发送方的数据从应用层通过层层包装传递到物理层,接收方又将数据从物理层向上层层解包到应用层,这会花费大量的时间和网络资源,所以redis自身的很多命令支持一次性进行多次操作。
del命令
DEL key [key...]
可以依次删除一个或者多个key
时间复杂度:O(n) 取决于删除的个数O(1)
返回值:删除key的个数
Redis 作为缓存层时存储的是热点数据,全量数据仍保存在 MySQL 数据库中。在这种情况下,删除 Redis 中的少量 Key 通常不会对系统造成显著影响。然而,如果短时间内大量删除 Redis 中的 Key(例如执行 FLUSHALL 删除全部或大部分缓存数据),将导致缓存雪崩 ------ 大量请求会直接穿透到 MySQL 数据库进行查询,可能瞬间引发数据库负载激增、连接数打满,甚至导致 MySQL 服务崩溃,进而造成整个系统不可用。
expire命令
EXPIRE key seconds [NX | XX ]
NX表示仅当key未设置过期时间才会对其设置;XX表示只有该键被设置过期时间才会设置
给指定的key(必须存在)设置过期时间 key存货时间超出这个指定的值就会被自动删除。
pexpire 以毫秒作为单位设置过期时间
ttl命令
TTL key
查询指定key的过期时间
时间复杂度:O(1)
返回值:如果key还存在返回剩余的过期时间,-1标识没有关联过期时间,-2标识key不存在。
问题:Redis的key的过期策略是如何实现的?
一个redis中同时可能会存在很多很多的key,这些key中可能有很大一部分都有过期时间。此时,redis服务器怎么知道哪些key已经过期需要被删除,哪些key还没有过期?
Redis的删除策略:
1.定期删除
规定时间后,每次抽取一部分进行过期时间检测,保证抽取检查的过程足够的快。
2.惰性删除
假设这个key已经到过期时间了,但暂时还没删除它,key存在。紧接着后面的又一次访问正好就是这个key,于是该次访问就会让redis服务器触发删除key的操作同时返回一个nil。
虽然有上述两种策略结合,但整体的效果一般,仍然会有过期的key残留,无法及时删除,因此redis还提供了一系列内存淘汰机制来补足。
问题:为什么对删除的时间有明确的要求呢?
因为redis1是一个单线程程序,主要的任务是处理每个任务的指令。如果扫描过期key消耗时间过多,就会导致正常的请求命令被阻塞。
定时器的两种方案
1.优先级队列实现定时器。核心思路是将所有定时事件按到期时间升序排列,通常采用小顶堆结构来维护。每次调度时,只需检查堆顶元素的到期时间,若未到达则等待;到期后执行对应任务并移除堆顶,然后重新调整堆结构。这种方法获取最近到期事件的时间复杂度为O(1),但插入和删除操作需要O(log n)的堆调整开销。虽然实现简单,但当定时器数量庞大时,频繁的堆调整可能影响性能,且无法高效处理系统时间回拨等边界情况。
2.时间轮算法。通过循环数组和链表相结合(或二维数组)的方式,将定时事件散列到时间轮的槽位中。单层时间轮像一个环形队列,每个槽位对应一个时间间隔,槽内链表存储该时刻到期的所有任务。指针按固定频率推进,执行当前槽位中的所有任务。多层时间轮可扩展时间范围,类似时钟的时针、分针和秒针协同工作,高层时间轮负责远期任务,当高层指针前进时会将到期任务重新映射到低层轮中。时间轮的插入和删除操作时间复杂度可达O(1),特别适合高并发场景,但指针空推进可能造成CPU资源浪费。
实际应用中常结合两种方案的优势:Linux内核采用多级时间轮,Netty等框架则使用分层时间轮加优先级队列的混合机制。例如,可将短期定时任务置于时间轮中以获得O(1)操作效率,而长期任务存放于优先级队列,待其接近到期时再转移到时间轮中执行。这种设计既保证了高频操作性能,又避免了时间轮空间过度膨胀,同时通过惰性推进策略减少空转消耗,最终实现高精度、低延迟的定时调度系统。
type命令
TYPE key
返回key对应的value的数据类型(返回可以是none、string、list、set、zset、hash和stream(redis作为消息队列时候才会有该类型))
时间复杂度为O(1)
渐进式遍历
Redis使用scan命令进行渐进式遍历键,进而解决直接使用keys获取键时可能会出现的阻塞问题。每次scan命令的时间复杂度位O(1),但要遍历完所有的键需要多次scan。
SCAN(遍历数据库键)
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
cursor:起始游标(首次遍历传 0,返回结果中的第一个值是下一次的游标,返回 0 表示遍历完成);注意:不能当成下标进行理解
MATCH pattern:可选,按通配符匹配键(如 user:*),规则同 KEYS;
COUNT count:可选,指定每次遍历的 "期望返回数量"(非绝对,Redis 仅作参考/建议,默认 10);
TYPE type:可选(Redis 6.0+),按数据类型过滤(如 string、hash、zset)。
返回值:二维列表 → [下一次游标, [匹配的键列表]];游标返回 0 表示遍历完成。时间复杂度:O (N)(全量遍历总复杂度,单次遍历为 O (1)~O (N) 取决于 COUNT)。
Redis的服务器不保留任何状态,每次渐进式遍历是可以随时中断的!
Redis是有database的概念,只不过不像MySQL那么随意,redis中的database是现成的,用户不能创建新的数据库也不可以删除,但是可以切换不同的数据库。默认Redis向我们提供了十六个数据库(编号0~15),默认情况下使用的是0号数据库。
数据库核心命令
数据库切换(SELECT)
SELECT dbindex
功能:切换到指定编号的数据库(dbindex 为 0~ 配置的数据库数量 - 1),默认连接后处于数据库 0。
返回值:OK(切换成功);若 dbindex 超出范围,返回错误。
时间复杂度:O (1)
清空数据库(FLUSHDB/FLUSHALL)
(1) FLUSHDB(清空当前数据库)
FLUSHDB [ASYNC|SYNC]
功能:删除当前数据库中的所有键,不同数据库互不影响;
可选参数(Redis 4.0+):
ASYNC:异步清空(Redis 后台异步删除,不阻塞主线程);
SYNC:同步清空(立即删除,阻塞主线程,默认)。
返回值:OK。
时间复杂度:O (N)(N 为当前数据库的键数量)。
(2)FLUSHALL(清空所有数据库)
FLUSHALL [ASYNC|SYNC]
功能:删除 Redis 服务器中所有数据库的所有键(0~15 全部清空),参数含义同 FLUSHDB。返回值:OK。
时间复杂度:O (N)(N 为所有数据库的总键数量)。
Redis中Value的数据类型

Redis底层在实现上述数据结构的时候,会在源码层面针对上述实现进行特定的优化来达到节省时间或空间的效果,内部的具体实现的数据结构可能会与表述的不同。
Redis承诺,例如对hash表进行查询、插入、删除、操作都能保证O(1)的时间复杂度,但背后的具体实现并不能保证是一个标准的hash表,可能在特定达到场景下使用别的数据结构实现。
使用指令OBJECT encoding key就可以查看key对应的value的实际编码方式
String类型
内部编码
1.raw 最基本的字符串 底层就是一个char类型的数组
2.int redis通常也可以用来实现一些"计数"之类的功能。当value是一个整数的时候,此时redis会使用int来保存(8个字节的整数)。
3.embstr是针对短字符串的特殊优化(压缩字符串)。
如果使用string来存储小数,小数是使用字符串的形式进行存储,这意味着每次进行小数运算需要进行类型转换,这会有所开销。
String类型的一些操作
APPEND
APPEND key value
如果key已经存在并且是一个string,命令会将value追加到原有的string后边。如果key不存在,那么该命令等效于SET
时间复杂度:O(1)
返回值:添加后的字符串长度

问题:为什么两个汉字的字节数为6?
Redis的字符串,不会对字符编码做任何的处理,只负责保存。当前等等xshell终端默认的字符编码为utf-8,因此大小为6。
问题:使用get获取的时候为什么返回的不是我们输入的原文本
这是使用对照utf8码表使用十六位将其表示出来了
在启动redis客户端的时候,加上一个--raw这样的选项就可以使用redis客户端能够自动的把二进制数据尝试翻译。

GETRANGE
GETRANGE key start end
返回key对应的string的字串,由start和end确定(左闭右闭)。可以使用负数表示倒数第几个字符。-1表示倒数第一个字符,依次推类。超过范围的偏移量会根据string的长度进行调整成正确的值。
时间复杂度:O(N)
返回值:切割后的字串
如果是汉字,进行字串切割很可能会出现问题,不再是汉字。上述问题同时在C++中存在,因为C++中字符串的基本单位是字节,Java中字符串的基本单位是字符,就不会出现上面的情况。
SETRANGE
SETRANGE key offset value
从offset个字节出开始进行替换value
返回值:替换之后的新字符串
当操作的 key 不存在时,Redis 会先将该 key 初始化为一个空字符串,然后再执行 SETRANGE 的逻辑 ------ 相当于对一个空字符串进行覆盖写入,具体规则:初始化 key 为空字符串(长度 0);按 offset 偏移量填充空字节(\x00),直到偏移量位置;从 offset 位置开始写入 value 的字节内容;最终 key 的类型为字符串(string)。
STRLEN
STRLEN key
返回该字符串的字节长度(而非字符数),因为 Redis 字符串是二进制安全的,按字节存储。
对于普通 ASCII 字符串:字节数 = 字符数;
对于多字节字符(如 UTF-8 的中文):字节数 ≠ 字符数(一个中文通常占 3 字节)。
如果key不存在,redis会将其视为空字符串,因此返回值为0。
如果 key 存在但不是字符串类型(如 hash、list、set、zset),STRLEN 会直接返回类型错误(WRONGTYPE),这是 Redis 命令的通用规则:命令只能操作对应类型的 key。
特殊字符串场景(空字节/空字符串)
空字符串 key:返回 0;
含空字节(\x00)的字符串:空字节会被计入长度(符合二进制安全特性)。
时间复杂度:O(1)
使用场景
缓存(Cache)功能
图中展示了Redis作为缓冲层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。

整体思路:
应用服务器访问数据的时候,先查询redis,如果redis中有所需要的数据,就直接从redis中取出数据返回给应用服务器;如果redis中没有目标数据,就去MySQL中查询数据然后返回给应用服务器同时将数据同步到redis(存储热点数据)。
但是这种策略会有一个明显的问题------随这时间推移,越来越多的key在redis上访问不到,因此从MySQL中读取越来越多数据到redis中,那redis中的数据不就越来越多了吗?
解决方案:
1.把数据写到redis的时候为key设置一个过期时间
2.在内存不足时候,redis为我们提供了淘汰策略
计数(Counter)功能
许多应用都会使用Redis作为计数的基础工具。它可以实现快速计数、查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。如下图所示,例如视频网站的视频播放次数可以通过Redis来完成------用户每播放一次视频就让播放量加一。

session分享存储
一个分布式 Web 服务将用户的 Session 信息(例如用户登录信息)保存在各自的服务器中,但这样会造成⼀个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同⼀台服务器上,这样当用户刷新⼀次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。(如下图所示)

为了解决上述问题,我们将各个用户的session存储在redis中统一进行管理,只需保证redis是高可用和扩展性,无论用户访问被分配到哪一个web服务器中,都会从redis中获取session信息,避免不一致情况发生。

手机验证码
很多应用处于安全考虑,需要确认是否是电话号码所属人在操作,因此设置了手机验证码这一项安全检查。为了让短信接口不会被频繁访问,程序员会去限制用户获取验证码的频率,例如,一分钟最多可以获取五次验证码。

伪代码的基本思路:

Hash类型
内部编码
1.hashtable 最基本的哈希表
2.压缩列表 ziplist 哈希表中元素比较少的时候(value的长度也有限制,可以在redis.conf中设置hash-max-ziplist-value的大小)就会优化为ziplist,从而压缩占据的空间
问题:为什么要进行压缩?
Redis上有很多很多key。此时,如果某些key的value是hash。那么这种key特别多,对应的hash也特别多,但每个hash又不大,通过尽量压缩的方式就可以让整体占用的内存更小。
在Redis中,哈希类型是指value本身也是一个键值对的结构,形如 key = "key",value = { {
field1, value1 }, ..., {fieldN, valueN } },Redis的key-value模式和value的哈希类型的关系如下图

注意:哈希类型中的映射关系通常称为 field-value,⽤于区分Redis整体的键值对(key-value), 注意这里的 value 是指field对应的值,不是键(key)对应的值,请注意value在不同上下文的作用。
Hash类型的一些操作
HSET
HSET key field value [field value ...] [NX | XX]
获取hash中的所有字段(field信息)
返回值;如果key存在返回field的列表,反之返回空表
时间复杂度:O(N) N代表field的个数
HGET
HGET key field
获取hash中指定字段的值
返回值:如果字段存在则字段对应的值;如果字段不存在则返回nil
时间复杂度:O(1)
HEXISTS
HEXISTS key field
判断hash中是否存在指定字段
返回值:如果字段存在返回1;反之返回0
时间复杂度:O(1)
HDEL
HDEL key field [field ...]
删除hash中一个或多个指定的字段。如果指定的字段不存在于哈希表中,则会被忽略。
返回值:如果key存在返回删除字段的个数,反之返回0
时间复杂度:O(1)
注意HDEL命令和DEL命令的区别,HDEL删除的是field-value但key会被保留,而DEL是将整个key-value键值对删除。
HKEYS
HKEYS key
获取hash中的所有字段(field信息)
返回值;如果key存在返回field的列表,反之返回空表
时间复杂度:O(N) N代表field的个数
HVALS
HVALS key
获取hash中的值(value信息)
返回值:如果key存在返回value的列表,反之返回空表
时间复杂度:O(N) N代表value的个数
以上两个命令都需要注意使用。如果哈希非常大,该操作可能会阻塞住redis服务器。
HGETALL
HGETALL key
获取hash中所有字段和值
返回值:如果key存在返回字段-值交替的列表,反之返回空表
时间复杂度:O(N) N代表field的个数
HMSET
HMSET key field value [field value ...]
设置hash中指定字段的值
返回值:设置成功的字段数目
时间复杂度:O(N) N代表设置字段的数目
HMGET
HMGET key field [field ...]
获取hash中指定字段的值
返回值:字段值(value)列表 多个value的顺序和查询的field顺序匹配
时间复杂度:O(N) N代表查询field的个数
HSCAN
HSCAN key cursor [MATCH pattern] [COUNT count]
HSCAN 是 Redis 中用于增量式迭代哈希表字段的命令。它是解决大哈希表遍历问题的关键工具,避免了 HKEYS 或 HGETALL 可能导致的 Redis 服务器阻塞。
参数:key指要遍历的Hash键的名称;cursor游标,初始值为 0,每次返回结果会包含新游标,游标为 0 时遍历结束;MATCH(可选)按字段名(field)匹配模式(支持通配符,如 *、?、[]);COUNT(可选)指定每次迭代返回的元素数量(Redis 不保证严格遵守),默认 10。
返回值:一个固定的二元数组,整体格式为 [cursor, [field1, value1, field2, value2, ...]],其中第一个元素 cursor 是整数类型的游标,用于标记下一次遍历的起始位置,若游标返回 0,表示整个哈希已遍历完成,若为非 0 整数,则需要用该游标作为入参继续调用 HSCAN;第二个元素是字符串数组,按 "字段、值" 交替的格式返回一批哈希数据,与 HGETALL 的返回数据格式完全一致,若当前批次无匹配结果或哈希无数据,该数组会返回空。特殊场景下的返回值有明确规则:若目标哈希键不存在,会返回 [0, 空数组];若目标键并非哈希类型,会直接返回 WRONGTYPE 类型错误;若 MATCH 参数无匹配结果,仍会返回非 0 的游标和空数组,需继续遍历直到游标为 0。
时间复杂度:单次 HSCAN 调用的时间复杂度为 O (1),因为单次仅按指定提示返回一批数据,无全量遍历的开销;若要完整遍历整个哈希,总时间复杂度为 O (N)(N 为哈希的总字段数),虽总复杂度与 HGETALL 一致,但会将遍历开销分摊到多次调用中,不会造成单次阻塞。
HLEN
HLEN KEY
获取hash中所有字段的个数
返回值:如果字段存在字段的个数,不存在则返回0
时间复杂度:O(1)
HSETNX
HSETNX key field value
在字段不存在的情况下,设置hash中的字段和值
返回值:设置成功返回1,反之返回0
时间复杂度:O(1)
HINCRBY
HINCRBY key field increment
将hash中字段对应的数值添加指定的值
时间复杂度:O(1)
返回值:该字段变化后的值
HINCRBYFLOAT
HINCRBYFLOAT key field increment
将hash中字段对应的竖直添加指定的值(浮点数版本)
时间复杂度:O(1)
返回值:该字段变化后的值
使用场景
存储用户信息
每一列都是一个用户一类信息,每一行都是单个用户的所有信息。

使用hash作为value,然后使用usr:1充当key,uid、name、age、city等充当field,对应的详情信息充当value。

这种存储方式相比使用JSON存储单个用户的全部信息,在修改方面更加灵活,不需要对数据先解码再修改然后再编码写回redis。并且使用hash更加直观。
但是需要注意的是哈希类型和关系型数据库有两点不同之处:
1.哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的 field,而关系型数据库⼀旦添加新的列,所有行都要为其设置值,即使为 null。
2.关系数据库可以做复杂的关系查询,而Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等基本不可能,维护成本高。

问题:user中的uid可以不进行存储吗?直接使用key的值不就好了吗?
确实可以不进行存储,但工程实践中一般都会把uid在value中存储一份,方便后续相关的代码进行开发。
List类型
内部编码
1.linklist链表
2.ziplist压缩列表
从redis 3.2开始引入新的实现方式------quicklist,同时兼顾了linklist和ziplist优点,quicklist就是一个链表,每一个节点是一个ziplist,它将空间和效率都兼顾到。

正数下标 = list的元素个数 + 倒数下标
列表的特性:
1.列表中的元素是有序的,此处的有序是指的插入的顺序,如果将元素位置颠倒、顺序调换,新的List和调换之前的List是不等价的。
2.区分获取和删除的区别,删除的时候,如果删除成功会返回被删除下标对应的value,但会改变列表的长度,但执行获取元素的时候,列表长度是不会变化的。
3.列表中的元素是允许重复的
4.List可以支持头和尾的搞笑插入和删除元素,可以让List充当栈/队列。
List类型的一些操作
LPUSH
LPUSH KEY value [value ...]
将一个或多个值插入到列表的头部(左侧)。如果 key 不存在,会先创建一个空列表再执行插入操作;若 key 存在但不是列表类型,操作失败。
返回值:插入操作完成后,列表的总长度;若 key 不是列表类型返回错误信息
时间复杂度:O (N) N 指的是插入的 value 个数
RPUSH
RPUSH KEY value [value ...]
将一个或多个值插入到列表的尾部(右侧)。如果 key 不存在,会先创建一个空列表再执行插入操作;若 key 存在但不是列表类型,操作失败。
返回值:插入操作完成后,列表的总长度;若 key 不是列表类型返回错误信息
时间复杂度:O (N) N 指的是插入的 value 个数
LPUSHX
LPUSHX KEY value [value ...]
仅当 key 存在且为列表类型时,才将一个或多个值插入到列表的头部(左侧);若 key 不存在或不是列表类型,插入失败。
返回值:成功插入的 value 个数;若 key 不存在 / 不是列表类型返回 0
时间复杂度:O (N) N 指的是插入的 value 个数
RPUSHX
RPUSHX KEY value [value ...]
仅当 key 存在且为列表类型时,才将一个或多个值插入到列表的尾部(右侧);若 key 不存在或不是列表类型,插入失败。
返回值:成功插入的 value 个数;若 key 不存在 / 不是列表类型返回 0
时间复杂度:O (N) N 指的是插入的 value 个数
LPOP
LPOP KEY [count] (Redis6.2版本以后才支持指定删除的个数count)
无 count 参数:移除并返回列表的头部(左侧)第一个元素;
带 count 参数:移除并返回列表头部的 count 个元素(count 为正整数)。
若 key 不存在或列表为空,返回 nil(无 count)/ 空列表(带 count);若 key 不是列表类型返回错误信息。
返回值:被移除的元素(无 count)/ 被移除的元素列表(带 count);若列表为空返回 nil / 空列表
时间复杂度:O (N) N 指的是移除的元素个数(无 count 时为 O (1))
RPOP
RPOP KEY [count] (Redis6.2版本以后才支持指定删除的个数count)
无 count 参数:移除并返回列表的尾部(右侧)第一个元素;
带 count 参数:移除并返回列表尾部的 count 个元素(count 为正整数)。
若 key 不存在或列表为空,返回 nil(无 count)/ 空列表(带 count);若 key 不是列表类型返回错误信息。
返回值:被移除的元素(无 count)/ 被移除的元素列表(带 count);若列表为空返回 nil / 空列表
时间复杂度:O (N) N 指的是移除的元素个数(无 count 时为 O (1))
LRANGE (L指的是List而不是left)
LRANGE KEY start stop
获取列表中指定区间内的元素,区间以偏移量 start 和 stop 指定(包含两端):索引从 0 开始,0 表示第一个元素,1 表示第二个元素;支持负数索引,-1 表示最后一个元素,-2 表示倒数第二个元素;若 start >列表最大索引,返回空列表;若 stop 超出列表最大索引,仅返回到列表末尾的元素。若 key 不存在,返回空列表;若 key 不是列表类型返回错误信息。
返回值:指定区间内的元素列表;若 key 不存在返回空列表
时间复杂度:O (S+N) S 是 start 偏移量,N 是返回的元素个数
Redis对下标越界有几种不同的处理方式。
1.如果起始下标start已经超出了列表的最大有效下标,也就是说,start指向了一个根本不存在的元素位置,那么无论stop下标是多少,Redis都会直接返回一个空列表。比如列表只有5个元素(下标0到4),你查询LRANGE key 10 20,因为10已经大于4,所以Redis会返回空结果。
2.对于结束下标stop超出范围的情况,Redis的处理更加灵活。如果stop下标超过了列表的最大下标,Redis不会报错,而是会自动将stop调整为列表的最后一个元素的下标。例如列表有5个元素,你查询LRANGE key 2 10,Redis会理解为"从第3个元素到最后一个元素",返回下标2到4的所有元素。这种设计非常实用,因为在实际编程中,我们经常想要"从这个位置到列表末尾的所有元素"。
3.当起始下标start大于结束下标stop时,Redis也会返回空列表。这是符合逻辑的,因为一个区间的起始点不可能在结束点之后。比如LRANGE key 5 2,即使这些下标都在有效范围内,因为5>2,所以返回空列表。
负数下标的处理需要特别注意。Redis会先将负数下标转换为实际的正数下标,转换公式是"实际下标 = 列表长度 + 负数下标"。比如列表长度为5,下标-1转换为4(5+(-1)=4),下标-3转换为2(5+(-3)=2)。但如果转换后的结果小于0,就相当于下标超出了范围,会按照上述规则处理。
优点:在实际应用中,这种宽容的处理方式带来了很多便利。比如在实现分页功能时,我们不需要精确计算每页的结束下标,即使计算出的stop下标稍微大了一些,Redis也会自动调整。又比如在获取列表最后几个元素时,我们可以直接用LRANGE key -5 -1来获取最后5个元素,而不需要先查询列表长度再计算下标范围。
缺点:这种宽容性也需要开发者注意。因为LRANGE不会报错,如果程序逻辑有bug,可能会导致查询结果与预期不符而不容易被发现。比如你误写了LRANGE key 5 2,程序不会报错,只是返回空列表,可能需要调试才能发现这个逻辑错误。
LLEN
LLEN KEY
获取列表的元素个数。如果 key 不存在,视为空列表,返回 0;若 key 存在但不是列表类型,返回错误信息。
返回值:列表的元素个数;若 key 不存在返回 0
时间复杂度:O (1)
LINDEX
LINDEX KEY index
通过索引获取列表中的元素:索引从0开始,支持负数索引(-1 表示最后一个元素);若 key 不存在、索引超出范围(正数索引≥列表长度,负数索引≤ -列表长度 - 1),返回 nil;若 key 不是列表类型返回错误信息。
返回值:指定索引位置的元素;若 key 不存在 / 索引越界返回 nil
时间复杂度:O (N) N 是到达指定索引所需遍历的元素个数(列表头 / 尾操作接近 O (1))
LINSERT
LINSERT KEY <BEFORE>|<AFTER> pivot element
在列表中找到第一个与 pivot 匹配的元素,根据 BEFORE/AFTER 关键字,将 value 插入到该元素的前面或后面。若 key 不存在,列表视为空,插入操作失败;若 pivot 元素在列表中不存在,插入操作也会失败。
返回值:成功插入后列表的长度;若 key 不存在 /pivot 不存在,返回 -1
时间复杂度:O (N) N 指的是遍历列表找到 pivot 元素所需的元素个数
LREM
LREM KEY count value
根据参数 count 的值,移除列表中与参数 value 相等的元素:count > 0:从列表头部开始向尾部遍历,移除最多 count 个匹配元素;count < 0:从列表尾部开始向头部遍历,移除最多|count|个匹配元素;count = 0:移除列表中所有匹配的元素。若 key 不存在,视为空列表,返回 0;若 key 不是列表类型返回错误信息。
返回值:成功移除的元素个数;若 key 不存在返回 0
时间复杂度:O (N) N 是列表的总长度
LTRIM
LTRIM KEY start stop
对列表进行修剪,仅保留指定区间内的元素(区间规则同 LRANGE),超出区间的元素全部删除:若 start > stop,会清空整个列表;若 start/stop 超出列表索引范围,会自动调整为列表的首尾索引;若 key 不存在,视为空列表,返回 OK;若 key 不是列表类型返回错误信息。
返回值:操作成功返回 OK
时间复杂度:O (N) N 是被删除的元素个数
RPOPLPUSH
RPOPLPUSH SOURCE DESTINATION
原子性地将 SOURCE 列表尾部的元素移除,并将该元素插入到 DESTINATION 列表的头部:
若 SOURCE 不存在或为空,返回 nil,DESTINATION 无变化;
若 DESTINATION 不存在,会先创建空列表再插入元素;
若 SOURCE/DESTINATION 不是列表类型返回错误信息。
返回值:被移动的元素;若 SOURCE 为空返回 nil
时间复杂度:O (1)LINSERT命令
阻塞版命令
BLPOP和BRPOP是LPOP和RPOP的阻塞版本
Redis中的list相当于"阻塞队列",线程安全是通过单线程模型支持的,阻塞则只支持"队列为空"的情况,不考虑"队列为满"的情况。
如果list中存在元素,blpop和brpop的作用和lpop和rpop一样;如果list为空,那么会一直阻塞到队列不为空为止。同时blpop和brpop可以显示的设置timeout阻塞时间,期间Redis可以执行其他命令。因为被阻塞的是"客户端"而不是"服务器"。例如,当你在一个客户端执行 BLPOP key 10(阻塞 10 秒)时,这个客户端会进入 "阻塞队列",等待满足条件(key 有元素可弹出)或超时。但 Redis 服务器的主线程不会被阻塞,依然可以接收并处理其他客户端的指令(如 SET、GET、LPUSH、HSET 等)。
命令中如果设置了多个键,那么会从左向右遍历,一旦有一个键对应的列表中可以弹出元素,则命令立即返回。
如果多个客户端同时对一个键执行pop,则最先执行命令的客户端会获得弹出元素。
BLPOP
BLPOP key[key....] timeout
可以指定一个key或者多个key,每个key都对应一个list,可以指定超时时间(单位是秒,Redis6后允许超时时间设定为小数)
如果这些list有任何一个非空元素,blpop都能够把此元素获取并且立即返回;如果这些list都为空,此时就要在timeout时间内进行阻塞等待,等待这些list中存在元素后进行操作。
返回值:当有元素可弹出时:返回一个包含两个元素的数组第一个元素是弹出元素的列表键名,第二个元素是弹出的元素值;当超时且没有元素可弹出时返回 nil。如果在等待期间有新的value被添加到list,那么返回时候还会显示从执行该命令到获取到该value所花费的时间。
时间复杂度:O(N) N指的是操作key的个数
BRPOP
BRPOP key [key ...] timeout
从多个列表的右侧阻塞式弹出元素。可以指定一个或多个列表键,每个键对应一个列表,可以指定超时时间(单位是秒,Redis 6 后允许超时时间设定为小数)。如果这些列表中有任何一个包含元素,BRPOP 能够从该列表的右侧弹出最后一个元素并立即返回;如果所有列表都为空,客户端将在指定的超时时间内阻塞等待,直到这些列表中有元素被添加为止。
返回值:当有元素可弹出时:返回一个包含两个元素的数组第一个元素:弹出元素的列表键名第二个元素:从列表右侧弹出的元素值当超时且没有元素可弹出时:返回 nil如果在等待期间有新的元素被添加到列表,返回时还会显示从执行该命令到获取到该元素所花费的时间时间复杂度:O(N),其中 N 是命令中指定的键的数量。
以上两种命令的使用场景:消息队列(实现生产者-消费者模式)
使用场景
消息队列
Redis可以使用lpush和brpop命令组合实现经典的阻塞式生产者-消费者模型队列,生产者客户端使用lpush从列表的左侧插入元素,多个消费者客户端使用brpop命令阻塞式地从队列中获取元素(严格按照brpop命令到来的顺序执行)。

Redis分频道阻塞消息队列模型
Redis巧妙地利用LPUSH和BRPOP命令组合,通过不同的键名实现了类似消息中间件的多频道订阅机制。这种设计让不同消费者能够通过监听不同的键来选择性接收特定类型的消息。

优势:消息处理的逻辑完全解耦,如果一个通道出现问题不会影响到其他的通道,并且便于监控和问题排查。
微博Timeline
每个用户都有属于自己的timeline(微博列表),现需要分页进行暂时文章列表。此时就可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
(1)每篇微博使用哈希结构去存储,例如微博中的三个属性titile、timestamp、content

(2)向用户Timeline添加微博,user:<uid>:mblogs作为微博等待键

(3)分页获取用户的Timeline,例如获取用户1到前10篇微博

但上述方案存在两个问题
1.1+n问题。如果每次分页获取的微博个数较多,需要执行多次hgetall操作,此时可以考虑使用pipeline(流水线/管道 指将多个Redis命令合并成一个网络请求进行通信)模式批量提交命令,或者微博不采用哈希类型,而是使用序列化的字符串类型,然后使用mget命令。
2.分裂获取文章时,lrange在列表两端表现较好,但获取列表中间的元素时候表现较差,可以考虑将原有的列表进行拆分。
注意:选择列表类型时,请注意
同侧存取(lpush + lpop or rpush + rpop)为栈
异侧存取(lpush + rpop or rpush + lpop)为队列
Set类型
内部编码
1.intset当集合中的元素均为整数并且元素的个数小于set-max-intset-entries配置的时候,set类型的内部会使用intset来实现,从而减少内存消耗
2.hashtable
特点:
1.集合中的元素是无序的!
2.集合中的元素是不能重复的(唯一性)
Set类型的一些操作
SADD
SADD key member [member ...]
将一个或多个 member 元素加入到集合 key 中,已存在于集合的 member 元素将被忽略,不会重复添加。如果 key 不存在,则先创建一个空集合再执行添加操作。
返回值:成功添加的新成员的数量(不包括被忽略的已存在成员)。
时间复杂度:O (N),N 是被添加的成员数量。
SMEMBERS
SMEMBERS key返回集合 key 中的所有成员元素。
返回值:集合中的所有成员组成的列表(无序);如果 key 不存在,返回空列表。
时间复杂度:O (N),N 是集合中元素的数量。
SISMEMBER
SISMEMBER key member
判断 member 元素是否是集合 key 的成员。
返回值:1 表示 member 是集合成员;0 表示 member 不是集合成员,或 key 不存在。
时间复杂度:O (1)(集合的哈希特性保证成员判断为常数时间)。
SCARD
SCARD key
返回集合 key 中元素的基数(即元素的数量)。
返回值:集合的基数;如果 key 不存在,返回 0。
时间复杂度:O (1)(Redis 会缓存集合基数,无需遍历)。
SPOP
SPOP key [count]
随机移除并返回集合中的一个或多个元素(count 为可选参数,指定返回的元素数量)。
如果 count 超过集合基数,返回全部元素。
返回值:返回被移除的元素;如果 key 不存在或集合为空,返回 nil(单元素)/ 空列表(多元素)。
时间复杂度:O (1)(单元素);O (N)(count 个元素,N 是 count 值)。
SRANDMEMBER
SRANDMEMBER key [count]
随机返回集合中的一个或多个元素,不会移除元素。count 为正数时返回不重复的元素,超过基数则返回全部;为负数时返回可能重复的元素,数量为绝对值。
返回值:单元素时返回字符串,多元素时返回列表;key 不存在则返回 nil / 空列表。
时间复杂度:O (1)(单元素);O (N)(count 个元素,N 是 count 值)。
SMOVE
SMOVE source destination member
将 member元素从source集合移动到destination集合。如果source不存在或无该member,操作失败;如果destination已存在该member,仅移除source中的member。
返回值:1 表示移动成功;0 表示移动失败(source 无该 member 或 source 不存在)。
时间复杂度:O (1)。
SREM
SREM key member [member ...]
移除集合 key 中的一个或多个member元素,不存在的member元素将被忽略。
返回值:成功移除的成员数量(不包括被忽略的不存在成员);如果 key 不存在,返回 0。时间复杂度:O (N),N 是被移除的成员数量。
SINTER
SINTER key [key ...]
返回所有给定集合的交集(同时属于所有集合的元素)。
返回值:交集元素组成的列表;如果任意集合不存在或无交集,返回空列表。
时间复杂度:O (N*M),N 是最小集合的基数,M 是集合的数量。
SINTERSTORE
SINTERSTORE destination key [key ...]
将所有给定集合的交集存储到 destination 集合中(会覆盖 destination 原有内容,如果 destination 不存在会被创建)。
返回值:交集的元素数量。
时间复杂度:O (N*M),N 是最小集合的基数,M 是集合的数量。
SUNION
SUNION key [key ...]
返回所有给定集合的并集(属于任意一个集合的元素)。
返回值:并集元素组成的列表;如果所有 key 都不存在,返回空列表。
时间复杂度:O (N),N 是所有集合中元素的总数量。
SUNIONSTORE
SUNIONSTORE destination key [key ...]
将所有给定集合的并集存储到 destination 集合中(会覆盖 destination 原有内容,如果 destination 不存在会被创建)。
返回值:并集的元素数量。
时间复杂度:O (N),N 是所有集合中元素的总数量。
SDIFF
SDIFF key [key ...]
返回第一个集合与其他集合的差集(属于第一个集合但不属于其他集合的元素)。
返回值:差集元素组成的列表;如果所有 key 都不存在,返回空列表。
时间复杂度:O (N),N 是所有集合中元素的总数量。
SDIFFSTORE
SDIFFSTORE destination key [key ...]
将第一个集合与其他集合的差集,存储到 destination 集合中(会覆盖 destination 原有内容,如果 destination 不存在会被创建)。
返回值:差集的元素数量。
时间复杂度:O (N),N 是所有集合中元素的总数量。
使用场景
使用set来保存用户的"标签"
用户画像------分析出你这个人的一些特征,分析清除特征之后再投其所好。
下面使用set类型来实现标签的若干功能
(1)给用户添加标签

(2)给标签添加用户

(3)删除用户下的标签

(4)删除标签下的用户

(5)计算用户共同的标签

使用set来获得可能认识的人
核心依托集合的交集、差集、并集等原生运算,结合用户的社交关系数据(如好友列表、关注列表)完成推荐逻辑。具体实现时,首先为每个用户维护一个存储其好友 / 关注对象的 Set 集合,例如用户 A 的好友列表对应键 friend:userA,集合内存储该用户所有好友的唯一标识(如 userID);要为用户 A 推荐 "可能认识的人",核心思路是先通过 SINTER 命令计算用户 A 与目标好友(如 userB)的共同好友集合(SINTER friend:userA friend:userB),再通过 SDIFF 命令筛选出 "userB 的好友中不属于 userA 好友的部分"(SDIFF friend:userB friend:userA),这部分结果即为基于共同好友的潜在推荐对象;也可扩展为批量计算,比如先通过 SUNION 合并用户 A 所有好友的好友列表(SUNION friend:user1 friend:user2 ...),再用 SDIFF 排除用户 A 已添加的好友和自身,最终得到候选推荐列表。
使用set类型统计UV
UV指的是User View 每个用户访问服务器都会产生一次用户访问,但同一个用户访问页面多次不会使访问次数增加。
具体实现逻辑为:首先为统计维度(如某页面、某活动、某时间段)创建对应的 Set 键,例如统计 "2026 年 1 月 27 日首页 UV" 可创建键 uv:homepage:20260127;当有访客访问时,通过 SADD 命令将该访客的唯一标识插入集合(如 SADD uv:homepage:20260127 user123),即使同一访客多次触发插入操作,集合也只会保留一个该访客的标识,确保每个访客仅被计数一次;统计 UV 时,只需执行 SCARD 命令获取集合的元素数量(如 SCARD uv:homepage:20260127),返回的整数即为该维度下的独立访客数。
该方案的优势在于实现简单、原子性强(SADD 是原子操作,多客户端并发插入不会导致重复计数),且支持灵活的维度扩展(如按天、按小时、按活动拆分集合键);但需注意局限性:当 UV 量级极大(如千万级以上)时,集合会占用较多内存,且 SCARD 命令虽时间复杂度为 O (1),但大集合的存储成本会显著上升。因此在超大规模 UV 统计场景中,可结合 Redis HyperLogLog 类型(如 PFADD/PFCOUNT)作为替代,HyperLogLog 仅需固定内存(约 12KB)即可统计海量 UV,且误差率仅 0.81%,能在内存开销和精度间取得平衡;而 Set 类型则更适合对精度要求 100%、UV 量级适中的场景(如单页面日 UV 百万级以内)。
此外,使用 Set 统计 UV 时可搭配过期时间优化内存:例如按天统计的 UV 集合,可在次日通过 EXPIRE 命令设置过期时间(如 EXPIRE uv:homepage:20260127 86400),让 Redis 自动清理历史数据,避免无效内存占用;若需统计跨时间段的 UV(如周 UV、月 UV),可通过 SUNION 命令合并多个日 UV 集合(如 SUNION uv:homepage:20260127 uv:homepage:20260128),再用 SCARD 统计合并后的元素数量,实现多维度 UV 汇总。
Zset类型
内部编码
1.skiplist(跳表)
2.ziplist(压缩列表,元素个数较少或单个元素体积较小时候使用节省内存空间)
特点:
1.内部元素唯一
2.元素排列有序(升序/降序)

Zset类型的一些操作
ZADD
ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]
将一个或多个 member 元素及其 score 值加入到有序集合 key 中。可通过可选参数控制行为:NX(仅添加不存在的member )、XX(仅更新已存在的member )、GT/LT(仅当新 score 大于/小于旧值时更新,并不会阻止添加新的元素)、CH(返回值原本表示新添加的元素个数,添加CH选项后返回值包含修改的成员数)、INCR(对 score 做增量操作,类似于ZINCRBY)。如果 key 不存在,则先创建一个空有序集合再执行添加操作。
返回值:默认返回新添加的成员数量(不包括更新的);使用CH参数时返回修改的成员总数(添加 + 更新);使用INCR参数时返回成员更新后的 score 值。
时间复杂度:O (M*log (N)),M 是被添加的成员数量,N 是有序集合中元素的数量。
ZRANGE
ZRANGE key start stop [WITHSCORES]
按 score 从小到大的顺序,返回有序集合 key 中索引在 [start, stop] 范围内的成员(start/stop 支持负数,-1 表示最后一个元素)。WITHSCORES 参数会同时返回成员的 score 值。
返回值:指定范围内的成员列表;如果 WITHSCORES 则返回 [member1, score1, member2, score2...] 格式的列表;key 不存在时返回空列表。
时间复杂度:O (log (N)+M),N 是有序集合的元素数量,M 是返回的成员数量。
ZREVRANGE
ZREVRANGE key start stop [WITHSCORES]
按 score 从大到小的顺序,返回有序集合 key 中索引在 [start, stop] 范围内的成员,其余规则同 ZRANGE。
返回值:同 ZRANGE(但顺序相反);key 不存在时返回空列表。
时间复杂度:O (log (N)+M),N 是有序集合的元素数量,M 是返回的成员数量。
ZRANGEBYSCORE 命令(被标记为弃用,后续会被ZRANGE替代)
语法:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
按 score 从小到大的顺序,返回有序集合 key 中 score 在 [min, max] 范围内的成员。min/max 支持 ( 表示开区间(如 (10 表示 >10),LIMIT 用于分页。
返回值:符合条件的成员列表;WITHSCORES 则同时返回 score;无符合条件的成员时返回空列表。
时间复杂度:O (log (N)+M),N 是有序集合的元素数量,M 是返回的成员数量。
ZCARD
ZCARD key
返回有序集合 key 中元素的基数(即成员数量)。返回值:有序集合的基数;如果 key 不存在,返回 0。
时间复杂度:O (1)。
ZCOUNT
ZCOUNT key min max(默认是闭区间,min和max可以写成浮点数)
返回有序集合 key 中 score 在 [min, max] 范围内的成员数量。min/max支持"("表示开区间。
返回值:符合条件的成员数量;key 不存在时返回 0。
时间复杂度:O (log (N)),N 是有序集合的元素数量。
ZCOUNT myset -inf +inf 的核心作用是:统计有序集合 myset 中,所有 score 值在 "负无穷到正无穷" 范围内的成员数量 ------ 本质就是查询 myset 这个有序集合的总成员数,效果等同于 ZCARD myset。
ZPOPMAX
ZPOPMAX key [count]
移除并返回有序集合 key 中 score 最大的一个或多个成员(count 为可选参数,指定返回的数量)。如果 count 超过集合基数,返回全部元素。
返回值:返回被移除的成员(带 score);如果 key 不存在或集合为空,返回 nil(单元素)/ 空列表(多元素)。
时间复杂度:O (M*log (N)),M 是移除的成员数量,N 是有序集合的元素数量。
关于时间复杂度的说明:ZPOPMAX 的时间复杂度由 "跳跃表节点删除" 这一步决定,而跳跃表删除节点需要维护多层指针,层级数是 log (N),因此整体复杂度为 O (log (N))
BZPOPMAX
BZPOPMAX key [key ...] [timeout] (timeout是double类型)
阻塞式移除并返回给定的一个或多个有序集合 key 中,score 最大的成员。如果所有给定 key 都为空(或不存在),则客户端会被阻塞,直到有元素可弹出,或等待超时。timeout 以秒为单位,设为 0 表示永久阻塞。
返回值:成功弹出元素时,返回一个包含三个元素的列表:[被弹出元素的key, 成员名, 成员的score值];如果超时仍无元素可弹出,返回 nil;若指定多个 key,会按参数顺序遍历,弹出第一个非空 key 中的最大 score 成员。
时间复杂度:O (log (N)),M 是移除的成员数量(默认 1),N 是对应有序集合的元素数量;阻塞阶段无时间复杂度消耗。
问题:如果当前的BZPOPMAX同时监听了多个key,假设key的个数为M个,此时时间复杂度是O(log(N)*M)吗?
不是,BZPOPMAX命令监听多个key时候,当其中任意一个key满足有元素可以弹出的时候就会执行,因此只会弹出一个key中的member,故时间复杂度为O (log (N))。
ZPOPMIN
ZPOPMIN key [count]
功能:非阻塞式移除并返回有序集合 key 中 score 值最小的成员。可以指定要弹出的成员数量,默认弹出 1 个成员。该命令是原子操作,保证在多客户端环境下的数据一致性。
返回值:当有序集合非空时:返回一个列表,包含被弹出的成员及其 score 值,格式为 [成员1, score1, 成员2, score2, ...]当有序集合为空或不存在时:返回空列表 []
时间复杂度:O(log(N) * M),其中 N 是有序集合的元素数量,M 是弹出的成员数量
BZPOPMIN
BZPOPMIN key [key ...] timeout
功能:阻塞式移除并返回给定的一个或多个有序集合 key 中,score 值最小的成员。如果所有给定 key 都为空(或不存在),则客户端会被阻塞,直到有元素可弹出,或等待超时。timeout 以秒为单位,Redis 6 后支持小数精度,设为 0 表示永久阻塞。
返回值:成功弹出元素时:返回一个包含三个元素的列表:[被弹出元素的key, 成员名, 成员的score值]
如果超时仍无元素可弹出:返回 nil。若指定多个 key,会按参数顺序遍历,弹出第一个非空 key 中的最小 score 成员
时间复杂度:弹出操作:O(log(N)),其中 N 是对应有序集合的元素数量 阻塞阶段:无时间复杂度消耗
ZRANK
ZRANK key member
返回有序集合 key 中 member 元素的排名(按 score 从小到大排序,排名从 0 开始)。返回值:member 的排名(整数);如果 member 不存在或 key 不存在,返回 nil。
时间复杂度:O (log (N)),N 是有序集合的元素数量。
ZREVRANK
ZREVRANK key member
返回有序集合 key 中 member 元素的排名(按 score 从大到小排序,排名从 0 开始)。返回值:member 的排名(整数);如果 member 不存在或 key 不存在,返回 nil。
时间复杂度:O (log (N)),N 是有序集合的元素数量。
ZSCORE
ZSCORE key member
返回有序集合 key 中 member 元素的 score 值。
返回值:member 的 score 值(以字符串形式返回);如果 member 不存在或 key 不存在,返回 nil。
时间复杂度:O (1)。
注意:ZSCORE 的 O(1) 时间复杂度得益于 Redis 有序集合使用哈希表作为成员到分数的直接映射,避免了排序结构的遍历,这是 Redis 在性能优化上的经典设计。
ZREM
ZREM key member [member ...]
移除有序集合 key 中的一个或多个 member 元素,不存在的 member 元素将被忽略。
返回值:成功移除的成员数量;如果 key 不存在,返回 0。
时间复杂度:O (M*log (N)),M 是被移除的成员数量,N 是有序集合的元素数量。
ZREMRANGEBYRANK
ZREMRANGEBYRANK key start stop
移除有序集合 key 中,按 score 从小到大排序后排名(索引) 在 [start, stop] 范围内的所有成员。排名从 0 开始(0 是 score 最小的成员),支持负数索引(-1 表示最后一个成员,即 score 最大的成员)。
返回值:成功移除的成员数量;如果 key 不存在(或不是有序集合),返回 0。
时间复杂度:O (log (N)+M),N 是有序集合的元素数量,M 是被移除的成员数量。
ZREMRANGEBYSCORE
ZREMRANGEBYSCORE key min max
移除有序集合 key 中,score 值落在 [min, max] 范围内的所有成员。min/max 支持(前缀表示开区间(如 (10 表示 score > 10,(20 30表示 20 < score ≤ 30),也支持 -inf(负无穷)、+inf(正无穷)表示全范围。
返回值:成功移除的成员数量;如果 key 不存在(或不是有序集合),返回 0。
时间复杂度:O (log (N)+M),N 是有序集合的元素数量,M 是被移除的成员数量。
ZINCRBY
ZINCRBY key increment member
为有序集合 key 中 member 元素的 score 值加上增量 increment(可正可负,负数表示减量)。如果 member 不存在,则先将其 score 设为 0 再执行增量操作;如果 key 不存在,则先创建空有序集合再执行操作。
返回值:member 更新后的 score 值(以字符串形式返回)。
时间复杂度:O (log (N)),N 是有序集合的元素数量。
ZINTERSTORE
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
计算多个有序集合的交集,并将结果存储到 destination 有序集合中(覆盖原有内容)。WEIGHTS 为每个源集合的 score 设置权重(相乘 double类型),AGGREGATE 指定交集成员 score 的聚合方式(SUM 求和、MIN 取最小、MAX 取最大,如果未指明则默认是SUM)。numkeys 必须指定源集合的数量。返回值:结果有序集合的成员数量。
时间复杂度:O (N*K)+O (M*log (M)),N 是最小源集合的基数,K 是源集合数量,M 是结果集合的基数。
注意;需要指明key的个数,来区分key和后面的可选选项
ZUNIONSTORE
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
计算多个有序集合的并集,并将结果存储到 destination 有序集合中(覆盖原有内容)。WEIGHTS 为每个源集合的 score 设置权重(相乘 double类型),AGGREGATE 指定交集成员 score 的聚合方式(SUM 求和、MIN 取最小、MAX 取最大,如果未指明则默认是SUM)。numkeys 必须指定源集合的数量。返回值:结果有序集合的成员数量。
时间复杂度:O (N)+O (M*log (M)),N 是所有源集合的总元素数量,M 是结果集合的基数。
使用场景
排行榜系统
核心依托 Zset"元素唯一 + 分值排序" 的特性,可高效完成实时、动态的排名统计与查询。具体实现时,首先为不同维度的排行榜创建对应的 Zset 键(如 "日销量排行榜" 对应 rank:sales:day、"用户积分排行榜" 对应 rank:score:user),将需排名的对象(如商品 ID、用户 ID)作为 Zset 的成员(member),将排名依据的数值(如销量、积分、时长)作为成员的分值(score);当数据更新时,通过 ZADD 命令原子性更新成员分值(如 ZADD rank:score:user 1000 user123 表示为用户 123 设置 1000 积分,若该用户已存在则自动更新分值),结合 INCRBY 类逻辑还可实现分值的增量更新(如 ZINCRBY rank:sales:day 5 goods456 为商品 456 增加 5 销量),保证排行榜数据的实时性。
其他类型
Streams类型
XADD
XADD key [NOMKSTREAM] [MAXLEN [~] count] *|ID field value [field value ...]
向 Stream 键 key 中添加一条 / 多条消息。* 表示由 Redis 自动生成唯一消息 ID(格式:时间戳-序列号),也可手动指定 ID(需大于已有最大 ID);field value 是消息的键值对内容。
可选参数:NOMKSTREAM:若 key 不存在,不自动创建(默认会创建);MAXLEN [~] count:限制 Stream 最大长度,~ 表示近似截断(性能更优),超出则删除最早消息。
返回值:新增消息的唯一 ID(如 1737840000000-0);若手动指定 ID 无效(如小于已有 ID),返回错误。
时间复杂度:O (1)(无 MAXLEN 时);O (N)(有 MAXLEN 且需截断时,N 为截断的消息数)。
XLEN
XLEN key
返回 Stream 键 key 中的消息总数。
返回值:消息数量(整数);若 key 不存在或不是 Stream 类型,返回 0。
时间复杂度:O (1)(Redis 缓存了 Stream 长度,无需遍历)。
XRANGE
XRANGE key start end [COUNT count]
按消息 ID 从小到大的顺序,查询 key 中 ID 在 [start, end] 范围内的消息。特殊值:- 表示最小 ID,+ 表示最大 ID;COUNT:限制返回的消息数量(分页)。
返回值:符合条件的消息列表,每条消息格式为 [ID, [field1, value1, field2, value2...]];无匹配消息时返回空列表。
时间复杂度:O (log (N)+M),N 是 Stream 总消息数,M 是返回的消息数。
XREAD
XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]
读取一个 / 多个 Stream 的消息,支持阻塞模式(核心消费命令)。COUNT:限制每个 Stream 返回的消息数;BLOCK:阻塞时间(毫秒),0 表示永久阻塞;STREAMS:后接要读取的 Stream 键列表;ID:每个 Stream 对应的 "起始读取 ID",$ 表示只读取当前之后新增的消息(消费新消息),具体 ID 表示读取该 ID 之后的消息(消费历史消息)。
返回值:非阻塞 / 阻塞超时:无新消息返回空列表;阻塞成功读取:返回列表,格式为 [key, [[ID, [field1, value1...]], ...]];键不存在:若 ID 不是 ,返回空列表;若 ID 是 且 BLOCK,阻塞直到有消息。
时间复杂度:O (log (N)+M),N 是 Stream 总消息数,M 是返回的消息数。
Geospatial类型
Redis Geospatial(GEO)是 Redis 3.2 新增的功能,专为存储地理位置信息、实现距离计算 / 位置检索设计,底层基于 Sorted Set(Zset)实现(将经纬度编码为 score 存储),因此可复用 Zset 的部分命令(如 ZREM、ZRANGE)。
GEOADD
GEOADD key longitude latitude member [longitude latitude member ...]
将一个或多个 member(如商家 ID / 用户 ID)及其对应的经纬度(经度 longitude、纬度 latitude)添加到 Geospatial 键 key 中。经纬度范围限制:经度 -180~180,纬度 -85.05112878~85.05112878(超出则返回错误);底层:将经纬度编码为 52 位浮点数作为 Zset 的 score,member 作为 Zset 的成员。
返回值:成功添加的成员数量(整数);已存在的 member 会被更新,不计入返回值。
时间复杂度:O (M*log (N)),M 是添加的成员数量,N 是 key 中已有的成员数量。
GEOPOS
GEOPOS key member [member ...]
返回 key 中指定 member 的经纬度坐标。返回值:列表格式,每个 member 对应 [longitude, latitude];member 不存在则返回 nil。时间复杂度:O (M),M 是查询的成员数量。
GEODIST
GEODIST key member1 member2 [unit]
计算 key 中 member1 和 member2 之间的直线距离,可选单位(默认 m):m:米(默认);km:千米;mi:英里;ft:英尺。
返回值:距离值(字符串形式,保留小数);任一 member 不存在则返回 nil。
时间复杂度:O (1)。
GEORADIUS
GEORADIUS key longitude latitude radius m|km|mi|ft [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
以指定经纬度(longitude, latitude)为中心,检索半径 radius 范围内的所有 member。
可选参数:
WITHCOORD:返回成员的经纬度;
WITHDIST:返回成员与中心点的距离;
WITHHASH:返回经纬度编码后的哈希值;
COUNT:限制返回的成员数量;
ASC|DESC:按距离从近到远 / 从远到近排序;
STORE:将结果存储到指定 Zset 键;
STOREDIST:将成员与中心点的距离存储到指定 Zset 键(score 为距离)。
返回值:符合条件的成员列表(带可选参数则返回嵌套列表);无匹配成员返回空列表。
时间复杂度:O (N+log (M)),N 是 key 中成员总数,M 是返回的成员数量。
HyperLogLog类型
HyperLogLog(简称 HLL)是 Redis 2.8.9 新增的高级数据类型,专为海量数据的基数统计设计(基数:集合中不重复元素的数量)。
HyperLogLog不存储元素的内容,但是能够记录"元素的特征",从而在新增元素的时候,能够知道当前新增的元素是否是一个已经被添加的元素,还是一个新出现的元素。(只是用来计数但不能记录每个元素内容是什么,但有一些误差 0.87%)
PFADD
PFADD key element [element ...]
将一个或多个 element 加入到 HyperLogLog 键 key 中。如果 key 不存在,会自动创建一个空的 HyperLogLog 结构。返回值:
1:如果 HyperLogLog 的内部状态因本次添加发生了改变;
0:如果本次添加的元素均已存在,内部状态无变化(或 key 已存在但无新元素)。
时间复杂度:O (1)(每个元素的添加操作是常数时间)。
PFCOUNT
PFCOUNT key [key ...]
单个 key:返回该 HyperLogLog 中统计的基数(近似值);
多个 key:返回所有 HyperLogLog 合并后的基数(近似值,即所有 key 中不重复元素的总数)。
返回值:基数的近似值(整数);若 key 不存在,返回 0。
时间复杂度:
单个 key:O (1)(缓存了基数结果,直接返回);
多个 key:O (N)(N 是 key 的数量),且会触发一次合并计算,之后缓存结果。
PFMERGE
PFMERGE destkey sourcekey [sourcekey ...]
将一个或多个源 HyperLogLog(sourcekey)合并为一个新的 HyperLogLog(destkey),destkey 会存储所有源 key 的合并基数(不重复元素总数)。如果 destkey 已存在,会覆盖其原有内容。返回值:OK(操作成功)。时间复杂度:O (N),N 是源 key 的数量。
Bitmaps类型
Redis Bitmaps 并非独立的数据类型,而是基于 String 类型 实现的 "位操作" 功能(String 最大支持 512MB,可存储 2^32 个比特位)。
SETBIT
语法:SETBIT key offset value将 Bitmaps 键 key 中第 offset 位(偏移量,从 0 开始)设置为 value(0 或 1)。如果 key 不存在,会自动创建;如果 offset 超过当前 String 长度,会自动扩展并填充 0。返回值:该位的原始值(0 或 1);若该位从未设置过,返回 0。时间复杂度:O (1)。
GETBIT
GETBIT key offset
返回 Bitmaps 键 key 中第 offset 位的值(0 或 1)。如果 offset 超过当前 String 长度,返回 0。返回值:0 或 1;key 不存在时返回 0。时间复杂度:O (1)。
BITCOUNT
BITCOUNT key [start end]
统计 key 中值为 1 的比特位数量,可选 start/end 参数(按字节数范围统计,非位数)。返回值:置 1 的位数量(整数);key 不存在时返回 0。时间复杂度:O (N),N 是统计的字节数。
BITOP
BITOP operation destkey key [key ...]
对一个或多个 Bitmaps 执行位运算,结果存储到 destkey 中。operation 支持:
AND:按位与(全 1 则 1);
OR:按位或(有 1 则 1);
XOR:按位异或(不同则 1);
NOT:按位非(仅支持单个 key)。
返回值:结果位图 destkey 的字节长度。
时间复杂度:O (N),N 是参与运算的位图的总字节数。
BITPOS
BITPOS key bit [start] [end]
查找 key 中第一个值为 bit(0 或 1)的比特位偏移量,可选 start/end(字节范围)。返回值:第一个匹配位的偏移量;无匹配则返回 -1。时间复杂度:O (N),N 是查找的字节数。
Bitfields类型
位段(Bitfield)是 Redis 基于 String 类型实现的精细化位操作功能,它允许将一个二进制字符串(String)按自定义规则拆分为多个 "位段"------ 每个位段可指定长度(1~64 位)、类型(有符号 / 无符号整数),能独立对单个位段执行读取、赋值、原子增减操作,还可设置溢出策略;相比仅能操作单个比特位的 Bitmaps,位段可直接存储和操作整数型状态(如用户等级、积分),能在单个 key 中打包存储多个维度的数值状态,既保持了按位存储的极致内存效率,又解决了普通位操作无法处理多位数的问题,常用于多维度状态打包、高性能原子计数器等场景。
读取操作(GET)
BITFIELD key GET type offset(只读版:BITFIELD_RO key GET type offset)
功能:从 key 对应的二进制串中,读取以 offset(位偏移量)为起始、类型为 type 的位段值。type 支持 uN(无符号整数,1≤N≤64)或 iN(有符号整数,1≤N≤63),如 u8=8 位无符号、i16=16 位有符号;offset 为位偏移(从 0 开始),也可写 #n 表示 "上一个位段结束后第 n 位"。返回值:按位段类型解析的整数;key 不存在时返回 0。时间复杂度:O (1)
写入操作(SET)
BITFIELD key SET type offset value
功能:将 key 中指定 type 和 offset 的位段值设置为 value,value 需符合位段类型的取值范围(如 u8 需 0~255)。若 key 不存在,自动创建空字符串并扩展二进制位以适配偏移量(未设置的位默认 0)。返回值:该位段的原始值(未设置过则返回 0)。时间复杂度:O (1)
增量操作(INCRBY)
BITFIELD key [OVERFLOW strategy] INCRBY type offset increment
功能:对指定位段的值执行原子增量操作,increment 可正可负(负数为减量);可选 OVERFLOW 指定溢出策略(默认 WRAP):
WRAP:循环溢出(如 u8 最大值 255+1=0);
SAT:饱和溢出(如 u8 255+1=255,0-1=0);
FAIL:溢出时返回 nil,不修改值。
返回值:增量后的位段值;溢出且策略为 FAIL 时返回 nil。
时间复杂度:O (1)
注意:BITFIELD bike:1:stats INCRBY u32 #0 -50 INCRBY u32 #1 1 这里的 #N 不是第 N 个字段,而是:第 N 个「u32 宽度」字段