文章目录
value数据类型与内部编码
常用的value数据类型有哪些?

既然Redis中的value有很多数据类型,那这些数据类型分别都如何创建呢?
基本上每种类型都有专门的创建指令
- 比如string类型就直接通过
set key value来创建 - List主要通过LPUSH和RPUSH来创建,语法是
LPUSH key value1 value2...
头插尾插操作时,如果key对应的List不存在,会自动创建对应的List - 集合(Set)利用 SADD 命令创建,语法是
SADD key member1 member2 ...,可将一个或多个 member 元素加入到集合 key 中,重复元素会被忽略
例如SADD myset 1 2 3,创建名为 myset 的集合,并添加元素 1 、2 、3 - 哈希(Hash)使用 HSET 命令创建,基本语法为
HMSET key field1 value1 field2 value2 ... - 有序集合(Sorted Set)通过 ZADD 命令创建,语法为
ZADD key score1 member1 score2 member2 ...,含义是将一个或多个 member 元素及其 score 值加入到有序集 key 当中 。
什么是数据类型的内部编码?
对于同一种数据类型,Redis内部可能会有好几种实现方式
Redis中声明的数据类型,其实不是按照我们在C++语言中理解的对应类的实现方式来实现的,Redis有自己的实现方式,但是对外提供的接口在使用上和我们的理解是一致的,所以我们感知不到这个区别
举个例子比如鸭脖店,承诺卖给你的这个东西吃起来和鸭脖是一样的。但是,内部的数据结构,是否真的是鸭脖,可能会根据实际情况,做出一定的优化~~
再比如Redis中声明为string的数据类型,其内部C++代码实现的时候,不是直接用C++里面的string类,而是会在这上面的基础上进行一定的优化。
这时候有人就问了,为啥要优化呢?
主要是为了 在保证功能性的基础上尽可能地节省时间/节省空间 ,在性能、内存效率和灵活性之间找到最佳平衡
例如:
- 对于很短的字符串或者很小的哈希表,其内部使用的就是
ziplist(压缩列表)来存储的,这样做可以进一步节省内存 - 当数据量增大(如列表元素增多、字符串变长)时,
ziplist的修改效率会下降(需要频繁移动内存),此时Redis会自动切换到linkedlist或raw等结构,保证操作效率。
这种“小数据用紧凑结构,大数据用高效结构”的策略,能显著减少内存浪费。
再比如:有序集合在数据量小时用ziplist,这样虽然查的慢一些,但存储空间小,量大时切换到skiplist(跳跃表),因为量大之后ziplist查的太慢了
Redis通过内部编码的动态切换,使其在“小数据省内存”和“大数据高性能”之间自动适配。
String类型的内部编码
-
raw
最基本的字符串,底层实现就是char []数组 -
int
redis 通常也可以用来实现一些“计数”这样的功能。当 value 就是一个整数的时候,此时可能 redis 会直接使用 int 来保存~~ -
embstr
针对短字符串进行的特殊优化
hash类型的内部编码
-
hashtable
最基本的哈希表,redis 内部的哈希表的实现虽然可能和STL不一样,但是整体思想是和之前学过的一致的~~ -
ziplist
在哈希表里面元素比较少的时候,为了节省空间,可能就优化成 Ziplist 了.
ZipList,顾名思义——压缩列表,为啥要压缩??
为了节省空间~~
redis 上有很多很多 key.可能是某些 key 的 value 是 hash.此时,如果 key 特别多,对应的 hash 也特别多,但是每个 hash 又不大的情况下,就尽量去压缩. 压缩之后就可以让整体占用的内存更小了.
list类型的内部编码
- linkedist (链表)
- ziplist(压缩链表)
从 redis 3.2 开始,引入了新的实现方式quicklist,从此之后list类型的内部编码就统一都采用quicklist了
quicklist 比较类似于 C++ 中的 std::deque,其外部是一个链表,链表中每个元素都是一个 ziplist,这样quicklist 就能同时兼顾 linkedlist 和 ziplist 的优点。把空间和效率都折衷的兼顾到
set类型的内部编码
-
hashtable
就是我们STL里面基于哈希表实现的set -
inset
一种特殊情况,当set集合中每个元素都是int类型的整数时,为了节省空间,set类型的内部编码就会被优化成inset,
zset类型的内部编码
1.skiplist
跳表也是链表。普通的链表每个节点只有一个指针指向下一个节点,而跳表每个节点上有多个指针,分别指向不同的元素。通过巧妙的搭配这些指针域的指向,我们就可以做到,从跳表上查询元素的时间复杂度达到O(logN)
2.ziplist(压缩链表)
当zset比较小的时候,其内部编码就会采用ziplist,进一步节省空间,
如何查看Redis中某个value数据类型的内部编码?
调用object encoding key

Redis的单线程模型
Redis服务器内部是不是只有一个线程呢?
Redis的“单线程”是其核心设计特性之一,指的是Redis服务器在处理客户端请求时,核心的命令执行(如读写数据、键操作等)由一个主线程完成。那这是不是意味着Redis服务器内部只有一个线程呢?不是
Redis的“单线程”并非指整个进程只有一个线程,而是说 “用户命令的执行阶段”由单个线程处理。实际上,Redis会启动多个辅助线程处理非核心操作,例如:
- 持久化(RDB/AOF的磁盘IO)
- 异步删除大键(
UNLINK命令) - 网络IO的监听(部分版本通过IO多路复用配合少量线程)
这些辅助线程不参与核心的数据读写逻辑,确保主线程专注于命令执行,避免多线程竞争带来的复杂度。
redis采用单线程模型的目的
Redis选择单线程的核心目的是想要 “避免多线程带来的并发安全问题和性能损耗”:
- 无锁竞争:多线程操作共享内存(如Redis的数据集)需要加锁,而锁的获取释放会带来额外开销,甚至可能因锁竞争导致性能下降。单线程无需考虑锁,操作更高效。
- 内存操作快:Redis的数据都在内存中,命令执行速度极快(微秒级),单线程足以处理大部分场景(每秒数万至数十万命令),多线程带来的并行收益不足以抵消其开销。
- 简化设计:单线程模型让Redis的代码逻辑更简单,调试和维护成本低,也减少了因多线程交互引入的bug。
为什么Redis可以用单线程模型?
主要原因就是, redis 的核心业务逻辑,都是短平快的(都不复杂)~~不太消耗 cpu 资源,不太吃多核!!!
单线程如何支撑高并发?
单线程的Redis能实现高并发(每秒处理10万+请求),关键在于 “IO多路复用”和“高效的数据结构”:
- IO多路复用:Redis通过
select/epoll/kqueue等IO多路复用技术,在单线程中同时监听多个客户端的网络连接,实现“非阻塞IO”。当客户端发送请求时,Redis能快速响应,无需为每个连接创建线程。 - 高效数据结构:Redis的内部编码(如哈希表、跳跃表、压缩列表等)设计极致优化,保证了命令操作的时间复杂度极低(如
GET/SET是O(1)),单线程能在短时间内处理大量命令。
Redis采用单线程模型的弊端是什么?
尽管单线程优势明显,但也存在局限:
- 无法利用多核CPU:核心命令执行只能用一个CPU核心,多核服务器的其他核心无法参与命令处理(可通过部署多个Redis实例利用多核)。
- 阻塞风险:如果执行耗时命令(如
KEYS *、大列表的LRANGE),会阻塞整个主线程,导致其他请求排队超时。因此redis 必须要特别小心,某个操作占用时间长,就会阻塞其他命令的执行!!
Redis的“单线程”是针对核心命令执行的设计,通过规避多线程的并发问题,结合IO多路复用和高效数据结构,在保证简单性的同时实现了高性能。这一设计并非“落后”,而是Redis根据自身“内存操作快、避免锁开销”的场景做出的最优选择。理解这一点,也能帮助我们更好地使用Redis(如避免慢命令、合理利用多核)。
既然Redis内部不止一个线程,那会不会出现线程安全的问题呢?
幸运的是,并不会,因为redis 服务器实际上是单线程模型,只有一个请求队列,即使我多个客户端同时发起请求,最终这些请求对应的读写事件进入epoll的就绪队列时,排序依然是有先有后,然后等待 redis 服务器一个一个的取出队列里面的命令再执行。这就保证了当前收到的这多个请求是串行执行的!!!
举个例子,学校食堂,只有一个打饭窗口。每次一到放学,一群住校生就赶紧往食堂跑~~宏观上,我们一群人都在操场上跑(多个请求同时发出,同时到达 redis 服务器)。并发执行,微观上,进了食堂的门,还是要排队(先在队列中排队,然后redis 服务器是串行/顺序执行这多个命令的)
业务
什么是业务?
业务其实就是一个公司/一个产品,是如何解决一个/一系列问题的的过程,就可以称为是 “业务”。
优化业务
不同的公司,不同的产品,有不同的业务。不同的业务就需要不同的技术作为支撑!!!(业务是非常重要的!!)很多时候,优化技术解决不了的问题,可以通过优化业务来解决(什么叫做优化业务?举个例子:12306 卖火车票)
12306 这个网站背后的相关技术积累,可以说全国甚至全世界独一档。
这个网站支撑的业务是极其恐怖的!!在春运抢火车票的时候。12306的APP一定会有超高的并发量!!比如我要买 2.1 的票,我就需要在 1.1 一大早 8:00 的时候准时登录 12306 尝试买票,在这个放票的一瞬间,因为抢的人太多了,就会导致服务器压力一下就拉满。虽然当时引入了非常多的技术,提高网站的访问速度和可用性,但是在放票的时候,整体的压力还是很大,还是很容易出问题。正因如此,所以在这种情况下,不仅要优化技术,还要优化业务!
有人提出方案:分时段放票,8:00 放一批(几个车次)10:00 再放一批12:00 14:00 ……本来是一次放所有票现在是分 5 次,每次放五分之一,这样就相当于把瞬间的压力降低到原来的 五分之一了,这样就不需要那么高的技术力了
到公司,最重要的就是学习业务、理解业务,业务决定技术走向
Redis中缓存用户信息的方式有哪些?
1. 原生字符串类型—— 使用字符串类型,每个属性一个键。
原生字符串类型 —— 使用字符串类型,每个属性一个键。
优点:实现简单,针对个别属性变更也很灵活。
缺点:占用过多的键,内存占用量较大,同时用户信息在Redis中比较分散,缺少内聚性,所以这种方案基本没有实用性。(低内聚,但我们写代码和管理数据都比较想要高内聚、低耦合,所以这种方案不使用)
什么叫做高内聚?什么叫做低耦合?
高内聚:把有关联的东西放在一起,最好能放在指定的地方~~这样就好找(把一个业务相关的所有东西都放在一个类中)
低耦合:不同类之间的相关性很低,不会相互影响,不会出现牵一发而动全身的情况
耦合:指的就是两个模块/代码之间的关联关系。
关联关系越大,越容易相互影响.认为是耦合越大(高耦合)“藕断丝连”。而我们追求的是“低耦合”,即不同类之间没啥关系,你改一个类,另一个类不会收到什么影响,避免“牵一发而动全身”,避免这边一改出bug,影响到了其他的地方
2. 原生字符串类型 —— 序列化对象存储
将用户的所有属性(如 ID、姓名、年龄等)封装成一个对象(如 JSON、XML 或自定义序列化格式),然后将整个对象序列化为字符串,用一个键存储。
示例:
- 键:
user:1001 - 值:
{"id":1001,"name":"张三","age":25,"email":"zhangsan@example.com"}
优点:减少键的数量,便于整体操作(如删除整个用户信息);
缺点:修改单个属性时需先反序列化整个对象,修改后再重新序列化存储,效率较低。
3. 哈希表(Hash)类型
使用 Redis 的 Hash 类型,将用户的每个属性作为 Hash 中的一个字段(field),用一个键关联整个用户的所有属性。
示例:
- 键:
user:1001 - 字段与值:
id -> 1001、name -> 张三、age -> 25、email -> zhangsan@example.com
优点:可直接操作单个属性(如HSET user:1001 age 26),无需序列化/反序列化,效率高;节省内存(Hash 类型对小数据有压缩存储优化);
缺点:不适合存储复杂嵌套结构(如用户的地址列表),需额外设计键或序列化嵌套部分。
4. 集合(Set)或有序集合(Sorted Set)类型(辅助存储)
通常不直接存储用户完整信息,而是配合其他类型用于快速查询用户 ID 集合。
- 示例 1(Set):用
user:ids存储所有用户 ID,便于快速获取全部用户:SADD user:ids 1001 1002 1003; - 示例 2(Sorted Set):用
user:rank:age存储用户 ID 及年龄作为分数,便于按年龄排序查询:ZADD user:rank:age 25 1001 30 1002。
redis常用命令(全局命令/通用命令)
Redis操作:通过 redis - cli 客户端发送指令和 redis 服务器交互. 涉及到很多的 redis的命令。redis 的命令非常非常多!!!想要掌握常用命令,只能多操作多练习
Redis中最核心的两个命令
redis 是按照键值对的方式存储数据的,最核心的两个命令,一个是存键值对(set),一个是根据键来取值(get)
get 和set
get:根据 key 来取 value(使用格式是 get + key值)
get 命令直接输入 key 就能得到 value.如果当前 key 不存在, 会返回 nil ,和 null/NULL 是一个意思
注意:GET key只支持字符串类型的 value,如果 value 是其他类型,使用 GET 获取就会出错!!
set 把 <key, value> 存储进去(使用格式是set + key值 + value值)
提问:redis 中的命令区分大小写吗?输具体kv值的时候需要加引号吗?
- redis 中的命令不区分大小写
- 对于redis命令中的key value, 不需要加上引号(mysql中加引号表示数据是字符串类型),当然,如果要是给 key and value 加上引号,也是可以的 (单引号或者双引号都行)
mset与mget
在set/get的前面加个m,就相当于一次性插入/查找多组键值对
语法:
mget key1 key2 ...
mset key1 value1 key2 value2 ...
时间复杂度:mget和mset都是O(N),N表示插入/查找的键值对数量
此处的 N 不是 整个 redis 服务器中所有 key 的数量,而只是当前命令中,给出的 key 的数量~~
Redis中如何删库?
一个快速失去年终奖的小技巧:redis命令行输入FLUSHALL ,可以清除 redis 上所有的数据~删库操作,等价与 mysql 里的 drop database
这个操作可以把 redis 上所有的键值对都带走
各种环境的区别
办公环境
入职公司之后,公司给你发个电脑,办公环境指的就是公司给你发的电脑的硬件配置
开发环境
有的时候,开发环境和办公环境是一个~~
有的时候,开发环境是单独的服务器. 28C128G4T
对于做前端 / 做客户端的同学,一般来说,开发环境就是办公环境了.
但对于做后端来说,开发环境的配置一般会比办公环境更高,一般CPU的核更多,内外存也会更大(为什么?)
-
很多复杂的后端程序,靠办公电脑的性能去编译,太慢了
有的后端程序,会比较复杂~~而大部分的后端程序是用C++写的,C++写的复杂程序编译一次时间特别久(#include 要接锅) ,直到C++ 23 引入 module,编译速度才稍微快一点。但是我们大多数编译器都不支持C++23,所以还是编起来还是很慢~~为了提升编译速度,我们必须使用更高性能的CPU,进行编译~~ -
很多复杂的后端程序一启动要消耗很多的内存资源,办公电脑难以支撑
比如商业搜索,服务器通常启动起来要吃 100G 的内存 -
很多后端程序比较依赖 linux, 在 windows 环境搭不起来
测试环境
基本和后端开发的环境配置是一样的,都是28C128G4T
线上环境/生产环境
前面我们说的办公环境,开发环境,测试环境,也统称为线下环境,这都是外界用户无法访问到的,而我们现在说的线上环境,则是外界用户 能够访问到的。
线上环境出现问题,影响是最严重的!因为一旦生产环境上出问题,一定会对于用户的使用产生影响!!直接的影响到公司营收!!!
很多公司的营收都是靠广告。广告一般是按照 展示 / 点击 次数来计费的~~线上环境一崩,用户看不到广告,公司就赚不了钱了
所以未来咱们去操作线上环境的任何一个设备 / 程序都要怀着 12 分的谨慎!!
既然这么严重,那我以后不操作生产环境了行不行??
不行!!把一个程序 “上线” 才算是把活干完,上线也可以认为是程序猿的一个重要考核指标~衡量一个实习生能不能转正留用,就看上线次数, 一两个月才上线一次, 基本凉。如果一周能上线两三次, 基本稳
全局命令
全局命令也称通用命令,即无论value的类型是什么,都可以使用的命令。那全局命令有哪些呢?
1. keys
作用:keys 用来查询当前服务器上匹配的 key,通过一些特殊符号 (通配符) 来描述 key 的模样,匹配上述模样的 key 就能被查询出来.
使用格式:keys + pattern
什么是pattern?
pattern通过给出字符串的一些特征,来描述字符串长啥样
如何使用pattern?
说说下面几个字符代表的匹配规则吧:? * [abcde] [^e] [a-c]
?表示匹配任意一个字符
* 表示匹配任意多个字符
[abcde]表示匹配abcde中的任意一个字符
[^e]表示这个位置匹配除e之外的任意一个字符
[a-b]表示匹配a b c 即[a,c]区间中的任意一个字符
为什么实际生产中禁止使用 keys 命令?
keys 命令的时间复杂度是 O(N),而且生产环境上的 键值对可能会非常多!! 这就导致执行 keys * 的时间非常长。
并且redis 是一个单线程的服务器, 这么长的时间内,redis只能执行keys *这一条命令,就会使得在很长一段时间内,redis服务器无法处理来自其他客户端的请求,就会导致 redis 服务器被阻塞, 无法给其他客户端提供服务!!这样的结果可能是灾难性的,为什么呢?
因为redis 经常会用于做缓存,挡在mysql前面,当一个替mysql负重前行的人。万一 redis 被一个 keys * 阻塞住了, 此时其他的查询 redis 操作就超时了。我们学过内存管理的都知道,查Cache发现没有命中,那你肯定要接着去主存中查,这里也是一样,Redis中查不到,那前面这些被key *阻塞导致超时的请求就会转向mysql。突然一大波请求过来了, mysql 措手不及, 就容易挂了,整个系统就基本瘫痪了。如果你要是没能及时发现, 及时恢复的话, 年终奖妥妥的就没啦~~更严重, 把你的工作带走也不是没可能
所以,在生产环境上,一般都会禁止使用 keys 命令. 尤其是大杀器 keys * (查询 redis 中所有的 key !!! )
2. exists
功能:用于查找数据库中key=key1或者key2或…或keyn的键值对是否存在,返回值为满足条件的键值对个数
语法:exists + key1 + key2 + ... + keyn
时间复杂度:O(N)
N指的是exists后面选项的个数,哈希映射找键值对的时间复杂度就是O(1),找N个就是O(N)
exists hello hello
exists hello;
exists hello;
这两种操作,最大的区别是什么?
第一种写法,只需要一次网络通信,而第二种需要两次网络通信,通信的次数越多,耗时越长,占用资源数量越多,效率越低
现在花的话,他没有收到所有的报文的正确率就降低了。然后并且传功率低,然后装的是那个爆头一多,有个数据占比就低,所以数据传输率低。第二个你多次传输他。他的那个正确率也会降低。
比较形象的方式可以理解成,第一种写法就是把所有结果放到一个快递箱子中,一次性发出,而第二种写法就是,每一个结果都单独放一个快递盒子,一个一个分开发。大家想想这两种发送方法,哪一种更好呢?为什么呢?
第一种更好,分开发送耗费了更多的快递盒(报头太多,导致有效数据占比低,导致数据传输率低),同时每个快递到达目的地的时间也不能保证相同,分开发送,你中间丢包的可能性也会大大增加(假设每个快递盒丢包的概率是1%,第一种情况就是1%,第二种情况就是1 - (1 - 0.01) n
redis自身也非常清楚上述问题。所以redis的很多命令都设计了一次性操作多个key的操作,就是因为一次性收发多条指令,效率会更高
3.del
作用:删除所有指定的键值对,返回删除掉的键值对的个数
语法:del key1 key2 .....
时间复杂度:O(N),N指的是del后面选项的个数,哈希映射找键值对的时间复杂度就是O(1),找N个就是O(N)
为什么说redis中误删数据的影响没有mysql大?
之前学 mysql 的时候强调,删除类的操作drop database,drop table,delete from......都是非常危险的操作!!一旦删除了之后,数据就没了
但redis 主要的应用场景,就是作为缓存。此时 redis 里存的只是mysql中一个热点数据的副本。全量数据是在 mysql 数据库中。此时,如果把 redis 中的 key 删除了几个,一般来说,问题不大
但是,当然如果把所有的数据或者一大半数据一下都干没,这种影响会很大。(本来 redis 是帮 mysql 负重前行,redis 没数据了,大部分的请求就直接打给 mysql 然后就容易把 mysql 搞挂)
相比之下,如果是 mysql 这样的数据,哪怕误删了一个数据,都可能是影响很大的
但如果redis不做缓存,而是作为数据库 此时误删数据的影响就大了~
如果是把 redis 作为消息队列(mq) ,这种情况误删数据影响大不大呢?这个不能一概而论,需要具体问题具体分析了.
4.expire
作用:给key对应的键值对设置一个生存时间seconds(单位是s),过了生存时间,该键值对会被删除
语法:expire + key + seconds
时间复杂度:O(1)
有的人说秒做单位不够精细,有没有办法把生存时间设的更精细一点呢?
有的兄弟,有的,用pexpire + key + 毫秒
expire指令返回值:设置成功返回1,不成功返回0
举例:注意看下面的例子,我们给<hello,111>这个键值对设置了10s的生存时间,设置完毕之后,一开始我们get还能正常返回,10s之后再get就发现找不到这个键值对了

5.ttl
作用:查看key对应键值对的生存时间(单位是s)
格式:tll + key
举例:

补充:pttl作用和ttl一样,只不过返回的结果单位是毫秒
6.type
作用:返回key对应键值对的value的类型
语法:type + key
value具体类型有none, string, list, set, zset, hash and stream,只用type指令时就返回这些中的一种
Redis 中 key 的过期检测策略
一个 redis 中可能同时存在很多很多 key。这些 key 中可能有很大一部分都有过期时间。此时,redis 服务器咋知道哪些 key 已经过期要被删除,哪些 key 还没过期??
如果直接遍历所有的 key,显然是行不通的,效率非常低~~redis 采用的检测策略是:
1.定期删除
Redis 每隔一段时间(默认每隔 100ms ,由配置参数 hz 决定 ),随机抽取一部分设置了过期时间的 key ,检查它们是否过期,并删除其中已过期的 key 。比如每次随机抽取 20 个设置了过期时间的 key ,若过期 key 占比超过 25% ,说明这时候已经有很多key过期了,我们就再抽20个检测,并删除其中过期的key,如果此次检测过期 key 占比依然超过 25% ,我就再抽二十个,以此类推。但是为了防止它一直删(每次都超过25%),我们会设置一个执行时间(比如这个循环检测最多不超过 25ms,超过就退出循环 )
为啥要设置执行时间?让它一直删,直到删到25%一下不好吗?
因为 redis 是单线程的程序。主要的任务(处理每个命令的任务,刚才扫描过期 key ……)如果扫描过期 key 消耗的时间太多了,就可能导致正常处理请求命令就被阻塞了。(产生了类似于执行 keys * 这样的效果)
定期删除的优缺点
优点:定期删除能在一定程度上弥补惰性删除不能及时释放过期 key 占用内存的问题,防止过期 key 大量堆积造成内存过度占用 。
缺点:由于是随机抽取检查,可能存在部分过期 key 长时间未被检查到的情况 。若抽取频率过高或每次检查时间过长,会对 CPU 造成较大开销,影响 Redis 处理其他请求的性能;若频率过低或检查时间过短,又无法有效清理过期 key 。
2.惰性删除
在惰性删除策略中,当一个键值对的生存时间结束时,我们并不会直接把它删了,而是把它放在那,等到下次我再访问到这个键值对的时候,如果我发现它的生存时间为0,那我并不会将这个键值对返回,而是把它删了,然后返回nil表示没有查到
例如:如果我们采用惰性删除策略,当客户端尝试访问某个 key 时,Redis 会检查该 key 是否设置了过期时间且是否已过期。如果已过期,Redis 会在这次访问时立即将该 key 删除,并返回 nil 给客户端,表示该 key 已不存在 。例如客户端执行 GET key 操作,若 key 已过期,Redis 就会删除它并返回空值 。
惰性删除 优点: 最大程度减少了删除操作对 CPU 资源的占用 。因为只有在实际访问 key 时才进行过期检查和删除,不会在后台无意义地遍历检查所有 key ,特别适合 CPU 资源紧张的场景 。
惰性删除 缺点: 若大量 key 过期后长时间未被访问,这些过期 key 会一直占用内存空间,造成内存浪费 。极端情况下可能导致内存溢出 。
3.定时删除
原理:在设置 key 的过期时间时,同时创建一个定时器,当过期时间到达时,定时器触发并立即删除对应的 key 。
优点:能最及时地删除过期 key ,保证内存空间尽快被释放 。
缺点:如果有大量 key 设置了过期时间,就需要创建大量定时器,会严重消耗 CPU 和内存资源 ,影响 Redis 整体性能 。
Redis并没有采用定时删除的策略,为什么?
主要因为定时器太占CPU资源了,Redis是单执行流,经不起你这么造
虽然有了上述1和2两种策略结合,整体的效果一般仍然可能会有很多过期的 key 被残留了,没有及时删除掉 redis 为了对上述进行补充,还提供了一系列的内存淘汰策略~~
补充:定时器的实现方式
前面说定时删除时,提到了定时器,其实我们目前有两种很优秀的定时器实现方式,虽然在删除策略中没有采用,但是在其他很多地方都有用到,所以我们得了解一下
1.优先级队列(基于优先级队列实现的定时器)
这个就是把剩余时间的多少当做优先级评判的标准,剩余时间越少,优先级越高,在优先级队列中,越靠近队头。
我们将Redis中所有具有生存时间的键值对按照优先级依次插入到优先级队列中,那么队首的键值对就是整个数据库中剩余寿命最短的键值对。
对于基于优先级队列实现的定时器,我的扫描线程只需要去盯着队头的这个键值对就行,只要他还没到期,那所有的键值对都不会到期
当然也不用一直检查,我检查一次,发现它生存时间还剩多少,那我就让扫描进程休眠多少时间,约摸着快到了,我再唤醒,这样我就把CPU的开销也省下来了
万一在扫描线程休眠期间,新插进来一个键值对,这个键值对比队首元素剩余寿命还要短怎么办呢?
为了应对这个情况,当队列中新插入元素时,我们要唤醒扫描线程,让线描进程根据这个新插入元素的情况,动态调整阻塞队列
汤老师板书
2.时间轮(基于时间轮实现的定时器)
首先我们画一个圆盘,其实就是你以某个时刻为0时刻,我们将从这个时刻算起,剩余寿命还有100ms、900ms、1700ms…的键值对,放在100对应的盘面中,用队列串起来,将从这个时刻算起,剩余寿命还有200ms、1000ms、1800ms…的键值对,放在200对应的盘面中,用队列串起来,后面的以此类推。

这个指针从0这个格子开始,每隔固定的间隔(此处是约定 100ms)走一格,每次走到一个格子,就会把这个格子上链表的任务尝试执行一下
对于时间轮来说,每个格子是多少时间,一共多少个格子,都可以根据实际场景,灵活调配
2141

被折叠的 条评论
为什么被折叠?



