redis内部原理揭秘

课程目标

  1. 过期时间设置及原理分析
  2. 发布订阅模式
  3. Redis持久化及原理分析
  4. Redis的内存回收策略
  5. Redis单线程为什么性能很高?
  6. Lua脚本在Redis中的应用

1. 过期时间

在Redis中提供了expire命令设置一个键的过期时间,到期以后redis会自动删除它。

  1. expire命令使用方法

expire key seconds
其中seconds参数表示键的过期时间,单位为秒
expire返回值为1表示设置成功,0表示设置失败或者键不存在
如果想知道一个键还有多久时间被删除,可以使用TTL命令
ttl key
当键不存在时,ttl命令会返回-2;对于没有给指定键设置过期时间的,通过ttl命令会返回-1
如果想取消键的过期时间设置(使该键恢复成为永久的),可以使用persist命令,如果该命令执行成功或者成功清除了过期时间,返回1;否则返回0(键不存在或者本身就是永久的)
expire命令的seconds参数必须是整数,最小单位秒
pexpire命令的seconds单位毫秒
pexire key 1000
expire key 1
上面两条命令效果相同
pttl key 获取剩余有效时间

  1. 针对字符串独有的过期时间设置方式
    setex(String key, int seconds, String value)

过期删除的原理

redis删除失效主键的方法主要有两种

1.消极方法(passive way)

在主键被访问时如果发现它已经失效,那么就删除它

2.积极方法(active way)

周期性的从设置了失效时间的主键中选择一部分失效的主键删除
对于那些从未被查询的key,即便他们已经过期,被动方式也无法清除。因此redis会周期性地随机测试一些key,已过期的key将会被删掉。redis每秒会进行10次操作,具体的流程

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

2. 发布订阅模式

pub/sub

127.0.0.1:6379> publish channel.mic hello
(integer) 1
127.0.0.1:6379> publish channel.mic waorld
(integer) 1
127.0.0.1:6379> publish channel.mic world
(integer) 1

127.0.0.1:6379> subscribe channel.mic
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.mic"
3) (integer) 1
1) "message"
2) "channel.mic"
3) "hello"
1) "message"
2) "channel.mic"
3) "waorld"
1) "message"
2) "channel.mic"
3) "world"

结构图

image

3. Redis持久化及原理分析

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

RDB 方式

当符合一定【条件】时,redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,等到持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
RDB的缺点是最后一次持久化后的数据可能丢失 生成dump.rdb快照文件
【条件】:

  1. 根据配置规则进行自动快照
    save second changes
// 满足一个就符合条件
save 900 1 // 在900秒内有一个以上的键被更改
save 300 10
save 60 10000
  1. 用户执行save或者bgsave命令
    save 会阻塞所有客户端的请求
    bgsave:在后台异步快照,不影响客户端请求
  2. 执行flushall命令
    清除所有的内存数据
  3. 执行复制(replication)操作
    该操作主要是在主从模式下,redis会在复制初始化时进行自动快照。这里只需要了解当执行复制操作时,即使没有定义自动快照规则,并且没有手动执行过快照操作,它仍然会生成RDB文件

AOF方式

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

开启AOF

默认情况下redis没有开启AOF方式的持久化,可以通过appendonly参数启用
需要redis.conf开启appendonly yes
开启AOF持久化后每执行一条会更改redis中数据的命令后,redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB的位置相同,都是通过dir参数设置的,默认文件名是appendonly.aof,可以在redis.conf中appendfilename appendonly.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-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb // 文件小于 64m时 没必要重写

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过程是比较可靠的。

// 当目前的aof文件超过上一次的百分之多少时 重写
auto-aof-rewrite-percentage 100
// 文件小于 64m时 没必要重写
auto-aof-rewrite-min-size 64mb
# appendfsync always
// 每秒种执行一次
appendfsync everysec
# appendfsync no

4. redis内存回收策略

redis中提供了多种内存回收策略,当内存容量不足时,为了保证程序的运行,这时就不得不淘汰内存中的一些对象,释放这些对象占用的空间,那么选择淘汰哪些对象呢?

其中,默认的策略为noeviction策略,当内存使用达到阈值的时候,所有引起申请内存的命令会报错

maxmemory-policy 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更适合被淘汰,我们可以自己控制

总结

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

关注公众号,获取海量免费java进阶视频

推荐阅读更多精彩内容

  • 从这篇文章开始,将依次介绍Redis高可用相关的知识——持久化、复制(及读写分离)、哨兵、以及集群。 本文将先说明...
    不变甄心阅读 253评论 0 3
  • 前言 在上一篇文章中,介绍了Redis内存模型,从这篇文章开始,将依次介绍Redis高可用相关的知识——持久化、复...
    Java架构阅读 994评论 3 18
  • 企业级redis集群架构的特点 海量数据 高并发 高可用 要达到高可用,持久化是不可减少的,持久化主要是做灾难恢复...
    lucode阅读 426评论 0 6
  • 超强、超详细Redis入门教程 转载2017年03月04日 16:20:02 16916 转载自: http://...
    邵云涛阅读 10,713评论 2 309
  • 今天湖南省长骑自行上班被交警拦住教训,被出租车司机骂上了新闻。这其实是每天马路上都在上演的千千万万稀松平常的小插曲...
    思妤的奇想国阅读 76评论 0 1