getHandel redis_Redis Slave进行数据备份BGSAVE时可能内存突发跑满

前几天突然线上的redis slave 把64G的内存给占用满了,本来机器上的数据正常只会占用55.5%左右35G的内存的。

查看进程情况,当时redis正好在做BGSAVE操作,所以有2个Redis进程存在,初步怀疑是因为这个原因,但是理论上redis的BGSAVE是fork出来的进程,他们刚开始是共用物理内存的(Redis源码学习-AOF数据持久化原理分析(0)),除非主进程有数据修改,其实就是利用了操作系统的COW机制,巧妙的对redis做了一个数据快照。但明显线上系统不可能短时间内触发大部分数据的修改,所以排除这个的原因。

然后查看redis的日志发现了问题:

[2187] 08 Apr 17:11:49 * MASTER SLAVE sync started //由于网络抖动,到master的连接断开,需要重连接

[2187] 08 Apr 17:11:49 * Non blocking connect for SYNC fired the event.

[2187] 08 Apr 17:28:23 * MASTER SLAVE sync: receiving 35120836844 bytes from master //开始去接收35G的数据到本地;

[2187] 09 Apr 00:10:19 * 1 changes in 43200 seconds. Saving...      //悲剧的事还么有SYNC完,就正好撞到了一次BGSAVE备份操作

[2187] 09 Apr 00:10:20 * Background saving started by pid 20456

[2187] 09 Apr 00:10:24 * MASTER SLAVE sync: Loading DB in memory        //备份操作还没完成的时候,从master的数据读取完毕,准备要加载数据到内存了。,,这个时候就出问题了。

[2187] 09 Apr 00:45:42 * MASTER SLAVE sync: Finished with success    //完成SYNC,但此时机器物理内存占用满了,系统进入频繁的swap的颠簸期

[20456] 09 Apr 04:45:07 * DB saved on disk

[2187] 09 Apr 04:45:14 * Background saving terminated with success //4个小时才备份完,本来只需要10分支的。

从上面可以看出,本来很正常的BGSAVE被主从同步给搞垮了, 因为SYNC主从同步会彻底删掉内存中的数据,然后加载从master读取回来的整个RDB文件,这个操作是非常重的,可能会阻塞花费几十分钟甚至上小时。

void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {

//读取master发过来的RDB大小以及文件内容保存到本地文件中;

//如果读取完毕,那么调用rdbLoad加载文件内容。并考虑重新启动startAppendOnly

if (server.repl_transfer_read == server.repl_transfer_size) {//看看是否文件全部接收完毕,如果完毕,GOOD

if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {

redisLog(REDIS_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER SLAVE synchronization: %s", strerror(errno));

replicationAbortSyncTransfer();

return;

}

redisLog(REDIS_NOTICE, "MASTER SLAVE sync: Loading DB in memory");

signalFlushedDb(-1);

emptyDb();//清空整个数据库,这个操作非常重,如果当前正在做BGSAVE,那么会导致快照的COW写时复制机制失效,严重耗费物理内存。

/* Before loading the DB into memory we need to delete the readable

* handler, otherwise it will get called recursively since

* rdbLoad() will call the event loop to process events from time to

* time for non blocking loading. */

aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);//上面注释说了,避免循环进入。

if (rdbLoad(server.rdb_filename) != REDIS_OK) {//开始加载RDB文件到内存数据结构中,这个要花费不少时间的。

redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk");

replicationAbortSyncTransfer();

return;

}

/* Final setup of the connected slave

zfree(server.repl_transfer_tmpfile);

close(server.repl_transfer_fd);

server.master = createClient(server.repl_transfer_s);//重新注册可读事件毁掉为readQueryFromClient

server.master->flags |= REDIS_MASTER;

server.master->authenticated = 1;

server.repl_state = REDIS_REPL_CONNECTED;

//当切换server.repl_state 为 REDIS_REPL_CONNECTED的时候,新来的查询请求就能够被处理了,

//在processCommand里面就不会过滤非STALE请求,同时本slave也能接受下一级slave的SYNC指令了。

redisLog(REDIS_NOTICE, "MASTER SLAVE sync: Finished with success");

//···

}

这里面有2个很重的操作:emptyDb() 和 rdbLoad(server.rdb_filename), 前者会清空整个数据库,这样势必导致会扫遍所有申请的物理内存并释放;后者加载整个RDB文件就不用说了,重新申请内存,并且一定会申请那么多物理内存的,因为会访问。

本来巧妙的BGSAVE快照能利用COW, 但此时却被emptyDb 和rdbLoad 给失效了,从而导致本来只占用55%物理内存的redis这下需要110%的物理内存,于是没办法只能swap,引起系统进入颠簸状态。

建议:

理解其实在做SYNC的时候,是不需要做BGSAVE的,因为没用,等SYNC完成后,自然就会将同步回来的RDB文件覆盖BGSAVE的文件的:rename(server.repl_transfer_tmpfile,server.rdb_filename), 所以BGSAVE等于白做了,甚至更严重的还会导致如下时序竞争条件:

1. SYNC将同步回来的RDB文件临时文件rename成server.rdb_filename,并加载到内存;

2.BGSAVE完成备份到临时文件后,又将其过期的老的备份文件覆盖了相对更新的server.rdb_filename文件,从而就给后面挖坑了····。

当然slave做BGSAVE肯定是最安全的,但有SYNC操作在的时候可以不用做BGSAVE的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值