ClickHouse源码分析:磁盘检测机制
日志分析
磁盘配置如下:
如果有磁盘检测线程,如果某次检测时间超过200ms,则会打印如下日志:
如果在出现慢盘或者磁盘io很高的时候,可以看到上述日志。
如果磁盘出现损坏导致不能读写的情况,则会出现“Disk {} is broken”或者“Disk {} is readonly”的日志。
源码分析
ClickHouse版本为v22.3.2-lts
线程启动
入口:
DiskSelectorPtr Context::getDiskSelector(std::lock_guard<std::mutex> & /* lock */) const
{
if (!shared->merge_tree_disk_selector)
{
constexpr auto config_name = "storage_configuration.disks";
const auto & config = getConfigRef();
shared->merge_tree_disk_selector = std::make_shared<DiskSelector>(config, config_name, shared_from_this());
}
return shared->merge_tree_disk_selector;
}
StoragePolicySelectorPtr Context::getStoragePolicySelector(std::lock_guard<std::mutex> & lock) const
{
if (!shared->merge_tree_storage_policy_selector)
{
constexpr auto config_name = "storage_configuration.policies";
const auto & config = getConfigRef();
shared->merge_tree_storage_policy_selector = std::make_shared<StoragePolicySelector>(config, config_name, getDiskSelector(lock));
}
return shared->merge_tree_storage_policy_selector;
}
可以看到通过policies和disks的配置来初始化一个StoragePolicySelector和DiskSelector。
其中,DiskSelector的构造函数:
DiskSelector::DiskSelector(const Poco::Util::AbstractConfiguration & config, const String & config_prefix, ContextPtr context)
{
Poco::Util::AbstractConfiguration::Keys keys;
config.keys(config_prefix, keys);
auto & factory = DiskFactory::instance();
constexpr auto default_disk_name = "default";
bool has_default_disk = false;
for (const auto & disk_name : keys) // 遍历所有配置的disk,即:disk_name_1,disk_name_2,default
{
if (!std::all_of(disk_name.begin(), disk_name.end(), isWordCharASCII))
throw Exception("Disk name can contain only alphanumeric and '_' (" + disk_name + ")", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG);
if (disk_name == default_disk_name)
has_default_disk = true;
auto disk_config_prefix = config_prefix + "." + disk_name;
disks.emplace(disk_name, factory.create(disk_name, config, disk_config_prefix, context, disks));
}
if (!has_default_disk) // 如果在disk中没有配置default,则将default带进去
{
disks.emplace(
default_disk_name,
std::make_shared<DiskLocal>(
default_disk_name, context->getPath(), 0, context, config.getUInt("local_disk_check_period_ms", 0)));
}
}
代码逻辑很清晰,就是解析disks的配置,根据路径和名称创建DiskLocal。
如果在disks没有创建default的目录,则会使用config.xml中path指定的路径进行初始化。
这里注意下factory.create的调用。
factory.create --> DiskFactory::create --> registerDiskLocal中的creator(在启动阶段就注册了此接口) --> DiskLocal::startup --> DiskLocalCheckThread::startup(线程启动) --> DiskLocalCheckThread::run
其实也是初始化DiskLocal对象。但是,区别是,在creator中调用了disk->startup,直接启动了磁盘检测线程。
也就是说,如果走上面if (!has_default_disk) 分支的话,对应的磁盘是没有检测线程的。
检测机制
在disk路径下,会有一个用于磁盘读写能力检测的文件:
void DiskLocalCheckThread::run()
{
if (need_stop)
return;
bool can_read = disk->canRead();
bool can_write = disk->canWrite();
...
}
启动时,如果该文件不存在,则会新建,然后写入魔鬼数字。
检测阶段,定期读此文件的内容,校验内容是否有变更。如果不变,则认为磁盘是可读的。
bool DiskLocal::canRead() const noexcept
{
if (FS::canRead(fs::path(disk_path) / disk_checker_path))
{
auto magic_number = readDiskCheckerMagicNumber();
if (magic_number && *magic_number == disk_checker_magic_number)
return true;
}
return false;
}
检测阶段,通过写此目录下的临时文件,来校验磁盘是否可写。
bool DiskLocal::canWrite() const noexcept
{
static DiskWriteCheckData data;
String tmp_template = fs::path(disk_path) / "";
{
auto buf = WriteBufferFromTemporaryFile::create(tmp_template);
buf->write(data.data, data.PAGE_SIZE_IN_BYTES);
buf->sync();
}
return true;
}
临时文件在创建后会立马删除(通过Poco::TemporaryFile来实现),文件名类似下面:
调用堆栈
如下堆栈是在DiskLocal构造函数中打断点,以便查看磁盘检测相关的类是如何一步一步调用到的。
见附件
- 点赞
- 收藏
- 关注作者
评论(0)