Redis持久化的几种方式


Redis 支持持久化, 是 Redis 和 Memcached 的主要区别之一,因为 Memcached 是不具备持久化功能的。

Redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。redis提供两种方式进行持久化,一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化),另外一种是AOF(append only file)持久化(原理是将Reids的操作日志以追加的方式写入文件)。

(其实还有一种被弃用的虚拟内存方式以及替代这种方式的diskstore方式; 4.0之后还引入了一种混合持久化方式. 但目前用使用中主要还是RDB和AOF两种方式)


快照方式(RDB, Redis DataBase)将某一个时刻的内存数据,以二进制的方式写入磁盘;

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

1)RDB 优点

  • RDB 的内容为二进制的数据,占用内存更小,更紧凑,更适合做为备份文件;
  • 采用该方式, 整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的; RDB 对灾难恢复非常有用,它是一个紧凑的文件,可以更快的传输到远程服务器进行 Redis 服务恢复;
  • 性能最大化. RDB 可以更大程度的提高 Redis 的运行速度,因为每次持久化时 Redis 主进程都会 fork() 一个子进程,进行数据持久化到磁盘,Redis 主进程并不会执行磁盘 I/O 等操作;
  • 与 AOF 格式的文件相比,尤其当数据集很大时, RDB 文件可以更快的重启。

2)RDB 缺点

  • 因为 RDB 只能保存某个时间间隔的数据,如果中途 Redis 服务被意外终止了,则会丢失一段时间内的 Redis 数据; 如果想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。
  • RDB 需要经常 fork() 才能使用子进程将其持久化在磁盘上。如果数据集很大,fork() 可能很耗时,可能导致 Redis 停止为客户端服务几百毫秒甚至一秒钟。


AOF (Append-only file)以日志形式记录每一个写操作;

1)AOF 优点

  • 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3种同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的, 但数据安全性最高 。

  • 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。

  • 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。

  • AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

2)AOF 缺点

  • 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

  • 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。




Redis内存淘汰策略

长期将Redis作为缓存使用,难免会遇到内存空间存储瓶颈,当Redis内存超出物理内存限制时,内存数据就会与磁盘产生频繁交换,使Redis性能急剧下降。此时如何淘汰无用数据释放空间,存储新数据就变得尤为重要了。


redis采用配置参数maxmemory 的方式来限制内存大小. 当实际存储内存超出maxmemory 参数值时, 可以通过这几种淘汰策略,选出无用的key进行淘汰.

当前支持的淘汰策略有6种:

1
2
3
4
5
6
volatile	英[ˈvɒlətaɪl]
美[ˈvɑːlətl]
adj. 易变的; 无定性的; 无常性的; 可能急剧波动的; 不稳定的; 易恶化的; 易挥发的; 易发散的;

There have been riots before and the situation is volatile
以前曾发生过暴乱,现在局势不太稳定。
  1. volatile-lru:从设置过期时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。

  2. volatile-ttl:除了淘汰机制采用TTL,策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,ttl值越大越优先被淘汰。

  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key。

  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合。

  5. allkeys-random:从数据集(server.db[i].dict)中选择任意数据淘汰。

  6. no-enviction:禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。

一些使用:

  • 在Redis中,数据有一部分访问频率较高,其余部分访问频率较低,或者无法预测数据的使用频率时,设置allkeys-lru是比较合适的。

  • 如果所有数据访问概率大致相等时,可以选择allkeys-random。

  • 如果开发者需要通过设置不同的ttl来判断数据过期的先后顺序,此时可以选择volatile-ttl策略。

  • 如果希望一些数据能长期被保存,而一些数据可以被淘汰掉时,选择volatile-lru或volatile-random都是比较不错的。

  • 由于设置expire会消耗额外的内存,如果计划避免Redis内存在此项上的浪费,可以选用allkeys-lru 策略,这样就可以不再设置过期时间,高效利用内存了。

几种淘汰机制:

LRU淘汰

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

在服务器配置中保存了 lru 计数器 server.lrulock,会定时(redis 定时程序 serverCorn())更新,server.lrulock 的值是根据 server.unixtime 计算出来进行排序的,然后选择最近使用时间最久的数据进行删除。另外,从 struct redisObject 中可以发现,每一个 redis 对象都会设置相应的 lru。每一次访问数据,会更新对应redisObject.lru。

在Redis中,LRU算法是一个近似算法,默认情况下,Redis会随机挑选5个键,并从中选择一个最久未使用的key进行淘汰。在配置文件中,按maxmemory-samples选项进行配置,选项配置越大,消耗时间就越长,但结构也就越精准。

关于LRU算法,参见 LRU算法浅析

TTL淘汰

Redis 数据集数据结构中保存了键值对过期时间的表,即 redisDb.expires。与 LRU 数据淘汰机制类似,TTL 数据淘汰机制中会先从过期时间的表中随机挑选几个键值对,取出其中 ttl 最大的键值对淘汰。同样,TTL淘汰策略并不是面向所有过期时间的表中最快过期的键值对,而只是随机挑选的几个键值对。

随机淘汰

在随机淘汰的场景下获取待删除的键值对,随机找hash桶再次hash指定位置的dictEntry即可。

参考:

Redis内存淘汰策略




Redis缓存更新策略


定时删除 :

在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除

惰性删除 :

也叫懒汉式式删除, key过期的时候不删除,每次通过key获取值的时候去检查是否过期,若过期,则删除,返回null

定期删除 :

每隔一段时间执行一次删除过期key操作 ;上面两种一种占cpu,一种占内存,这种方案算是以上两种的折中方案



memcached只是用了惰性删除,而redis同时使用了惰性删除与定期删除,这也是二者的一个不同点(可以看做是redis优于memcached的一点)

在使用懒汉式删除+定期删除时, 控制时长和频率这个尤为关键, 需要结合服务器性能, 以及并发量等情况进行调整.

1是主动删除的, 实时执行, 对CPU不是很友好, 但在最大程度上释放了内存. 所以这种方式算是一种内存优先优化策略;

2、3为被动删除, 所以过期键不会被立马删除, 还会存在一定的时间, 仍然占用着内存. 但是惰性删除的时候一般是单个删除,相对来说对CPU是友好的.

而定期删除这种策略, 既有避免1、2两种策略劣势的可能, 也有同时发生1、2两种策略劣势的可能.. 如果定期删除执行得过于频繁就可能会演变成定时删除, 如果执行得过少, 就有可能造成过多过期键未被删除而占用过多内存, 如果时间的设置不是太好, 既可能占用过多内存,又同时对CPU较大负载.. 故而使用定期删除策略的时候, 一定要把握好删除的时间点.

参考:

了解Redis过期策略及实现原理

Redis如果过期,是根据什么策略来进行删除的



Redis五种数据结构

1
2
3
4
5
String——字符串
Hash——字典
List——列表
Set——集合
Sorted Set——有序集合

String

1
2
3
4
5
6
7
1.LEN cuishuang:O(1)获取字符串长度
2.APPEND uishuang redis:往字符串 append 内容,而且采用智能分配内存(每次2倍)
3.设置和获取字符串的某一段内容
4.设置及获取字符串的某一位(bit)
5.批量设置一系列字符串的内容
6.原子计数器
7.GETSET 命令的妙用,请于清空旧值的同时设置一个新值,配合原子计数器使用


Hash

存储、读取、修改用户属性

如将uid作为key, values中存放用户的nickname,phone,avatar,gender等信息

1
2
3
Redis使用哈希表作为字典的底层实现,每个字典都有两个哈希表,一个平时使用,另一个仅在进行rehash时使用

哈希表使用链表来解决键冲突问题,被分配到同一个索引上的多个键值对会连接成一个单向链表


List

List 说白了就是链表(redis 使用双端链表实现的 List)。使用 List 结构,我们可以轻松地实现最新消息排行等功能(比如新浪微博的 TimeLine )。

List 的另一个应用就是消息队列,可以利用 List 的 *PUSH 操作,将任务存在 List 中,然后工作线程再用 POP 操作将任务取出进行执行。Redis 还提供了操作 List 中某一段元素的 API,你可以直接查询,删除 List 中某一段的元素

Redis构建了自己的链表的实现,其特性如下:

1
2
3
4
5
6
7
8
9
双端:链表节点提供有prev和next对象,获取某个节点的前置节点和下一个节点的速度为O(1).

无环:表头节点prev对象和表尾节点next对象都指向NULL,链表的访问都是以NULL访问为终点.

带有表头和表尾对象:通过list结构的head和tail,获取表头和表尾对象的速度为O(1).

带有长度计数器:获取链表长度的直接读取len字段值.速度为O(1).

多态:通过dup、free、match三个方法,实现链表的多态,保存不同类型的值


Set

Redis 集合是 string 类型的无序集合。set 元素最大可以包含(2 的 32 次方)个元素。set 是通过 hash table 实现的,hash table 会随着添加或者删除自动的调整大小。调整 hash table 大小时候需要同步(获取写锁)会阻塞其他读写操作。

利用 Redis 提供的 Set 数据结构,可以存储一些集合性的数据。比如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。因为 Redis 非常人性化的为集合提供了求交集、并集、差集等操作,那么就可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

1
2
3
1.共同好友、二度好友
2.利用唯一性,可以统计访问网站的所有独立 IP
3.好友推荐的时候,根据 tag 求交集,大于某个 threshold 就可以推荐


Zset

zset是set的一个升级版本,它在set的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,zset会自动重新按新的值调整顺序。 可以对指定键的值进行排序权重的设定,它应用排名模块比较多。

比如一个存储全班同学成绩的 Sorted Sets,其集合 value 可以是同学的学号,而 score 就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。另外还可以用 Sorted Sets 来做带权重的队列,比如普通消息的 score 为1,重要消息的 score 为2,然后工作线程可以选择按 score 的倒序来获取工作任务,让重要的任务优先执行。

zset集合可以完成有序执行、按照优先级执行的情况;

1
2
1.带有权重的元素,比如一个游戏的用户得分排行榜
2.比较复杂的数据结构,一般用到的场景不算太多