MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

MariaDB 中 purge 命令清理 binlog 的底层实现

2021-10-195.7k 阅读

MariaDB 中 binlog 简介

binlog 的作用

MariaDB 中的二进制日志(binlog)是一种重要的日志机制,它记录了数据库中所有更改数据的操作,例如 INSERT、UPDATE 和 DELETE 语句等。binlog 的主要作用之一是用于数据备份和恢复。当数据库出现故障时,可以通过重放 binlog 中的记录来恢复到故障前的某个时间点,确保数据的一致性和完整性。

另一个关键作用是在主从复制架构中。主服务器将 binlog 发送给从服务器,从服务器通过重放这些日志来保持与主服务器的数据同步。这使得 MariaDB 能够构建高可用、可扩展的数据库集群。

binlog 的结构

binlog 由一系列的日志文件组成,每个文件都有一个唯一的文件名,通常命名格式为 hostname-bin.xxxxxx,其中 xxxxxx 是一个递增的数字。每个 binlog 文件包含了多个日志事件(log event),这些事件记录了具体的数据库操作。

日志事件有不同的类型,例如 Format_description_log_event 用于描述 binlog 的格式信息,Query_log_event 用于记录 SQL 查询语句,Rows_log_event 用于记录行级别的数据更改等。每个日志事件都有特定的结构,包含事件头和事件体。事件头中包含了事件的类型、长度、时间戳等元信息,而事件体则包含了具体的操作数据。

purge 命令概述

purge 命令的功能

purge 命令在 MariaDB 中用于清理 binlog 文件。随着数据库的运行,binlog 文件会不断增长,如果不进行清理,会占用大量的磁盘空间。purge 命令允许管理员指定清理哪些 binlog 文件,确保磁盘空间得到合理利用。

例如,管理员可以使用 PURGE BINARY LOGS TO 'hostname-bin.000010'; 命令来清理所有早于 hostname-bin.000010 的 binlog 文件。这样,系统会删除这些旧的 binlog 文件,释放相应的磁盘空间。

purge 命令的使用场景

  1. 磁盘空间管理:当数据库服务器磁盘空间紧张时,通过 purge 命令清理不再需要的 binlog 文件是一种有效的释放空间的方法。特别是在一些写入操作频繁的数据库中,binlog 文件增长迅速,定期清理 binlog 可以避免磁盘空间耗尽的问题。
  2. 主从复制维护:在主从复制环境中,主服务器上的 binlog 文件需要保留足够长的时间,以便从服务器能够获取并同步数据。但是,当从服务器已经成功同步了某些 binlog 文件中的数据后,主服务器上对应的 binlog 文件就可以安全地清理掉。通过 purge 命令,可以精确控制主服务器上 binlog 文件的保留策略,确保复制的稳定性和高效性。

purge 命令清理 binlog 的底层实现流程

1. 权限检查

当用户执行 purge 命令时,MariaDB 首先会进行权限检查。只有具有 RELOADSUPER 权限的用户才能执行 purge 操作。这是为了防止普通用户误操作导致 binlog 数据丢失,影响数据库的备份和复制功能。

在代码层面,权限检查主要涉及到 sql_parse.cc 文件中的相关逻辑。例如,在 mysql_parse() 函数中,当解析到 PURGE BINARY LOGS 语句时,会调用 check_access() 函数来检查当前用户是否具备相应权限。以下是简化的代码示例:

if (LEX_CMP(str, "PURGE BINARY LOGS", length) == 0) {
    if (!check_access(SUPER_ACL, RELOAD_ACL)) {
        my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "RELOAD and SUPER");
        return;
    }
    // 权限通过,继续处理 purge 命令
}

2. 确定要清理的 binlog 文件范围

在权限检查通过后,MariaDB 需要确定要清理哪些 binlog 文件。这取决于 purge 命令的参数。如果使用 PURGE BINARY LOGS TO 'log_name'; 格式,系统会清理所有早于指定 log_name 的 binlog 文件;如果使用 PURGE BINARY LOGS BEFORE 'date'; 格式,系统会清理所有修改时间早于指定日期的 binlog 文件。

这部分逻辑主要在 log.cc 文件中实现。以 PURGE BINARY LOGS TO 'log_name'; 为例,系统会遍历当前所有的 binlog 文件列表,对比文件名来确定要清理的文件范围。以下是相关代码的简化示意:

List<IO_CACHE> binlog_files;
// 获取所有 binlog 文件列表
get_binlog_files(binlog_files);

const char *target_log_name = "hostname-bin.000010"; // 假设的目标文件名
IO_CACHE *file_to_start = nullptr;
for (IO_CACHE *file : binlog_files) {
    if (strcmp(file->name, target_log_name) == 0) {
        file_to_start = file;
        break;
    }
}

if (file_to_start) {
    IO_CACHE *current_file = binlog_files.first;
    while (current_file && current_file != file_to_start) {
        // 这里可以添加删除文件的逻辑
        my_delete(current_file->name, MYF(0));
        current_file = current_file->next;
    }
}

3. 检查主从复制状态

在真正删除 binlog 文件之前,MariaDB 会检查主从复制状态。如果当前服务器是主服务器,并且有从服务器正在同步数据,那么系统需要确保不会删除从服务器还未同步的 binlog 文件。

这一检查过程涉及到与复制相关的元数据。MariaDB 会查询 mysql.slave_master_info 表(在从服务器上)或内部维护的复制状态信息(在主服务器上),来确定哪些 binlog 文件已经被从服务器成功接收和应用。

rpl_master.cc 文件中,有相关逻辑用于在主服务器上检查从服务器的复制进度。例如,Master_info::is_log_file_needed() 函数可以判断某个 binlog 文件是否还被从服务器需要。简化代码如下:

bool Master_info::is_log_file_needed(const char *log_name) {
    // 遍历从服务器列表,检查每个从服务器的复制进度
    for (Slave_status *slave : slave_list) {
        if (strcmp(slave->get_master_log_name(), log_name) >= 0) {
            return true;
        }
    }
    return false;
}

4. 删除 binlog 文件

在经过前面的权限检查、文件范围确定和主从复制状态检查后,MariaDB 会开始删除确定要清理的 binlog 文件。这一过程相对较为直接,通过调用操作系统的文件删除函数(如 unlinkmy_delete)来删除对应的 binlog 文件。

例如,在 log.cc 文件中,当确定要删除某个 binlog 文件时,会调用 my_delete 函数:

my_delete(file->name, MYF(0));

这里的 file->name 就是要删除的 binlog 文件的路径和文件名。在删除文件后,MariaDB 还会更新内部维护的 binlog 文件列表,确保系统对 binlog 文件的状态有准确的记录。

5. 更新相关元数据

在 binlog 文件被删除后,MariaDB 需要更新相关的元数据。例如,更新 mysql.slave_master_info 表(在从服务器上)或内部维护的 binlog 索引信息(在主服务器上),以反映 binlog 文件的变化。

在主服务器上,log.cc 文件中的 update_binlog_index() 函数会负责更新 binlog 索引。简化代码如下:

void update_binlog_index() {
    // 重新构建 binlog 索引信息,例如将已删除文件从索引中移除
    List<IO_CACHE> binlog_files;
    get_binlog_files(binlog_files);
    // 重新生成索引文件内容,省略具体实现
}

在从服务器上,当检测到主服务器上的 binlog 文件被删除后,会通过复制协议获取新的 binlog 索引信息,并更新本地的 mysql.slave_master_info 表,确保从服务器能够准确地从主服务器获取后续的 binlog 数据。

底层实现中的关键数据结构与函数

关键数据结构

  1. IO_CACHE:这个结构用于管理 binlog 文件的输入输出缓存。在 MariaDB 中,每个 binlog 文件都由一个 IO_CACHE 实例来表示。它包含了文件的路径名、文件描述符、缓存区等信息。例如,在 log.cc 文件中,通过 IO_CACHE 结构来操作 binlog 文件的读写:
struct IO_CACHE {
    char *name;
    File file;
    char *buffer;
    // 其他缓存管理相关的成员变量
};
  1. Master_info:在主从复制环境中,Master_info 结构用于存储主服务器的相关信息,包括主服务器的地址、端口、复制用户信息以及从服务器的复制进度等。如前文提到的 is_log_file_needed() 函数就是 Master_info 结构的成员函数,用于判断某个 binlog 文件是否还被从服务器需要。
class Master_info {
public:
    char *master_host;
    uint16 master_port;
    char *user;
    char *password;
    // 从服务器复制进度相关的成员变量
    bool is_log_file_needed(const char *log_name);
};
  1. Slave_status:从服务器使用这个结构来存储自身的复制状态信息,如当前正在同步的主服务器的 binlog 文件和位置等。在主服务器检查从服务器复制进度时,会遍历 Slave_status 实例列表来获取相关信息。
class Slave_status {
public:
    char *master_log_name;
    long master_log_pos;
    // 其他复制状态相关的成员变量
};

关键函数

  1. check_access():这个函数位于 sql_parse.cc 文件中,用于检查用户是否具备执行特定操作(如 purge 命令)所需的权限。它接受权限类型作为参数,并根据当前用户的权限设置返回相应的结果。
bool check_access(uint access_type, uint additional_type) {
    // 检查用户权限逻辑,简化示意
    if (current_user_has_permission(access_type) && current_user_has_permission(additional_type)) {
        return true;
    }
    return false;
}
  1. get_binlog_files():在 log.cc 文件中,该函数用于获取当前所有的 binlog 文件列表,并将这些文件信息存储在一个 List<IO_CACHE> 容器中。这个列表在确定要清理的 binlog 文件范围以及更新 binlog 索引等操作中都起到关键作用。
void get_binlog_files(List<IO_CACHE> &binlog_files) {
    // 遍历 binlog 文件目录,将每个文件信息填充到 binlog_files 中
    DIR *dir = opendir(binlog_dir);
    struct dirent *entry;
    while ((entry = readdir(dir))!= nullptr) {
        if (is_binlog_file(entry->d_name)) {
            IO_CACHE *cache = new IO_CACHE();
            cache->name = strdup(entry->d_name);
            // 打开文件并设置文件描述符等操作,省略具体实现
            binlog_files.push_back(cache);
        }
    }
    closedir(dir);
}
  1. update_binlog_index():同样在 log.cc 文件中,该函数用于在 binlog 文件发生变化(如删除)后,更新系统内部维护的 binlog 索引信息。它会重新扫描当前存在的 binlog 文件,并生成新的索引内容,确保系统能够准确地定位和管理 binlog 文件。
void update_binlog_index() {
    List<IO_CACHE> binlog_files;
    get_binlog_files(binlog_files);
    // 根据 binlog_files 重新生成索引文件内容,省略具体实现
}

异常处理与注意事项

异常处理

  1. 权限不足异常:如果用户在执行 purge 命令时没有足够的权限,MariaDB 会抛出 ER_SPECIFIC_ACCESS_DENIED_ERROR 错误,提示用户需要 RELOADSUPER 权限。在 sql_parse.cc 文件中的权限检查部分,如前文所述,通过 my_error() 函数来抛出这个错误:
if (!check_access(SUPER_ACL, RELOAD_ACL)) {
    my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "RELOAD and SUPER");
    return;
}
  1. 文件删除失败异常:在删除 binlog 文件时,如果由于文件系统权限问题、文件正在被其他进程使用等原因导致文件删除失败,MariaDB 会记录错误日志。在 log.cc 文件中,调用 my_delete 函数删除文件时,如果返回非零值(表示删除失败),可以进行相应的错误处理:
if (my_delete(file->name, MYF(0))!= 0) {
    my_error(ER_COULD_NOT_DELETE_FILE, MYF(0), file->name);
    // 可以选择继续尝试删除或进行其他处理
}
  1. 主从复制冲突异常:当主服务器在清理 binlog 文件时,如果检测到某个要删除的 binlog 文件仍被从服务器需要,MariaDB 会停止删除操作,并记录相应的警告信息。在 rpl_master.cc 文件中,Master_info::is_log_file_needed() 函数用于检测这种情况,如果某个 binlog 文件被从服务器需要,会阻止该文件的删除。
bool Master_info::is_log_file_needed(const char *log_name) {
    for (Slave_status *slave : slave_list) {
        if (strcmp(slave->get_master_log_name(), log_name) >= 0) {
            // 记录警告信息,提示该文件不能删除
            my_warning(ER_BINLOG_CANNOT_DELETE, MYF(0), log_name);
            return true;
        }
    }
    return false;
}

注意事项

  1. 谨慎使用 purge 命令:由于 purge 命令会永久性地删除 binlog 文件,在执行之前一定要确保这些文件不再被用于备份、恢复或主从复制。特别是在主从复制环境中,错误地删除从服务器还未同步的 binlog 文件可能导致数据不一致或复制中断。

  2. 定期清理与监控:为了避免 binlog 文件占用过多磁盘空间,建议定期执行 purge 命令进行清理。同时,要监控 binlog 文件的增长速度和磁盘空间使用情况,根据实际情况调整清理策略。可以通过设置合理的 purge 周期,如每周或每月清理一次,确保磁盘空间的有效利用。

  3. 备份与恢复考虑:在执行 purge 命令之前,要确认数据库的备份策略是否完整。如果依赖 binlog 进行基于时间点的恢复(Point-in-Time Recovery, PITR),确保在清理 binlog 时不会影响到备份的有效性。可以结合全量备份和 binlog 备份来制定全面的数据恢复方案,在清理 binlog 时不会丢失重要的恢复数据。

  4. 主从复制拓扑变化:当主从复制拓扑发生变化,如添加或移除从服务器时,要重新评估 binlog 文件的保留策略。新添加的从服务器可能需要从较早的 binlog 文件开始同步数据,因此在这种情况下,可能需要暂时保留更多的 binlog 文件,直到新从服务器完成初始同步。

  5. 系统性能影响:虽然 purge 命令本身不会对数据库的正常运行产生太大的性能影响,但在删除大量 binlog 文件时,可能会对文件系统的 I/O 性能产生一定压力。因此,建议在系统负载较低的时间段执行 purge 操作,以减少对业务的影响。同时,可以通过优化文件系统配置,如使用高性能的存储设备或调整文件系统参数,来降低这种性能影响。