Allow including config elements from ZooKeeper [#CLICKHOUSE-2794]

This commit is contained in:
Alexey Zatelepin 2017-03-17 03:44:00 +03:00 committed by alexey-milovidov
parent 8098389c04
commit faadab3034
19 changed files with 713 additions and 326 deletions

View File

@ -1,6 +1,8 @@
#pragma once #pragma once
#include <string> #include <string>
#include <unordered_set>
#include <Poco/DOM/Document.h> #include <Poco/DOM/Document.h>
#include <Poco/DOM/DOMParser.h> #include <Poco/DOM/DOMParser.h>
#include <Poco/DOM/DOMWriter.h> #include <Poco/DOM/DOMWriter.h>
@ -12,8 +14,15 @@
#include <Poco/DirectoryIterator.h> #include <Poco/DirectoryIterator.h>
#include <Poco/ConsoleChannel.h> #include <Poco/ConsoleChannel.h>
#include <Poco/Util/AbstractConfiguration.h> #include <Poco/Util/AbstractConfiguration.h>
#include <common/logger_useful.h> #include <common/logger_useful.h>
namespace zkutil
{
class ZooKeeperNodeCache;
}
using ConfigurationPtr = Poco::AutoPtr<Poco::Util::AbstractConfiguration>; using ConfigurationPtr = Poco::AutoPtr<Poco::Util::AbstractConfiguration>;
using XMLDocumentPtr = Poco::AutoPtr<Poco::XML::Document>; using XMLDocumentPtr = Poco::AutoPtr<Poco::XML::Document>;
@ -25,6 +34,8 @@ public:
/// log_to_console нужно использовать, если система логгирования еще не инициализирована. /// log_to_console нужно использовать, если система логгирования еще не инициализирована.
ConfigProcessor(bool throw_on_bad_incl = false, bool log_to_console = false, const Substitutions & substitutions = Substitutions()); ConfigProcessor(bool throw_on_bad_incl = false, bool log_to_console = false, const Substitutions & substitutions = Substitutions());
~ConfigProcessor();
/** Выполняет подстановки в конфиге и возвращает XML-документ. /** Выполняет подстановки в конфиге и возвращает XML-документ.
* *
* Пусть в качестве path передана "/path/file.xml" * Пусть в качестве path передана "/path/file.xml"
@ -37,12 +48,34 @@ public:
* 3) Заменяем элементы вида "<foo incl="bar"/>" на "<foo>содержимое элемента yandex.bar из metrika.xml</foo>" * 3) Заменяем элементы вида "<foo incl="bar"/>" на "<foo>содержимое элемента yandex.bar из metrika.xml</foo>"
* 4) Заменяет "<layer/>" на "<layer>номер слоя из имени хоста</layer>" * 4) Заменяет "<layer/>" на "<layer>номер слоя из имени хоста</layer>"
*/ */
XMLDocumentPtr processConfig(const std::string & path); XMLDocumentPtr processConfig(
const std::string & path,
bool * has_zk_includes = nullptr,
zkutil::ZooKeeperNodeCache * zk_node_cache = nullptr);
struct LoadedConfig
{
ConfigurationPtr configuration;
bool has_zk_includes;
bool loaded_from_preprocessed;
bool preprocessed_written;
};
/** Делает processConfig и создает из результата Poco::Util::XMLConfiguration. /** Делает processConfig и создает из результата Poco::Util::XMLConfiguration.
* Еще сохраняет результат в файл по пути, полученному из path приписыванием строки "-preprocessed" к имени файла. * Еще сохраняет результат в файл по пути, полученному из path приписыванием строки "-preprocessed" к имени файла.
*/ */
ConfigurationPtr loadConfig(const std::string & path);
/// If allow_zk_includes is true, expects that the configuration xml can contain from_zk nodes.
/// If the xml contains them, set has_zk_includes to true and don't write config-preprocessed.xml,
/// expecting that config would be reloaded with zookeeper later.
LoadedConfig loadConfig(const std::string & path, bool allow_zk_includes = false);
LoadedConfig loadConfigWithZooKeeperIncludes(
const std::string & path,
zkutil::ZooKeeperNodeCache & zk_node_cache,
bool fallback_to_preprocessed = false);
public: public:
@ -51,21 +84,31 @@ public:
static Files getConfigMergeFiles(const std::string & config_path); static Files getConfigMergeFiles(const std::string & config_path);
private: private:
bool throw_on_bad_incl;
Logger * log; Logger * log;
Poco::AutoPtr<Poco::Channel> channel_ptr; Poco::AutoPtr<Poco::Channel> channel_ptr;
bool throw_on_bad_incl;
Substitutions substitutions; Substitutions substitutions;
using DocumentPtr = XMLDocumentPtr; Poco::AutoPtr<Poco::XML::NamePool> name_pool;
Poco::XML::DOMParser dom_parser;
private:
using NodePtr = Poco::AutoPtr<Poco::XML::Node>; using NodePtr = Poco::AutoPtr<Poco::XML::Node>;
void mergeRecursive(DocumentPtr config, Poco::XML::Node * config_node, Poco::XML::Node * with_node); void mergeRecursive(XMLDocumentPtr config, Poco::XML::Node * config_node, Poco::XML::Node * with_node);
void merge(DocumentPtr config, DocumentPtr with); void merge(XMLDocumentPtr config, XMLDocumentPtr with);
std::string layerFromHost(); std::string layerFromHost();
void doIncludesRecursive(DocumentPtr config, DocumentPtr include_from, Poco::XML::Node * node); void doIncludesRecursive(
XMLDocumentPtr config,
XMLDocumentPtr include_from,
Poco::XML::Node * node,
zkutil::ZooKeeperNodeCache * zk_node_cache,
std::unordered_set<std::string> & contributing_zk_paths);
void doIncludes(DocumentPtr config, DocumentPtr include_from); void savePreprocessedConfig(const XMLDocumentPtr & config, const std::string & preprocessed_path);
}; };

View File

@ -3,12 +3,15 @@
#include <cerrno> #include <cerrno>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <functional>
#include <Poco/DOM/Text.h> #include <Poco/DOM/Text.h>
#include <Poco/DOM/Attr.h> #include <Poco/DOM/Attr.h>
#include <Poco/DOM/Comment.h> #include <Poco/DOM/Comment.h>
#include <Poco/Util/XMLConfiguration.h> #include <Poco/Util/XMLConfiguration.h>
#include <zkutil/ZooKeeperNodeCache.h>
using namespace Poco::XML; using namespace Poco::XML;
@ -37,7 +40,13 @@ static std::string numberFromHost(const std::string & s)
} }
ConfigProcessor::ConfigProcessor(bool throw_on_bad_incl_, bool log_to_console, const Substitutions & substitutions_) ConfigProcessor::ConfigProcessor(bool throw_on_bad_incl_, bool log_to_console, const Substitutions & substitutions_)
: throw_on_bad_incl(throw_on_bad_incl_), substitutions(substitutions_) : throw_on_bad_incl(throw_on_bad_incl_)
, substitutions(substitutions_)
/// We need larger name pool to allow to support vast amount of users in users.xml files for ClickHouse.
/// Size is prime because Poco::XML::NamePool uses bad (inefficient, low quality)
/// hash function internally, and its size was prime by default.
, name_pool(new Poco::XML::NamePool(65521))
, dom_parser(name_pool)
{ {
if (log_to_console && Logger::has("ConfigProcessor") == nullptr) if (log_to_console && Logger::has("ConfigProcessor") == nullptr)
{ {
@ -50,6 +59,13 @@ ConfigProcessor::ConfigProcessor(bool throw_on_bad_incl_, bool log_to_console, c
} }
} }
ConfigProcessor::~ConfigProcessor()
{
if (channel_ptr) /// This means we have created a new console logger in the constructor.
Logger::destroy("ConfigProcessor");
}
/// Вектор из имени элемента и отсортированного списка имен и значений атрибутов (кроме атрибутов replace и remove). /// Вектор из имени элемента и отсортированного списка имен и значений атрибутов (кроме атрибутов replace и remove).
/// Взаимно однозначно задает имя элемента и список его атрибутов. Нужен, чтобы сравнивать элементы. /// Взаимно однозначно задает имя элемента и список его атрибутов. Нужен, чтобы сравнивать элементы.
using ElementIdentifier = std::vector<std::string>; using ElementIdentifier = std::vector<std::string>;
@ -67,7 +83,7 @@ static ElementIdentifier getElementIdentifier(Node * element)
{ {
Node * node = attrs->item(i); Node * node = attrs->item(i);
std::string name = node->nodeName(); std::string name = node->nodeName();
if (name == "replace" || name == "remove" || name == "incl") if (name == "replace" || name == "remove" || name == "incl" || name == "from_zk")
continue; continue;
std::string value = node->nodeValue(); std::string value = node->nodeValue();
attrs_kv.push_back(std::make_pair(name, value)); attrs_kv.push_back(std::make_pair(name, value));
@ -104,7 +120,14 @@ static bool allWhitespace(const std::string & s)
return s.find_first_not_of(" \t\n\r") == std::string::npos; return s.find_first_not_of(" \t\n\r") == std::string::npos;
} }
void ConfigProcessor::mergeRecursive(DocumentPtr config, Node * config_root, Node * with_root) static std::string preprocessedConfigPath(const std::string & path)
{
Poco::Path preprocessed_path(path);
preprocessed_path.setBaseName(preprocessed_path.getBaseName() + "-preprocessed");
return preprocessed_path.toString();
}
void ConfigProcessor::mergeRecursive(XMLDocumentPtr config, Node * config_root, Node * with_root)
{ {
NodeListPtr with_nodes = with_root->childNodes(); NodeListPtr with_nodes = with_root->childNodes();
using ElementsByIdentifier = std::multimap<ElementIdentifier, Node *>; using ElementsByIdentifier = std::multimap<ElementIdentifier, Node *>;
@ -137,7 +160,7 @@ void ConfigProcessor::mergeRecursive(DocumentPtr config, Node * config_root, Nod
bool replace = with_element->hasAttribute("replace"); bool replace = with_element->hasAttribute("replace");
if (remove && replace) if (remove && replace)
throw Poco::Exception("remove and replace attributes on the same element"); throw Poco::Exception("both remove and replace attributes set for element <" + with_node->nodeName() + ">");
ElementsByIdentifier::iterator it = config_element_by_id.find(getElementIdentifier(with_node)); ElementsByIdentifier::iterator it = config_element_by_id.find(getElementIdentifier(with_node));
@ -171,7 +194,7 @@ void ConfigProcessor::mergeRecursive(DocumentPtr config, Node * config_root, Nod
} }
} }
void ConfigProcessor::merge(DocumentPtr config, DocumentPtr with) void ConfigProcessor::merge(XMLDocumentPtr config, XMLDocumentPtr with)
{ {
mergeRecursive(config, getRootNode(&*config), getRootNode(&*with)); mergeRecursive(config, getRootNode(&*config), getRootNode(&*with));
} }
@ -189,7 +212,12 @@ std::string ConfigProcessor::layerFromHost()
return layer; return layer;
} }
void ConfigProcessor::doIncludesRecursive(DocumentPtr config, DocumentPtr include_from, Node * node) void ConfigProcessor::doIncludesRecursive(
XMLDocumentPtr config,
XMLDocumentPtr include_from,
Node * node,
zkutil::ZooKeeperNodeCache * zk_node_cache,
std::unordered_set<std::string> & contributing_zk_paths)
{ {
if (node->nodeType() == Node::TEXT_NODE) if (node->nodeType() == Node::TEXT_NODE)
{ {
@ -226,62 +254,93 @@ void ConfigProcessor::doIncludesRecursive(DocumentPtr config, DocumentPtr includ
NamedNodeMapPtr attributes = node->attributes(); NamedNodeMapPtr attributes = node->attributes();
Node * incl_attribute = attributes->getNamedItem("incl"); Node * incl_attribute = attributes->getNamedItem("incl");
Node * from_zk_attribute = attributes->getNamedItem("from_zk");
if (incl_attribute && from_zk_attribute)
throw Poco::Exception("both incl and from_zk attributes set for element <" + node->nodeName() + ">");
/// Заменять имеющееся значение, а не добавлять к нему. /// Заменять имеющееся значение, а не добавлять к нему.
bool replace = attributes->getNamedItem("replace"); bool replace = attributes->getNamedItem("replace");
if (incl_attribute) auto process_include = [&](const Node * include_attr, const std::function<Node * (const std::string &)> & get_node, const char * error_msg)
{ {
std::string name = incl_attribute->getNodeValue(); std::string name = include_attr->getNodeValue();
Node * included_node = include_from ? include_from->getNodeByPath("yandex/" + name) : nullptr; Node * node_to_include = get_node(name);
if (!included_node) if (!node_to_include)
{ {
if (attributes->getNamedItem("optional")) if (attributes->getNamedItem("optional"))
node->parentNode()->removeChild(node); node->parentNode()->removeChild(node);
else if (throw_on_bad_incl) else if (throw_on_bad_incl)
throw Poco::Exception("Include not found: " + name); throw Poco::Exception(error_msg + name);
else else
LOG_WARNING(log, "Include not found: " << name); LOG_WARNING(log, error_msg << name);
} }
else else
{ {
Element * element = dynamic_cast<Element *>(node);
element->removeAttribute("incl");
element->removeAttribute("from_zk");
if (replace) if (replace)
{
while (Node * child = node->firstChild()) while (Node * child = node->firstChild())
node->removeChild(child); node->removeChild(child);
NodeListPtr children = included_node->childNodes(); element->removeAttribute("replace");
}
NodeListPtr children = node_to_include->childNodes();
for (size_t i = 0; i < children->length(); ++i) for (size_t i = 0; i < children->length(); ++i)
{ {
NodePtr new_node = config->importNode(children->item(i), true); NodePtr new_node = config->importNode(children->item(i), true);
node->appendChild(new_node); node->appendChild(new_node);
} }
Element * element = dynamic_cast<Element *>(node); NamedNodeMapPtr from_attrs = node_to_include->attributes();
element->removeAttribute("incl");
if (replace)
element->removeAttribute("replace");
NamedNodeMapPtr from_attrs = included_node->attributes();
for (size_t i = 0; i < from_attrs->length(); ++i) for (size_t i = 0; i < from_attrs->length(); ++i)
{ {
element->setAttributeNode(dynamic_cast<Attr *>(config->importNode(from_attrs->item(i), true))); element->setAttributeNode(dynamic_cast<Attr *>(config->importNode(from_attrs->item(i), true)));
} }
} }
};
auto get_incl_node = [&](const std::string & name)
{
return include_from ? include_from->getNodeByPath("yandex/" + name) : nullptr;
};
if (incl_attribute)
process_include(incl_attribute, get_incl_node, "Include not found: ");
if (from_zk_attribute)
{
contributing_zk_paths.insert(from_zk_attribute->getNodeValue());
if (zk_node_cache)
{
XMLDocumentPtr zk_document;
auto get_zk_node = [&](const std::string & name) -> Node *
{
std::experimental::optional<std::string> contents = zk_node_cache->get(name);
if (!contents)
return nullptr;
/// Enclose contents into a fake <from_zk> tag to allow pure text substitutions.
zk_document = dom_parser.parseString("<from_zk>" + contents.value() + "</from_zk>");
return getRootNode(zk_document.get());
};
process_include(from_zk_attribute, get_zk_node, "Could not get ZooKeeper node: ");
}
} }
NodeListPtr children = node->childNodes(); NodeListPtr children = node->childNodes();
for (size_t i = 0; i < children->length(); ++i) for (size_t i = 0; i < children->length(); ++i)
{ {
doIncludesRecursive(config, include_from, children->item(i)); doIncludesRecursive(config, include_from, children->item(i), zk_node_cache, contributing_zk_paths);
} }
} }
void ConfigProcessor::doIncludes(DocumentPtr config, DocumentPtr include_from)
{
doIncludesRecursive(config, include_from, getRootNode(&*config));
}
ConfigProcessor::Files ConfigProcessor::getConfigMergeFiles(const std::string & config_path) ConfigProcessor::Files ConfigProcessor::getConfigMergeFiles(const std::string & config_path)
{ {
Files res; Files res;
@ -314,15 +373,12 @@ ConfigProcessor::Files ConfigProcessor::getConfigMergeFiles(const std::string &
return res; return res;
} }
XMLDocumentPtr ConfigProcessor::processConfig(const std::string & path_str) XMLDocumentPtr ConfigProcessor::processConfig(
const std::string & path_str,
bool * has_zk_includes,
zkutil::ZooKeeperNodeCache * zk_node_cache)
{ {
/// We need larger name pool to allow to support vast amount of users in users.xml files for ClickHouse. XMLDocumentPtr config = dom_parser.parse(path_str);
/// Size is prime because Poco::XML::NamePool uses bad (inefficient, low quality)
/// hash function internally, and its size was prime by default.
Poco::AutoPtr<Poco::XML::NamePool> name_pool(new Poco::XML::NamePool(65521));
Poco::XML::DOMParser dom_parser(name_pool);
DocumentPtr config = dom_parser.parse(path_str);
std::vector<std::string> contributing_files; std::vector<std::string> contributing_files;
contributing_files.push_back(path_str); contributing_files.push_back(path_str);
@ -331,7 +387,7 @@ XMLDocumentPtr ConfigProcessor::processConfig(const std::string & path_str)
{ {
try try
{ {
DocumentPtr with = dom_parser.parse(merge_file); XMLDocumentPtr with = dom_parser.parse(merge_file);
merge(config, with); merge(config, with);
contributing_files.push_back(merge_file); contributing_files.push_back(merge_file);
} }
@ -341,10 +397,11 @@ XMLDocumentPtr ConfigProcessor::processConfig(const std::string & path_str)
} }
} }
std::unordered_set<std::string> contributing_zk_paths;
try try
{ {
Node * node = config->getNodeByPath("yandex/include_from"); Node * node = config->getNodeByPath("yandex/include_from");
DocumentPtr include_from; XMLDocumentPtr include_from;
std::string include_from_path; std::string include_from_path;
if (node) if (node)
{ {
@ -362,13 +419,16 @@ XMLDocumentPtr ConfigProcessor::processConfig(const std::string & path_str)
include_from = dom_parser.parse(include_from_path); include_from = dom_parser.parse(include_from_path);
} }
doIncludes(config, include_from); doIncludesRecursive(config, include_from, getRootNode(config.get()), zk_node_cache, contributing_zk_paths);
} }
catch (Poco::Exception & e) catch (Poco::Exception & e)
{ {
throw Poco::Exception("Failed to preprocess config: " + e.displayText()); throw Poco::Exception("Failed to preprocess config `" + path_str + "': " + e.displayText(), e);
} }
if (has_zk_includes)
*has_zk_includes = !contributing_zk_paths.empty();
std::stringstream comment; std::stringstream comment;
comment << " This file was generated automatically.\n"; comment << " This file was generated automatically.\n";
comment << " Do not edit it: it is likely to be discarded and generated again before it's read next time.\n"; comment << " Do not edit it: it is likely to be discarded and generated again before it's read next time.\n";
@ -377,6 +437,13 @@ XMLDocumentPtr ConfigProcessor::processConfig(const std::string & path_str)
{ {
comment << "\n " << path; comment << "\n " << path;
} }
if (zk_node_cache && !contributing_zk_paths.empty())
{
comment << "\n ZooKeeper nodes used to generate this file:";
for (const std::string & path : contributing_zk_paths)
comment << "\n " << path;
}
comment << " "; comment << " ";
NodePtr new_node = config->createTextNode("\n\n"); NodePtr new_node = config->createTextNode("\n\n");
config->insertBefore(new_node, config->firstChild()); config->insertBefore(new_node, config->firstChild());
@ -386,20 +453,74 @@ XMLDocumentPtr ConfigProcessor::processConfig(const std::string & path_str)
return config; return config;
} }
ConfigurationPtr ConfigProcessor::loadConfig(const std::string & path) ConfigProcessor::LoadedConfig ConfigProcessor::loadConfig(const std::string & path, bool allow_zk_includes)
{ {
DocumentPtr res = processConfig(path); bool has_zk_includes;
XMLDocumentPtr config_xml = processConfig(path, &has_zk_includes);
Poco::Path preprocessed_path(path); if (has_zk_includes && !allow_zk_includes)
preprocessed_path.setBaseName(preprocessed_path.getBaseName() + "-preprocessed"); throw Poco::Exception("Error while loading config `" + path + "': from_zk includes are not allowed!");
bool preprocessed_written = false;
if (!has_zk_includes)
{
savePreprocessedConfig(config_xml, preprocessedConfigPath(path));
preprocessed_written = true;
}
ConfigurationPtr configuration(new Poco::Util::XMLConfiguration(config_xml));
return LoadedConfig{configuration, has_zk_includes, /* loaded_from_preprocessed = */ false, preprocessed_written};
}
ConfigProcessor::LoadedConfig ConfigProcessor::loadConfigWithZooKeeperIncludes(
const std::string & path,
zkutil::ZooKeeperNodeCache & zk_node_cache,
bool fallback_to_preprocessed)
{
std::string preprocessed_path = preprocessedConfigPath(path);
XMLDocumentPtr config_xml;
bool has_zk_includes;
bool processed_successfully = false;
try try
{ {
DOMWriter().writeNode(preprocessed_path.toString(), res); config_xml = processConfig(path, &has_zk_includes, &zk_node_cache);
processed_successfully = true;
}
catch (const Poco::Exception & ex)
{
if (!fallback_to_preprocessed)
throw;
const auto * zk_exception = dynamic_cast<const zkutil::KeeperException *>(ex.nested());
if (!zk_exception)
throw;
LOG_WARNING(
log,
"Error while processing from_zk config includes: " + zk_exception->message() +
". Config will be loaded from preprocessed file: " + preprocessed_path);
config_xml = dom_parser.parse(preprocessed_path);
}
if (processed_successfully)
savePreprocessedConfig(config_xml, preprocessed_path);
ConfigurationPtr configuration(new Poco::Util::XMLConfiguration(config_xml));
return LoadedConfig{configuration, has_zk_includes, !processed_successfully, processed_successfully};
}
void ConfigProcessor::savePreprocessedConfig(const XMLDocumentPtr & config, const std::string & preprocessed_path)
{
try
{
DOMWriter().writeNode(preprocessed_path, config);
} }
catch (Poco::Exception & e) catch (Poco::Exception & e)
{ {
LOG_WARNING(log, "Couldn't save preprocessed config to " << preprocessed_path.toString() << ": " << e.displayText()); LOG_WARNING(log, "Couldn't save preprocessed config to " << preprocessed_path << ": " << e.displayText());
} }
return new Poco::Util::XMLConfiguration(res);
} }

View File

@ -51,4 +51,4 @@ target_link_libraries (in_join_subqueries_preprocessor dbms)
add_check(in_join_subqueries_preprocessor) add_check(in_join_subqueries_preprocessor)
add_executable (users users.cpp) add_executable (users users.cpp)
target_link_libraries (users dbms ${Boost_FILESYSTEM_LIBRARY}) target_link_libraries (users dbms ${Boost_FILESYSTEM_LIBRARY} zkutil dbms)

View File

@ -195,7 +195,7 @@ void runOneTest(size_t test_num, const TestDescriptor & test_descriptor)
try try
{ {
config = ConfigProcessor{}.loadConfig(path_name); config = ConfigProcessor{}.loadConfig(path_name).configuration;
} }
catch (const Poco::Exception & ex) catch (const Poco::Exception & ex)
{ {

View File

@ -13,42 +13,31 @@
namespace DB namespace DB
{ {
namespace ErrorCodes { extern const int FILE_DOESNT_EXIST; }
constexpr decltype(ConfigReloader::reload_interval) ConfigReloader::reload_interval; constexpr decltype(ConfigReloader::reload_interval) ConfigReloader::reload_interval;
ConfigReloader::ConfigReloader(
ConfigReloader::ConfigReloader(const std::string & main_config_path_, const std::string & users_config_path_, const std::string & path_,
const std::string & include_from_path_, Context * context_) const std::string & include_from_path_,
: main_config_path(main_config_path_), users_config_path(users_config_path_), zkutil::ZooKeeperNodeCache && zk_node_cache_,
include_from_path(include_from_path_), context(context_) Updater && updater_,
bool already_loaded)
: path(path_), include_from_path(include_from_path_)
, zk_node_cache(std::move(zk_node_cache_))
, updater(std::move(updater_))
{ {
/// If path to users' config isn't absolute, try guess its root (current) dir. if (!already_loaded)
/// At first, try to find it in dir of main config, after will use current dir. reloadIfNewer(/* force = */ true, /* throw_on_error = */ true, /* fallback_to_preprocessed = */ true);
if (users_config_path.empty() || users_config_path[0] != '/')
{
std::string config_dir = Poco::Path(main_config_path).parent().toString();
if (Poco::File(config_dir + users_config_path).exists())
users_config_path = config_dir + users_config_path;
}
/// Setup users on server init
reloadIfNewer(false, true);
thread = std::thread(&ConfigReloader::run, this); thread = std::thread(&ConfigReloader::run, this);
} }
ConfigReloader::~ConfigReloader() ConfigReloader::~ConfigReloader()
{ {
try try
{ {
{
std::lock_guard<std::mutex> lock{mutex};
quit = true; quit = true;
} zk_node_cache.getChangedEvent().set();
cond.notify_one();
thread.join(); thread.join();
} }
catch (...) catch (...)
@ -62,104 +51,106 @@ void ConfigReloader::run()
{ {
setThreadName("ConfigReloader"); setThreadName("ConfigReloader");
std::unique_lock<std::mutex> lock{mutex};
while (true) while (true)
{ {
if (cond.wait_for(lock, reload_interval, [this] { return quit; })) bool zk_changed = zk_node_cache.getChangedEvent().tryWait(std::chrono::milliseconds(reload_interval).count());
break; if (quit)
return;
reloadIfNewer(false, false); reloadIfNewer(zk_changed, /* throw_on_error = */ false, /* fallback_to_preprocessed = */ false);
} }
} }
void ConfigReloader::reloadIfNewer(bool force, bool throw_on_error, bool fallback_to_preprocessed)
ConfigReloader::FilesChangesTracker ConfigReloader::getFileListFor(const std::string & root_config_path)
{ {
FilesChangesTracker file_list; FilesChangesTracker new_files = getNewFileList();
if (force || new_files.isDifferOrNewerThan(files))
file_list.addIfExists(root_config_path);
file_list.addIfExists(include_from_path);
for (const auto & path : ConfigProcessor::getConfigMergeFiles(root_config_path))
file_list.addIfExists(path);
return file_list;
}
ConfigurationPtr ConfigReloader::loadConfigFor(const std::string & root_config_path, bool throw_on_error)
{ {
ConfigurationPtr config; ConfigProcessor::LoadedConfig loaded_config;
LOG_DEBUG(log, "Loading config '" << root_config_path << "'");
try try
{ {
config = ConfigProcessor().loadConfig(root_config_path); LOG_DEBUG(log, "Loading config `" << path << "'");
loaded_config = ConfigProcessor().loadConfig(path, /* allow_zk_includes = */ true);
if (loaded_config.has_zk_includes)
loaded_config = ConfigProcessor().loadConfigWithZooKeeperIncludes(
path, zk_node_cache, fallback_to_preprocessed);
} }
catch (...) catch (...)
{ {
if (throw_on_error) if (throw_on_error)
throw; throw;
tryLogCurrentException(log, "Error loading config from '" + root_config_path + "' "); tryLogCurrentException(log, "Error loading config from `" + path + "'");
return nullptr; return;
} }
return config;
}
void ConfigReloader::reloadIfNewer(bool force_main, bool force_users)
{
FilesChangesTracker main_config_files = getFileListFor(main_config_path);
if (force_main || main_config_files.isDifferOrNewerThan(last_main_config_files))
{
ConfigurationPtr config = loadConfigFor(main_config_path, force_main);
if (config)
{
/** We should remember last modification time if and only if config was sucessfully loaded /** We should remember last modification time if and only if config was sucessfully loaded
* Otherwise a race condition could occur during config files update: * Otherwise a race condition could occur during config files update:
* File is contain raw (and non-valid) data, therefore config is not applied. * File is contain raw (and non-valid) data, therefore config is not applied.
* When file has been written (and contain valid data), we don't load new data since modification time remains the same. * When file has been written (and contain valid data), we don't load new data since modification time remains the same.
*/ */
last_main_config_files = std::move(main_config_files); if (!loaded_config.loaded_from_preprocessed)
files = std::move(new_files);
try try
{ {
context->setClustersConfig(config); updater(loaded_config.configuration);
} }
catch (...) catch (...)
{ {
if (force_main) if (throw_on_error)
throw; throw;
tryLogCurrentException(log, "Error updating remote_servers config from '" + main_config_path + "' "); tryLogCurrentException(log, "Error updating configuration from `" + path + "' config.");
} }
} }
} }
FilesChangesTracker users_config_files = getFileListFor(users_config_path); struct ConfigReloader::FileWithTimestamp
if (force_users || users_config_files.isDifferOrNewerThan(last_users_config_files))
{ {
ConfigurationPtr config = loadConfigFor(users_config_path, force_users); std::string path;
if (config) time_t modification_time;
{
last_users_config_files = std::move(users_config_files);
try FileWithTimestamp(const std::string & path_, time_t modification_time_)
: path(path_), modification_time(modification_time_) {}
bool operator < (const FileWithTimestamp & rhs) const
{ {
context->setUsersConfig(config); return path < rhs.path;
} }
catch (...)
static bool isTheSame(const FileWithTimestamp & lhs, const FileWithTimestamp & rhs)
{ {
if (force_users) return (lhs.modification_time == rhs.modification_time) && (lhs.path == rhs.path);
throw;
tryLogCurrentException(log, "Error updating users config from '" + users_config_path + "' ");
}
} }
};
void ConfigReloader::FilesChangesTracker::addIfExists(const std::string & path)
{
if (!path.empty() && Poco::File(path).exists())
{
files.emplace(path, Poco::File(path).getLastModified().epochTime());
} }
} }
bool ConfigReloader::FilesChangesTracker::isDifferOrNewerThan(const FilesChangesTracker & rhs)
{
return (files.size() != rhs.files.size()) ||
!std::equal(files.begin(), files.end(), rhs.files.begin(), FileWithTimestamp::isTheSame);
}
ConfigReloader::FilesChangesTracker ConfigReloader::getNewFileList() const
{
FilesChangesTracker file_list;
file_list.addIfExists(path);
file_list.addIfExists(include_from_path);
for (const auto & merge_path : ConfigProcessor::getConfigMergeFiles(path))
file_list.addIfExists(merge_path);
return file_list;
}
} }

View File

@ -1,6 +1,8 @@
#pragma once #pragma once
#include <DB/Common/ConfigProcessor.h> #include <DB/Common/ConfigProcessor.h>
#include <zkutil/Common.h>
#include <zkutil/ZooKeeperNodeCache.h>
#include <time.h> #include <time.h>
#include <string> #include <string>
@ -19,89 +21,57 @@ class Context;
/** Every two seconds checks configuration files for update. /** Every two seconds checks configuration files for update.
* If configuration is changed, then config will be reloaded by ConfigProcessor * If configuration is changed, then config will be reloaded by ConfigProcessor
* and the reloaded config will be applied via setUsersConfig() and setClusters() methods of Context. * and the reloaded config will be applied via Updater functor.
* So, ConfigReloader actually reloads only <users> and <remote_servers> "tags". * It doesn't take into account changes of --config-file, <users_config> and <include_from> parameters.
* Also, it doesn't take into account changes of --config-file, <users_config> and <include_from> parameters.
*/ */
class ConfigReloader class ConfigReloader
{ {
public: public:
/** main_config_path is usually /path/to/.../clickhouse-server/config.xml (i.e. --config-file value) using Updater = std::function<void(ConfigurationPtr)>;
* users_config_path is usually /path/to/.../clickhouse-server/users.xml (i.e. value of <users_config> tag)
* include_from_path is usually /path/to/.../etc/metrika.xml (i.e. value of <include_from> tag) /** include_from_path is usually /etc/metrika.xml (i.e. value of <include_from> tag)
*/ */
ConfigReloader(const std::string & main_config_path_, const std::string & users_config_path_, const std::string & include_from_path_, Context * context_); ConfigReloader(
const std::string & path,
const std::string & include_from_path,
zkutil::ZooKeeperNodeCache && zk_node_cache,
Updater && updater,
bool already_loaded);
~ConfigReloader(); ~ConfigReloader();
private: private:
void run();
struct FileWithTimestamp void reloadIfNewer(bool force, bool throw_on_error, bool fallback_to_preprocessed);
{
std::string path;
time_t modification_time;
FileWithTimestamp(const std::string & path_, time_t modification_time_) struct FileWithTimestamp;
: path(path_), modification_time(modification_time_) {}
bool operator < (const FileWithTimestamp & rhs) const
{
return path < rhs.path;
}
static bool isTheSame(const FileWithTimestamp & lhs, const FileWithTimestamp & rhs)
{
return (lhs.modification_time == rhs.modification_time) && (lhs.path == rhs.path);
}
};
struct FilesChangesTracker struct FilesChangesTracker
{ {
std::set<FileWithTimestamp> files; std::set<FileWithTimestamp> files;
void addIfExists(const std::string & path) void addIfExists(const std::string & path);
{ bool isDifferOrNewerThan(const FilesChangesTracker & rhs);
if (!path.empty() && Poco::File(path).exists())
{
files.emplace(path, Poco::File(path).getLastModified().epochTime());
}
}
bool isDifferOrNewerThan(const FilesChangesTracker & rhs)
{
return (files.size() != rhs.files.size()) ||
!std::equal(files.begin(), files.end(), rhs.files.begin(), FileWithTimestamp::isTheSame);
}
}; };
private: FilesChangesTracker getNewFileList() const;
/// Make sense to separate this function on two threads
void reloadIfNewer(bool force_main, bool force_users);
void run();
FilesChangesTracker getFileListFor(const std::string & root_config_path);
ConfigurationPtr loadConfigFor(const std::string & root_config_path, bool throw_error);
private: private:
static constexpr auto reload_interval = std::chrono::seconds(2); static constexpr auto reload_interval = std::chrono::seconds(2);
std::string main_config_path;
std::string users_config_path;
std::string include_from_path;
Context * context;
FilesChangesTracker last_main_config_files;
FilesChangesTracker last_users_config_files;
bool quit {false};
std::mutex mutex;
std::condition_variable cond;
std::thread thread;
Poco::Logger * log = &Logger::get("ConfigReloader"); Poco::Logger * log = &Logger::get("ConfigReloader");
std::string path;
std::string include_from_path;
FilesChangesTracker files;
zkutil::ZooKeeperNodeCache zk_node_cache;
Updater updater;
std::atomic<bool> quit{false};
std::thread thread;
}; };
} }

View File

@ -243,7 +243,9 @@ try
/// Load config files if exists /// Load config files if exists
if (config().has("config-file") || Poco::File("config.xml").exists()) if (config().has("config-file") || Poco::File("config.xml").exists())
{ {
ConfigurationPtr processed_config = ConfigProcessor(false, true).loadConfig(config().getString("config-file", "config.xml")); ConfigurationPtr processed_config = ConfigProcessor(false, true)
.loadConfig(config().getString("config-file", "config.xml"))
.configuration;
config().add(processed_config.duplicate(), PRIO_DEFAULT, false); config().add(processed_config.duplicate(), PRIO_DEFAULT, false);
} }
@ -438,7 +440,7 @@ void LocalServer::setupUsers()
if (config().has("users_config") || config().has("config-file") || Poco::File("config.xml").exists()) if (config().has("users_config") || config().has("config-file") || Poco::File("config.xml").exists())
{ {
auto users_config_path = config().getString("users_config", config().getString("config-file", "config.xml")); auto users_config_path = config().getString("users_config", config().getString("config-file", "config.xml"));
users_config = ConfigProcessor().loadConfig(users_config_path); users_config = ConfigProcessor().loadConfig(users_config_path).configuration;
} }
else else
{ {

View File

@ -3,6 +3,7 @@
#include <daemon/BaseDaemon.h> #include <daemon/BaseDaemon.h>
#include <DB/Common/setThreadName.h> #include <DB/Common/setThreadName.h>
#include <DB/Common/CurrentMetrics.h> #include <DB/Common/CurrentMetrics.h>
#include <DB/Common/Exception.h>
#include <DB/Interpreters/AsynchronousMetrics.h> #include <DB/Interpreters/AsynchronousMetrics.h>

View File

@ -209,6 +209,23 @@ int Server::main(const std::vector<std::string> & args)
global_context->setGlobalContext(*global_context); global_context->setGlobalContext(*global_context);
global_context->setApplicationType(Context::ApplicationType::SERVER); global_context->setApplicationType(Context::ApplicationType::SERVER);
bool has_zookeeper = false;
if (config().has("zookeeper"))
{
global_context->setZooKeeper(std::make_shared<zkutil::ZooKeeper>(config(), "zookeeper"));
has_zookeeper = true;
}
zkutil::ZooKeeperNodeCache main_config_zk_node_cache([&] { return global_context->getZooKeeper(); });
if (loaded_config.has_zk_includes)
{
auto old_configuration = loaded_config.configuration;
loaded_config = ConfigProcessor().loadConfigWithZooKeeperIncludes(
config_path, main_config_zk_node_cache, /* fallback_to_preprocessed = */ true);
config().removeConfiguration(old_configuration.get());
config().add(loaded_config.configuration.duplicate(), PRIO_DEFAULT, false);
}
std::string path = getCanonicalPath(config().getString("path")); std::string path = getCanonicalPath(config().getString("path"));
std::string default_database = config().getString("default_database", "default"); std::string default_database = config().getString("default_database", "default");
@ -278,13 +295,6 @@ int Server::main(const std::vector<std::string> & args)
Poco::File(path + "flags/").createDirectories(); Poco::File(path + "flags/").createDirectories();
global_context->setFlagsPath(path + "flags/"); global_context->setFlagsPath(path + "flags/");
bool has_zookeeper = false;
if (config().has("zookeeper"))
{
global_context->setZooKeeper(std::make_shared<zkutil::ZooKeeper>(config(), "zookeeper"));
has_zookeeper = true;
}
if (config().has("interserver_http_port")) if (config().has("interserver_http_port"))
{ {
String this_host = config().getString("interserver_http_host", ""); String this_host = config().getString("interserver_http_host", "");
@ -309,11 +319,29 @@ int Server::main(const std::vector<std::string> & args)
if (config().has("macros")) if (config().has("macros"))
global_context->setMacros(Macros(config(), "macros")); global_context->setMacros(Macros(config(), "macros"));
/// Initialize automatic config updater /// Initialize main config reloader.
std::string main_config_path = config().getString("config-file", "config.xml");
std::string users_config_path = config().getString("users_config", main_config_path);
std::string include_from_path = config().getString("include_from", "/etc/metrika.xml"); std::string include_from_path = config().getString("include_from", "/etc/metrika.xml");
auto config_reloader = std::make_unique<ConfigReloader>(main_config_path, users_config_path, include_from_path, global_context.get()); auto main_config_reloader = std::make_unique<ConfigReloader>(
config_path, include_from_path,
std::move(main_config_zk_node_cache),
[&](ConfigurationPtr config) { global_context->setClustersConfig(config); },
/* already_loaded = */ true);
/// Initialize users config reloader.
std::string users_config_path = config().getString("users_config", config_path);
/// If path to users' config isn't absolute, try guess its root (current) dir.
/// At first, try to find it in dir of main config, after will use current dir.
if (users_config_path.empty() || users_config_path[0] != '/')
{
std::string config_dir = Poco::Path(config_path).parent().toString();
if (Poco::File(config_dir + users_config_path).exists())
users_config_path = config_dir + users_config_path;
}
auto users_config_reloader = std::make_unique<ConfigReloader>(
users_config_path, include_from_path,
zkutil::ZooKeeperNodeCache([&] { return global_context->getZooKeeper(); }),
[&](ConfigurationPtr config) { global_context->setUsersConfig(config); },
/* already_loaded = */ false);
/// Limit on total number of coucurrently executed queries. /// Limit on total number of coucurrently executed queries.
global_context->getProcessList().setMaxSize(config().getInt("max_concurrent_queries", 0)); global_context->getProcessList().setMaxSize(config().getInt("max_concurrent_queries", 0));
@ -535,7 +563,8 @@ int Server::main(const std::vector<std::string> & args)
LOG_DEBUG(log, "Closed all connections."); LOG_DEBUG(log, "Closed all connections.");
config_reloader.reset(); main_config_reloader.reset();
users_config_reloader.reset();
}); });
/// try to load dictionaries immediately, throw on error and die /// try to load dictionaries immediately, throw on error and die

View File

@ -6,6 +6,9 @@
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <functional> #include <functional>
#include <experimental/optional>
#include <mutex>
#include <condition_variable>
#include <Poco/Process.h> #include <Poco/Process.h>
#include <Poco/ThreadPool.h> #include <Poco/ThreadPool.h>
@ -23,9 +26,7 @@
#include <daemon/GraphiteWriter.h> #include <daemon/GraphiteWriter.h>
#include <experimental/optional> #include <DB/Common/ConfigProcessor.h>
#include <zkutil/ZooKeeperHolder.h>
namespace Poco { class TaskManager; } namespace Poco { class TaskManager; }
@ -207,6 +208,9 @@ protected:
std::condition_variable signal_event; std::condition_variable signal_event;
std::atomic_size_t terminate_signals_counter{0}; std::atomic_size_t terminate_signals_counter{0};
std::atomic_size_t sigint_signals_counter{0}; std::atomic_size_t sigint_signals_counter{0};
std::string config_path;
ConfigProcessor::LoadedConfig loaded_config;
}; };

View File

@ -480,8 +480,9 @@ void BaseDaemon::reloadConfiguration()
* При этом, параметры логгирования, заданные в командной строке, не игнорируются. * При этом, параметры логгирования, заданные в командной строке, не игнорируются.
*/ */
std::string log_command_line_option = config().getString("logger.log", ""); std::string log_command_line_option = config().getString("logger.log", "");
ConfigurationPtr processed_config = ConfigProcessor(false, true).loadConfig(config().getString("config-file", "config.xml")); config_path = config().getString("config-file", "config.xml");
config().add(processed_config.duplicate(), PRIO_DEFAULT, false); loaded_config = ConfigProcessor(false, true).loadConfig(config_path, /* allow_zk_includes = */ true);
config().add(loaded_config.configuration.duplicate(), PRIO_DEFAULT, false);
log_to_console = !config().getBool("application.runAsDaemon", false) && log_command_line_option.empty(); log_to_console = !config().getBool("application.runAsDaemon", false) && log_command_line_option.empty();
} }

View File

@ -9,6 +9,7 @@ add_library(zkutil
src/SingleBarrier.cpp src/SingleBarrier.cpp
src/RWLock.cpp src/RWLock.cpp
src/ZooKeeperHolder.cpp src/ZooKeeperHolder.cpp
src/ZooKeeperNodeCache.cpp
include/zkutil/Increment.h include/zkutil/Increment.h
include/zkutil/LeaderElection.h include/zkutil/LeaderElection.h
@ -19,7 +20,8 @@ add_library(zkutil
include/zkutil/RWLock.h include/zkutil/RWLock.h
include/zkutil/ZooKeeper.h include/zkutil/ZooKeeper.h
include/zkutil/Types.h include/zkutil/Types.h
include/zkutil/ZooKeeperHolder.h) include/zkutil/ZooKeeperHolder.h
include/zkutil/ZooKeeperNodeCache.h)
find_package (Threads) find_package (Threads)

View File

@ -123,4 +123,13 @@ namespace CreateMode
using EventPtr = std::shared_ptr<Poco::Event>; using EventPtr = std::shared_ptr<Poco::Event>;
class ZooKeeper;
/// Callback to call when the watch fires.
/// zookeeper - zookeeper session to which the fired watch belongs
/// type - event type, one of the *_EVENT constants from zookeeper.h
/// state - session connection state, one of the *_STATE constants from zookeeper.h
/// path - znode path to which the change happened. if event == ZOO_SESSION_EVENT it is either NULL or empty string.
using WatchCallback = std::function<void(ZooKeeper & zookeeper, int type, int state, const char * path)>;
} }

View File

@ -31,7 +31,7 @@ const UInt32 DEFAULT_SESSION_TIMEOUT = 30000;
const UInt32 MEDIUM_SESSION_TIMEOUT = 120000; const UInt32 MEDIUM_SESSION_TIMEOUT = 120000;
const UInt32 BIG_SESSION_TIMEOUT = 600000; const UInt32 BIG_SESSION_TIMEOUT = 600000;
struct WatchWithEvent; struct WatchContext;
/** Сессия в ZooKeeper. Интерфейс существенно отличается от обычного API ZooKeeper. /** Сессия в ZooKeeper. Интерфейс существенно отличается от обычного API ZooKeeper.
@ -151,14 +151,17 @@ public:
*/ */
int32_t tryRemoveEphemeralNodeWithRetries(const std::string & path, int32_t version = -1, size_t * attempt = nullptr); int32_t tryRemoveEphemeralNodeWithRetries(const std::string & path, int32_t version = -1, size_t * attempt = nullptr);
bool exists(const std::string & path, Stat * stat = nullptr, EventPtr watch = nullptr); bool exists(const std::string & path, Stat * stat = nullptr, const EventPtr & watch = nullptr);
bool existsWatch(const std::string & path, Stat * stat, const WatchCallback & watch_callback);
std::string get(const std::string & path, Stat * stat = nullptr, EventPtr watch = nullptr); std::string get(const std::string & path, Stat * stat = nullptr, const EventPtr & watch = nullptr);
/** Не бросает исключение при следующих ошибках: /** Не бросает исключение при следующих ошибках:
* - Такой ноды нет. В таком случае возвращает false. * - Такой ноды нет. В таком случае возвращает false.
*/ */
bool tryGet(const std::string & path, std::string & res, Stat * stat = nullptr, EventPtr watch = nullptr, int * code = nullptr); bool tryGet(const std::string & path, std::string & res, Stat * stat = nullptr, const EventPtr & watch = nullptr, int * code = nullptr);
bool tryGetWatch(const std::string & path, std::string & res, Stat * stat, const WatchCallback & watch_callback, int * code = nullptr);
void set(const std::string & path, const std::string & data, void set(const std::string & path, const std::string & data,
int32_t version = -1, Stat * stat = nullptr); int32_t version = -1, Stat * stat = nullptr);
@ -175,14 +178,14 @@ public:
Strings getChildren(const std::string & path, Strings getChildren(const std::string & path,
Stat * stat = nullptr, Stat * stat = nullptr,
EventPtr watch = nullptr); const EventPtr & watch = nullptr);
/** Не бросает исключение при следующих ошибках: /** Не бросает исключение при следующих ошибках:
* - Такой ноды нет. * - Такой ноды нет.
*/ */
int32_t tryGetChildren(const std::string & path, Strings & res, int32_t tryGetChildren(const std::string & path, Strings & res,
Stat * stat = nullptr, Stat * stat = nullptr,
EventPtr watch = nullptr); const EventPtr & watch = nullptr);
/** Транзакционно выполняет несколько операций. При любой ошибке бросает исключение. /** Транзакционно выполняет несколько операций. При любой ошибке бросает исключение.
*/ */
@ -331,15 +334,17 @@ public:
zhandle_t * getHandle() { return impl; } zhandle_t * getHandle() { return impl; }
private: private:
friend struct WatchWithEvent; friend struct WatchContext;
friend class EphemeralNodeHolder; friend class EphemeralNodeHolder;
void init(const std::string & hosts, int32_t session_timeout_ms); void init(const std::string & hosts, int32_t session_timeout_ms);
void removeChildrenRecursive(const std::string & path); void removeChildrenRecursive(const std::string & path);
void tryRemoveChildrenRecursive(const std::string & path); void tryRemoveChildrenRecursive(const std::string & path);
void * watchForEvent(EventPtr event);
watcher_fn callbackForEvent(EventPtr event); static WatchCallback callbackForEvent(const EventPtr & event);
static void processEvent(zhandle_t * zh, int type, int state, const char * path, void * watcherCtx); WatchContext * createContext(WatchCallback && callback);
static void destroyContext(WatchContext * context);
static void processCallback(zhandle_t * zh, int type, int state, const char * path, void * watcher_ctx);
template <class T> template <class T>
int32_t retry(T && operation, size_t * attempt = nullptr) int32_t retry(T && operation, size_t * attempt = nullptr)
@ -367,14 +372,11 @@ private:
/// методы не бросают исключений, а возвращают коды ошибок /// методы не бросают исключений, а возвращают коды ошибок
int32_t createImpl(const std::string & path, const std::string & data, int32_t mode, std::string & path_created); int32_t createImpl(const std::string & path, const std::string & data, int32_t mode, std::string & path_created);
int32_t removeImpl(const std::string & path, int32_t version = -1); int32_t removeImpl(const std::string & path, int32_t version = -1);
int32_t getImpl(const std::string & path, std::string & res, Stat * stat = nullptr, EventPtr watch = nullptr); int32_t getImpl(const std::string & path, std::string & res, Stat * stat, WatchCallback watch_callback);
int32_t setImpl(const std::string & path, const std::string & data, int32_t setImpl(const std::string & path, const std::string & data, int32_t version = -1, Stat * stat = nullptr);
int32_t version = -1, Stat * stat = nullptr); int32_t getChildrenImpl(const std::string & path, Strings & res, Stat * stat, WatchCallback watch_callback);
int32_t getChildrenImpl(const std::string & path, Strings & res,
Stat * stat = nullptr,
EventPtr watch = nullptr);
int32_t multiImpl(const Ops & ops, OpResultsPtr * out_results = nullptr); int32_t multiImpl(const Ops & ops, OpResultsPtr * out_results = nullptr);
int32_t existsImpl(const std::string & path, Stat * stat_, EventPtr watch = nullptr); int32_t existsImpl(const std::string & path, Stat * stat_, WatchCallback watch_callback);
std::string hosts; std::string hosts;
int32_t session_timeout_ms; int32_t session_timeout_ms;
@ -383,7 +385,7 @@ private:
ACLPtr default_acl; ACLPtr default_acl;
zhandle_t * impl; zhandle_t * impl;
std::unordered_set<WatchWithEvent *> watch_store; std::unordered_set<WatchContext *> watch_context_store;
/// Количество попыток повторить операцию чтения при OperationTimeout, ConnectionLoss /// Количество попыток повторить операцию чтения при OperationTimeout, ConnectionLoss
static constexpr size_t retry_num = 3; static constexpr size_t retry_num = 3;

View File

@ -0,0 +1,57 @@
#pragma once
#include <unordered_map>
#include <unordered_set>
#include <mutex>
#include <memory>
#include <experimental/optional>
#include <Poco/Event.h>
#include <zkutil/ZooKeeper.h>
#include <zkutil/Common.h>
namespace DB
{
namespace ErrorCodes
{
extern const int NO_ZOOKEEPER;
}
}
namespace zkutil
{
/// This class allows querying the contents of ZooKeeper nodes and caching the results.
/// Watches are set for cached nodes and for nodes that were nonexistent at the time of query.
/// After a watch fires, a notification is generated for the change event.
/// NOTE: methods of this class are not thread-safe.
class ZooKeeperNodeCache
{
public:
ZooKeeperNodeCache(GetZooKeeper get_zookeeper);
ZooKeeperNodeCache(const ZooKeeperNodeCache &) = delete;
ZooKeeperNodeCache(ZooKeeperNodeCache &&) = default;
std::experimental::optional<std::string> get(const std::string & path);
Poco::Event & getChangedEvent() { return context->changed_event; }
private:
GetZooKeeper get_zookeeper;
struct Context
{
Poco::Event changed_event;
std::mutex mutex;
zkutil::ZooKeeperPtr zookeeper;
std::unordered_set<std::string> invalidated_paths;
};
std::shared_ptr<Context> context;
std::unordered_set<std::string> nonexistent_nodes;
std::unordered_map<std::string, std::string> node_cache;
};
}

View File

@ -43,42 +43,30 @@ void check(int32_t code, const std::string path = "")
} }
} }
struct WatchWithEvent struct WatchContext
{ {
/// существует все время существования WatchWithEvent /// существует все время существования WatchContext
ZooKeeper & zk; ZooKeeper & zk;
EventPtr event; WatchCallback callback;
CurrentMetrics::Increment metric_increment{CurrentMetrics::ZooKeeperWatch}; CurrentMetrics::Increment metric_increment{CurrentMetrics::ZooKeeperWatch};
WatchWithEvent(ZooKeeper & zk_, EventPtr event_) : zk(zk_), event(event_) {} WatchContext(ZooKeeper & zk_, WatchCallback callback_) : zk(zk_), callback(std::move(callback_)) {}
void process(zhandle_t * zh, int32_t event_type, int32_t state, const char * path) void process(int32_t event_type, int32_t state, const char * path)
{ {
if (event) if (callback)
{ callback(zk, event_type, state, path);
event->set();
event = nullptr;
}
} }
}; };
void ZooKeeper::processEvent(zhandle_t * zh, int type, int state, const char * path, void * watcherCtx) void ZooKeeper::processCallback(zhandle_t * zh, int type, int state, const char * path, void * watcher_ctx)
{ {
if (watcherCtx) WatchContext * context = static_cast<WatchContext *>(watcher_ctx);
{ context->process(type, state, path);
WatchWithEvent * watch = static_cast<WatchWithEvent *>(watcherCtx);
watch->process(zh, type, state, path);
/// Гарантируется, что не-ZOO_SESSION_EVENT событие придет ровно один раз (https://issues.apache.org/jira/browse/ZOOKEEPER-890). /// Гарантируется, что не-ZOO_SESSION_EVENT событие придет ровно один раз (https://issues.apache.org/jira/browse/ZOOKEEPER-890).
if (type != ZOO_SESSION_EVENT) if (type != ZOO_SESSION_EVENT)
{ destroyContext(context);
{
std::lock_guard<std::mutex> lock(watch->zk.mutex);
watch->zk.watch_store.erase(watch);
}
delete watch;
}
}
} }
void ZooKeeper::init(const std::string & hosts_, int32_t session_timeout_ms_) void ZooKeeper::init(const std::string & hosts_, int32_t session_timeout_ms_)
@ -147,40 +135,62 @@ ZooKeeper::ZooKeeper(const Poco::Util::AbstractConfiguration & config, const std
init(args.hosts, args.session_timeout_ms); init(args.hosts, args.session_timeout_ms);
} }
void * ZooKeeper::watchForEvent(EventPtr event) WatchCallback ZooKeeper::callbackForEvent(const EventPtr & event)
{ {
WatchCallback callback;
if (event) if (event)
{ {
WatchWithEvent * res = new WatchWithEvent(*this, event); callback = [e=event](ZooKeeper &, int, int, const char *) mutable
{
if (e)
{
e->set();
e.reset(); /// The event is set only once, even if the callback can fire multiple times due to session events.
}
};
}
return callback;
}
WatchContext * ZooKeeper::createContext(WatchCallback && callback)
{
if (callback)
{
WatchContext * res = new WatchContext(*this, std::move(callback));
{ {
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);
watch_store.insert(res); watch_context_store.insert(res);
if (watch_store.size() % 10000 == 0) if (watch_context_store.size() % 10000 == 0)
{ {
LOG_ERROR(log, "There are " << watch_store.size() << " active watches. There must be a leak somewhere."); LOG_ERROR(log, "There are " << watch_context_store.size() << " active watches. There must be a leak somewhere.");
} }
} }
return res; return res;
} }
else else
{
return nullptr; return nullptr;
} }
}
watcher_fn ZooKeeper::callbackForEvent(EventPtr event) void ZooKeeper::destroyContext(WatchContext * context)
{ {
return event ? processEvent : nullptr; if (context)
{
std::lock_guard<std::mutex> lock(context->zk.mutex);
context->zk.watch_context_store.erase(context);
}
delete context;
} }
int32_t ZooKeeper::getChildrenImpl(const std::string & path, Strings & res, int32_t ZooKeeper::getChildrenImpl(const std::string & path, Strings & res,
Stat * stat_, Stat * stat_,
EventPtr watch) WatchCallback watch_callback)
{ {
String_vector strings; String_vector strings;
int code; int code;
Stat stat; Stat stat;
code = zoo_wget_children2(impl, path.c_str(), callbackForEvent(watch), watchForEvent(watch), &strings, &stat); watcher_fn watcher = watch_callback ? processCallback : nullptr;
WatchContext * context = createContext(std::move(watch_callback));
code = zoo_wget_children2(impl, path.c_str(), watcher, context, &strings, &stat);
ProfileEvents::increment(ProfileEvents::ZooKeeperGetChildren); ProfileEvents::increment(ProfileEvents::ZooKeeperGetChildren);
ProfileEvents::increment(ProfileEvents::ZooKeeperTransactions); ProfileEvents::increment(ProfileEvents::ZooKeeperTransactions);
@ -193,11 +203,16 @@ int32_t ZooKeeper::getChildrenImpl(const std::string & path, Strings & res,
res[i] = std::string(strings.data[i]); res[i] = std::string(strings.data[i]);
deallocate_String_vector(&strings); deallocate_String_vector(&strings);
} }
else
{
/// The call was unsuccessful, so the watch was not set. Destroy the context.
destroyContext(context);
}
return code; return code;
} }
Strings ZooKeeper::getChildren( Strings ZooKeeper::getChildren(
const std::string & path, Stat * stat, EventPtr watch) const std::string & path, Stat * stat, const EventPtr & watch)
{ {
Strings res; Strings res;
check(tryGetChildren(path, res, stat, watch), path); check(tryGetChildren(path, res, stat, watch), path);
@ -205,9 +220,9 @@ Strings ZooKeeper::getChildren(
} }
int32_t ZooKeeper::tryGetChildren(const std::string & path, Strings & res, int32_t ZooKeeper::tryGetChildren(const std::string & path, Strings & res,
Stat * stat_, EventPtr watch) Stat * stat_, const EventPtr & watch)
{ {
int32_t code = retry(std::bind(&ZooKeeper::getChildrenImpl, this, std::ref(path), std::ref(res), stat_, watch)); int32_t code = retry(std::bind(&ZooKeeper::getChildrenImpl, this, std::ref(path), std::ref(res), stat_, callbackForEvent(watch)));
if (!( code == ZOK || if (!( code == ZOK ||
code == ZNONODE)) code == ZNONODE))
@ -356,11 +371,13 @@ int32_t ZooKeeper::tryRemoveEphemeralNodeWithRetries(const std::string & path, i
} }
} }
int32_t ZooKeeper::existsImpl(const std::string & path, Stat * stat_, EventPtr watch) int32_t ZooKeeper::existsImpl(const std::string & path, Stat * stat_, WatchCallback watch_callback)
{ {
int32_t code; int32_t code;
Stat stat; Stat stat;
code = zoo_wexists(impl, path.c_str(), callbackForEvent(watch), watchForEvent(watch), &stat); watcher_fn watcher = watch_callback ? processCallback : nullptr;
WatchContext * context = createContext(std::move(watch_callback));
code = zoo_wexists(impl, path.c_str(), watcher, context, &stat);
ProfileEvents::increment(ProfileEvents::ZooKeeperExists); ProfileEvents::increment(ProfileEvents::ZooKeeperExists);
ProfileEvents::increment(ProfileEvents::ZooKeeperTransactions); ProfileEvents::increment(ProfileEvents::ZooKeeperTransactions);
@ -369,13 +386,18 @@ int32_t ZooKeeper::existsImpl(const std::string & path, Stat * stat_, EventPtr w
if (stat_) if (stat_)
*stat_ = stat; *stat_ = stat;
} }
if (code != ZOK && code != ZNONODE)
{
/// The call was unsuccessful, so the watch was not set. Destroy the context.
destroyContext(context);
}
return code; return code;
} }
bool ZooKeeper::exists(const std::string & path, Stat * stat_, EventPtr watch) bool ZooKeeper::exists(const std::string & path, Stat * stat_, const EventPtr & watch)
{ {
int32_t code = retry(std::bind(&ZooKeeper::existsImpl, this, path, stat_, watch)); int32_t code = retry(std::bind(&ZooKeeper::existsImpl, this, path, stat_, callbackForEvent(watch)));
if (!( code == ZOK || if (!( code == ZOK ||
code == ZNONODE)) code == ZNONODE))
@ -385,13 +407,28 @@ bool ZooKeeper::exists(const std::string & path, Stat * stat_, EventPtr watch)
return true; return true;
} }
int32_t ZooKeeper::getImpl(const std::string & path, std::string & res, Stat * stat_, EventPtr watch) bool ZooKeeper::existsWatch(const std::string & path, Stat * stat_, const WatchCallback & watch_callback)
{
int32_t code = retry(std::bind(&ZooKeeper::existsImpl, this, path, stat_, watch_callback));
if (!( code == ZOK ||
code == ZNONODE))
throw KeeperException(code, path);
if (code == ZNONODE)
return false;
return true;
}
int32_t ZooKeeper::getImpl(const std::string & path, std::string & res, Stat * stat_, WatchCallback watch_callback)
{ {
char buffer[MAX_NODE_SIZE]; char buffer[MAX_NODE_SIZE];
int buffer_len = MAX_NODE_SIZE; int buffer_len = MAX_NODE_SIZE;
int32_t code; int32_t code;
Stat stat; Stat stat;
code = zoo_wget(impl, path.c_str(), callbackForEvent(watch), watchForEvent(watch), buffer, &buffer_len, &stat); watcher_fn watcher = watch_callback ? processCallback : nullptr;
WatchContext * context = createContext(std::move(watch_callback));
code = zoo_wget(impl, path.c_str(), watcher, context, buffer, &buffer_len, &stat);
ProfileEvents::increment(ProfileEvents::ZooKeeperGet); ProfileEvents::increment(ProfileEvents::ZooKeeperGet);
ProfileEvents::increment(ProfileEvents::ZooKeeperTransactions); ProfileEvents::increment(ProfileEvents::ZooKeeperTransactions);
@ -405,10 +442,15 @@ int32_t ZooKeeper::getImpl(const std::string & path, std::string & res, Stat * s
else else
res.assign(buffer, buffer_len); res.assign(buffer, buffer_len);
} }
else
{
/// The call was unsuccessful, so the watch was not set. Destroy the context.
destroyContext(context);
}
return code; return code;
} }
std::string ZooKeeper::get(const std::string & path, Stat * stat, EventPtr watch) std::string ZooKeeper::get(const std::string & path, Stat * stat, const EventPtr & watch)
{ {
int code; int code;
std::string res; std::string res;
@ -418,9 +460,23 @@ std::string ZooKeeper::get(const std::string & path, Stat * stat, EventPtr watch
throw KeeperException("Can't get data for node " + path + ": node doesn't exist", code); throw KeeperException("Can't get data for node " + path + ": node doesn't exist", code);
} }
bool ZooKeeper::tryGet(const std::string & path, std::string & res, Stat * stat_, EventPtr watch, int * return_code) bool ZooKeeper::tryGet(const std::string & path, std::string & res, Stat * stat_, const EventPtr & watch, int * return_code)
{ {
int32_t code = retry(std::bind(&ZooKeeper::getImpl, this, std::ref(path), std::ref(res), stat_, watch)); int32_t code = retry(std::bind(&ZooKeeper::getImpl, this, std::ref(path), std::ref(res), stat_, callbackForEvent(watch)));
if (!(code == ZOK ||
code == ZNONODE))
throw KeeperException(code, path);
if (return_code)
*return_code = code;
return code == ZOK;
}
bool ZooKeeper::tryGetWatch(const std::string & path, std::string & res, Stat * stat_, const WatchCallback & watch_callback, int * return_code)
{
int32_t code = retry(std::bind(&ZooKeeper::getImpl, this, std::ref(path), std::ref(res), stat_, watch_callback));
if (!(code == ZOK || if (!(code == ZOK ||
code == ZNONODE)) code == ZNONODE))
@ -626,11 +682,11 @@ ZooKeeper::~ZooKeeper()
LOG_ERROR(&Logger::get("~ZooKeeper"), "Failed to close ZooKeeper session: " << zerror(code)); LOG_ERROR(&Logger::get("~ZooKeeper"), "Failed to close ZooKeeper session: " << zerror(code));
} }
LOG_INFO(&Logger::get("~ZooKeeper"), "Removing " << watch_store.size() << " watches"); LOG_INFO(&Logger::get("~ZooKeeper"), "Removing " << watch_context_store.size() << " watches");
/// удаляем WatchWithEvent которые уже никогда не будут обработаны /// удаляем WatchContext которые уже никогда не будут обработаны
for (WatchWithEvent * watch : watch_store) for (WatchContext * context : watch_context_store)
delete watch; delete context;
LOG_INFO(&Logger::get("~ZooKeeper"), "Removed watches"); LOG_INFO(&Logger::get("~ZooKeeper"), "Removed watches");
} }

View File

@ -0,0 +1,99 @@
#include <zkutil/ZooKeeperNodeCache.h>
namespace zkutil
{
ZooKeeperNodeCache::ZooKeeperNodeCache(GetZooKeeper get_zookeeper_)
: get_zookeeper(std::move(get_zookeeper_))
, context(std::make_shared<Context>())
{
}
std::experimental::optional<std::string> ZooKeeperNodeCache::get(const std::string & path)
{
zkutil::ZooKeeperPtr zookeeper;
std::unordered_set<std::string> invalidated_paths;
{
std::lock_guard<std::mutex> lock(context->mutex);
if (!context->zookeeper)
{
/// Possibly, there was a previous session and it has expired. Clear the cache.
nonexistent_nodes.clear();
node_cache.clear();
context->zookeeper = get_zookeeper();
}
zookeeper = context->zookeeper;
invalidated_paths.swap(context->invalidated_paths);
}
if (!zookeeper)
throw DB::Exception("Could not get znode: `" + path + "'. ZooKeeper not configured.", DB::ErrorCodes::NO_ZOOKEEPER);
for (const auto & path : invalidated_paths)
{
nonexistent_nodes.erase(path);
node_cache.erase(path);
}
if (nonexistent_nodes.count(path))
return std::experimental::nullopt;
auto watch_callback = [context=context](zkutil::ZooKeeper & zookeeper, int type, int state, const char * path)
{
if (!(type != ZOO_SESSION_EVENT || state == ZOO_EXPIRED_SESSION_STATE))
return;
bool changed = false;
{
std::lock_guard<std::mutex> lock(context->mutex);
if (&zookeeper != context->zookeeper.get())
return;
if (type != ZOO_SESSION_EVENT)
changed = context->invalidated_paths.emplace(path).second;
else if (state == ZOO_EXPIRED_SESSION_STATE)
{
context->zookeeper = nullptr;
context->invalidated_paths.clear();
changed = true;
}
}
if (changed)
context->changed_event.set();
};
std::string contents;
auto cache_it = node_cache.find(path);
if (cache_it != node_cache.end())
{
return cache_it->second;
}
if (zookeeper->tryGetWatch(path, contents, /* stat = */nullptr, watch_callback))
{
node_cache.emplace(path, contents);
return contents;
}
/// Node doesn't exist. Create a watch on node creation.
nonexistent_nodes.insert(path);
if (!zookeeper->existsWatch(path, /* stat = */nullptr, watch_callback))
return std::experimental::nullopt;
/// Node was created between the two previous calls, try again. Watch is already set.
if (zookeeper->tryGet(path, contents))
{
nonexistent_nodes.erase(path);
node_cache.emplace(path, contents);
return contents;
}
return std::experimental::nullopt;
}
}

View File

@ -24,7 +24,7 @@ int main(int argc, char ** argv)
} }
ConfigProcessor processor(false, true); ConfigProcessor processor(false, true);
auto config = processor.loadConfig(argv[1]); auto config = processor.loadConfig(argv[1]).configuration;
zkutil::ZooKeeper zk(*config, "zookeeper"); zkutil::ZooKeeper zk(*config, "zookeeper");
zkutil::EventPtr watch = std::make_shared<Poco::Event>(); zkutil::EventPtr watch = std::make_shared<Poco::Event>();

View File

@ -1,4 +1,4 @@
add_executable (config-processor config-processor.cpp) add_executable (config-processor config-processor.cpp)
target_link_libraries (config-processor daemon) target_link_libraries (config-processor dbms zkutil dbms)
INSTALL(TARGETS config-processor RUNTIME DESTINATION bin COMPONENT config-processor) INSTALL(TARGETS config-processor RUNTIME DESTINATION bin COMPONENT config-processor)