redis启动加载过程、数据持久化
2017-12-06 14:14
435 查看
背景
公司一年的部分业务数据放在redis服务器上,但数据量比较大,单纯的string类型数据一年就将近32G,而且是经过压缩后的。所以我在想能否通过获取string数据的时间改为保存list数据类型,或者将数据持久化到硬盘上,或者放在不同库上,解决未来数据过大导致down机的问题。
相关知识点
string数据类型数据持久化
数据加载
Redis的字符串(string)的实现原理
Redis是由C语言编写的,以高效和轻量著称。比如一个简单的字符串”hello world”,其实是一个如下的字符的数组:
[‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ‘ ‘, ‘w’, ‘o’, ‘r’, ‘l’, ‘d’, ‘\0’]
最后的一个’\0’是空字符,表示字符串的结尾。
Redis由于各种原因,并没有直接使用了C语言的字符串结构,而是对其做了一些封装,得到了自己的简单动态字符串(simple dynamic string, SDS)的抽象类型。
Redis中,默认以SDS作为自己的字符串表示。只有在一些字符串不可能出现变化的地方使用C字符串。
SDS定义中有三个参数:
buf是一块可用的内存空间,通常大小会大于等于需要存储的字符串的大小
len表示字符串的长度,也表示buf中已经被使用的空间的大小
free表示buf中没有被使用的空间的大小。
要注意的是,buf的大小等于len+free+1,其中多余的1个字节是用来存储’\0’的
那么这么封装到底有什么好处呢?
1.常数复杂度获取字符串长度
在C语言中的字符串只是简单的字符的数组,当使用strlen获取字符串长度的时候,C语言内部其实是直接顺序遍历数组的内容,找到对应的’\0’对应的字符,从而计算出字符串的长度。显然这个算法复杂度和字符串的长度成正比,即O(N)。而对于SDS来说,只需要访问SDS的len属性就能得到字符串的长度,复杂度为O(1)。这样,获取字符串长度的操作就不会成为Redis的瓶颈。2.杜绝缓冲区溢出
C++里面的字符串使用了STL的string类型,我们开发者不太需要关注内存的分配和释放的过程。但是Redis是C语言编写的,并没有这么方便的数据类型。对于字符串的拼接、复制等操作,C语言开发者必须确保目标字符串的空间足够大,不然就会出现溢出的情况。char a[10]="hello";
strcar(a,"world");
strcpy(a,"hello world");
上面的三句代码,就是C语言的字符串拼接和复制的使用,但是明显出现了缓冲区溢出的问题。字符数组a的长度是10,而”hello world”字符串的长度为11,则需要12个字节的空间来存储(不要忘记了’\0’)。
然而当使用SDS的API对字符串进行修改的时候,API内部第一步会检测字符串的大小是否满足。
如果空间已经满足要求,那么就像C语言一样操作即可。
如果不满足,则拓展buf的空间,使得满足操作的需求,之后再进行操作。
每次操作之后,len和free的值会做相应的修改。
这就是SDS的全部的高明之处了吗?当然不!
当API发现SDS的buf的容量不够的时候,并不是简单申请正好适合的大小,而是额外申请了一倍的空间!我们以sds的API sdscat函数为例,该函数实现了sds的拼接的功能。
3.减少修改字符串时带来的内存重新分配次数
c语言底层是一个N+1长的字符数组,长度的变化都会引起内存的重新分配。而对于SDS则通过一些策略去解决这些问题:空间预分配
这种方式用于处理字符串长度增加的问题。如果对字符串的修改使得字符串的长度增加,API首先会判断buf的空间大小是否满足,如果满足则直接操作,如果不满足,则进行如下操作:
如果对SDS进行修改之后的,SDS的长度(即len的值)小于1MB。程序将额外分配和len一样大小的未使用空间。以上面的”hello” + ” world”的操作为例。
在这个例子中”hello”的len是5(不考虑’\0′),修改之后的字符串”hello world”长度为11,那么新的SDS的buf的容量就是11*2+1。其中len和free都是11,多余的1字节用来存储’\0’。
惰性空间释放
当执行字符串长度缩短的操作的时候,SDS并不直接重新分配多出来的字节,而是修改len和free的值(len相应减小,free相应增大,buf的空间大小不变化)。通过惰性空间释放,可以很好的避免缩短字符串需要的内存重分配的情况。而且多余的空间也可以为将来可能有的字符串增长的操作做优化。当然,SDS也提供直接释放未使用空间的API,在需要的时候,也能真正的释放掉多余的空间。
数据持久化
filesnapshotting(快照)Append-only(aof)
filesnapshotting
默认redis是会以快照的形式将数据持久化到磁盘的(一个二进 制文件,xx.rdb)在配置文件中的格式是:save N M表示在N秒之内,redis至少发生M次修改则redis抓快照到磁盘。
当然我们也可以手动执行save或者bgsave(异步)做快照。
工作原理简单介绍:当redis需要做持久化时,redis会fork一个子进程;子进程将数据写到磁盘上一个临时RDB文件中;当子进程完成写临时文件后,将原来的RDB替换掉,这样的好处就是可以copy-on-write
缺点:filesnapshotting方法在redis异常死掉时, 最近的数据会丢失(丢失数据的多少视你save策略的配置),所以这是它最大的缺点,当业务量很大时,丢失的数据是很多的
Appened-only
可以做到全部数据不丢失,但redis的性能就要差些。AOF就可以做到全程持久化,只需要在配置文件中开启(默认是no),appendonly yes开启AOF之后,可以选择三种不同的策略,都会把它添加到aof文件中,当redis重启时,将会读取AOF文件进行“重放”以恢复到 redis关闭前的最后时刻。AOF的三种策略
appendfsync :appendfsync always每提交一个修改命令都调用fsync刷新到AOF文件,非常非常慢,但也非常安全;
appendfsync everysec每秒钟都调用fsync刷新到AOF文件,很快,但可能会丢失一秒以内的数据;
appendfsync no依靠OS进行刷新,redis不主动刷新AOF,这样最快,但安全性就差。默认并推荐每秒刷新,这样在速度和安全上都做到了兼顾。
LOG Rewriting随着修改数据的执行AOF文件会越来越大,其中很多内容记录某一个key的变化情况。
因此redis有了一种比较有意思的特性:在后台重建AOF文件,而不会影响client端操作。在任何时候执行BGREWRITEAOF命令,都会把当前内存中最短序列的命令写到磁盘,这些命令可以完全构建当前的数据情况,而不会存在多余的变化情况(比如状态变化,计数器变化等),缩小的AOF文件的大小。
所以当使用AOF时,redis推荐同时使用BGREWRITEAOF。
LOG Rewrite的工作原理:同样用到了copy-on-write:首先redis会fork一个子进程;子进程将最新的AOF写入一个临时文件;父进程 增量的把内存中的最新执行的修改写入(这时仍写入旧的AOF,rewrite如果失败也是安全的);当子进程完成rewrite临时文件后,父进程会收到 一个信号,并把之前内存中增量的修改写入临时文件末尾;这时redis将旧AOF文件重命名,临时文件重命名,开始向新的AOF中写入。
Redis启动加载过程
1. 初始化全局服务器配置2. 加载配置文件(如果指定了配置文件,否则使用默认配置)
3. 初始化服务器
4. 加载数据库
5. 网络监听
下面对上面这些步骤进行介绍
初始化全局服务器配置
初始化全局服务器配置通过initServerConfig()函数完成,主要是初始化server变量初始化的内容包括下面几个方面:
1. 网络监听相关,如绑定地址,TCP端口等
2. 虚拟内存相关,如swap文件、page大小等
3. 保存机制,多长时间内有多少次更新才进行保存
4. 复制相关,如是否是slave,master地址、端口
5. Hash相关设置
6. 初始化命令表
如其中的保存机制中,服务器初始化策略为:
$ cat appendonly.aof *2 $6 SELECT $1 0 *3 $3 SET $8 mykey001 $10 myvalue001
View Code
在appendonly.aof文件中保存的正是从客户端发过来的请求命令,还可以看到对于GET命令,并没有保存。
既然appendonly.aof中保存了所有写入数据的请求命令,那么在加载数据的时候只要重新执行一遍这些命令即可。
事实上Redis也正是这么做的,在开始加载之前暂时关闭appendonly,然后Redis创建一个假的Redis客户端。
然后读取appendonly.aof文件中的命令,在假的Redis客户端上下文中执行,同时服务器也不对该客户端做任何应答。
如果加载过程中物理内存不够用,并且Redis开启了VM,则还需要处理swap操作,最后加载完成后重新设置appendonly标志。
2. 从dbfile中加载数据:rdbLoad()函数
如果Redis没有开启appendonly,就需要从数据库文件中加载数据到内存,基本步骤如下:
a. 处理SELECT命令,即选择数据库
b. 读取key
c. 读取value
d. 检测key是否过期
e. 添加新的对象到哈希表
f. 设置过期时间(如果需要)
g. 如果开启了VM,处理swap操作
网络监听
在完成了初始化配置和数据加载后,Redis启动监听。Redis的网络库没有使用libevent或者libev,而是作者自己实现的一个非常轻量级的库(主要实现在ae.c文件中)然而回到我的问题上,如果数据库中数据已经放入了32G的数据,在启动redis时加载数据库这部分必定会相当相当慢,而且涉及到宕机的问题(物理内存到达峰值)
这时可能单台redis很难解决这个问题,而应该考虑集群。
相关文章推荐
- Redis分析系列:启动加载过程
- 虚拟机安装部署redis 启动 五种数据类型 key命令(过期时间) redis持久化方案
- 实现数据加载过程中菊花的方法
- spring启动component-scan类扫描加载过程---源码分析
- 计算机的启动过程加载要点
- 【Redis】redis介绍-启动过程
- Linux内核启动及根文件系统加载过程
- Redis持久化RDB简介及简单数据恢复案例
- 你所不知道的SQL Server数据库启动过程(用户数据库加载过程的疑难杂症)
- redis,mysql,memcache的区别与比较,redis两种数据存储持久化方式
- openstack升级过程中持久化数据DB的升级
- Spring Boot 启动加载数据 CommandLineRunner
- Spring启动时加载数据
- Tomcat7源码分析——启动过程和类加载器
- Oracle数据中启动脚本跟踪存储过程
- 在初始化的过程中将一些数据文件加载到系统目录中
- 使用viewpager+fragment,在activity启动模式为singleTask,跳转到当前页面重新加载数据fragment数据不更新
- Redis持久化-数据丢失及解决【转载|linux】
- 用oralce连接.net客户端出现问题:“数据连接不成功,请检查该数据库是否已启动尝试加载oracle客户端时引发BadImageFormatException.如果在安装32位Oracle客户端组
- linux环境下安装Redis 启动Redis 在Redis创建KV键值对数据(修)