Redis 核心概念、命令详解与应用实践:从基础到分布式集成
目录
1. 认识 Redis
2. Redis 特性
2.1 操作内存
2.2 速度快
2.3 丰富的功能
2.4 简单稳定
2.5 客户端语言多
2.6 持久化
2.7 主从复制
2.8 高可用 和 分布式
2.9 单线程架构
2.9.1 引出单线程模型
2.9.2 单线程快的原因
2.10 Redis 和 MySQL 的特性对比
2.11 Redis 使用场景
3. 分布式系统
3.1 分布式系统的核心概念
3.1.1 应用 (Application) / 系统 (System)
3.1.2 模块 (Module) / 组件 (Component)
3.1.3 分布式 (Distributed)
3.1.4 集群 (Cluster)
3.1.5 主 (Master) / 从 (Slave)
3.1.6 中间件 (Middleware)
3.2 分布式系统的由来
4. Redis 常用命令
4.1 KESY
4.2 EXISTS
4.3 DEL
4.4 EXPIRE
4.5 TTL
4.6 键过期的机制
4.7 TYPE
4.8 Redis 数据结构和内部编码
4.9 String 字符串
4.9.1 SET
4.9.2 GET
4.9.3 MGET
4.9.4 MSET
4.9.5 SETNX
4.9.6 INCR
4.9.7 INCRBY
4.9.8 DECR
4.9.9 DECRBY
4.9.10 INCRBYFLOAT
4.9.11 APPEND
4.9.12 GETRANGE
4.9.13 SETRANGE
4.9.14 STRLEN
4.9.15 String 命令小结
4.10 Hash 哈希
4.10.1 HSET
4.10.2 HEGT
4.10.3 HEXISTS
4.10.3 HDEL
4.10.4 HKEYS
4.10.5 HVALS
4.10.6 HGETALL
4.10.7 HMGET
4.10.8 HLEN
4.10.9 HSETNX
4.10.9 HCRBY
4.10.9 HCRBYFLOAT
4.10.10 Hash 命令小结
4.10.11 Hash 应用场景
4.11 List 列表
4.11.1 列表的特点
4.11.2 LPUSH
4.11.3 LPUSHX
4.11.4 PUSH
4.11.5 PUSHX
4.11.6 LRANGE
4.11.7 LPOP
4.11.8 RPOP
4.11.9 LINDEX
4.11.9 LINSERT
4.11.10 LEN
4.11.11 BLPOP
4.11.12 BRPOP
4.11.13 LREM
4.11.14 LTRIM
4.11.15 List 命令小结
4.11.16 List 使用场景
4.12 SET 集合
4.12.1 SADD
4.12.2 SMEMBERS
4.12.3 SISMEMBER
4.12.4 SCARD
4.12.5 SPOP
4.12.6 SMOVE
4.12.7 SREM
4.12.8 SINTER
4.12.9 SINTERSTORE
4.12.10 SUNION
4.12.11 SUNIONSTORE
4.12.12 SDIFF
4.12.13 SDIFFSTORE
4.12.14 命令小结
4.12.15 Set 使用场景
4.13 Zset 有序集合
4.13.1 ZADD
4.13.2 ZCARD
4.13.3 ZCOUNT
4.13.4 ZRANGE
4.13.5 ZREVRANGE
4.13.6 ZRANGEBYSCORE
4.13.7 ZPOPMAX
4.13.8 BZPOPMAX
4.13.9 ZPOPMIN
4.13.9 BZPOPMIN
4.13.10 ZRANK
4.13.11 ZSCORE
4.13.12 ZREM
4.13.12 ZREMRANGEBYRANK
4.13.13 ZREMRANGEBYSCORE
4.13.13 ZINCRBY
4.13.13 ZINTERSTORE
4.13.14 ZUNIONSTORE
4.13.15 Zset 命令小结
4.13.16 Zset 使用场景
4.14 渐进式遍历
4.15 数据库管理
4.15.1 select
4.15.2 flushdb / flushall
4.16 不常用命令集合
4.16.1 Stream
4.16.2 Geospatial
4.16.3 Hyperloglog
4.16.4 Bitmaps
4.16.5 Bitfields
5. RESP
6. Jedis
6.1 引入 Jedis 依赖
6.2 Jedis 命令集
7 Spring Boot 使用 Redis
7.1 配置依赖
7.2 String 类
7.3 List 类
7.3 Hash 类
7.4 Set 类
7.5 Zset 类
1. 认识 Redis
Redis 是一种 基于键值对(key - value) 的 NoSQL(Not Only SQL) 数据库,属于非关系型数据库。与很多键值对数据库不同的是,Redis 中 key - value 中 value ,数据类型可以为 string、hash、list、set、zset 等等,因此 Redis 可以 满足很多应用场景。
非关系型数据库:它不依赖固定的表结构,通常以键值对、文档、列族或图等形式存储数据,更适合处理大规模、高并发或非结构化数据。
关系型数据库:比如 MySQL 就是一个典型的关系型数据库。
2. Redis 特性
2.1 操作内存
Redis 会将所有数据存放在 内存中,所以 Redis 的读写性能非常恐怖;此外 Redis 还可以将内存数据 利用快照和日志 的形式保存在硬盘上,所以在发生 类似断电 的故障时,内存中 Redis 管理的数据不会丢失。
2.2 速度快
(1) Redis 是用 C 语言实现的,“距离” 操作系统更近,执行速度相对会更快。
(2) Redis 使用了单线程,预防了多线程可能产生的竞技问题。
Redis 在 6.0 版本引入了多线程机制,但主要也是在 处理网络和IO,不涉及到数据命令,即命令的执行仍采用了单线程模式。不会产生 多个 进程访问造成的数据异常现象。
Redis 在执行策略上,核心执行模型基于单线程事件循环,主要依赖 I/O 多路复用技术(如 epoll、kqueue)处理高并发请求。
2.3 丰富的功能
除了 5种 数据结构,Redis 还提供了 许多额外的功能:
(1) 提供了键过期功能,可以用来实现缓存。
(2) 支持 Lua 脚本功能,可以利用 Lua 创造出新的 Redis 命令。
(3) 提供了简单的事务功能,能在一定程度上保证事务特性。
(4) 提供了流水线(Pipeline) 功能,这样客户端能将 一批 命令 一次性 传到 Redis,减少了网络的开销。
Redis 基于网络 可以把自己内存中的变量,给别的进程,甚至别的主机进行使用。Redis 在分布式系统中,才能发挥威力。如果 是单机程序,那么定义变量在内存中,是比 Redis 更好的选择。
2.4 简单稳定
Redis 的简单主要体现在三个方面
首先,Redis 的源码少,早起版本只有 2万 行左右。3.0 版本以后由于 添加了集群特性,代码增至 5 万行左右。其次 Redis 使用单线程模型,使得 Redis 服务器端 和 客户端的开发变得简单。最后 Redis 不依赖于操作系统中的类库,Redis 自己实现了事件处理的相关功能。
分布式系统:由多台计算机 通过网络连接,协同完成任务。各节点通常具备独立计算和存储能力,通过消息传递实现协作。
集群系统:多台计算机 集中部署在同一局域网内,通过同一管理对外提供服务,节点间共享任务负载,通常用于提供性能或可靠性
关联性:均通过多台机器协作提高系统的可用性、扩展性或容错性。实际场景中常结合部署,例如分布式集群(如Hadoop集群分部署在不同机房)。
2.5 客户端语言多
Redis 基于 TCP 通信协议,很多编程语言可以很方便的 接入Redis,所以支持 Redis 的客户端非常多,列入 C、C++、JAVA 等多种语言
2.6 持久化
Redis 提供了两种 持久化方式:RDB 和 AOF,可以用两种策略将内存的数据保存在硬盘中,就保证了数据的可持久性
2.7 主从复制
Redis 提供了复制功能,实现了多个相同数据 的 Redis 副本,复制功能是 分布式 Redis 的基础吗。
2.8 高可用 和 分布式
Redis 提供了 高可用实现的 Redis 哨兵 (Redis Sentinel),能够保证 Redis 节点的故障发现和故障自动转移。也提供了 Redis 集群 (Redis Cluster),是真正的分布式实现,提供了高可用、读写和容量的拓展性。
2.9 单线程架构
Redis 使用了单线程架构 来实现高性能内存数据库服务。我将先通过 多个客户端命令调用的例子 说明 Redis 单线程命令处理机制,接着分析 Redis 单线程模型性能为什么 如此之高。
2.9.1 引出单线程模型
现在开启了三个 redis-cli 客户端同时执行命令
客户端 1 设置一个字符串键值对:
127.0.0.1:6379> set hello world
客户端 2 对 counter 做自增操作:
127.0.0.1:6379> incr counter
客户端 3 对 counter 做自增操作:
127.0.0.1:6379> incr counter
我们知道 Redis 使用的是单线程模型,理论上 这些命令是按照 线性方式执行的,只是原则上 命令的执行顺序是不确定的,但是 一定不会有两条命令被同步执行。两条 incr 命令无论执行顺序,结果一定是 2,这个就是 Redis 的单线程执行模型。
2.9.2 单线程快的原因
通常来说,单线程处理能力要比多线程差,但是 Redis 单线程之所以快,分为一下三个原因。
(1) 纯内存访问,Redis 将所有数据都放在内存中,内存的响应大约 100 纳秒,是 Redis 访问快的重要基础。
(2) 非阻塞 IO,Redis 使用 epoll 作为 I/O 多路复用技术的实现,加上 Redis 本身的事件处理模型 将 epoll 中的连接、读写、关闭都转为事件,不在网络 I/O 上浪费过多时间
(3) 单线程 避免了 线程切换和竞态产生的消耗。单线程可以简化数据结构和算法的实现,让程序模拟更简单。
(4) Redis 和 mysql 访问速度对比
<1> Redis 访问内存,MySQL 访问硬盘
<2> Redis 核心功能 比 MySQL 数据库核心功能简单,mysql 在插入、删除和更新时,具有更复杂的功能支持,也有数据约束性。
<3> redis 是单线线程操作,对比多线程减少了不必要的线程竞争开销
<4>处理网络 IO 时,redis 使用了 epoll 这样的 IO 多路复用机制
虽然单线程给 Redis 带来了很多好处,但是 每一个命令 不能执行过长,不然就会造成 Redis 客户端的阻塞。
2.10 Redis 和 MySQL 的特性对比
说到数据库,那么我们一定就会想到 MySQL。那么 Redis 和 MySQL 有什么比较明显的差异呢?
(1) MySQL 数据查询时 会因为 涉及数据约束等因素,IO查询次数会涉及多次,而 Redis 的查询功能 不像 MySQL 那么强大,所以 Redis 查询速度会更快;
(2) MySQL 储存数据 在 硬盘,Redis 储存数据在内存。对于计算机来说,储存在 内存中的速度 远远大于 储存在硬盘中的速度,大约会快 10万 倍。
(3) MySQL 储存在硬盘,所带来的优势就是 储存的空间要很大。而 Redis 储存在内存中,储存空间 就要小的很多了。相比扩充 内存 的消费来说,扩充 硬盘的消费 要小的很多。
2.11 Redis 使用场景
redis 是一个基于内存存储的中间件,访问速度快,常用于 缓存、实时性数据存储、会话(session)存储、消息队列、排行榜系统、计数器应用、社交网络、消息队列系统、⼿机验证码等。
在计算机领域中,有一个"二八原则",具体指的是,20% 的数据 要满足 80% 的访问需求。
3. 分布式系统
上述 Redis 介绍中提到过分布式系统,那么什么是分布式系统呢?
3.1 分布式系统的核心概念
3.1.1 应用 (Application) / 系统 (System)
由单个程序 或 一组协同工作的 程序群组成,用于完成特定服务或任务。
3.1.2 模块 (Module) / 组件 (Component)
复杂系统中 为了分离职责 而 分离的单元,具有 高内聚性 和 明确功能。
3.1.3 分布式 (Distributed)
系统的模块部署于 不同的服务器,依赖网络通信协作。
3.1.4 集群 (Cluster)
多台服务器上部署同一组件群,共同实现特定目标。逻辑上强调目标一致性。
3.1.5 主 (Master) / 从 (Slave)
集群中分工角色:主节点承担核心职责(如数据写入),从节点执行辅助任务(如数据同步)。例如数据库主库负责写操作,从库同步数据并提供读服务。
3.1.6 中间件 (Middleware)
一类提供 不同应用程序 相互通信的软件,即处于不同技术、工具和数据库之间的桥梁。如 数据库、缓存、消息队列等.....。
3.2 分布式系统的由来
(1) 期初用户访问量少,没有对性能、安全等提出很高的要求时,单机架构是合适的。单机架构 简单,无需专业的运维团队。
(2) 随着系统的上线,用户的访问量逐步上升,逐渐逼近了硬件资源的极限。面对性能压力,提出了应用数据分离架构。选择将应用和数据分离的做法,可以最小代价的提升系统的承载能力。
和单机架构的主要区别在于将数据库服务独⽴部署在同⼀个数据中⼼的其他服务器上,应⽤服务通过 ⽹络访问数据。
(3) 随着爆款的出现,单台应用服务器已经无法满足需求了。解决方案有以下两条:
垂直拓展 / 纵向拓展 Scale Up,通过购买性能更优,价格更高的应用服务器来应对更多流量。优势在于不需要对系统软件做调整,缺点就是 硬件性能 不是线性提升,费钱的同时 提升却不大。
水平拓展 / 横向拓展 Scale Out,通过增加应用层硬件,将用户流量分配到不同的服务器上,来提升系统的 承载能力。优势在于 成本低,提升的上限空间大,缺点是 复杂的系统,需要 丰富经验的 技术团队维护。
显然,水平拓展更能解决问题。为了使用水平拓展方案,不得不引入一个新的 组件------负载均衡;负载均衡 是一个专门的做 流量分发的系统组件,流量调度算法有多种,简单且常见的为以下几种:
<1> Round-Robin 轮询算法:即公平地将请求依次分给不同的应用服务器
<2> Weight-Round-Robin 轮询算法:为不同的服务器 赋予不同的 权重
<3> 哈希散列算法:通过计算用户的特征值(如 IP 地址) 得到哈希值,根据哈希值结果做分发,优点是确保来自相同用户的请求总是被分给指定的服务器。
(4) 随着业务的增长,可以动态扩张服务器数量来缓解压力。但是现在的架构中,无论拓展多少台服务器,请求最终都会从数据区 读写数据,我们不能像 拓展服务器一样 拓展数据库,因为数据库的 数据有其特殊性。
可以采用的解决方法为,保留一个主要的数据库作为写入数据库,其他的数据库作为从数据库。从数据库所有数据来自 主数据库,经过同步后,从库可以维护与主库一样的数据,为主库分担读数据的压力。
(5) 随着访问数据量的增加,一些数据的读取频率 远远大于 其他数据的读取频率。我们把 这部分数据称为 热点数据,与之对应的是 冷数据。针对 热数据,为了提升读取的响应时间,可以增加本地缓存,并在外部增加分布式缓存。
(6) 随着业务的数据量增大,如果想进一步减少服务器压力,则可以引入多个数据库服务器,每个数据库服务器存储一个或多个表。 如果其中表过大也可以针对表进行拆分,存储到不同的数据库服务器,以上可以称为"分库分表",解决存储空间不足的问题。
微服务: 微服务是一种软件架构风格,将单一应用程序划分为一组小型、独立的服务。每个服务运行在自己的进程中,通过轻量级通信机制(如HTTP/REST或消息队列)进行交互。微服务通常围绕业务功能进行组织,可以独立部署、扩展和维护。为了 精确分工,代码解耦合。微服务需要网络通信,比硬盘来说还要慢很多。但是万兆网卡读写速度比硬盘快,唯一的缺点就是 贵。
4. Redis 常用命令
object encoding key ,查看 key 对应的 value 实际的编码方式
4.1 KESY
通配符模式 | 通配符说明 | 匹配示例 | 不匹配示例 |
---|---|---|---|
h?llo | ? 匹配1 个任意字符 | hello 、hallo 、hxllo | heeello (中间 3 个e )、hllo (中间 0 个字符) |
h*llo | * 匹配0 个或多个任意字符 | hllo (0 个字符)、heeeello (多个e ) | 理论无(* 覆盖所有中间字符数量) |
h[ae]llo | [ae] 匹配 **a 或 e 中的 1 个 ** | hello (e )、hallo (a ) | hillo (中间i )、hxllo (中间x ) |
h[^e]llo | [^e] 匹配非 e 的 1 个字符 | hallo (a )、hbllo (b )、hcllo (c )… | hello (中间e ) |
h[a-b]llo | [a-b] 匹配 **a 或 b 中的 1 个 **(按 ASCII 范围) | hallo (a )、hbllo (b ) | hcllo (中间c )、hdllo (中间d ) |
语法:
KEYS pattern
时间复杂度 O(N)
返回值:匹配 pattern 的所有 key
4.2 EXISTS
判断某个 key 是否存在
语法:
EXISTS key [key....]
时间复杂度O(1)
返回值:key 存在的个数
示例:
4.3 DEL
删除指定的 key
语法:
DEL key [key...]
时间复杂度O(1)
返回值:删除掉 key 的个数
示例:
4.4 EXPIRE
为指定的 key 添加秒级的过期时间
语法:
EXPIRE key seconds
时间复杂度O(1)
返回值:1 表示设置成功,0 表示设置失败
示例:
4.5 TTL
获取指定 key 的过期时间,秒级
时间复杂度O(1)
返回值: 过期时间,-1 表示没有关联过时间,-2 表示 key 不存在
语法:
TTL key
示例:
EXPIRE 和 TTL 命令都有对应的⽀持毫秒为单位的版本:PEXPIRE 和 PTTL。
IP 协议中的 TTL 不是用时间衡量,而是用次数。
4.6 键过期的机制
我们知道 直接遍历所有的 key 效率非常低,redis 的策略有两种,定期删除 和 惰性删除。
定期删除:Redis 默认每隔 100 毫秒随机抽取一部分设置了过期时间的键,检查是否过期,如果过期则删除。这种策略通过分散操作来减少对系统性能的影响,但无法保证所有过期键都会被立即清理。
惰性删除:当客户端尝试访问某个键时,Redis 会先检查该键是否已过期,如果过期则直接删除并返回空值。惰性删除确保只有在键被访问时才会触发清理操作,节省了不必要的 CPU 开销,但可能导致大量过期键堆积。
除了上述两种,redis 还提供了多种内存淘汰策略;redis 中没有采取 定时器 的方式来实现过期 key 删除。如果有多个 key 过期,也可以基于 优先级队列或时间轮 实现定时器,来高效/节省 cpu 的前提下来处理多个 key。
4.7 TYPE
返回 key 对应的 数据类型
语法:
TYPE key
时间复杂度O(1)
返回值:value 对应的数据类型,包括 none,string,list,set,zset,hash....等
示例:
4.8 Redis 数据结构和内部编码
数据结构 | 内部编码 | 编码特点 / 设计逻辑 | 适用条件(或触发转换的阈值) |
---|---|---|---|
string | int | 存储整数型值,直接以 long 类型存储,节省空间、读写高效(无需字符串解析)。 | 值可转为整数(如 "123" ),且在 Redis 整数范围(通常 ±9007199254740992,即 2^53 边界)。 |
embstr | 短字符串专用,内存连续分配(对象头 + 字符串内容),只读(修改会转为 raw )。 | 字符串长度 ≤39 字节(Redis 不同版本可能微调,如早期 ≤39,后续版本可能因内存优化调整),且未被修改过。 | |
raw | 通用字符串存储,内存分开分配(对象头和字符串内容独立),支持任意长度修改。 | 字符串长度 >39 字节,或 embstr 被修改(如 APPEND 操作)。 | |
hash | ziplist | 紧凑的压缩列表,字段和值依次存储,适合小数据量,读写需遍历(性能依赖数据量)。 | 字段数量 ≤hash-max-ziplist-entries (默认 512),且字段 / 值长度 ≤hash-max-ziplist-value (默认 64)。 |
hashtable | 哈希表结构,字段为键、值为哈希节点,支持 O (1) 查找,但内存开销稍大。 | 字段数量 / 长度超过上述阈值,自动转为 hashtable (转换不可逆)。 | |
list | ziplist | 压缩列表存储,元素按顺序排列,适合少量短元素,插入 / 查询需遍历。 | 元素数量 ≤list-max-ziplist-entries (默认 512),且元素长度 ≤list-max-ziplist-value (默认 64)。 |
linkedlist | 双向链表结构,插入 / 删除高效(O (1) 定位后操作),但内存开销大(每个节点存指针)。 | Redis 3.2 前:元素数量 / 长度超过阈值时转为 linkedlist ;Redis 3.2+:默认用 quicklist (ziplist 链表,兼顾紧凑性和效率)。 | |
set | intset | 整数集合,紧凑数组存储(按升序排列),通过二分查找,仅支持整数元素。 | 所有元素为整数,且数量 ≤set-max-intset-entries (默认 512)。 |
hashtable | 哈希表结构,元素为键(值为 null ),支持任意类型元素,查找 O(1)。 | 存在非整数元素,或数量超过阈值,自动转为 hashtable (转换不可逆)。 | |
zset | ziplist | 压缩列表存储,元素按分数排序(需手动保证有序插入),适合小数据量,读写需遍历。 | 元素数量 ≤zset-max-ziplist-entries (默认 128),且元素 / 分数长度 ≤zset-max-ziplist-value (默认 64)。 |
skiplist | 跳表 + 字典结构(跳表存有序元素,字典存分数映射),支持快速范围查询(O(logN))。 | 元素数量 / 长度超过上述阈值,自动转为 skiplist (转换不可逆)。 |
Redis 这样设计有两个好处:
(1) 可以改进内部编码,而对外的数据结构和命令没有任何影响,开发出更优秀的内部编码,无需改动外部数据结构和命令。例如 Redis 3.2 在 list 结构中 提供了 quicklist,结合了 ziplist 和 linkedlist 两者的优势。
(2) 多种内部编码实现 可以在不同的场景下发挥各自的优势,例如 ziplist 比较节省内存,但是在列表元素较多的情况下,性能会下降,这时候 Redis 会根据配置选项 将列表类型内部实现自动转换为 linkedlist ,整个过程用户无感知。
quicklist 是一个链表,每个元素是一个 ziplist;当set内元素都是int 时,使用 intset。
4.9 String 字符串
字符串类型是 Redis 最基础的数据类型。Redis 中所周键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的。Redis 有五种数据结构,但都是键值对中的值。
由于 Redis 内部存储字符串完全是按照⼆进制流的形式保存的,所以 Redis 不处理字符集 编码问题的,客⼾端传⼊的命令中使⽤的是什么字符集编码,就存储什么字符集编码。
4.9.1 SET
将 string 类型的 value 设置到 key 中。如果 key 之前存在 则覆盖,无论之前 key 是什么类型。
语法:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
时间复杂度:O(1)
返回值:设置成功返回 OK,如果 SET 制定了 NX 或 XX 条件不满足,返回 nil
选项 | 参数格式 | 作用描述 | 等效旧命令 | 示例(Redis 命令行) | 关键注意事项 |
---|---|---|---|---|---|
EX | EX <seconds> | 设置 key 的秒级过期时间(TTL) | SETEX | SET mykey EX 60 "value" | 过期时间为 60 秒,过期后 key 自动删除 |
PX | PX <milliseconds> | 设置 key 的毫秒级过期时间(TTL) | PSETEX | SET mykey PX 5000 "value" | 过期时间为 5000 毫秒(即 5 秒) |
NX | NX (无值参数) | 仅当 key 不存在时,才执行 SET 操作 | SETNX | SET mykey NX "new-value" | 若 key 已存在,命令无效果(返回 nil ) |
XX | XX (无值参数) | 仅当 key 存在时,才执行 SET 操作 | 无直接等效(需手动 EXISTS 判断) | SET mykey XX "update-value" | 若 key 不存在,命令无效果(返回 nil ) |
XX 可以理解为更新选项,NX 可以理解为创建选项
4.9.2 GET
获取 key 对应的 value,如果 key 不存在,则返回 nil。如果 value 的数据类型不是 string ,会报错。
语法:
GET key
时间复杂度O(1)
返回值:key 对应的 value,当 key 不存在时 返回 nil
示例:
4.9.3 MGET
一次性获取多个 key 的值,如果对应的 key 不存在或者对应的数据类型不是 string,返回 nil
语法:
MGET key [key ...]
时间复杂度O(N),N 是 key 的数量
返回值:对应 value 的列表
示例:
4.9.4 MSET
一次性设置多个 key 的值
语法:
MSET key value [key value ...]
时间复杂度:O(N),N 是 key 的数量
返回值:OK
示例:
使⽤ mget / mset 由于可以有效地减少了⽹络时间,所以性能相较更⾼。
4.9.5 SETNX
只允许在 key 不存在的情况下设置 key-value
语法:
SETNX key value
时间复杂度O(1)
返回值:1 表示设置成功,0 表示设置失败
示例:
除了 SETNX 还有 SETXX,具体使用规则跟上述规则一样
4.9.6 INCR
将 key 对应的 string 表示的数字加一,如果 key 不存在,则视 key 对应的 value 为 0。如果 key 对应的 不是一个整型、string 类型 或 范围超过了 64 位有符号整型则报错。
语法:
INCR key
时间复杂度:O(1)
返回值:integer 类型的加完后的数值
示例:
4.9.7 INCRBY
将 key 对应的 string 表示的数字加上对应的值。如果对应的值是负数,则视为减去对应的值。如果 key 不存在,则视 key 对应的 value 值为 0。如果 key 对应的 不是一个整型、string 类型、或者范围超过了 64 位有符号整型,则报错。
语法:
INCRBY key decrement
时间复杂度:O(1)
返回值:integer 类型的加完后的数值
示例:
4.9.8 DECR
将 key 对应的 string 表示的数字减一。如果 key 不存在,则视 key 对应的 value 为 0。如果 key 对应的 不是一个整型、string 类型 或范围超过了 64 为有符号整型,则报错。
语法:
DECR key
时间复杂度:O(1)
返回值:integer 类型的减完后的数值
示例:
4.9.9 DECRBY
将 key 对应的 string 表示的数字减去对应的值。如果 key 不存在,则视 key 对应的 value 为 0。如果对应的值是负数,则视为加上对应的值。如果 key 对应的不是一个整型、string 类型 或范围超过了 64 为有符号整型,则报错。
语法:
DECRBY key decrement
时间复杂度:O(1)
返回值:integer 类型的减完后的数值
示例:
4.9.10 INCRBYFLOAT
将 key 对应的 string 表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果 key 不存在,则视 key 对应的 value 为 0。如果 key 对应的 不是一个 string 类型 、浮点数 或 整数,则报错。允许采用科学技术表示浮点数。
语法:
INCRBYFLOAT key increment
时间复杂度:O(1)
返回值:加/减 完之后的数值
示例:
4.9.11 APPEND
如果 key 已经存在并且是一个 string 或者可以转换成 string 类型的,命令会将 vlaue 追加到原有的 string 的后边。如果 key 不存在,则效果等同于 SET 命令
语法:
APPEND KEY VALUE
时间复杂度:O(1)
返回值:追加完之后的 string 长度
示例:
4.9.12 GETRANGE
返回 key 对应的 string 的子串,由 start 和 end 确定 (闭区间)。可以使用负数表示倒数,-1 代表队尾。超过 string 长度 会自动调整成正确的值
语法:
GETRANGE key start end
时间复杂度:O(N),N 为 [start , end] 区间的长度。由于 string 通常比较短,可以视为是 O(1)
返回值:string 类型的子串
示例:
4.9.13 SETRANGE
覆盖字符串的一部分,从指定的偏移量开始
语法:
SETRANGE key offset value
时间复杂度:O(N),N 为 value 的长度。由于一般给的 value 比较短,通常视为 O(1)
返回值:替换后的 string 长度
示例:
4.9.14 STRLEN
获取 key 对应的 string 长度。当 key 存放的类型不是 string 时报错
语法:
STRLEN key
时间复杂度:O(1)
返回值:string 的长度。当 key 不存在时 返回 0
示例:
4.9.15 String 命令小结
命令 | 执行效果 | 时间复杂度 |
---|---|---|
set key value [key value...] | 设置 key 的值是 value | O (k),k 是键个数 |
get key | 获取 key 的值 | O(1) |
del key [key ...] | 删除指定的 key | O (k),k 是键个数 |
mset key value [key value ...] | 批量设置指定的 key 和 value | O (k),k 是键个数 |
mget key [key ...] | 批量获取 key 的值 | O (k),k 是键个数 |
incr key | 指定的 key 的值 +1 | O(1) |
decr key | 指定的 key 的值 -1 | O(1) |
incrby key n | 指定的 key 的值 +n | O(1) |
decrby key n | 指定的 key 的值 -n | O(1) |
incrbyfloat key n | 指定的 key 的值 +n | O(1) |
append key value | 指定的 key 的值追加 value | O(1) |
strlen key | 获取指定 key 的值的长度 | O(1) |
setrange key offset value | 覆盖指定 key 的从 offset 开始的部分值 | O (n),n 是字符串长度,通常视为 O (1) |
getrange key start end | 获取指定 key 的从 start 到 end 的部分值 | O (n),n 是字符串长度,通常视为 O (1) |
4.10 Hash 哈希
几乎所有的主流变成语言都提供了 哈希 (hash) 类型,哈希类型指本身是一个键值对结构。Redis 键值对和哈希类型二者的关系可以由下图表示
哈希类型中的映射关系通常被称为 field-value ,用于区分 Redis 整体的键值对 (key-value),注意这里的 value 是指 filed 对应的值,不是键 (key) 对应的值。
4.10.1 HSET
设置 hash 中指定的 字段 (field) 的 值 (value)
语法:
HSET key field value [field value ...]
时间复杂度:插入一组 field 为 O(1),插入 N 组 field 为 O(N)
返回值:添加的字段个数
示例:
4.10.2 HEGT
获取 hash 中指定 field 的值
语法:
HGET key field
时间复杂度:O(1)
返回值:字段对应的值或nil
示例:
4.10.3 HEXISTS
判断 hash 中是否有指定的 field
语法:
HEXISTS key field
时间复杂度:O(1)
返回值:1 表示存在,0 表示不存在
示例:
4.10.3 HDEL
删除 hash 中指定的 field
语法:
HDEL key field [field ...]
时间复杂度:删除一个元素为 O(1),删除 N 个元素为 O(N)
返回值:本次操作删除的字段个数
示例:
4.10.4 HKEYS
获取 hash 中所有的 field
语法:
HKEYS key
时间复杂度:O(N),N 为 field 的个数
返回值:字段列表
示例:
4.10.5 HVALS
获取 hash 中的所有 value
语法:
HVALS key
时间复杂度:O(N),N 为 field 个数
返回值:所有的值
示例:
4.10.6 HGETALL
获取 hash 中所有的 field 和所对应的 value
语法:
HGETALL key
时间复杂度:O(N),N 为 field 的个数
返回值:field 和对应的 value
示例:
4.10.7 HMGET
一次获取 hash 中多个 field 所对应的 value
语法:
HMGET key field [field ...]
时间复杂度:只查询一个元素为 O(1),查询N个元素为 O(N)
返回值:字段对应的值或 nil
示例:
4.10.8 HLEN
获取 hash 中的所有字段的个数
语法:
HLEN key
时间复杂度:O(1)
返回值:field 个数
示例:
4.10.9 HSETNX
在 field 不存在的情况下,设置 hash 中的 field 和所对应的 value
语法:
HSETNX key field value
时间复杂度:O(1)
返回值:1 表示设置成功,0 表示失败
示例:
4.10.9 HCRBY
将 hash 中 field 对应的 value 值添加指定的值
语法:
HINCRBY key field increment
时间复杂度:O(1)
返回值:该 field 所对应的 value 变化之后的值
示例:
4.10.9 HCRBYFLOAT
HINCRBY 的浮点数版本
语法:
HINCRBYFLOAT key field increment
时间复杂度:O(1)
返回值:该 field 所对应的 value 变化之后的值
示例:
4.10.10 Hash 命令小结
命令 | 执行效果 | 时间复杂度 |
---|---|---|
hset key field value | 设置值 | O(1) |
hget key field | 获取值 | O(1) |
hdel key field [field ...] | 删除 field | O (k),k 是 field 个数 |
hlen key | 计算 field 个数 | O(1) |
hgetall key | 获取所有的 field-value | O (k),k 是 field 个数 |
hmget field [field ...] | 批量获取 field-value | |
hmset field value [field value ...] | 批量获取 field-value | O (k),k 是 field 个数 |
hexists key field | 判断 field 是否存在 | O(1) |
hkeys key | 获取所有的 field | O (k),k 是 field 个数 |
hvals key | 获取所有的 value | O (k),k 是 field 个数 |
hsetnx key field value | 设置值,但必须在 field 不存在时才能设置成功 | O(1) |
hincrby key field n | 对应 field-value +n | O(1) |
hincrbyfloat key field n | 对应 field-value +n | O(1) |
hstrlen key field | 计算 value 的字符串长度 | O(1) |
4.10.11 Hash 应用场景
存储结构化的数据,比如说表的结构,一个 userinfo 里有多个 属性。 如果要通过 String 存储,就需要使用 像 Json 这样的格式。而 Json 这样的格式 不能单独操作单一属性,需要转化来转化去,相比于 Redis 能直接修改单一属性就会增加运行时间。但是 Json 比 Redis 的需要的内存空间要少,因为 Redis 的实现是 Hash 类型,需要大量的 内存空间。
Hash 类型做缓存的优缺点
优点:简单、直观、灵活。尤其是针对信息的局部变换或者获取操作。
缺点:需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,可能会造成内存的较大消耗。
4.11 List 列表
列表类型是用来存储多个有序的字符串,列表中的每个字符串被称为 元素 (element)。在 Redis ,可以对列表两端插入 (push) 和弹出 (pop)。还可以获取指定范围的元素列表,获取指定索引下标的元素等。Redis 中的 List 可以充当栈和队列的角色,在实际开发中有很多应用场景。
4.11.1 列表的特点
(1) 列表中的元素是有序的,意味着可以通过索引下标获取某个元素或某个范围的元素列表
(2) 区分获取和删除的区别, lrem 操作会删除列表中的元素并且获取值,但是 lindex 操作只会获取元素,列表的长度不变
(3) 列表中的元素是允许重复的
4.11.2 LPUSH
讲一个或多个元素从左侧头插到 lsit 中
语法:
LPUSH key element [element ...]
时间复杂度:直插入一个元素为 O(1),插入多个元素为 O(N),N 为插入元素个数
返回值:插入后 lsit 的长度
示例:
4.11.3 LPUSHX
在 key 存在时,将一个或多个元素从左侧头插到 lsit 中,不存在则直接返回 0
语法:
LPUSHX key element [element ...]
时间复杂度:只插入一个元素为 O(1),插入 N 个元素为 O(N)。
返回值:插入后 lsit 的长度
示例:
4.11.4 PUSH
将一个或多个元素从右侧尾插到 lsit 中
语法:
RPUSH key element [element ...]
时间复杂度:只插入一个元素为 O(1),插入 N 个元素为 O(N)。
返回值:插入后 lsit 的长度
示例:
4.11.5 PUSHX
将一个或多个元素从右侧尾插到 lsit 中
语法:
RPUSHX key element [element ...]
时间复杂度:只插入一个元素为 O(1),插入 N 个元素为 O(N)。
返回值:插入后 lsit 的长度
示例:
4.11.6 LRANGE
获取 start 到 end 区间的所有元素,闭区间。
语法:
LRANGE key start stop
时间复杂度:O(N)
返回值:指定区间的元素
示例:
4.11.7 LPOP
从 list 左侧取出元素(头删)
语法:
LPOP key
时间复杂度:O(1)
返回值:取出的元素或 nil
示例:
4.11.8 RPOP
从 list 右侧取出元素(尾删)
语法:
RPOP key
时间复杂度:O(1)
返回值:取出的元素或 nil
示例:
4.11.9 LINDEX
获取从左侧树第 index 位置的元素
语法:
LINDEX key index
时间复杂度:O(N)
返回值:取出的元素或nil
示例:
4.11.9 LINSERT
在特定位置插入元素
语法:
LINSERT key <BEFORE | AFTER> pivot element
时间复杂度:O(N)
返回值:插入后 list 的长度
示例:
4.11.10 LEN
获取 list 长度
语法:
LLEN key
时间复杂度:O(1)
返回值:list 的长度
示例:
4.11.11 BLPOP
LPOP 的阻塞版本,可以通过 timeout 设置最长等待时间
语法:
BLPOP key [key ...] timeout
时间复杂度:O(1)
返回值:取出的元素或nil
示例:
4.11.12 BRPOP
RPOP 的阻塞版本,可以通过 timeout 设置最长等待时间
语法:
BRPOP key [key ...] timeout
时间复杂度:O(1)
返回值:取出的元素或nil
示例:
4.11.13 LREM
删除制定个数元素,count 指删除的个数,element 删除的元素
语法:
LREM key count element
count > 0 从左往右找 对应个数的 元素
count < 0 从右往左找 对应个数的 元素
count = 0 删除所有的 对应的 元素
时间复杂度:O(N),N 为列表总元素
示例:
4.11.14 LTRIM
删除 start 和 stop 区间之外的所有元素
语法:
LTRIM key start stop
时间复杂度:O(N) ,N 为列表总元素
示例:
4.11.15 List 命令小结
操作类型 | 命令语法 | 时间复杂度说明 |
---|---|---|
添加 | rpush key value [value ...] | O(k) ,k 为新增元素的个数 |
添加 | lpush key value [value ...] | O(k) ,k 为新增元素的个数 |
添加 | linsert key before | after pivot value | O(n) ,n 是 pivot 元素距离列表头 / 尾的遍历距离(需遍历找位置) |
查找 | lrange key start end | O(s+n) ,s 是 start 偏移量,n 是 start 到 end 的元素数量 |
查找 | lindex key index | O(n) ,n 是目标索引的遍历偏移量(从表头 / 尾遍历到索引) |
查找 | llen key | O (1) (直接读取内部维护的列表长度,无需遍历) |
删除 | lpop key | O (1) (直接移除列表头元素) |
删除 | rpop key | O (1) (直接移除列表尾元素) |
删除 | lrem key count value | O(k) ,k 为列表总元素个数(需遍历匹配并删除) |
删除 | ltrim key start end | O(k) ,k 为列表总元素个数(保留范围、删除其余元素) |
修改 | lset key index value | O(n) ,n 是目标索引的遍历偏移量(遍历到索引后修改) |
阻塞操作 | blpop / brpop | O (1) (操作本身直接访问头尾;阻塞为等待逻辑,不影响复杂度) |
4.11.16 List 使用场景
(1) 消息队列
Redis 可以使用 lpush + brpop 命令组合实现经典的阻塞式生产证-消费者模型列队。生产者客户端使用 lpush 从列表左侧插入元素,多个消费者客户端使用 brpop 命令阻塞式的从队列中“争抢”队首元素。通过多个客户端来保证消费的负载均衡和高可用性
(2) 分频道的消息队列
Redis 同样使用 lpush + brpop 命令,但通过不同的键模拟频道的概念,不同的消费者可以通过 brpop 不同的键,实现订阅不同频道的理念。
同侧存取 (lpush + lpop 或 rpush + rpop) 为栈
异侧存取 (lpush + rpop 或 rpush + lpop) 为队列
现在 redis 内的 list 大多使用 quicklist 结构,相当于 ziplist 和 linkedlist 的结合,整体是一个链表,但是链表的每个节点都是一个 压缩列表;每个压缩列表都不大,把多个压缩列表通过链式结构连接起来。
4.12 SET 集合
集合类型也是保存多个字符串类型的元素的,但和列表类型不同的是,集合中的元素是不能重复 和无序的。Redis 除了支持集合内的增删改查操作,同时还支持多个集合取交集、并集、差集。
4.12.1 SADD
将一个或多个元素添加到 set 中,重复的元素无法添加到 set 中。
语法:
SADD key member [member ...]
时间复杂度:O(1)
返回值:本次成功添加的元素个数
示例:
4.12.2 SMEMBERS
获取一个 set 中的所有元素,元素间的顺序是无序的
语法:
SMEMBERS key
时间复杂度:O(N)
返回值:所有元素的列表
示例:
4.12.3 SISMEMBER
判断一个元素在不在 set 中
语法:
SISMEMBER key member
时间复杂度:O(1)
返回值:1 表示在 set 中,0 表示不在 set 中或 key 不存在
示例:
4.12.4 SCARD
获取一个 set 的基数,即 set 中的元素个数
语法:
SCARD key
时间复杂度:O(1)
返回值:set 内的元素个数
示例:
4.12.5 SPOP
从 set 中删除并返回一个或多个元素,注意 set 内元素是无序的,所以取出哪个元素实际上是随机的。
语法:
SPOP key [count]
时间复杂度:O(N),,N 是 count
返回值:取出的元素
示例:
4.12.6 SMOVE
将一个元素从源 set 取出并放入目标 set 中
语法:
SMOVE source destination member
时间复杂度:O(1)
返回值:1 表示移动成功,0 表示移动失败
示例:
4.12.7 SREM
将指定的元素从 set 中删除
语法:
SREM key member [member ...]
时间复杂度:O(N),N 是要删除的元素个数
返回值:本次操作删除的元素个数
示例:
4.12.8 SINTER
获取给定 set 的交集中的元素
语法:
SINTER key [key ...]
时间复杂度:O(N*M),N 是最小的集合元素个数,M 是最大的集合元素个数
返回值:交集的元素
示例
4.12.9 SINTERSTORE
获取给定 set 的交集中的元素 并保存到目标 set 中
语法:
SINTERSTORE destination key [key ...]
时间复杂度:O(N*M),N 是最小的集合元素个数,M 是最大的集合元素个数
返回值:交集的元素个数
示例:
4.12.10 SUNION
获取给定 set 的并集中的元素
语法:
SUNION key [key ...]
时间复杂度:O(N),N 给定的所有集合的总的元素个数
返回值:并集的元素
示例:
4.12.11 SUNIONSTORE
获取给定 set 的并集中的元素 储存到 新的集合中
语法:
SUNIONSTORE destination key [key ...]
时间复杂度:O(N),N 给定的所有集合的总的元素个数
返回值:并集的元素个数
示例:
4.12.12 SDIFF
获取给定 set 的差集中的元素
语法:
SDIFF key [key ...]
时间复杂度:O(N),N 给定的所有的集合的总的元素个数
返回值:差集的元素
示例:
4.12.13 SDIFFSTORE
获取给定 set 的差集中的元素,存储到 目标集合中。
语法:
SDIFFSTORE destination key [key ...]
时间复杂度:O(N),N 给定的所有的集合的总的元素个数
返回值:差集的元素的个数
示例:
4.12.14 命令小结
命令语法 | 时间复杂度说明 |
---|---|
sadd key element [element ...] | O(k) ,k 是新增元素的总个数(哈希表批量添加,与元素数量线性相关) |
srem key element [element ...] | O(k) ,k 是待移除元素的个数(哈希表批量删除,与元素数量线性相关) |
scard key | O(1) (直接读取集合内部维护的元素数量计数器,无需遍历) |
sismember key element | O(1) (哈希表特性支持快速存在性判断,类似字典查键) |
srandmember key [count] | O(n) ,n 是 count (需随机选取 / 处理 count 个元素,与数量线性相关) |
spop key [count] | O(n) ,n 是 count (删除操作需修改哈希表结构,与处理元素数量线性相关) |
smembers key | O(k) ,k 是集合总元素个数(需遍历哈希表所有元素,返回全量结果) |
sinter key [key ...] / sinterstore | O(m * k) :- k 是参与交集的集合中元素最少的集合的大小(以最小集合为基准遍历);- m 是参与交集的键的数量(多集合对比需多轮遍历) |
sunion key [key ...] / sunionstore | O(k) ,k 是所有参与并集的集合的元素个数总和(合并去重需遍历全量元素) |
sdiff key [key ...] / sdiffstore | O(k) ,k 是所有参与差集的集合的元素个数总和(遍历对比全量元素找差异) |
4.12.15 Set 使用场景
集合类型比较典型的使用场景式 标签。例如 A 用户对娱乐感兴趣,B 用户对历史感兴趣。有了 用户的 标签,就可以 推送 用户喜欢的内容,并且 将标签相同的用户聚集起来。也可以给用户推荐 好友相似度高的账号。
(1) 计算 UV 每个用户访问服务器,都会产生一个 uv,但是同一个用户多次访问,不会使 uv 增加,需要 uv 按照用户进行去重,上述去重过程,就可以使用 set 来实现。
(2) 给用户添加标签
(3) 给标签添加用户
(4) 删除用户下的标签
(5) 删除标签下的用户
(6) 计算用户的共同兴趣标签
4.13 Zset 有序集合
有序集合相对于字符串、列表、哈希、集合来说,Zset 保留了集合不能有重复成员的特点,但与集合不同的是,有序集合中的每个元素 都有一个唯一的浮点数的分数 (score) 与之相关。有序集合按照分数的大小排列,按照分数 升序排列,即 从小到大排列。zset 中的 member 要求仍是唯一,但是 score 可以相同。
列表、集合、有序集合三者的异同点:
数据结构类型 | 是否允许重复元素 | 有序性特点 | 有序依据(排序规则) | 典型应用场景举例 |
---|---|---|---|---|
列表(List) | 是 | 物理索引有序 | 索引下标(按插入 / 操作维护顺序) | 时间轴(如动态朋友圈)、消息队列(任务排队) |
集合(Set) | 否 | 完全无序 | 无(仅保证元素唯一性) | 标签系统(用户兴趣标签去重)、社交关系(共同好友交集) |
有序集合(Sorted Set) | 否 | 逻辑分数有序 | 分数(score,支持自定义规则) | 排行榜(游戏段位、积分排名)、带权重的社交场景(影响力排序) |
4.13.1 ZADD
添加或更新指定的元素及关联的分数到 zset 中,分数应该符合 double 类型,+inf/-inf 作为正负极限也是合法的。
使用 zset 时,跳表可以使查询的时间复杂度变到 O(logN)。
ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member ...]
选项 | 作用描述 |
---|---|
XX | 仅仅用于更新已经存在的元素,不会添加新元素 |
NX | 仅用于添加新元素,不会更新已经存在的元素 |
CH | 默认情况下,ZADD 返回的是本次添加的元素个数,指定此选项后,返回结果会包含本次更新的元素个数 |
INCR | 效果类似 ZINCRBY,将元素的分数加上指定分数,且此时仅能指定一个元素和对应分数 |
时间复杂度:O(logN)
返回值:本次添加成功的元素个数
示例:
4.13.2 ZCARD
获取一个 zset 的基数,即 zset 中的元素个数
语法:
ZCARD key
时间复杂度:O(1)
返回值:zset 内的元素个数
示例:
4.13.3 ZCOUNT
返回分数在 min 和 max 之间的元素个数,默认情况下 闭区间,可以通过“(”排除
语法:
ZCOUNT key min max
时间复杂度:O(logN)
返回值:满足条件的元素列表个数
示例:
4.13.4 ZRANGE
返回指定区间里的元素,分数按照升序排列,带上 WITHSCORES 可以吧分数也返回,闭区间。
语法:
ZRANGE key start stop [WITHSCORES]
时间复杂度:O(log(N)+M)
O(log(N)+M) 因为只需要查找到一个 start,然后挨个去删除就行 。
返回值:区间内的元素列表
示例:
4.13.5 ZREVRANGE
返回指定区间里的元素,分数降序排列,带上 WITHSCORES 可以把分数也返回,闭区间。
语法:
ZREVRANGE key start stop [WITHSCORES]
时间复杂度:O(log(N)+M)
返回值:区间内的元素列表
示例:
4.13.6 ZRANGEBYSCORE
返回分数在 min 和 max 之间的元素,默认情况下闭区间,可以通过“(”排除
语法:
ZRANGEBYSCORE key min max [WITHSCORES]
时间复杂度:O(log(N)+M)
返回值:区间内的元素列表
示例:
4.13.7 ZPOPMAX
删除并返回分数最高的 count 个元素
语法:
ZPOPMAX key [count]
时间复杂度:O(log(N)*M)
返回值:分数和元素列表
示例:
4.13.8 BZPOPMAX
ZPOPMAX 的阻塞版本,可以通过 timeout 设置阻塞的最长时间。
语法:
BZPOPMAX key [key ...] timeout
时间复杂度:O(log(N))
返回值:元素列表
示例:
4.13.9 ZPOPMIN
删除并返回分数最低的 count 个元素
语法:
ZPOPMIN key [count]
时间复杂度:O(log(N)*M)
返回值:分数和元素列表
示例:
4.13.9 BZPOPMIN
ZPOPMIN 的阻塞版本,可以通过 timeout 设置阻塞的最长时间
语法:
BZPOPMIN key [key...] timeout
时间复杂度:O(log(N)*M)
返回值:元素列表
示例:
4.13.10 ZRANK
返回指定元素的排名,从左往右数
语法:
ZRANK key member
时间复杂度:O(log(N))
返回值:排名
示例:
4.13.10 ZREVRANK
返回指定元素的排名,从右往左数
时间复杂度:O(log(N))
返回值:排名
示例:
4.13.11 ZSCORE
返回指定元素分数
语法:
ZSCORE key member
时间复杂度:O(1)
返回值:分数
示例:
4.13.12 ZREM
删除指定的元素
语法:
ZREM key member [member ...]
时间复杂度:O(log(N)*M)
返回值:本次操作删除的元素个数
示例:
4.13.12 ZREMRANGEBYRANK
删除指定范围内的元素,闭区间
语法:
ZREMRANGEBYRANK key start stop
时间复杂度:O(log(N)+M)
返回值:本次操作删除的元素个数
示例:
4.13.13 ZREMRANGEBYSCORE
按照分数删除指定范围的元素,闭区间。
时间复杂度:O(log(N)+M)
返回值:本次操作删除的元素个数
示例:
4.13.13 ZINCRBY
未指定的元素相关联的分数添加指定的分值
语法:
ZINCRBY key increment member
时间复杂度:O(log(N))
返回值:增加后元素的分数
示例:
4.13.13 ZINTERSTORE
求出给定有序集合中元素的交集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元 素对应的分数按照不同的聚合方式和权重得到新的分数。
语法:
ZINTERSTORE destination numkeys key [key ....] [ WEIGHTS weight [weight.....]] [AGGREGATE <SUM | MIN | MAX>]
命令元素 | 含义说明 |
---|---|
numkeys | 整数,指定参与交集计算的有序集合总数量(需与后续 key 列表的长度严格一致) |
key [key ...] | 参与交集计算的有序集合键列表(需提供 numkeys 个键,如 key1 key2 key3 ,数量与 numkeys 匹配) |
WEIGHTS weight [weight ...] | 可选参数,为每个输入集合设置分数权重(默认全为 1 )。元素分数会先乘以对应权重,再参与交集计算;权重数量需与 numkeys 一致 |
AGGREGATE <SUM|MIN|MAX> | 可选参数,指定交集结果中元素分数的聚合规则: - SUM (默认):将所有集合对应元素的「分数 × 权重」求和- MIN :取所有集合对应元素的「分数 × 权重」最小值- MAX :取所有集合对应元素的「分数 × 权重」最大值 |
有序集合中 member 才是交集的参考量, score 只是一个排序的工具,如果 member 相同, score 不同,aggregate 有三种策略来决定 交集的 score,aggregate 不写默认 SUM 策略
时间复杂度 O(N*K)+O(M*log(M)),近似来看相当于 O(M*logM)
返回值:目标集合中的元素个数
示例:
4.13.14 ZUNIONSTORE
求出给定有序集合中元素的并集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元 素对应的分数按照不同的聚合方式和权重得到新的分数。
语法:
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
时间复杂度 O(N*K)+O(M*log(M)),近似来看相当于 O(M*logM)
返回值:目标集合中的元素个数
示例:
4.13.15 Zset 命令小结
命令语法 | 功能说明 | 时间复杂度 |
---|---|---|
zadd key score member [score member ...] | 向有序集合添加 / 更新成员(按 score 排序,支持批量操作) | O(k · log(n)) - k :新增 / 更新的成员数量;- n :当前有序集合的总元素数 |
zcard key | 获取有序集合总成员数量(cardinality,即元素总数) | O(1) (直接读取内部计数器) |
zscore key member | 查询指定成员的分数(score) | O(1) (哈希表直接定位成员) |
zrank key member zrevrank key member | 查询成员的正序排名(按 score 升序) / 逆序排名(按 score 降序) | O(log(n)) - n :当前有序集合的总元素数(跳表查找位置) |
zrem key member [member ...] | 从有序集合删除一个 / 多个成员 | O(k · log(n)) - k :删除的成员数量;- n :当前有序集合的总元素数 |
zincrby key increment member | 给指定成员增加分数(按增量调整排序,支持分数动态更新) | O(log(n)) - n :当前有序集合的总元素数(跳表查找并更新分数) |
zrange key start end [withscores] zrevrange key start end [withscores] | 按索引范围获取成员(正序 / 逆序,支持带分数返回) | O(k + log(n)) - k :获取的成员数量;- n :当前有序集合的总元素数(跳表定位范围 + 遍历元素) |
zrangebyscore key min max [withscores] zrevrangebyscore key max min [withscores] | 按分数范围获取成员(正序 / 逆序,支持带分数返回) | O(k + log(n)) - k :获取的成员数量;- n :当前有序集合的总元素数(跳表定位分数区间 + 遍历元素) |
zcount key min max | 统计分数范围内的成员数量(无需返回成员,仅计数) | O(log(n)) - n :当前有序集合的总元素数(跳表定位区间后直接计数) |
zremrangebyrank key start end | 按排名范围删除成员(根据正序排名区间删除) | O(k + log(n)) - k :删除的成员数量;- n :当前有序集合的总元素数(跳表定位范围 + 删除元素) |
zremrangebyscore key min max | 按分数范围删除成员(根据分数区间删除) | O(k + log(n)) - k :删除的成员数量;- n :当前有序集合的总元素数(跳表定位区间 + 删除元素) |
zinterstore destination numkeys key [key ...] | 计算多有序集合的交集,结果存入新键(支持权重、聚合规则) | O(n · k) + O(m · log(m)) - n :输入集合中元素最少的集合的大小;- k :输入集合的数量(多集合对比次数);- m :目标集合的元素数(跳表构建结果) |
zunionstore destination numkeys key [key ...] | 计算多有序集合的并集,结果存入新键(支持权重、聚合规则) | O(n) + O(m · log(m)) - n :所有输入集合的元素总数(合并去重遍历次数);- m :目标集合的元素数(跳表构建结果) |
4.13.16 Zset 使用场景
有序集合比较典型的使用场景就是排行榜系统,榜单的维度可能是多方面的:按照时间、阅读量 等。
4.14 渐进式遍历
Redis 使用 scan 命令进行渐进式遍历键,进而解决直接使用 keys 获取键时可能出现的阻塞问题。每次 scan 命令的时间复杂度是 O(1) ,但是要完整的遍历所有键,需要多次执行 scan。
首次 scan 从0 开始,当 sacn 返回的下次位置为 0 时,遍历结束。
语法:
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
时间复杂度:O(1)
返回值:下一次 scan 的游标 (cursor) 以及本次 得到的键
示例:
(1) count 可以指定一次遍历的个数,不是一个连续递增的整数。
(2) 渐进式遍历 不会在服务器存储 任何的状态信息,随时可以终止,不会对服务器产生任何的副作用
(3) count 要获取的个数,默认是10,此处的 count 只是 给 redis 服务器一个提示,会差但不会很差很多。
(4) 渐进性遍历 scan 虽然解决了阻塞的问题,但如果在遍历期间键有所变化(增删改),可能会导致遍历时键的重复遍历或遗漏。
thousand -- KB
million 百万 -- MB兆
billion 十亿--- GB
4.15 数据库管理
Redis 提供了几个面向 Redis 数据库的操作,分别是 dbsize、select、flushdb、flushall 命令。
4.15.1 select
可以通过 select 切换数据库
语法:
select dbIndex
许多关系型数据库,例如 MySQL 支持在一个实例下有多个数据库,但是与关系型数据库用字符来区分不同,Redis 用数字 进行 数据库的区分。Redis 默认配置中有 16 个数据库,不能添加也不能删除,默认处于 0 号数据库。
Redis 虽然支持多数据库,但不建议使用。更好的做法 是维护多个 Redis 实例,而不是在一个 Redis 实例中维护多数据库。无论是否有多个数据库,Redis 都是使用单线程模型,彼此之间还是需要排队等待命令执行。
4.15.2 flushdb / flushall
flushdb / flushall 命令用于清除数据库,区别在于 flushdb 只清除当前数据库,flushall 会清除所有数据库。
4.16 不常用命令集合
4.16.1 Stream
Stream 是 Redis 为消息队列场景设计的结构,支持阻塞读取,解决了 List 作为队列时的诸多局限(如无法持久化消息状态、无消费确认机制等),因此可以视为 List 阻塞队列的 “增强升级版本”。
Stream 的阻塞读取特性能实现 “事件驱动” 的效果 —— 消费者可以阻塞等待新消息(事件),当消息(事件)到达时,阻塞被唤醒,进而触发后续处理逻辑(类似 “回调”),符合事件传播 “按需触发” 的特点。
4.16.2 Geospatial
Redis Geospatial 用于存储地理位置的经纬度坐标(格式为 精度 维度 成员名),并提供了基于坐标的范围查询能力,包括:
(1) 半径查询:根据指定坐标(或已存成员)为中心,查找指定半径内的所有成员(支持单位:米、千米、英里、英尺);
(2) 矩形区域查询:根据指定的矩形范围(左上角和右下角坐标),查找区域内的所有成员;
(3) 额外支持距离计算:计算两个已存坐标之间的直线距离(同样支持多单位)。
4.16.3 Hyperloglog
Hyperloglog 用来估算(存在误差)集合中的元素个数,不存元素内容,只是计数效果。
在统计 UV 的场景中 Set 能精确存储元素、精确计数、支持获取具体元素,但内存随元素数量线性增长(比如 1 亿个 UUID,Set 可能需要几十 GB);而 HyperLogLog 完全不存元素,12KB 固定空间就能估算上亿元素的基数,但无法获取任何具体元素,且结果是估算值。因此,HyperLogLog 适合 “只需要知道‘有多少个不同的’,不需要知道‘具体是哪些’” 的场景(如 UV、独立 IP 统计)。
4.16.4 Bitmaps
使用 bit 位来表示整数,每个 bit 对应一个元素,值为 1
表示 “存在 / 有效”,0
表示 “不存在 / 无效”。位图本质上,还是一个集合。属于是 set 类型针对 整数的 特化版本
4.16.5 Bitfields
在编程语言(如 C)中,位域是结构体成员的 “精细化内存控制” 语法:
通过 int a : 8; 这类写法,强制指定成员占用的 bit 位数(而非默认的字节对齐),实现 精准位操作 + 极致空间压缩(比如用 8bit 存状态,而非 1 字节)。
5. RESP
RESP 是 Redis Serialization Protocol(Redis 序列化协议)的缩写,是 Redis 客户端与服务器之间标准的通信协议,用于规范命令的发送和响应的格式。它是 Redis 能高效跨语言交互的核心基础,设计目标是简单易实现、人类可读、支持多种数据类型。
传输层基于 TCP ,但是不合 TCP 强耦合;请求和相应之间的通信模型 是一问一答的形式,客户端给服务器发送一个请求,服务器给客户端发送一个响应。
数据类型 | 前缀标识 | 格式示例(以 SET key value 命令的响应为例) |
---|---|---|
简单字符串(simple string) | + | 服务器返回成功响应时使用,如 +OK\r\n (\r\n 是换行符,作为结束标志) |
错误(Error) | - | 命令执行失败时返回,如 -ERR wrong number of arguments\r\n (包含错误信息) |
整数(Integer) | : | 用于返回整数结果,如 INCR key 命令的响应 :1\r\n (表示自增后的值为 1) |
批量字符串(bulk string) | $ | 用于传输二进制安全的字符串(支持包含特殊字符),格式为 $长度\r\n内容\r\n ,如 GET key 响应 $5\r\nvalue\r\n (长度为 5 的字符串 "value") |
数组(Array) | * | 客户端发送命令、服务器返回列表 / 集合等多元素结果时使用,格式为 *元素数量\r\n[元素1]\r\n[元素2]... 。例如客户端发送 SET key value 时,实际传输的是 *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n (3 个元素:命令名、key、value) |
客户端以 bulk string 数组形式 发送 redis 命令给服务器;simple string (以 + 开头) 只能传输文本,bulk string (以 $ 开头)可以传输二进制
6. Jedis
Java 操作 redis 的客户端有很多,其中最知名的是 jedis,创建 maven 项目,把 jedis 的依赖拷贝到 pom.xml 中。
6.1 引入 Jedis 依赖
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.3.2</version>
</dependency>
6.2 Jedis 命令集
由于 Jedis 与 Redis 客户端操作风格区别并不大,以下集中演示常用命令:
public class Test {public static void main(String[] args) {JedisPool jedisPool = new JedisPool("tcp://127.0.0.1:8888");try (Jedis jedis = jedisPool.getResource()) {//set gerjedis.set("key1", "value1");jedis.get("key1");//exists deljedis.exists("key1");jedis.del("key1");//keysSet<String> keys = jedis.keys("*");//setex ttljedis.setex("key", 60, "value");long ttl = jedis.ttl("key");System.out.println(ttl);//typejedis.set("key1", "value");System.out.println(jedis.type("key1"));//mset megtjedis.mset("key1", "value1", "key2", "value2", "key3", "value3");List<String> values = jedis.mget("key1", "key2", "key3");System.out.println(values);//appendjedis.append("key", "aaa");String value = jedis.get("key");//getrange setrangeString value1 = jedis.getrange("key", 1, 4);System.out.println(value1);jedis.setrange("key", 0, "xyz");System.out.println(value1);//setnxlong n = jedis.setnx("key", "value");System.out.println(n);//psetexjedis.psetex("key", 1000, "value");long ttl1 = jedis.pttl("key");System.out.println(ttl1);//incr decrjedis.incr("key");System.out.println(jedis.get("key"));jedis.decr("key");System.out.println(jedis.get("key"));//incrby decrbyjedis.incrBy("key", 10);System.out.println(jedis.get("key"));jedis.decrBy("key", 5);System.out.println(jedis.get("key"));//lpush lpoplong n1 = jedis.lpush("key", "1", "2", "3", "4");System.out.println(n1);String value2 = jedis.lpop("key");System.out.println(value2);//rpush rpoplong n3 = jedis.rpush("key", "1", "2", "3", "4");System.out.println(n3);String value3 = jedis.rpop("key");System.out.println(value3);//lrangeList<String> value4 = jedis.lrange("key", 1, 3);System.out.println(value4);//blpopList<String> value5 = jedis.blpop(0, "key");System.out.println(value5);//brpopList<String> value6 = jedis.brpop(0, "key");System.out.println(value6);//lidexString value7 = jedis.lindex("key", 2);System.out.println(value7);//insertjedis.linsert("key", ListPosition.BEFORE, "c", "100");//llenlong n2 = jedis.llen("key");//hset hgetjedis.hset("key", "name", "zhangsan");jedis.hset("key", "age", "20");//hexists hedlboolean ok = jedis.hexists("key", "name");System.out.println(ok);jedis.hdel("key", "name");//hkeys hvalsSet<String> keys1 = jedis.hkeys("key");System.out.println(keys1);List<String> values2 = jedis.hvals("key");System.out.println(values2);//hmgetList<String> values3 = jedis.hmget("key", "name", "age");System.out.println(values3);//hlenlong n4 = jedis.hlen("key");System.out.println(n4);//hincrby hincrbyfloatlong n6 = jedis.hincrBy("key", "age", 10);System.out.println(n6);double dn = jedis.hincrByFloat("key", "age", 0.5);System.out.println(dn);//sadd smembersjedis.sadd("key", "aaa", "bbb", "ccc");Set<String> members = jedis.smembers("key");System.out.println(members);//srem sismemebrboolean ok1 = jedis.sismember("key", "aaa");System.out.println(ok1);long n11 = jedis.srem("key", "aaa", "bbb");System.out.println(n11);//scardlong n12 = jedis.scard("key");System.out.println(n12);//sinterSet<String> results = jedis.sinter("key1", "key2");System.out.println(results);//sunionSet<String> results1 = jedis.sunion("key1", "key2");System.out.println(results1);//sdffSet<String> results2 = jedis.sdiff("key1", "key2");System.out.println(results2);//zadd zrangejedis.zadd("key", 70, "zhangsan");List<String> members1 = jedis.zrange("key", 0, 4);System.out.println(members1);//zrem zcardlong n13 = jedis.zcard("key");System.out.println(n13);n = jedis.zrem("key", "zhangsan");System.out.println(n);//zcountlong n = jedis.zcount("key", 92, 98);System.out.println(n);//zpopmax zpopminTuple tuple = jedis.zpopmax("key");System.out.println(tuple);tuple = jedis.zpopmin("key");System.out.println(tuple);//zranklong n15 = jedis.zrank("key", "zhangsan");System.out.println(n15);//zscoredouble score = jedis.zscore("key", "zhangsan");System.out.println(score);//zincrbydouble n16 = jedis.zincrby("key", 10, "zhangsan");System.out.println(n16);//zinterstorelong n17 = jedis.zinterstore("key3", "key1", "key2");System.out.println(n17);//zunionstorelong n18 = jedis.zunionstore("key3", "key1", "key2");System.out.println(n18);}}
7 Spring Boot 使用 Redis
7.1 配置依赖
spring:redis:host: 127.0.0.1port: 8888
由于 Spring Boot 对 Redis 进行了进一步的封装,我们需要从 redisTemplate 中获取方法。下述将简单的介绍 Spring Boot 中如何使用 Redis 提供的各种类。
7.2 String 类
@GetMapping("/testString")
@ResponseBody
public String testString() {redisTemplate.opsForValue().set("key", "value");String value = redisTemplate.opsForValue().get("key");System.out.println(value);redisTemplate.delete("key");return "OK"
}
7.3 List 类
@GetMapping("/testList")
@ResponseBody
public String testList() {redisTemplate.opsForList().leftPush("key", "a");redisTemplate.opsForList().leftPushAll("key", "b", "c", "d");List<String> values = redisTemplate.opsForList().range("key", 1, 2);System.out.println(values);redisTemplate.delete("key");return "OK";
}
7.3 Hash 类
@GetMapping("/testHashmap")
@ResponseBody
public String testHashmap() {redisTemplate.opsForHash().put("key", "name", "zhangsan");String value = (String) redisTemplate.opsForHash().get("key", "name");System.out.println(value);redisTemplate.opsForHash().delete("key", "name");boolean ok = redisTemplate.opsForHash().hasKey("key", "name");System.out.println(ok);redisTemplate.delete("key");return "OK";
}
7.4 Set 类
@GetMapping("/testSet")
@ResponseBody
public String testSet() {redisTemplate.opsForSet().add("key", "aaa", "bbb", "ccc");boolean ok = redisTemplate.opsForSet().isMember("key", "aaa");System.out.println(ok);redisTemplate.opsForSet().remove("key", "aaa");long n = redisTemplate.opsForSet().size("key");System.out.println(n);redisTemplate.delete("key");return "OK";
}
7.5 Zset 类
@GetMapping("/testZSet")
@ResponseBody
public String testZSet() {redisTemplate.opsForZSet().add("key", "吕布", 100);redisTemplate.opsForZSet().add("key", "赵云", 98);redisTemplate.opsForZSet().add("key", "典⻙", 95);Set<String> values = redisTemplate.opsForZSet().range("key", 0, 2);System.out.println(values);long n = redisTemplate.opsForZSet().count("key", 95, 100);System.out.println(n);redisTemplate.delete("key");return "OK";
}
如果觉得对你有帮助的话,请给博主一键三连吧,这对我真的很重要
(>人<;) 求你了~
(๑・́ω・̀๑) 拜托啦~
(≧∇≦)ノ 求求你啦~
(ಥ_ಥ) 真的求你了…
(;へ:) 行行好吧~