type——返回 key 对应的数据类型
此处 Redis
所有的 key
都是 string
,但是 key
对应的 value
可能会存在多种类型
none
——key
不存在string
——字符串list
——列表set
——集合zset
——有序集合hash
——哈希表stream
——Redis 作为消息队列的时候,使用这个类型的value
在 Redis
中,上述操作方式差别很大,使用的命令都是完全不同的
定时器
定时器:在某个时间到达之后,执行指定的任务
基于优先级队列/堆
正常的队列是先进先出,优先级队列是按照指定的优先级先出
什么叫优先级高?这个是自定义的
- 在
redis
过期key
的场景中,就可以通过“过期时间越早,优先级越高”来进行约定
现在假定有很多的 key 设置了过期时间,就可以把这些 key 加入到一个优先级队列中,指定优先级规则是过期时间早的,先出队列;队首元素,就是最早的要过期的 key
key1
:12:00key2
:13:00key3
:14:00
此时定时器中只要分配一个线程,让这个线程去检查队首元素,看是否过期即可。如果队首元素还没过期,则后续元素一定没过期。- 此时“扫描线程“不需要遍历所有的
key
,只要盯住这一个队首元素即可。 - 另外再扫描线程检查队首元素过期时间的时候,也不能检查的太频繁,不然 CPU 就空转了。
- 此时我们可以根据当前时刻和队首元素的过期时间,来设置一个等待(主动阻塞),当时间差不多到了,系统再唤醒这个线程
- 此时扫描线程不需要高频扫描队首元素,把 CPU 的开销也节省下来了
万一在休眠的时候,突然来了一个新的任务要执行怎么办呢?
- 我们可以在添加新任务的时候,唤醒一下刚才的线程,重新检查一下对手元素,再根据时间差距重新调整阻塞时间即可
基于时间轮
把时间划分成很多小段(划分的粒度,看实际需求)
- 每个小段上都挂着一个链表,每个链表都代表一个要执行的任务(在 java 中可以通过对象来实现类似的效果)
- 此时这个指针,每隔固定的间隔(此处是约定
100ms
),每次走到一个格子,就会把这个格子上链表的任务尝试执行一下 - 假设需要添加一个
key
,这个key
在300ms
之后过期,那就停留在第三个格子之上,执行第三个格子的任务。就将第三个格子里的任务设置成del key
即可
假设指定的过期时间特别长,
3000ms
,就围着轮子一直转,直到到达时间停止
对于时间轮来说,每个格子是多少时间,一共是多少个格子,都是需要根据实际场景,灵活调配的
Redis
没有采取上述的方案
ttl——查询过期时间
time to live
在网络原理,
IP
协议报头中,就有一个TTL
字段
IP
中的TTL
不是用时间衡量过期的,而是次数
查询当前 key 的过期时间还剩多少
语法:
ttl key //秒
pttl key //毫秒
- 返回剩余过期时间
- 返回
-1
表示没有关联过期时间 - 返回
-2
表示key
不存在
过期策略是如何实现的
#高频面试
一个 Redis
中可能同时存在很多很多 key
,这些 key
中有很大一部分都有过期时间。此时,Redis
服务器怎么知道哪些 key
已经过期要被删除,哪些 key
还没过期?
- 如果直接遍历所有的 key,显然是行不通的,效率非常低
- Redis 整体的策略是两方面
- 定期删除
- 惰性删除
惰性删除
- 假设这个
key
已经到达过期时间了,但是暂时还没删除它,key
还在 - 紧接着,后面又一次访问,正好用到了这个
key
,于是这次访问就会让Redis
服务器触发删除key
的操作,同时再放回一个nil
- 你去超市买水,正要付钱的时候,看了一眼日期,发现过期了,于是老板就说不卖了,于是就把这瓶水下架了,这就是“惰性删除”
- 老板也不清楚哪些过期了,哪些没过期,就在卖出的时候做一次检查,如果过期了就不卖了,如果还没过期,就继续卖
但显然,单靠惰性删除肯定是不靠谱的,一个超市这么多商品,怎么可能全去靠用户去检查,所以肯定还得要有一个辅助的机制——定期删除
定期删除
这个超市老板,要定期查看超市里面的商品,看是否有过期产品
- 但是如果超市商品很多,那么每次遍历一遍就非常慢
- 所以,每次抽取一部分,进行验证过期时间。保证抽取检查的过程足够快
为什么这对定期删除的时间有明确的要求呢?
- 因为
Redis
是单线程程序,主要的任务是处理每个命令的任务(刚才扫描过期key
…) - 如果扫描过期
key
消耗的时间太多了,就可能导致正常处理请求命令就被阻塞了(产生了类似key *
的效果)
虽然有了上述两种策略结合,但整体的效果仍一般。仍然有可能会有很多过期的 key
被残留了,没有及时删除掉
但是 Redis
为了对上述进行补充,还提供了一系列的内存淘汰策略