Redis快照错误引发的血案

2022-12-02 20:49:18 浏览数 (1)

问题

早上来公司正常coding中,被测试同学一声吼给打断了原有节奏。

原来是测试环境多个接口返回错误。

经过简单定位是因为redis挂了引起的。

代码语言:javascript复制
MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk

用我那并不熟练的英语读了一遍,感觉好像是配置的rdb快照,但是现在却不能写入磁盘。所以报错了。

解决

情急之下 赶紧百度。

把这个错误一贴发现好像解决方案。抓紧一试:

代码语言:javascript复制
config set stop-writes-on-bgsave-error no

灵丹妙药 果然生效了。居然不报错了。先不阻塞测试同学测试流程。

但是我觉得这只是治标不治本啊,这是把bgsave的error给关了。但并不代表就没事儿了啊。

于是乎我还是想找到原因,都说“源码面前 了无秘密”。

排查原因

在server.c里发现了这个错误。

代码语言:javascript复制
shared.bgsaveerr = createObject(OBJ_STRING,sdsnew(
        "-MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.rn"));

这里看到这个错误是给到bgsaveerr.接下来就看这个错误在哪里被报出来的就可以了。

搜索代码发现 在replication 和 server里有用到。

个人感觉replication主要用于集群复制那块的。(不知道对不对啊,如果有错欢迎指点)。

于是直接看到server里面的processCommand方法里发现了他的踪迹。

代码语言:javascript复制

/* Don't accept write commands if there are problems persisting on disk
 * and if this is a master instance. */
int deny_write_type = writeCommandsDeniedByDiskError();
if (deny_write_type != DISK_ERROR_TYPE_NONE &&
    server.masterhost == NULL &&
    (c->cmd->flags & CMD_WRITE ||
     c->cmd->proc == pingCommand))
{
    flagTransaction(c);
    if (deny_write_type == DISK_ERROR_TYPE_RDB)
        addReply(c, shared.bgsaveerr);
    else
        addReplySds(c,
            sdscatprintf(sdsempty(),
            "-MISCONF Errors writing to the AOF file: %srn",
            strerror(server.aof_last_write_errno)));
    return C_OK;
}

这里看到先调用了一下这个方法:writeCommandsDeniedByDiskError 跟进来看一下

代码语言:javascript复制
/* Sometimes Redis cannot accept write commands because there is a perstence
 * error with the RDB or AOF file, and Redis is configured in order to stop
 * accepting writes in such situation. This function returns if such a
 * condition is active, and the type of the condition.
 *
 * Function return values:
 *
 * DISK_ERROR_TYPE_NONE:    No problems, we can accept writes.
 * DISK_ERROR_TYPE_AOF:     Don't accept writes: AOF errors.
 * DISK_ERROR_TYPE_RDB:     Don't accept writes: RDB errors.
 */
int writeCommandsDeniedByDiskError(void) {
    if (server.stop_writes_on_bgsave_err &&
        server.saveparamslen > 0 &&
        server.lastbgsave_status == C_ERR)
    {
        return DISK_ERROR_TYPE_RDB;
    } else if (server.aof_state != AOF_OFF &&
               server.aof_last_write_status == C_ERR)
    {
        return DISK_ERROR_TYPE_AOF;
    } else {
        return DISK_ERROR_TYPE_NONE;
    }
}

这个方法只会返回三个值

DISK_ERROR_TYPE_NONE:代表硬盘没毛病,正常没错。

DISK_ERROR_TYPE_AOF:AOF的硬盘错误。

DISK_ERROR_TYPE_RDB:RDB的硬盘错误。

而我们这次的错误就是RDB的,仔细看下这3个条件。

第一个条件:stop_writes_on_bgsave_err 这不就是开始度娘告诉我们的解决方案 把他给关掉了么。看来把它关掉的确不会报错。

第二个条件:saveparamslen 字面意思是保存参数的长度。有点懵逼。看了一下启动的时候对于这块的赋值。

原来是我们经常在redis.conf里看到的那个rdb的触发条件。

代码语言:javascript复制
save 900 1
save 300 10
save 60 10000

如果是默认的那么saveparamslen 就是3。

第三个条件:lastbgsave_status 这个比较好理解,就是上次bgsave的状态是成功 还是失败。这个状态的保存是在rdbSaveBackground / rdbSave /backgroundSaveDoneHandlerDisk里都会设置。

这下就知道 在什么情况下会出现这种错误了。

但是至于为什么会出现这个错误呢?

我们打开redis的日志。

有以下发现

代码语言:javascript复制
24805:M 09 May 2020 12:46:02.111 * Background saving terminated with success
24805:M 09 May 2020 12:51:03.099 * 10 changes in 300 seconds. Saving...
24805:M 09 May 2020 12:51:03.100 * Background saving started by pid 3978
3978:C 09 May 2020 12:51:03.164 * DB saved on disk
3978:C 09 May 2020 12:51:03.165 * RDB: 0 MB of memory used by copy-on-write
24805:M 09 May 2020 12:51:03.200 * Background saving terminated with success
24805:M 09 May 2020 12:56:04.064 * 10 changes in 300 seconds. Saving...
24805:M 09 May 2020 12:56:04.065 * Background saving started by pid 6127
6127:C 09 May 2020 12:56:04.067 # Write error saving DB on disk: No space left on device
24805:M 09 May 2020 12:56:04.166 # Background saving error
24805:M 09 May 2020 12:56:10.080 * 10 changes in 300 seconds. Saving...
24805:M 09 May 2020 12:56:10.080 * Background saving started by pid 6150

从日志上可以看到

1. 在copy-on-write的时候用了0M内存。于是赶紧检查内存发现充足。

2. 300s内有10次修改,保存到硬盘的时候,设备上没有空间了。查看磁盘果然 40G都满了。好可怜 只有40G...

于是找到真正的原因,开始清理磁盘。并把stop_writes_on_bgsave_err打开。


总结

由此总结:我们在日常工作中不但要知其然,还要知其所以然。不能总是以解决完眼下问题就完事儿了。

0 人点赞