前言
简介
Redis是一种面向“key-value”类型数据的分布式NoSQL数据库系统,支持五种数据类型格式:**
String,hash,list,set ,socket set **。
字符串(String)
Redis中的字符串是一个字节序列。 常用操作命令有get、set等。
列表(list)
Redis列表知识字符串列表,按插入顺序排序。您可以向Redis列表的头部或尾部添加元素,也可以用作队列。
散列/哈希(hash)
Redis散列/哈希是键值对的集合。Redis散列/哈希是字符串字段和字符串值之间的映射,但字段值只能是字符串,不支持其他类型。因此,他们用于表示对象。应用场景:存储用户的基本信息等。
无序集合类型(set)
redis的set是String类型的无序集合,set元素最大可以包含2的32次方-1个元素。利用set集合类型,我们可以快速取出n个key之间的并集、交集、差集等.应用场景:取出两个QQ号中的共同的好友数。
有序集合类型(sorted set)
和set一样,sorted set也是string类型元素的集合,是一个没有重复元素的字符串集合。因为元素是有序的,所以使用有序集合你可以以非常快的速度(O(log(N)))添加,删除和更新元素,它也很擅长排序。应用场景:获取所有用户投票数最高的前10名。
Redis持久化方式
Redis由于支持非常丰富的内存数据结构类型,如何把这些复杂的内存组织方式持久化到磁盘上是一个难题,所以Redis的持久化方式与传统数据库的方式有比较多的差别,Redis一共支持四种持久化方式,分别是:
RDB定时快照方式(snapshot)
RDB将数据库的快照(snapshot)以二进制的方式保存到磁盘中。
AOF基于语句追加文件的方式
则以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到AOF文件,以此达到记录数据库状态的目的。
虚拟内存(vm)(被废弃)
Diskstore方式(被废弃)
在设计思路上,前两种是基于全部数据都在内存中,即小数据量下提供磁盘落地功能,而后两种方式则是作者在尝试存储数据超过物理内存时,即大数据量的数据存储,截止到本文,后两种持久化方式仍然是在实验阶段,并且vm方式基本已经被作者放弃,所以实际能在生产环境用的只有前两种,换句话说Redis目前还只能作为小数据量存储(全部数据能够加载在内存中),海量数据存储方面并不是Redis所擅长的领域。
很多人开始会想象两者是互相结合的,即dump出一个snapshot到RDB文件,然后在此基础上记录变化日志到AOF文件。实际上两者毫无关系,完全独立运行,因为作者认为简单才不会出错。如果使用了AOF,重启时只会从AOF文件载入数据,不会再管RDB文件。
正确关闭服务器:redis-cli shutdown或者 kill,都会graceful shutdown,保证写RDB文件以及将AOF文件fsync到磁盘,不会丢失数据。如果是粗暴的Ctrl+C,或者kill-9就可能丢失。
下面分别介绍下这几种持久化方式:
RDB定时快照方式(snapshot)
RDB持久化的触发分为手动触发和自动触发两种。
1、手动触发
通过redis的save命令和bgsave命令,都可以生成RDB文件。
(1)save保存数据到磁盘的方式:
Redis Save命令执行一个同步保存操作,将当前Redis实例的所有数据快照(snapshot)以RDB文件的形式保存到硬盘。
语法:redis 127.0.0.1:6379> SAVE
返回值:保存成功时返回 OK 。
(2)BGSAVE保存数据到磁盘的方式:
BGSAVE命令执行之后立即返回OK ,然后 Redis fork 出一个新子进程,原来的Redis进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出。
客户端可以通过LASTSAVE命令查看相关信息,判断BGSAVE命令是否执行成功。
bgsave命令执行过程中,只有fork子进程时会阻塞服务器,而对于save命令,整个过程都会阻塞服务器,因此save已基本被废弃,线上环境要杜绝save的使用;后文中也将只介绍bgsave命令。此外,在自动触发RDB持久化时,Redis也会选择bgsave而不是save来进行持久化;下面介绍自动触发RDB持久化的条件。
2、自动触发
自动触发最常见的情况是在配置文件中通过save m n,指定当m秒内发生n次变化时,会触发bgsave。
例如,查看redis的默认配置文件redis.conf,可以看到如下配置信息:
其中save 900 1的含义是:当时间到900秒时,如果redis数据发生了至少1次变化,则执行bgsave;
当三个save条件满足任意一个时,都会引起bgsave的调用。
3、save m n的实现原理
save m n的含义:当时间到m秒时,如果redis数据发生了至少n次变化,则执行bgsave;
是将数据先存储在内存,然后当数据累计达到某些设定的伐值的时候,就会触发一次DUMP操作,将变化的数据一次性写入数据文件(RDB文件)。
RDB实际是在Redis内部一个定时器事件,每隔固定时间去检查当前数据发生的改变次数与时间是否满足配置的持久化触发的条件,如果满足则通过操作系统fork调用来创建出一个子进程,这个子进程默认会与父进程共享相同的地址空间,这时就可以通过子进程来遍历整个内存来进行存储操作,而主进程则仍然可以提供服务,当有写入时由操作系统按照内存页(page)为单位来进行copy-on-write保证父子进程之间不会互相影响。
**具体实现:是通过serverCron函数、dirty计数器、和lastsave时间戳来实现的。 **
redis.c文件的周期性函数serverCron:
/* This is our timer interrupt, called server.hz times per second.
* Here is where we do a number of things that need to be done asynchronously.
* For instance:
*
* - Active expired keys collection (it is also performed in a lazy way on
* lookup).
* - Software watchdog.
* - Update some statistic.
* - Incremental rehashing of the DBs hash tables.
* - Triggering BGSAVE / AOF rewrite, and handling of terminated children.
* - Clients timeout of different kinds.
* - Replication reconnection.
* - Many more...
*
* Everything directly called here will be called server.hz times per second,
* so in order to throttle execution of things we want to do less frequently
* a macro is used: run_with_period(milliseconds) { .... }
*/
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
serverCron是Redis服务器的周期性操作函数,默认每隔100ms执行一次;该函数对服务器的状态进行维护,其中一项工作就是检查 save m n 配置的条件是否满足,如果满足就执行bgsave。
dirty计数器是Redis服务器维持的一个状态,记录了上一次执行bgsave/save命令后,服务器状态进行了多少次修改(包括增删改);而当save/bgsave执行完成后,会将dirty重新置为0。
例如,如果Redis执行了set mykey helloworld,则dirty值会+1;如果执行了sadd myset v1 v2 v3,则dirty值会+3;注意dirty记录的是服务器进行了多少次修改,而不是客户端执行了多少修改数据的命令。
lastsave时间戳也是Redis服务器维持的一个状态,记录的是上一次成功执行save/bgsave的时间。
save m n的原理如下:
1)按定时执行
每隔100ms,执行serverCron函数;
2)遍历所有save m n配置
在serverCron函数中,遍历save m n配置的保存条件,只要有一个条件满足,就进行bgsave。
例如:
当三个save条件满足任意一个时,都会引起bgsave的调用。
3)对于每一个save m n条件,只有下面两条同时满足时才算满足:
(1)当前时间 - lastsave > m
(2)dirty >= n
4)save m n 执行日志
下图是save m n触发bgsave执行时,服务器打印日志的情况:
其他自动触发机制
除了save m n 以外,还有一些其他情况会触发bgsave:
在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令,并将rdb文件发送给从节点执行shutdown命令时,自动执行rdb持久化,如下图所示:
4、bgsave实现流程:
1)Redis父进程首先判断
当前是否在执行save,或bgsave/bgrewriteaof(后面会详细介绍该命令)的子进程,如果在执行则bgsave命令直接返回。bgsave/bgrewriteaof的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。
2)FORK子进程
RDB写入时