【Redis初阶】常见数据类型

📅 2026/7/16 12:49:10
【Redis初阶】常见数据类型
目录一、Redis常见数据类型1.1 前言1.1.1 基本全局命令1.1.1.1 SET1.1.1.2 GET1.1.1.3 KEYS1.1.1.4 EXISTS1.1.1.5 DEL1.1.1.6 EXPIRE1.1.1.7 TTL1.1.1.8 Redis的key的过期策略是怎么实现的一个经典的面试题1.1.1.9 定时器的实现原理1.1.1.10 TYPE1.1.1.11 全局命令小结1.1.2 数据类型和编码方式1.1.3 单线程架构一、Redis常见数据类型Redis 提供了 5 种数据结构理解每种数据结构的特点对于 Redis 开发运维⾮常重要同时掌握每种数据结构的常⻅命令会在使⽤ Redis 的时候做到游刃有余。1.1 前言在正式介绍 5 种数据结构之前了解⼀下 Redis 的⼀些全局命令、数据结构和内部编码、单线程命令处理机制是⼗分必要的它们能为后⾯内容的学习打下⼀个良好的基础.主要体现在两个⽅⾯1Redis 的命令有上百个如果纯靠死记硬背⽐较困难但是如果理解 Redis 的⼀些机制会发现这些命令有很强的通⽤性。2Redis 不是万⾦油有些数据结构和命令必须在特定场景下使⽤⼀旦使⽤不当可能对 Redis 本⾝或者应⽤本⾝造成致命伤害。1.1.1 基本全局命令Redis 有 5 种数据结构但它们都是键值对种的值对于键来说有⼀些通⽤的命令。1.1.1.1 SETSET把key和value存进去。1.1.1.2 GETGET根据key来取value1.1.1.3 KEYSKEYS返回所有满⾜样式pattern的 key。⽀持如下统配样式。h?llo匹配hello,hallo和hxlloh*llo匹配hllo和heeeelloh[ae]llo匹配hello和hallo但不匹配hilloh[^e]llo匹配hallo,hbllo, ... 但不匹配helloh[a-b]llo匹配hallo和hblloKEYS pattern命令有效版本1.0.0 之后时间复杂度O(N)返回值匹配 pattern 的所有 key。这些匹配规则不用特意去背。注意keys命令的时间复杂度是ON所以在生产环境上面一般都会禁止使用keys命令尤其是keys *查询redis中所有的key生成环境上面key可能会非常多而redis是一个单线程的服务器执行keys * 的事件非常长就使redis服务器被阻塞了无法给其它客户端提供服务1.1.1.4 EXISTSEXISTS判断某个 key 是否存在或者多个是否存在。EXISTS key [key ...]时间复杂度O(1)返回值key 存在的个数。示例redis SET key1 Hello OK redis EXISTS key1 (integer) 1 redis EXISTS nosuchkey (integer) 0 redis SET key2 World OK redis EXISTS key1 key2 nosuchkey (integer) 2图解1.1.1.5 DELDEL删除指定的 key或者多个。DEL key [key ...]时间复杂度O(1)返回值删除掉的 key 的个数。⽰例redis SET key1 HelloOKredis SET key2 WorldOKredis DEL key1 key2 key3(integer) 21.1.1.6 EXPIREEXPIRE为指定的 key 添加秒级的过期时间Time To Live TTLEXPIRE key seconds时间复杂度O(1)返回值1 表⽰设置成功。0 表示设置失败。示例redis SET mykey Hello OK redis EXPIRE mykey 10 (integer) 1 redis TTL mykey (integer) 101.1.1.7 TTLTTL用来查看当前key的过期时间还剩下多少。语法TTL key时间复杂度O(1)返回值剩余过期时间。-1 表⽰没有关联过期时间-2 表⽰ key 不存在。1.1.1.8 Redis的key的过期策略是怎么实现的一个经典的面试题一个redis中可能同时存在很多很多key。这些key中有很大可能都有过期时间。此时redis服务器咋知道哪些key已经过期要被删除哪些key还没过期如果直接遍历所有的key显然是行不通的效率非常低。redis整体的策略是1.定期删除每次抽取一部分进行验证过期时间保证这个抽取的过程足够快有点像数学概率里面的抽样检测那么为啥对于时间有着明确的要求呢因为redis是一个单线程的程序。主要的任务处理每个命令的任务刚才扫描过期key。。。。如果扫描过期key消耗的时间太多了就可能导致正常处理请求命令被阻塞了。产生了类似于keys *这样的效果2. 惰性删除假设这个key已经到了过期时间了但是暂时还没有删他key还存在。紧接着后面有一次访问正好用到了这个key于是这次访问就会让redis服务器触发删除key的操作同时再返回一个nil图解1.1.1.9 定时器的实现原理1. 基于优先级队列/堆正常的队列是先进先出。优先级队列则是按照指定的优先级先出。啥叫优先级高自定义的~~在redis过期key的场景中就可以通过“过期时间越早就是优先级越高”现在假定有很多key设置了过期时间.就可以把这些key加入到一个优先级队列中指定优先级规则是过期时间早的先出队列。队首元素就是最早的要过期的key此时定时器中只要分配一个线程让这个线程去检查对首元素看是否过期即可如果对手元素还么有过期后续元素一定没过期此时扫描线程不需要遍历所有key只盯住这一个队首元素即可另外在扫描线程检查队首元素过期时间的时候也不能检查的太频繁此时做法就是可以根据当前时刻和队首元素的过期时间设置一个等待时间当一个时间差不多了系统再唤醒这个线程。此时扫描线程不需要高频扫描队首元素把cpu的开销也节省下来了。万一再线程休眠的时候来了一个新任务要执行。可以再新任务添加的时候唤醒一下刚才的线程~~重新检查一下队首的元素再根据时间差距重新调整阻塞时间即可。2.基于时间轮实现的定时器把时间分成很多小段具体怎么划分看实际需求当设定的过期时间特别长就如上面的例子可能小段没有那么多但是还是要继续往后面数比如上面就八段但是还是要数九、十、十一、一直数到三十在后面加上该任务。此处我们一定要注意redis并没有采取上面的方案但是我们需要了解这两种方法都是属于高效的定时器实现方式很多场景都有可能会用到。在redis源码里面有一个比较核心的机制是事件循环。1.1.1.10 TYPETYPE返回 key 对应的数据类型。TYPE key时间复杂度O(1)返回值none,string,list,set,zset,hashandstream.。示例redis SET key1 value OK redis LPUSH key2 value (integer) 1 redis SADD key3 value (integer) 1 redis TYPE key1 string redis TYPE key2 list redis TYPE key3 set1.1.1.11 全局命令小结图解1.1.2 数据类型和编码方式type 命令实际返回的就是当前键的数据结构类型它们分别是string字符串、list列表、hash哈希、set集合、zset有序集合但这些只是 Redis 对外的数据结构Redis 的 5 种数据类型实际上 Redis 针对每种数据结构都有⾃⼰的底层内部编码实现⽽且是多种实现这样 Redis 会在合适的场景选择合适的内部编码数据结构内部编码stringrawstringintstringembstrhashhashtablehashziplistlistlinkedlistlistziplistsethashtablesetintsetzsetskiplistzsetziplist图解可以看到每种数据结构都有⾄少两种以上的内部编码实现例如 list 数据结构包含了 linkedlist 和ziplist 两种内部编码。同时有些内部编码例如 ziplist可以作为多种数据结构的内部实现可以通过 object encoding 命令查询内部编码127.0.0.1:6379 set hello world OK 127.0.0.1:6379 lpush mylist a b c (integer) 3 127.0.0.1:6379 object encoding hello embstr 127.0.0.1:6379 object encoding mylist quicklistredis会自动根据当前实际情况选择内部的编码方式自动适应的。Redis 这样设计有两个好处1可以改进内部编码⽽对外的数据结构和命令没有任何影响这样⼀旦开发出更优秀的内部编码⽆需改动外部数据结构和命令例如 Redis 3.2 提供了 quicklist结合了 ziplist 和 linkedlist 两者的优势为列表类型提供了⼀种更为优秀的内部编码实现⽽对⽤⼾来说基本⽆感知。2多种内部编码实现可以在不同场景下发挥各⾃的优势例如 ziplist ⽐较节省内存但是在列表元素⽐较多的情况下性能会下降这时候 Redis 会根据配置选项将列表类型的内部实现转换为linkedlist整个过程⽤⼾同样⽆感知。1.1.3 单线程架构Redis 使⽤了单线程架构来实现⾼性能的内存数据库服务本节⾸先通过多个客⼾端命令调⽤的例⼦说明 Redis 单线程命令处理机制接着分析 Redis 单线程模型为什么性能如此之⾼最终给出为什么理解单线程模型是使⽤和运维 Redis 的关键。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我们已经知道从客⼾端发送的命令经历了发送命令、执⾏命令、返回结果三个阶段其中我们重点关注第 2 步。我们所谓的 Redis 是采⽤单线程模型执⾏命令的是指虽然三个客⼾端看起来是同时要求 Redis 去执⾏命令的但微观⻆度这些命令还是采⽤线性⽅式去执⾏的只是原则上命令的执⾏顺序是不确定的但⼀定不会有两条命令被同步执⾏如下图所⽰可以想象 Redis内部只有⼀个服务窗⼝多个客⼾端按照它们达到的先后顺序被排队在窗⼝前依次接受 Redis 的服务所以两条 incr 命令⽆论执⾏顺序结果⼀定是 2不会发⽣并发问题这个就是 Redis 的单线程执⾏模型。redis能够使用单线程模型很好的工作原因主要在于redis的核心业务逻辑都是短平快的不太消耗cpu资源也就不太吃多核了弊端redis必须要特别小心某个操作占用时间特别长就会阻塞其它命令的执行为什么单线程还能这么快经典面试题通常来讲单线程处理能⼒要⽐多线程差例如有 10000 公⽄货物每辆⻋的运载能⼒是每次200 公⽄那么要 50 次才能完成但是如果有 50 辆⻋只要安排合理只需要依次就可以完成任务。那么为什么 Redis 使⽤单线程模型会达到每秒万级别的处理能⼒呢可以将其归结为三点a.纯内存访问。Redis 将所有数据放在内存中内存的响应时⻓⼤约为 100 纳秒这是 Redis 达到每秒万级别访问的重要基础。b.⾮阻塞 IO。Redis 使⽤ epoll 作为 I/O 多路复⽤技术的实现再加上 Redis ⾃⾝的事件处理模型将 epoll 中的连接、读写、关闭都转换为事件不在⽹络 I/O 上浪费过多的时间如下图。c.单线程避免了线程切换和竞态产⽣的消耗。单线程可以简化数据结构和算法的实现让程序模型更简单其次多线程避免了在线程竞争同⼀份共享数据时带来的切换和等待消耗。举个栗子方源商心慈谢含沫今晚要出去吃饭方源要吃方正小吃商心慈要吃黑土小吃谢含沫要吃人族蛊师小吃。有三种解决方法。一先买方正小吃然后等待。再买黑土小吃然后等待。再买人族蛊师小吃等待。各等各的。这是效率最低的方案。二三个人各自去买各自的小吃。效率大大提升了但是系统开销大了。三商心慈一个人去先买方正小吃在等的过程中去买黑土小吃等的过程中去买人族蛊师小吃这三样东西哪个先做好了对应的老板就可以喊一嗓子。这就是epoll事件通知/回调机制此时一个线程同时做三件事情能高效的完成的前提是这三件事的交互都不太频繁大部分都在等如果这三件事都是交互频繁的还是老老实实多搞几个线程靠谱~~一个线程容易忙不过来。