Redis系列之基础篇【下】

Redis系列之基础篇【下】

1.过期时间详解

2.发布订阅详解

3.缓存持久化详解

4.内存回收详解

====================================

一. 过期时间详解

过期时间设置
在Redis中提供了Expire命令设置一个键的过期时间,到期以后Redis会自动删除它。这个在我们实际使用过程中用得非常多。
EXPIRE命令的使用方法为

EXPIRE key seconds

其中seconds 参数表示键的过期时间,单位为秒。
EXPIRE 返回值为1表示设置成功,0表示设置失败或者键不存在
如果向知道一个键还有多久时间被删除,可以使用TTL命令

TTL key

当键不存在时,TTL命令会返回-2
而对于没有给指定键设置过期时间的,通过TTL命令会返回-1
如果向取消键的过期时间设置(使该键恢复成为永久的),可以使用PERSIST命令,如果该命令执行成功或者成功
清除了过期时间,则返回1 。 否则返回0(键不存在或者本身就是永久的)
EXPIRE命令的seconds命令必须是整数,所以最小单位是1秒,如果向要更精确的控制键的过期时间可以使用
PEXPIRE命令,当然实际过程中用秒的单位就够了。 PEXPIRE命令的单位是毫秒。即PEXPIRE key 1000与EXPIRE
key 1相等;对应的PTTL以毫秒单位获取键的剩余有效时间
还有一个针对字符串独有的过期时间设置方式

setex(String key,int seconds,String value)

过期删除的原理
Redis 中的主键失效是如何实现的,即失效的主键是如何删除的?实际上,Redis 删除失效主键的方法主要有两
种:

  1. 消极方法(passive way)
  2. 积极方法(active way)

消极方法(passive way):在主键被访问时如果发现它已经失效,那么就删除它
积极方法(active way):周期性地从设置了失效时间的主键中选择一部分失效的主键删除对于那些从未被查询的key,即便它们已经过期,被动方式也无法清除。因此Redis会周期性地随机测试一些key,已过期的key将会被删掉。Redis每秒会进行10次操作,具体的流程:

  1. 随机测试 20 个带有timeout信息的key;
  2. 删除其中已经过期的key;
  3. 如果超过25%的key被删除,则重复执行步骤1;
    这是一个简单的概率算法(trivial probabilistic algorithm),基于假设我们随机抽取的key代表了全部的key空间。

二. 发布订阅详解

Redis提供了发布订阅功能,可以用于消息的传输,Redis提供了一组命令可以让开发者实现“发布/订阅”模式(publish/subscribe) . 该模式同样可以实现进程间的消息传递,它的实现原理是
发布/订阅模式包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或多个频道,而发布者可以向指定的
频道发送消息,所有订阅此频道的订阅者都会收到该消息
发布者发布消息的命令是PUBLISH, 用法是
PUBLISH channel message
比如向channel.1发一条消息:hello
PUBLISH channel.1 “hello”
这样就实现了消息的发送,该命令的返回值表示接收到这条消息的订阅者数量。因为在执行这条命令的时候还没有
订阅者订阅该频道,所以返回为0. 另外值得注意的是消息发送出去不会持久化,如果发送之前没有订阅者,那么后
续再有订阅者订阅该频道,之前的消息就收不到了
订阅者订阅消息的命令是
SUBSCRIBE channel [channel …]
该命令同时可以订阅多个频道,比如订阅channel.1的频道。 SUBSCRIBE channel.1
执行SUBSCRIBE命令后客户端会进入订阅状态

结构图
channel分两类,一个是普通channel、另一个是pattern channel(规则匹配), producer1发布了一条消息
【publish abc hello】,redis server发给abc这个普通channel上的所有订阅者,同时abc也匹配上了pattern
channel的名字,所以这条消息也会同时发送给pattern channel *bc上的所有订阅者
在这里插入图片描述

三.缓存持久化详解

Redis支持两种方式的持久化,一种是RDB方式、另一种是AOF(append-only-file)方式。前者会根据指定的规
则“定时”将内存中的数据存储在硬盘上,而后者在每次执行命令后将命令本身记录下来。两种持久化方式可以单独
使用其中一种,也可以将这两种方式结合使用。

RDB方式

当符合一定条件时,Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,等到持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失–fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程Redis会在以下几种情况下对数据进行快照

  1. 根据配置规则进行自动快照
  2. 用户执行SAVE或者GBSAVE命令
  3. 执行FLUSHALL命令
  4. 执行复制(replication)时

根据配置规则进行自动快照
Redis允许用户自定义快照条件,当符合快照条件时,Redis会自动执行快照操作。快照的条件可以由用户在配置文件中配置。配置格式如下第一个参数是时间窗口,第二个是键的个数,也就是说,在第一个时间参数配置范围内被更改的键的个数大于后面的changes时,即符合快照条件。redis默认配置了三个规则
save 900 1
save 300 10
save 60 10000
每条快照规则占一行,每条规则之间是“或”的关系。 在900秒(15分)内有一个以上的键被更改则进行快照。

用户执行SAVE或BGSAVE命令
除了让Redis自动进行快照以外,当我们对服务进行重启或者服务器迁移我们需要人工去干预备份。redis提供了两
条命令来完成这个任务

  1. save命令
    当执行save命令时,Redis同步做快照操作,在快照执行过程中会阻塞所有来自客户端的请求。当redis内存中的数
    据较多时,通过该命令将导致Redis较长时间的不响应。所以不建议在生产环境上使用这个命令,而是推荐使用
    bgsave命令
  2. bgsave命令
    bgsave命令可以在后台异步地进行快照操作,快照的同时服务器还可以继续响应来自客户端的请求。执行BGSAVE
    后,Redis会立即返回ok表示开始执行快照操作。
    通过LASTSAVE命令可以获取最近一次成功执行快照的时间; (自动快照采用的是异步快照操作)

执行FLUSHALL命令
该命令在前面讲过,会清除redis在内存中的所有数据。执行该命令后,只要redis中配置的快照规则不为空,也就
是save 的规则存在。redis就会执行一次快照操作。不管规则是什么样的都会执行。如果没有定义快照规则,就不
会执行快照操作

执行复制时
该操作主要是在主从模式下,redis会在复制初始化时进行自动快照。这个会在后面讲到;
这里只需要了解当执行复制操作时,及时没有定义自动快照规则,并且没有手动执行过快照操作,它仍然会生成
RDB快照文件

AOF方式

当使用Redis存储非临时数据时,一般需要打开AOF持久化来降低进程终止导致的数据丢失。AOF可以将Redis执行
的每一条写命令追加到硬盘文件中,这一过程会降低Redis的性能,但大部分情况下这个影响是能够接受的,另外
使用较快的硬盘可以提高AOF的性能。

开启AOF
默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数启用,在redis.conf
中找到 appendonly yes
开启AOF持久化后每执行一条会更改Redis中的数据的命令后,Redis就会将该命令写入硬盘中的AOF文件。AOF文
件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是apendonly.aof. 可以在redis.conf
中的属性 appendfilename appendonlyh.aof修改

AOF的实现
AOF文件以纯文本的形式记录Redis执行的写命令例如开启AOF持久化的情况下执行如下4条命令
set foo 1
set foo 2
set foo 3
get
redis 会将前3条命令写入AOF文件中,通过vim的方式可以看到aof文件中的内容
我们会发现AOF文件的内容正是Redis发送的原始通信协议的内容,从内容中我们发现Redis只记录了3
条命令。然后这时有一个问题是前面2条命令其实是冗余的,因为这两条的执行结果都会被第三条命令覆
盖。随着执行的命令越来越多,AOF文件的大小也会越来越大,其实内存中实际的数据可能没有多少,
那这样就会造成磁盘空间以及redis数据还原的过程比较长的问题。因此我们希望Redis可以自动优化
AOF文件,就上面这个例子来说,前面两条是可以被删除的。 而实际上Redis也考虑到了,可以配置一
个条件,每当达到一定条件时Redis就会自动重写AOF文件,这个条件的配置

auto-aof-rewritepercentage 100 auto-aof-rewrite-min-size 64mb

auto-aof-rewrite-percentage 表示的是当目前的AOF文件大小超过上一次重写时的AOF文件大小的百分之多少时会
再次进行重写,如果之前没有重写过,则以启动时AOF文件大小为依据
auto-aof-rewrite-min-size 表示限制了允许重写的最小AOF文件大小,通常在AOF文件很小的情况下即使其中有很
多冗余的命令我们也并不太关心。
另外,还可以通过BGREWRITEAOF 命令手动执行AOF,执行完以后冗余的命令已经被删除了
在启动时,Redis会逐个执行AOF文件中的命令来将硬盘中的数据载入到内存中,载入的速度相对于RDB会慢一些

AOF的重写原理
在这里插入图片描述
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前
数据集所需的最小命令集合。
重写的流程是这样,主进程会fork一个子进程出来进行AOF重写,这个重写过程并不是基于原有的aof文件来做
的,而是有点类似于快照的方式,全量遍历内存中的数据,然后逐个序列到aof文件中。在fork子进程这个过程
中,服务端仍然可以对外提供服务,那这个时候重写的aof文件的数据和redis内存数据不一致了怎么办?不用担
心,这个过程中,主进程的数据更新操作,会缓存到aof_rewrite_buf中,也就是单独开辟一块缓存来存储重写期间
收到的命令,当子进程重写完以后再把缓存中的数据追加到新的aof文件。
当所有的数据全部追加到新的aof文件中后,把新的aof文件重命名为,此后所有的操作都会被写入新的aof文件。
如果在rewrite过程中出现故障,不会影响原来aof文件的正常工作,只有当rewrite完成后才会切换文件。因此这个
rewrite过程是比较可靠的
在这里插入图片描述

Redis启动加载持久文件流程

在这里插入图片描述
Redis4.0后的混合持久化机制
开启混合持久化
4.0版本的混合持久化默认关闭的,通过aof-use-rdb-preamble配置参数控制,yes则表示开启,no表示禁用,5.0之后默认开启。
混合持久化是通过bgrewriteaof完成的,不同的是当开启混合持久化时,fork出的子进程先将共享的内存副本全量的以RDB方式写入aof文件,然后在将重写缓冲区的增量命令以AOF方式写入到文件,写入完成后通知主进程更新统计信息,并将新的含有RDB格式和AOF格式的AOF文件替换旧的的AOF文件。简单的说:新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据,

优点:混合持久化结合了RDB持久化 和 AOF 持久化的优点, 由于绝大部分都是RDB格式,加载速度快,同时结合AOF,增量的数据以AOF方式保存了,数据更少的丢失。
缺点:兼容性差,一旦开启了混合持久化,在4.0之前版本都不识别该aof文件,同时由于前部分是RDB格式,阅读性较差。

常见问题:

  1. redis提供了rdb持久化方案,为什么还要aof?
    优化数据丢失问题,rdb会丢失最后一次快照后的数据,aof丢失不会超过2秒的数据

  2. 如果aof和rdb同时存在,听谁的?
    aof

  3. rdb和aof优势劣势
    rdb 适合大规模的数据恢复,对数据完整性和一致性不高 , 在一定间隔时间做一次备份,如果redis意外down机的话,就会丢失最后一次快照后的所有操作
    aof 根据配置项而定

官方建议
两种持久化机制同时开启,如果两个同时开启 优先使用aof持久化机制

性能建议(这里只针对单机版redis持久化做性能建议):
因为RDB文件只用作后备用途,只要15分钟备份一次就够了,只保留save 900 1这条规则。
如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。
代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。
只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。
默认超过原大小100%大小时重写可以改到适当的数值。

四. 内存回收详解

Redis中提供了多种内存回收策略,当内存容量不足时,为了保证程序的运行,这时就不得不淘汰内存中的一些对
象,释放这些对象占用的空间,那么选择淘汰哪些对象呢?
其中,默认的策略为noeviction策略,当内存使用达到阈值的时候,所有引起申请内存的命令会报错

  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰【适合场景】如果我们的应用对缓存的访问都是相对热点数据,那么可以选择这个策略
  • allkeys-random:随机移除某个key。 适合的场景:如果我们的应用对于缓存key的访问概率相等,则可以使用这个策略
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰【适合场景】这种策略使得我们可以向Redis提示哪些key更适合被淘汰,我们可以自己控制

实际上Redis实现的LRU并不是可靠的LRU,也就是名义上我们使用LRU算法淘汰内存数据,但是实际上被淘汰的键
并不一定是真正的最少使用的数据,这里涉及到一个权衡的问题,如果需要在所有的数据中搜索最符合条件的数
据,那么一定会增加系统的开销,Redis是单线程的,所以耗时的操作会谨慎一些。为了在一定成本内实现相对的
LRU,早期的Redis版本是基于采样的LRU,也就是放弃了从所有数据中搜索解改为采样空间搜索最优解。Redis3.0
版本之后,Redis作者对于基于采样的LRU进行了一些优化,目的是在一定的成本内让结果更靠近真实的LRU。

手写LRU
在这里插入图片描述
注:希望大家技术越来越6p

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章