当前位置 博文首页 > RtxTitanV的博客:Redis常见问题总结

    RtxTitanV的博客:Redis常见问题总结

    作者:[db:作者] 时间:2021-07-07 10:05

    一、Redis简介

    1.什么是Redis

    Redis是一个使用C语言开发的数据库,与传统数据库不同的是Redis的数据存在内存中 ,是内存数据库,所以读写速度非常快,因此Redis被广泛应用于缓存。另外,redis也经常用来做分布式锁和消息队列。redis提供了多种数据类型来支持不同的业务场景。redis还支持事务、持久化、LUA脚本、LRU驱动事件、多种集群方案。

    2.为什么要用Redis(缓存)

    用缓存的目的主要是为了高性能和高并发:

    • 高性能:直接访问数据库中的数据,这个过程比较慢,因为是从硬盘读取,而将访问的数据存到缓存中,下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可。
    • 高并发:直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库,也就提高了系统整体的并发。

    3.为什么用Redis而不用map/guava做缓存

    缓存分为本地缓存和分布式缓存。以Java为例,使用自带的map或者guava实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着jvm的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。

    使用redis或memcached之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持redis或memcached服务的高可用,整个程序架构上较为复杂。

    二、Redis和Memcached异同

    分布式缓存使用的比较多的主要是Memcached和Redis,不过现在基本上都用Redis来实现缓存。

    1.相同点

    1. 都是基于内存的数据库,一般都用来当做缓存使用。
    2. 都有过期策略。
    3. 两者的性能都非常高。

    2.区别

    1. 数据类型:Redis不仅支持简单的k/v(string)类型的数据,同时还支持list,set,sorted set,hash等数据类型;而Memcached只支持简单的数据类型(文本型、二进制类型)。
    2. 持久化:Redis可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,支持RDB和AOF两种持久化方式;而Memecache只能把数据全部存在内存中,不支持持久化。
    3. 灾难恢复:Redis有灾难恢复机制,是因为Redis支持数据持久化;Memcached没有灾难恢复机制。
    4. 内存管理:Redis在服务器内存使用完之后,可以将不用的数据放到磁盘上;Memcached在服务器内存使用完之后,会直接报异常。内存还没用完时Redis也可以将很久没用的数据交换到磁盘;Memcached的数据则长期存在内存,Memcached将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题,但是这种方式会使得内存的利用率不高。
    5. 集群模式:Redis原??持cluster模式,可以实现主从复制,读写分离;memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。
    6. 线程模型:Redis使用单线程的多路IO复用模型(Redis6.0引入了多线程IO);Memcached是多线程,非阻塞IO复用的网络模型。
    7. 附加功能:Redis支持发布订阅模型、主从分区、序列化、Lua脚本、事务等功能;而Memcached不支持。并且Redis支持更多的编程语言。
    8. 过期数据的删除策略:Redis同时使用惰性删除与定期删除;而Memcached只用了惰性删除。
    9. 事件库:Redis使用自封装简易事件库AeEvent;而Memcached使用贵族血统的LibEvent事件库。
    10. 查询操作:Redis支持批量操作,事务,不同类型有不同的CRUD;Memcached只支持常用的CRUD和少量其他命令。
    11. 适用场景:Redis适用于存储复杂数据结构,需要持久化和高可用的场景,value存储内容也较大;Memcached适用于存储纯k/v数据,数据量非常大,并发量非常高的场景。

    三、Redis常见数据类型及使用场景

    Redis并不是简单的key-value存储,实际上它是一个数据结构服务器,其中键都是字符串,值支持多种不同类型(有5种基础类型),不同类型数据结构的差异就在于值的结构不一样。

    1.数据类型

    Redis有5种基本数据类型:

    • string(字符串):是Redis最简单的数据类型,一般做简单的键值对缓存。
      • 常用命令简介:
        • SET key value:为key设置保存一个字符串值value。如果key已经保存了一个值,那么该操作会直接覆盖原来的值,并且忽略原始类型。当set命令执行成功之后,之前设置的过期时间都将失效。
        • GET key:获取key对应的值。如果key不存在,返回特殊值nil,如果key的值不是字符串,就返回错误。
        • STRLEN key:返回key所储存的字符串值的长度。当key保存的是非字符串值时将返回错误。
        • APPEND key value:如果键key已存在并且它的值是一个字符串,将把value追加到字符串末尾。如果key不存在,则会创建它并先将其值置为空字符串在执行追加操作。
        • INCR key:将key中储存的数字值加1。
        • DECR key:将key中储存的数字值减1。
        • MSET key value [key value …]:同时为多个键设置值。
        • MGET key [key …]:返回给定的一个或多个字符串键的值。
    • list(列表):即链表,链表是一种非常常见的数据结构,数据元素的插入和删除非常快并且可以灵活调整链表长度,但是随机访问困难。Redis的list的实现为一个双向链表,可以支持反向查找和遍历,不过带来了部分额外的内存开销。可以通过list存储一些列表型的数据结构,比如粉丝列表等。
      • 常用命令简介:
        • LPUSH key value [value …]:将所有指定的值插入存储在key的列表的开头(最左边,如有多个值从左到右依次插入)。
        • RPUSH key value [value …]:将所有指定的值插入存储在key的列表的末尾(最右边)。
        • LPOP key:删除并返回存储在key的列表中的第一个元素。
        • RPOP key:删除并返回存储在key的列表中的最后一个元素。
        • LRANGE key start stop:返回存储在key的列表中指定范围的元素。 偏移量startstop是基于零开始的索引。
        • LINDEX key index:返回存储在key的列表中索引index处的元素。索引从零开始,负索引可用于指定从列表末尾开始的元素。
        • LLEN key:返回存储在key的列表的长度。如果key不存在,则将其解释为空列表并返回0, 当存储在key的值不是列表时将返回错误。
    • hash(哈希):类似于JDK1.8前的HashMap,内部实现也差不多,不过,Redis的hash做了更多优化。hash是一个string类型的field和value的映射表,适合用于存储结构化的数据,比如对象(前提是这个对象没嵌套其他的对象),这样读写缓存时可以操作hash结构里的单个字段,但hash结构的存储消耗也要高于单个字符串。
      • 常用命令简介:
        • HSET key field value [field value ...]:将存储在key处的哈希表中的field的值设置为value
        • HGET key field:返回存储在key处的哈希表中field的值。
        • HEXISTS key field:查看存储在key处的哈希表中的field是否存在。
        • HGETALL key:返回存储在key处的哈希表中所有字段和值。
        • HKEYS key:返回存储在key处的哈希表中的所有字段名称。
        • HVALS key:返回存储在key处的哈希表中的所有值。
        • HLEN key:回存储在key处的哈希表中包含的字段数。
        • HDEL key field [field ...]:从存储在key处的哈希表中删除指定的字段。
    • set(集合):类似于Java中的HashSet ,无序唯一。基于set可以轻易实现交集、并集、差集的操作。 适用于需要存放的不重复数据以及获取多个数据源交集和并集等场景。
      • 常用命令简介:
        • SADD key member [member ...]:将指定元素添加到存储在key的集合中,若指定元素在该集合存在将被忽略。
        • SPOP key [count]:从存储在key的集合中删除并返回一个或多个随机元素。
        • SMEMBERS key:返回存储在key的集合中的所有元素。
        • SCARD key:返回存储在key的集合的元素数量。
        • SISMEMBER key member:检查member是否存储在key的集合中的元素。
        • SINTER key [key ...]:返回所有给定集合的交集产生的集合元素。
        • SINTERSTORE destination key [key ...]:该命令与SINTER类似,但是它并不是直接返回结果集,而是将结果保存在destination集合中,如果destination集合存在,则会被覆盖。
        • SREM key member [member ...]:在存储在key的集合中移除指定的元素。指定元素不是集合元素将被忽略,集合不存在会返回0,key的类型不是集合会返回错误。
        • SUNION key [key ...]:返回所有给定集合的并集产生的集合元素。
        • SUNIONSTORE destination key [key ...]:该命令与SUNION类似,但是它并不是直接返回结果集,而是将结果保存在destination集合中,如果destination集合存在,则会被覆盖。
    • sorted set(有序集合):和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,还可以通过score的范围来获取元素的列表。类似于Java的TreeSet和HashMap的结合体。适用于需要根据某种权重对数据排序的场景,比如直播间人气排行榜等。
      • 常用命令简介:
        • ZADD key score member [score member ...]:添加所有指定了score的指定元素到存储在key的有序集合中。如果指定元素已经是有序集中元素,则将更新score并在正确的位置重新插入元素,以确保正确排序。如果key不存在,则会创建一个以指定元素为唯一元素的新有序集。 如果键存在但不包含有序集,则返回错误。
        • ZCARD key:返回存储在key的有序集的元素数量。
        • ZRANGE key min max:返回存储在key的有序集中的指定范围的元素。
        • ZRANK key member:返回存储在key的有序集中的元素member的排名,其中所有元素按score从低到高排序,score值最小的元素排名为0。
        • ZREM key member [member ...]:从存储在key的排序集中删除指定的元素。 不存在的元素将被忽略,当key存在且不包含有序集时将返回错误。
        • ZSCORE key member:返回存储在key的有序集中的元素member的score。如果元素不存在于有序集中或者key不存在,则返回nil

    2.使用场景

    比较常见的几种使用场景如下:

    • 计数器:可以对string进行自增自减运算,从而实现计数器功能。Redis这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。
    • 缓存:将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。
    • 查找表:查找表和缓存类似,也是利用了Redis快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因为缓存不作为可靠的数据来源。例如DNS记录就很适合使用Redis进行存储。
    • 消息队列:List是一个双向链表,可以通过lpushrpop写入和读取消息。不过最好使用Kafka、RabbitMQ等消息中间件。
    • 会话缓存:可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。
    • 分布式锁:在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用Redis自带的 SETNX命令实现分布式锁,除此之外,还可以使用官方提供的RedLock分布式锁实现。
    • 其他:set可以实现交集、并集等操作,从而实现共同关注等功能。sorted set可以实现有序性操作,从而实现排行榜等功能。

    四、Redis单线程模型

    1.文件事件处理器

    Redis基于Reactor模式开发了自己的网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。由于文件事件处理器是以单线程方式运行的,所以才说Redis是单线程模型。它采?IO多路复?机制同时监听多个socket,根据socket上的事件
    来选择对应的事件处理器进?处理。

    • 文件事件处理器采?IO多路复?程序同时监听多个socket,并根据socket目前执行的任务来为套接字关联不同的事件处理器。
    • 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

    虽然文件事件处理器以单线程方式运行,但通过使用I/O多路复用程序来监听多个socket,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与Redis服务器中其他同样以单线程方式运行的模块进行对接,这保持了Redis内部单线程设计的简单性。并且Redis不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗。

    2.文件事件处理器的构成

    文件事件处理器包含4个组成部分:

    • socket
    • IO多路复用程序
    • 文件事件分派器
    • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

    文件事件是对套接字操作的抽象,当一个套接字准备执行连接应答、读取、写入、关闭等操作时,会产生一个与操作对应的文件事件。一个服务器通常会连接多个套接字,所以多个文件事件可能并发出现。但IO多路复用程序会负责监听多个套接字,并将所以产生事件的套接字都放到一个队列里,然后通过该队列,以有序、同步、每次一个套接字的方式将套接字传到文件事件分派器,当上一个套接字产生的事件被处理完毕之后(该套接字为事件所关联的事件处理器执行完毕),I/O多路复用程序才会继续向文件事件分派器传送下一个套接字。文件事件分派器接收I/O多路复用程序传来的套接字,并根据套接字产生的事件类型调用相应的事件处理器进行处理。文件事件处理器的组成部分及工作原理如下图:
    1

    3.事件类型

    I/O多路复用程序可以监听多个套接字的ae.h/AE_READABIE事件和ae.h/AE_WRITABLE事件。

    • AE_READABIE事件:当套接字变得可读时(客户端对套接字执行write或close操作),或者有新的可应答套接字出现时(客户端对服务器的监听套接字执行connect操作),套接字产生AE_READABLE事件。
    • AE_WRITABLE事件:当套接字变得可写时(客户端对套接字执行read操作),套接字产生AE_WRITABLE事件。

    I/O多路复用程序允许服务器同时监听套接字的AE_READABLE事件和AE_WRITABLE事件,如果一个套接字同时产生了这两种事件,那么文件事件分派器会优先处理AE_READABLE事件,等到AE_READABLE事件处理完之后,才处理AE_WRITABLE事件。即一个套接字又可读又可写,服务器将先读套接字,后写套接字。

    4.事件处理器

    事件处理器中最常用的就是与客户端进行通信的连接应答处理器、命令请求处理器和命令回复处理器。

    • 连接应答处理器:为networking.c/acceptTcpHandler函数,用于对连接服务器监听套接字的客户端进行应答。当Redis服务器进行初始化时,程序会将连接应答处理器和服务器监听套接字的AE_READABLE事件关联,当有客户端连接服务器监听套接字的时候,套接字就会产生AE_READABLE事件,引发连接应答处理器执行,并执行相应的套接字应答操作。
    • 命令请求处理器:为networking.c/readQueryFromClient函数,负责从套接字中读入客户端发送的命令请求内容。当一个客户端通过连接应答处理器成功连接到服务器之后,服务器会将客户端套接字的AE_READABLE事件和命令请求处理器关联,当客户端向服务器发送命令请求的时候,套接字就会产生AE_READABLE事件,引发命令请求处理器执行,并执行相应的套接字读入操作。
    • 命令回复处理器:为networking.c/sendReplyToClient函数,负责将服务器执行命令后得到的命令回复通过套接字返回给客户端。当服务器有命令回复需要传送给客户端时,服务器会将客户端套接字的AE_WRITABLE事件和命令回复处理器关联,当客户端准备好接收服务器传回的命令回复时,就会产生AE_WRITABLE事件,引发命令回复处理器执行,并执行相应的套接字写入操作。当命令回复发送完毕后,服务器就会解除命令回复处理器与客户端套接字的AE_WRITABLE事件之间的关联。

    5.客户端与Redis的一次通信流程

    下图是客户端与Redis的一次通信流程示例图:
    2
    客户端与Redis的一次通信流程如下:

    1. Redis服务端进程初始化的时候,会将服务器监听套接字(server socket)的AE_READABLE事件与连接应答处理器关联。
    2. 客户端向Redis的服务器监听套接字请求建立连接,服务器监听套接字会产生AE_READABLE事件。
    3. IO多路复用程序监听到服务器监听套接字产生的AE_READABLE事件后,将该套接字压入队列中,然后通过该队列将该套接字传给文件事件分派器。
    4. 文件事件分派器接收I/O多路复用程序传来的服务器监听套接字,调用与该套接字的AE_READABLE事件关联的连接应答处理器进行处理。
    5. 连接应答处理器会创建一个能与客户端通信的客户端套接字(socket01),并将该套接字的AE_READABLE事件与命令请求处理器关联。
    6. 客户端向redis发起一个命令请求,此时客户端套接字会产生AE_READABLE事件。
    7. IO多路复用程序监听到客户端套接字产生的AE_READABLE事件后,将该套接字压入队列中,然后通过该队列将该套接字传给文件事件分派器。
    8. 文件事件分派器接收I/O多路复用程序传来的客户端套接字,调用与该套接字的AE_READABLE事件关联的命令请求处理器进行处理。
    9. 命令请求处理器读取客户端的命令内容,然后传给相关程序去执行。
    10. 为了将执行命令产生的命令回复传回客户端,服务器会将客户端套接字的AE_WRITABLE事件与命令回复处理器关联。
    11. 当客户端准备好接受命令回复时,客户端套接字将产生AE_WRITABLE事件。
    12. IO多路复用程序监听到客户端套接字产生的AE_WRITABLE事件后,将该套接字压入队列中,然后通过该队列将该套接字传给文件事件分派器。
    13. 文件事件分派器接收I/O多路复用程序传来的客户端套接字,调用与该套接字的AE_WRITABLE事件关联的命令回复处理器进行处理。
    14. 命令回复处理器将命令回复全部写入到客户端套接字。
    15. 服务器解除客户端套接字的AE_WRITABLE事件与命令回复处理器之间的关联。

    6.Redis单线程模型效率高的原因

    • 纯内存操作。
    • 核心是基于非阻塞的IO多路复用机制。
    • C语言实现,一般C语言实现的程序“距离”操作系统更近,执行速度相对会更快。
    • 单线程反而避免了多线程的频繁上下文切换、会存在死锁等问题,预防了多线程可能产生的竞争问题。

    7.Redis6.0开始引入多线程

    Redis6.0之前为什么不使用多线程:

    1. 单线程编程容易并且更容易维护。
    2. Redis的性能瓶颈不在CPU,主要在内存和网络。
    3. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。

    Redis6.0之后为何引入多线程:

    Redis6.0引入多线程主要是为了提高网络IO读写性能,如果把网络读写做成多线程的方式对性能会有很大提升。虽然Redis6.0引入了多线程,但是Redis的多线程只是用来处理网络数据的读写等这类耗时的操作,执行命令仍然是单线程。

    五、数据库键空间

    Redis是一个键值对数据库服务器,服务器中的每个数据库都由一个redis.h/redisDb结构表示,其中,redisDb结构的dict字典被称为键空间(key space),它保存了数据库中的所有键值对。

    typedef struct redisDb {
        ...    
        dict *dict;     // 数据库键空间,保存数据库中所有键值对
        ...
    } redisDb;
    

    键空间的键也就是数据库的键,每个键都是一个string对象;键空间的值也就是数据库的值,每个值可以是string对象、list对象、hash对象、set对象和sorted set对象中的任意一种Redis对象。一个数据库键空间的示例图如下:
    3
    因为数据库的键空间是一个字典,所以所有针对数据库的操作,实际上都是通过对键空间字典进行操作来实现的。几种常见的数据库操作实现原理如下:

    • 添加键:实际是将新键值对添加到键空间中,其中键为string对象,值为任意一种Redis对象
    • 删除键:实际是在键空间删除键对应的键值对对象。
    • 更新键:实际是对键空间里键对应的值对象进行更新。
    • 对键取值:实际是在键空间中取出键对应的值对象。

    在对数据库进行读写,不仅会对键空间执行指定的读写操作,还会执行一些额外的维护操作:

    • 在读取一个键之后(读和写操作都要对键进行读取),服务器会根据键是否存在来更新服务器的键空间命中(hit)次数或键空间不命中(miss)次数,这两个值可以在INFO stats命令的keyspace_hits属性和keyspace_misses属性中查看。
    • 在读取一个键之后,服务器会更新键的LRU(最后一次使用)时间,这个值可以用于计算键的闲置时间,使用OBJECTidletime <key>命令可以查看键的闲置时间。
    • 在读取一个键时发现该键已过期,会先删除这个过期键再进行余下操作。
    • 如果有客户端使用WATCH命令监视了某个键,那么服务器在修改被监视的键之后,会将这个键标记为脏(dirty),从而让事务程序注意到这个键已经被修改过。
    • 服务器每次修改一个键之后,都会对脏(dirty)键计数器的值增1,这个计数器会触发服务器的持久化以及复制操作。如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器将按配置发送相应的数据库通知。

    六、设置键的过期时间或生存时间

    在Redis中通过EXPIREEXPIREATPEXPIREPEXPIREAT命令可以设置键的过期时间(键什么时候会被删除)或生存时间(键可以存在多久),超过过期时间后,它会被自动删除。

    注意:SETEX命令可以在设置一个字符串键的同时为键设置过期时间,这是一个类型限定的命令,只能用于字符串键,但SETEX命令设置过期时间的原理和EXPIRE命令完全一样。

    设置过期时间的作用:

    1. 设置过期时间有助于缓解对内存的消耗,因为内存有限,如果不设置过期时间,很容易内存溢出。
    2. 设置过期时间可以降低某些业务的复杂程度,比如验证码只需要在一分钟内有效,如果用传统数据库处理一般需要自己判断是否过期,增加了业务的复杂度并且性能不高。

    1.设置过期时间(生存时间)

    EXPIRE key seconds

    起始版本:1.0.0
    时间复杂度:O(1)

    key设置过期时间,超过过期时间后,它会被自动删除。对已经有过期时间的key执行EXPIRE操作,将会更新它的过期时间。如果设置过期时间成功,则返回1,如果key不存在,则返回0。

    注意:

    • 过期时间只有通过删除或覆盖的key的内容的命令来清除,这些命令包括DELSETGETSET和所有的*STORE命令。这意味着所有在概念上修改存储在键上的值而不用新键替换的操作都将保持过期时间不变。
    • 可以使用PERSIST命令清除过期时间,使key变回一个永久的key。
    • 如果使用RENAME命令重命名了key,则相关的过期生存时间将转移到新key上。例如原来存在key_A,使用RENAME Key_B Key_A命令,旧的key_A的值将被覆盖,并且不管原来Key_A是永久的还是有过期时间的,都会被Key_B的有效期状态覆盖。
    • 在Redis 2.1.3之前的版本中,使用修改key的值的命令修改具有过期时间的key的值会删除整个key,这一行为是受当时复制(replication)层的限制而作出的,现在这一限制已经被修复。

    EXPIREAT key timestamp

    起始版本:1.2.0
    时间复杂度:O(1)

    EXPIREAT的作用和语义与EXPIRE类似,但是它没有指定表示TTL(生存时间)的秒数,而是使用了绝对的Unix时间戳(自1970年1月1日以来的秒数)。设置在过去的时间戳会立即删除key。如果设置过期时间成功,则返回1,如果key不存在,则返回0。引入EXPIREAT是为了将AOF持久性模式的相对超时转换为绝对超时,它可以直接用于指定给定key未来的某个给定时间到期。

    PEXPIRE key milliseconds

    起始版本:2.6.0
    时间复杂度:O(1)

    此命令的工作原理与EXPIRE完全相同,但是key的生存时间以毫秒为单位而不是秒。如果设置过期时间成功,则返回1,如果key不存在,则返回0。

    PEXPIREAT key milliseconds-timestamp

    起始版本:2.6.0
    时间复杂度:O(1)

    PEXPIREAT的作用和语义与EXPIREAT类似,但它以毫秒为单位设置key的过期unix时间戳,而不是以秒为单位。如果设置过期时间成功,则返回1,如果key不存在,则返回0。

    虽然这几个命令以不同单位和不同形式来设置过期时间,但实际上EXPIREPEXPIREEXPIREAT三个命令都是使用PEXPIREAT命令来实现的:无论客户端执行的是以上四个命令中的哪一个,经过转换之后,最终的执行效果都和执行PEXPIREAT命令一样,即最终都会转换成PEXPIREAT执行。这几个命令的转换如下,EXPIRE----->PEXPIRE----->PEXPIREAT<-----EXPIREAT

    2.保存过期时间

    Redis通过过期字典即redisDb结构的expires字典来保存数据库中所有键的过期时间。过期字典的键是一个指针,指向键空间中的某个键对象(数据库键),过期字典的值是一个long long类型的整数,这个整数保存了键所指向的数据库键的过期时间——一个毫秒精度的UNIX时间戳。

    typedef struct redisDb {
        ...   
        dict *dict;     // 数据库键空间,保存数据库中所有键值对
        dict *expires   // 过期字典,保存键的过期时间
        ...
    } redisDb;
    

    一个带有过期字典的示例图如下:
    4

    注意:图中键空间和过期字典中重复出现了两次键对象。在实际中,键空间的键和过期字典的键都指向同一个键对象,所以不会出现任何重复对象,也不会浪费任何空间。

    当执行PEXPIREAT或者其他三个会转换成PEXPIREAT的命令为一个数据库键设置过期时间时,服务器会在数据库的过期字典中关联给定的数据库键和过期时间。通过过期字典,Redis可以判定给定键是否过期,首先检查给定键是否存在于过期字典,如果存在,取得键的过期时间,然后检查当前UNIX时间截是否大于键的过期时间,大于则键已过期,否则,键未过期。

    3.移除过期时间

    PERSIST key

    起始版本:2.2.0
    时间复杂度:O(1)

    移除给定key的过期时间,将这个key从『易失的』(设置有过期时间的key)转换成『持久的』(不带过期时间、永不过期的key)。如果移除过期时间成功,则返回1,如果key不存在或没有设置过期时间,则返回0。

    PERSIST命令就是PEXPIREAT命令的反操作:PERSIST命令在过期字典中查找给定的键,并解除键和值(过期时间)在过期字典中的关联。

    4.返回剩余生存时间

    TTL key

    起始版本:1.0.0
    时间复杂度:O(1)

    以秒为单位返回具有过期时间的key的剩余生存时间。在Redis 2.6及之前版本,如果key不存在或者key存在但未设置过期时间时返回-1。

    从Redis2.8开始,key不存在返回-2,key存在但未设置过期时间返回-1。

    PTTL key

    起始版本:2.6.0
    时间复杂度:O(1)

    以毫秒为单位返回具有过期时间的key的剩余生存时间。在Redis 2.6及之前版本,如果key不存在或者key存在但未设置过期时间时返回-1。从Redis2.8开始,key不存在返回-2,key存在但未设置过期时间返回-1。

    TTLPTTL两个命令都是通过计算键的过期时间(键会被删除的时间)和当前时间之间的差来实现的。

    七、Redis的过期策略

    一个键过期了,它什么时候会被删除取决于过期键的删除策略,而过期键的删除策略有以下三种:

    下一篇:没有了