mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-24 08:32:02 +00:00
Merge
This commit is contained in:
commit
5c53f51ef5
@ -17,6 +17,7 @@ namespace ErrorCodes
|
||||
extern const int AGGREGATE_FUNCTION_DOESNT_ALLOW_PARAMETERS;
|
||||
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
||||
extern const int ILLEGAL_TYPE_OF_ARGUMENT;
|
||||
extern const int ARGUMENT_OUT_OF_BOUND;
|
||||
}
|
||||
|
||||
using AggregateDataPtr = char *;
|
||||
|
11
dbms/include/DB/Common/ARMHelpers.h
Normal file
11
dbms/include/DB/Common/ARMHelpers.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#if !defined(__x86_64__)
|
||||
|
||||
inline unsigned int _bit_scan_reverse(unsigned int x)
|
||||
{
|
||||
return sizeof(unsigned int) * 8 - 1 - __builtin_clz(x);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,15 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/Common/Arena.h>
|
||||
|
||||
#if !defined(__x86_64__)
|
||||
|
||||
inline unsigned int _bit_scan_reverse(unsigned int x)
|
||||
{
|
||||
return sizeof(unsigned int) * 8 - 1 - __builtin_clz(x);
|
||||
}
|
||||
|
||||
#endif
|
||||
#include <DB/Common/ARMHelpers.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
|
@ -7,6 +7,10 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#include <smmintrin.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
@ -18,18 +22,30 @@ namespace ErrorCodes
|
||||
}
|
||||
|
||||
|
||||
struct StringSearcherBase
|
||||
{
|
||||
#if defined(__x86_64__)
|
||||
static constexpr auto n = sizeof(__m128i);
|
||||
const int page_size = getpagesize();
|
||||
|
||||
bool page_safe(const void * const ptr) const
|
||||
{
|
||||
return ((page_size - 1) & reinterpret_cast<std::uintptr_t>(ptr)) <= page_size - n;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
/// Performs case-sensitive and case-insensitive search of UTF-8 strings
|
||||
template <bool CaseSensitive, bool ASCII> class StringSearcher;
|
||||
|
||||
/// Case-insensitive UTF-8 searcher
|
||||
template <> class StringSearcher<false, false>
|
||||
template <>
|
||||
class StringSearcher<false, false> : private StringSearcherBase
|
||||
{
|
||||
private:
|
||||
using UTF8SequenceBuffer = UInt8[6];
|
||||
|
||||
static constexpr auto n = sizeof(__m128i);
|
||||
|
||||
const int page_size = getpagesize();
|
||||
|
||||
/// string to be searched for
|
||||
const UInt8 * const needle;
|
||||
const std::size_t needle_size;
|
||||
@ -38,6 +54,8 @@ template <> class StringSearcher<false, false>
|
||||
bool first_needle_symbol_is_ascii{};
|
||||
UInt8 l{};
|
||||
UInt8 u{};
|
||||
|
||||
#if defined(__x86_64__)
|
||||
/// vectors filled with `l` and `u`, for determining leftmost position of the first symbol
|
||||
__m128i patl, patu;
|
||||
/// lower and uppercase vectors of first 16 characters of `needle`
|
||||
@ -45,11 +63,7 @@ template <> class StringSearcher<false, false>
|
||||
int cachemask{};
|
||||
std::size_t cache_valid_len{};
|
||||
std::size_t cache_actual_len{};
|
||||
|
||||
bool page_safe(const void * const ptr) const
|
||||
{
|
||||
return ((page_size - 1) & reinterpret_cast<std::uintptr_t>(ptr)) <= page_size - n;
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
StringSearcher(const char * const needle_, const std::size_t needle_size)
|
||||
@ -80,6 +94,7 @@ public:
|
||||
u = u_seq[0];
|
||||
}
|
||||
|
||||
#if defined(__x86_64__)
|
||||
/// for detecting leftmost position of the first symbol
|
||||
patl = _mm_set1_epi8(l);
|
||||
patu = _mm_set1_epi8(u);
|
||||
@ -133,12 +148,14 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool compare(const UInt8 * pos) const
|
||||
{
|
||||
static const Poco::UTF8Encoding utf8;
|
||||
|
||||
#if defined(__x86_64__)
|
||||
if (page_safe(pos))
|
||||
{
|
||||
const auto v_haystack = _mm_loadu_si128(reinterpret_cast<const __m128i *>(pos));
|
||||
@ -172,6 +189,7 @@ public:
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (*pos == l || *pos == u)
|
||||
{
|
||||
@ -202,6 +220,7 @@ public:
|
||||
|
||||
while (haystack < haystack_end)
|
||||
{
|
||||
#if defined(__x86_64__)
|
||||
if (haystack + n <= haystack_end && page_safe(haystack))
|
||||
{
|
||||
const auto v_haystack = _mm_loadu_si128(reinterpret_cast<const __m128i *>(haystack));
|
||||
@ -257,6 +276,7 @@ public:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (haystack == haystack_end)
|
||||
return haystack_end;
|
||||
@ -286,13 +306,12 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// Case-insensitive ASCII searcher
|
||||
template <> class StringSearcher<false, true>
|
||||
template <>
|
||||
class StringSearcher<false, true> : private StringSearcherBase
|
||||
{
|
||||
static constexpr auto n = sizeof(__m128i);
|
||||
|
||||
const int page_size = getpagesize();
|
||||
|
||||
private:
|
||||
/// string to be searched for
|
||||
const UInt8 * const needle;
|
||||
const std::size_t needle_size;
|
||||
@ -300,16 +319,14 @@ template <> class StringSearcher<false, true>
|
||||
/// lower and uppercase variants of the first character in `needle`
|
||||
UInt8 l{};
|
||||
UInt8 u{};
|
||||
|
||||
#if defined(__x86_64__)
|
||||
/// vectors filled with `l` and `u`, for determining leftmost position of the first symbol
|
||||
__m128i patl, patu;
|
||||
/// lower and uppercase vectors of first 16 characters of `needle`
|
||||
__m128i cachel = _mm_setzero_si128(), cacheu = _mm_setzero_si128();
|
||||
int cachemask{};
|
||||
|
||||
bool page_safe(const void * const ptr) const
|
||||
{
|
||||
return ((page_size - 1) & reinterpret_cast<std::uintptr_t>(ptr)) <= page_size - n;
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
StringSearcher(const char * const needle_, const std::size_t needle_size)
|
||||
@ -321,6 +338,7 @@ public:
|
||||
l = static_cast<UInt8>(std::tolower(*needle));
|
||||
u = static_cast<UInt8>(std::toupper(*needle));
|
||||
|
||||
#if defined(__x86_64__)
|
||||
patl = _mm_set1_epi8(l);
|
||||
patu = _mm_set1_epi8(u);
|
||||
|
||||
@ -339,10 +357,12 @@ public:
|
||||
++needle_pos;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool compare(const UInt8 * pos) const
|
||||
{
|
||||
#if defined(__x86_64__)
|
||||
if (page_safe(pos))
|
||||
{
|
||||
const auto v_haystack = _mm_loadu_si128(reinterpret_cast<const __m128i *>(pos));
|
||||
@ -370,6 +390,7 @@ public:
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (*pos == l || *pos == u)
|
||||
{
|
||||
@ -393,6 +414,7 @@ public:
|
||||
|
||||
while (haystack < haystack_end)
|
||||
{
|
||||
#if defined(__x86_64__)
|
||||
if (haystack + n <= haystack_end && page_safe(haystack))
|
||||
{
|
||||
const auto v_haystack = _mm_loadu_si128(reinterpret_cast<const __m128i *>(haystack));
|
||||
@ -441,6 +463,7 @@ public:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (haystack == haystack_end)
|
||||
return haystack_end;
|
||||
@ -465,29 +488,26 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// Case-sensitive searcher (both ASCII and UTF-8)
|
||||
template <bool ASCII> class StringSearcher<true, ASCII>
|
||||
template <bool ASCII>
|
||||
class StringSearcher<true, ASCII> : private StringSearcherBase
|
||||
{
|
||||
static constexpr auto n = sizeof(__m128i);
|
||||
|
||||
const int page_size = getpagesize();
|
||||
|
||||
private:
|
||||
/// string to be searched for
|
||||
const UInt8 * const needle;
|
||||
const std::size_t needle_size;
|
||||
const UInt8 * const needle_end = needle + needle_size;
|
||||
/// first character in `needle`
|
||||
UInt8 first{};
|
||||
|
||||
#if defined(__x86_64__)
|
||||
/// vector filled `first` for determining leftmost position of the first symbol
|
||||
__m128i pattern;
|
||||
/// vector of first 16 characters of `needle`
|
||||
__m128i cache = _mm_setzero_si128();
|
||||
int cachemask{};
|
||||
|
||||
bool page_safe(const void * const ptr) const
|
||||
{
|
||||
return ((page_size - 1) & reinterpret_cast<std::uintptr_t>(ptr)) <= page_size - n;
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
StringSearcher(const char * const needle_, const std::size_t needle_size)
|
||||
@ -497,6 +517,8 @@ public:
|
||||
return;
|
||||
|
||||
first = *needle;
|
||||
|
||||
#if defined(__x86_64__)
|
||||
pattern = _mm_set1_epi8(first);
|
||||
|
||||
auto needle_pos = needle;
|
||||
@ -512,10 +534,12 @@ public:
|
||||
++needle_pos;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool compare(const UInt8 * pos) const
|
||||
{
|
||||
#if defined(__x86_64__)
|
||||
if (page_safe(pos))
|
||||
{
|
||||
const auto v_haystack = _mm_loadu_si128(reinterpret_cast<const __m128i *>(pos));
|
||||
@ -541,6 +565,7 @@ public:
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (*pos == first)
|
||||
{
|
||||
@ -564,6 +589,7 @@ public:
|
||||
|
||||
while (haystack < haystack_end)
|
||||
{
|
||||
#if defined(__x86_64__)
|
||||
if (haystack + n <= haystack_end && page_safe(haystack))
|
||||
{
|
||||
/// find first character
|
||||
@ -611,6 +637,7 @@ public:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (haystack == haystack_end)
|
||||
return haystack_end;
|
||||
|
@ -1,7 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/Core/Types.h>
|
||||
#include <x86intrin.h>
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#include <x86intrin.h>
|
||||
#else
|
||||
#include <DB/Common/ARMHelpers.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace DB
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include <Poco/UTF8Encoding.h>
|
||||
#include <Poco/Unicode.h>
|
||||
#include <ext/range.hpp>
|
||||
#include <x86intrin.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
|
80
dbms/include/DB/Dictionaries/Embedded/RegionsHierarchies.h
Normal file
80
dbms/include/DB/Dictionaries/Embedded/RegionsHierarchies.h
Normal file
@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/Dictionaries/Embedded/RegionsHierarchy.h>
|
||||
#include <Poco/DirectoryIterator.h>
|
||||
|
||||
|
||||
/** Содержит несколько иерархий регионов, загружаемых из нескольких разных файлов.
|
||||
* Используется, чтобы поддержать несколько разных точек зрения о принадлежности регионов странам.
|
||||
* В первую очередь, для Крыма (Российская и Украинская точки зрения).
|
||||
*/
|
||||
class RegionsHierarchies
|
||||
{
|
||||
private:
|
||||
typedef std::unordered_map<std::string, RegionsHierarchy> Container;
|
||||
Container data;
|
||||
Logger * log = &Logger::get("RegionsHierarchies");
|
||||
|
||||
public:
|
||||
static constexpr auto required_key = "path_to_regions_hierarchy_file";
|
||||
|
||||
/** path должен указывать на файл с иерархией регионов "по-умолчанию". Она будет доступна по пустому ключу.
|
||||
* Кроме того, рядом ищутся файлы, к имени которых (до расширения, если есть) добавлен произвольный _suffix.
|
||||
* Такие файлы загружаются, и иерархия регионов кладётся по ключу suffix.
|
||||
*
|
||||
* Например, если указано /opt/geo/regions_hierarchy.txt,
|
||||
* то будет также загружен файл /opt/geo/regions_hierarchy_ua.txt, если такой есть - он будет доступен по ключу ua.
|
||||
*/
|
||||
RegionsHierarchies(const std::string & default_path = Poco::Util::Application::instance().config().getString(required_key))
|
||||
{
|
||||
LOG_DEBUG(log, "Adding default regions hierarchy from " << default_path);
|
||||
|
||||
data.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(""),
|
||||
std::forward_as_tuple(default_path));
|
||||
|
||||
std::string basename = Poco::Path(default_path).getBaseName();
|
||||
|
||||
Poco::Path dir_path = Poco::Path(default_path).absolute().parent();
|
||||
|
||||
Poco::DirectoryIterator dir_end;
|
||||
for (Poco::DirectoryIterator dir_it(dir_path); dir_it != dir_end; ++dir_it)
|
||||
{
|
||||
std::string other_basename = dir_it.path().getBaseName();
|
||||
|
||||
if (0 == other_basename.compare(0, basename.size(), basename) && other_basename.size() > basename.size() + 1)
|
||||
{
|
||||
if (other_basename[basename.size()] != '_')
|
||||
continue;
|
||||
|
||||
std::string suffix = other_basename.substr(basename.size() + 1);
|
||||
|
||||
LOG_DEBUG(log, "Adding regions hierarchy from " << dir_it->path() << ", key: " << suffix);
|
||||
|
||||
data.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(suffix),
|
||||
std::forward_as_tuple(dir_it->path()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Перезагружает, при необходимости, все иерархии регионов.
|
||||
*/
|
||||
void reload()
|
||||
{
|
||||
for (auto & elem : data)
|
||||
elem.second.reload();
|
||||
}
|
||||
|
||||
|
||||
const RegionsHierarchy & get(const std::string & key) const
|
||||
{
|
||||
auto it = data.find(key);
|
||||
|
||||
if (data.end() == it)
|
||||
throw Poco::Exception("There is no regions hierarchy for key " + key);
|
||||
|
||||
return it->second;
|
||||
}
|
||||
};
|
301
dbms/include/DB/Dictionaries/Embedded/RegionsHierarchy.h
Normal file
301
dbms/include/DB/Dictionaries/Embedded/RegionsHierarchy.h
Normal file
@ -0,0 +1,301 @@
|
||||
#pragma once
|
||||
|
||||
#include <Poco/Util/Application.h>
|
||||
#include <Poco/Exception.h>
|
||||
#include <Poco/File.h>
|
||||
|
||||
#include <common/logger_useful.h>
|
||||
#include <common/singleton.h>
|
||||
|
||||
#include <DB/IO/ReadBufferFromFile.h>
|
||||
#include <DB/IO/ReadHelpers.h>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#define REGION_TYPE_CITY 6
|
||||
#define REGION_TYPE_AREA 5
|
||||
#define REGION_TYPE_DISTRICT 4
|
||||
#define REGION_TYPE_COUNTRY 3
|
||||
#define REGION_TYPE_CONTINENT 1
|
||||
|
||||
|
||||
/** Класс, позволяющий узнавать, принадлежит ли регион с одним RegionID региону с другим RegionID.
|
||||
* Информацию об иерархии регионов загружает из текстового файла.
|
||||
* Умеет, по запросу, обновлять данные.
|
||||
*/
|
||||
class RegionsHierarchy : private boost::noncopyable
|
||||
{
|
||||
private:
|
||||
std::string path;
|
||||
time_t file_modification_time;
|
||||
Logger * log;
|
||||
|
||||
typedef Int32 RegionID;
|
||||
typedef Int8 RegionType;
|
||||
typedef Int8 RegionDepth;
|
||||
typedef UInt32 RegionPopulation;
|
||||
|
||||
/// отношение parent; 0, если родителей нет - обычная lookup таблица.
|
||||
typedef std::vector<RegionID> RegionParents;
|
||||
/// тип региона
|
||||
typedef std::vector<RegionType> RegionTypes;
|
||||
/// глубина в дереве, начиная от страны (страна: 1, корень: 0)
|
||||
typedef std::vector<RegionDepth> RegionDepths;
|
||||
/// население региона. Если больше 2^32 - 1, то приравнивается к этому максимуму.
|
||||
typedef std::vector<RegionPopulation> RegionPopulations;
|
||||
|
||||
/// регион -> родительский регион
|
||||
RegionParents parents;
|
||||
/// регион -> город, включающий его или 0, если такого нет
|
||||
RegionParents city;
|
||||
/// регион -> страна, включающая его или 0, если такого нет
|
||||
RegionParents country;
|
||||
/// регион -> область, включающая его или 0, если такой нет
|
||||
RegionParents area;
|
||||
/// регион -> округ, включающий его или 0, если такого нет
|
||||
RegionParents district;
|
||||
/// регион -> континет, включающий его или 0, если такого нет
|
||||
RegionParents continent;
|
||||
|
||||
/// регион -> население или 0, если неизвестно.
|
||||
RegionPopulations populations;
|
||||
|
||||
/// регион - глубина в дереве
|
||||
RegionDepths depths;
|
||||
|
||||
public:
|
||||
RegionsHierarchy(const std::string & path_ = Poco::Util::Application::instance().config().getString("path_to_regions_hierarchy_file"))
|
||||
: path(path_), file_modification_time(0), log(&Logger::get("RegionsHierarchy"))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// Перезагружает, при необходимости, иерархию регионов. Непотокобезопасно.
|
||||
void reload()
|
||||
{
|
||||
time_t new_modification_time = Poco::File(path).getLastModified().epochTime();
|
||||
if (new_modification_time <= file_modification_time)
|
||||
return;
|
||||
file_modification_time = new_modification_time;
|
||||
|
||||
LOG_DEBUG(log, "Reloading regions hierarchy");
|
||||
|
||||
const size_t initial_size = 10000;
|
||||
|
||||
RegionParents new_parents(initial_size);
|
||||
RegionParents new_city(initial_size);
|
||||
RegionParents new_country(initial_size);
|
||||
RegionParents new_area(initial_size);
|
||||
RegionParents new_district(initial_size);
|
||||
RegionParents new_continent(initial_size);
|
||||
RegionPopulations new_populations(initial_size);
|
||||
RegionDepths new_depths(initial_size);
|
||||
RegionTypes types(initial_size);
|
||||
|
||||
DB::ReadBufferFromFile in(path);
|
||||
|
||||
RegionID max_region_id = 0;
|
||||
while (!in.eof())
|
||||
{
|
||||
RegionID region_id = 0;
|
||||
RegionID parent_id = 0;
|
||||
RegionType type = 0;
|
||||
RegionPopulation population = 0;
|
||||
|
||||
DB::readIntText(region_id, in);
|
||||
DB::assertString("\t", in);
|
||||
DB::readIntText(parent_id, in);
|
||||
DB::assertString("\t", in);
|
||||
DB::readIntText(type, in);
|
||||
|
||||
/** Далее может быть перевод строки (старый вариант)
|
||||
* или таб, население региона, перевод строки (новый вариант).
|
||||
*/
|
||||
if (!in.eof() && *in.position() == '\t')
|
||||
{
|
||||
++in.position();
|
||||
UInt64 population_big = 0;
|
||||
DB::readIntText(population_big, in);
|
||||
population = population_big > std::numeric_limits<RegionPopulation>::max()
|
||||
? std::numeric_limits<RegionPopulation>::max()
|
||||
: population_big;
|
||||
}
|
||||
DB::assertString("\n", in);
|
||||
|
||||
if (region_id <= 0)
|
||||
continue;
|
||||
|
||||
if (parent_id < 0)
|
||||
parent_id = 0;
|
||||
|
||||
if (region_id > max_region_id)
|
||||
{
|
||||
max_region_id = region_id;
|
||||
|
||||
while (region_id >= static_cast<int>(new_parents.size()))
|
||||
{
|
||||
new_parents.resize(new_parents.size() * 2);
|
||||
new_populations.resize(new_parents.size());
|
||||
types.resize(new_parents.size());
|
||||
}
|
||||
}
|
||||
|
||||
new_parents[region_id] = parent_id;
|
||||
new_populations[region_id] = population;
|
||||
types[region_id] = type;
|
||||
}
|
||||
|
||||
new_parents .resize(max_region_id + 1);
|
||||
new_city .resize(max_region_id + 1);
|
||||
new_country .resize(max_region_id + 1);
|
||||
new_area .resize(max_region_id + 1);
|
||||
new_district .resize(max_region_id + 1);
|
||||
new_continent .resize(max_region_id + 1);
|
||||
new_populations .resize(max_region_id + 1);
|
||||
new_depths .resize(max_region_id + 1);
|
||||
types .resize(max_region_id + 1);
|
||||
|
||||
/// пропишем города и страны для регионов
|
||||
for (RegionID i = 0; i <= max_region_id; ++i)
|
||||
{
|
||||
if (types[i] == REGION_TYPE_CITY)
|
||||
new_city[i] = i;
|
||||
|
||||
if (types[i] == REGION_TYPE_AREA)
|
||||
new_area[i] = i;
|
||||
|
||||
if (types[i] == REGION_TYPE_DISTRICT)
|
||||
new_district[i] = i;
|
||||
|
||||
if (types[i] == REGION_TYPE_COUNTRY)
|
||||
new_country[i] = i;
|
||||
|
||||
if (types[i] == REGION_TYPE_CONTINENT)
|
||||
{
|
||||
new_continent[i] = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
RegionDepth depth = 0;
|
||||
RegionID current = i;
|
||||
while (true)
|
||||
{
|
||||
++depth;
|
||||
|
||||
current = new_parents[current];
|
||||
if (current == 0)
|
||||
break;
|
||||
|
||||
if (current > max_region_id)
|
||||
throw Poco::Exception("Logical error in regions hierarchy: region " + DB::toString(current) + " (specified as parent) doesn't exist");
|
||||
|
||||
if (types[current] == REGION_TYPE_CITY)
|
||||
new_city[i] = current;
|
||||
|
||||
if (types[current] == REGION_TYPE_AREA)
|
||||
new_area[i] = current;
|
||||
|
||||
if (types[current] == REGION_TYPE_DISTRICT)
|
||||
new_district[i] = current;
|
||||
|
||||
if (types[current] == REGION_TYPE_COUNTRY)
|
||||
new_country[i] = current;
|
||||
|
||||
if (types[current] == REGION_TYPE_CONTINENT)
|
||||
{
|
||||
new_continent[i] = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
new_depths[i] = depth;
|
||||
}
|
||||
|
||||
parents.swap(new_parents);
|
||||
country.swap(new_country);
|
||||
city.swap(new_city);
|
||||
area.swap(new_area);
|
||||
district.swap(new_district);
|
||||
continent.swap(new_continent);
|
||||
populations.swap(new_populations);
|
||||
depths.swap(new_depths);
|
||||
}
|
||||
|
||||
|
||||
bool in(RegionID lhs, RegionID rhs) const
|
||||
{
|
||||
if (static_cast<size_t>(lhs) >= parents.size())
|
||||
return false;
|
||||
|
||||
while (lhs != 0 && lhs != rhs)
|
||||
lhs = parents[lhs];
|
||||
|
||||
return lhs != 0;
|
||||
}
|
||||
|
||||
RegionID toCity(RegionID region) const
|
||||
{
|
||||
if (static_cast<size_t>(region) >= city.size())
|
||||
return 0;
|
||||
return city[region];
|
||||
}
|
||||
|
||||
RegionID toCountry(RegionID region) const
|
||||
{
|
||||
if (static_cast<size_t>(region) >= country.size())
|
||||
return 0;
|
||||
return country[region];
|
||||
}
|
||||
|
||||
RegionID toArea(RegionID region) const
|
||||
{
|
||||
if (static_cast<size_t>(region) >= area.size())
|
||||
return 0;
|
||||
return area[region];
|
||||
}
|
||||
|
||||
RegionID toDistrict(RegionID region) const
|
||||
{
|
||||
if (static_cast<size_t>(region) >= district.size())
|
||||
return 0;
|
||||
return district[region];
|
||||
}
|
||||
|
||||
RegionID toContinent(RegionID region) const
|
||||
{
|
||||
if (static_cast<size_t>(region) >= continent.size())
|
||||
return 0;
|
||||
return continent[region];
|
||||
}
|
||||
|
||||
RegionID toParent(RegionID region) const
|
||||
{
|
||||
if (static_cast<size_t>(region) >= parents.size())
|
||||
return 0;
|
||||
return parents[region];
|
||||
}
|
||||
|
||||
RegionDepth getDepth(RegionID region) const
|
||||
{
|
||||
if (static_cast<size_t>(region) >= depths.size())
|
||||
return 0;
|
||||
return depths[region];
|
||||
}
|
||||
|
||||
RegionPopulation getPopulation(RegionID region) const
|
||||
{
|
||||
if (static_cast<size_t>(region) >= populations.size())
|
||||
return 0;
|
||||
return populations[region];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class RegionsHierarchySingleton : public Singleton<RegionsHierarchySingleton>, public RegionsHierarchy
|
||||
{
|
||||
friend class Singleton<RegionsHierarchySingleton>;
|
||||
protected:
|
||||
RegionsHierarchySingleton()
|
||||
{
|
||||
}
|
||||
};
|
208
dbms/include/DB/Dictionaries/Embedded/RegionsNames.h
Normal file
208
dbms/include/DB/Dictionaries/Embedded/RegionsNames.h
Normal file
@ -0,0 +1,208 @@
|
||||
#pragma once
|
||||
|
||||
#include <google/dense_hash_map>
|
||||
|
||||
#include <Poco/File.h>
|
||||
#include <Poco/NumberParser.h>
|
||||
#include <Poco/Util/Application.h>
|
||||
#include <Poco/Exception.h>
|
||||
|
||||
#include <common/Common.h>
|
||||
#include <common/logger_useful.h>
|
||||
|
||||
#include <DB/Core/StringRef.h>
|
||||
#include <DB/IO/ReadHelpers.h>
|
||||
#include <DB/IO/ReadBufferFromFile.h>
|
||||
|
||||
/** @brief Класс, позволяющий узнавать по id региона его текстовое название на одном из поддерживаемых языков: ru, en, ua, by, kz, tr.
|
||||
*
|
||||
* Информацию об именах регионов загружает из текстовых файлов с названиями следующего формата:
|
||||
* regions_names_xx.txt,
|
||||
* где xx - одно из двух буквенных обозначений следующих поддерживаемых языков:
|
||||
* ru, en, ua, by, kz, tr.
|
||||
*
|
||||
* Умеет, по запросу, обновлять данные.
|
||||
*/
|
||||
class RegionsNames
|
||||
{
|
||||
public:
|
||||
enum class Language
|
||||
{
|
||||
RU = 0,
|
||||
EN,
|
||||
UA,
|
||||
BY,
|
||||
KZ,
|
||||
TR,
|
||||
};
|
||||
|
||||
private:
|
||||
static const size_t ROOT_LANGUAGE = 0;
|
||||
static const size_t SUPPORTED_LANGUAGES_COUNT = 6;
|
||||
static const size_t LANGUAGE_ALIASES_COUNT = 7;
|
||||
|
||||
static const char ** getSupportedLanguages()
|
||||
{
|
||||
static const char * res[] { "ru", "en", "ua", "by", "kz", "tr" };
|
||||
return res;
|
||||
}
|
||||
|
||||
struct language_alias { const char * const name; const Language lang; };
|
||||
static const language_alias * getLanguageAliases()
|
||||
{
|
||||
static constexpr const language_alias language_aliases[] {
|
||||
{ "ru", Language::RU },
|
||||
{ "en", Language::EN },
|
||||
{ "ua", Language::UA },
|
||||
{ "uk", Language::UA },
|
||||
{ "by", Language::BY },
|
||||
{ "kz", Language::KZ },
|
||||
{ "tr", Language::TR }
|
||||
};
|
||||
|
||||
return language_aliases;
|
||||
}
|
||||
|
||||
typedef int RegionID_t;
|
||||
|
||||
typedef std::vector<char> Chars_t;
|
||||
typedef std::vector<Chars_t> CharsForLanguageID_t;
|
||||
typedef std::vector<time_t> ModificationTimes_t;
|
||||
typedef std::vector<StringRef> StringRefs_t; /// Lookup table RegionID -> StringRef
|
||||
typedef std::vector<StringRefs_t> StringRefsForLanguageID_t;
|
||||
|
||||
public:
|
||||
static constexpr auto required_key = "path_to_regions_names_files";
|
||||
|
||||
RegionsNames(const std::string & directory_ = Poco::Util::Application::instance().config().getString(required_key))
|
||||
: directory(directory_)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/** @brief Перезагружает, при необходимости, имена регионов.
|
||||
*/
|
||||
void reload()
|
||||
{
|
||||
LOG_DEBUG(log, "Reloading regions names");
|
||||
|
||||
RegionID_t max_region_id = 0;
|
||||
for (size_t language_id = 0; language_id < SUPPORTED_LANGUAGES_COUNT; ++language_id)
|
||||
{
|
||||
const std::string & language = getSupportedLanguages()[language_id];
|
||||
std::string path = directory + "/regions_names_" + language + ".txt";
|
||||
|
||||
Poco::File file(path);
|
||||
time_t new_modification_time = file.getLastModified().epochTime();
|
||||
if (new_modification_time <= file_modification_times[language_id])
|
||||
continue;
|
||||
file_modification_times[language_id] = new_modification_time;
|
||||
|
||||
LOG_DEBUG(log, "Reloading regions names for language: " << language);
|
||||
|
||||
DB::ReadBufferFromFile in(path);
|
||||
|
||||
const size_t initial_size = 10000;
|
||||
|
||||
Chars_t new_chars;
|
||||
StringRefs_t new_names_refs(initial_size, StringRef("", 0));
|
||||
|
||||
/// Выделим непрерывный кусок памяти, которого хватит для хранения всех имён.
|
||||
new_chars.reserve(Poco::File(path).getSize());
|
||||
|
||||
while (!in.eof())
|
||||
{
|
||||
RegionID_t region_id;
|
||||
std::string region_name;
|
||||
|
||||
DB::readIntText(region_id, in);
|
||||
DB::assertString("\t", in);
|
||||
DB::readString(region_name, in);
|
||||
DB::assertString("\n", in);
|
||||
|
||||
if (region_id <= 0)
|
||||
continue;
|
||||
|
||||
size_t old_size = new_chars.size();
|
||||
|
||||
if (new_chars.capacity() < old_size + region_name.length() + 1)
|
||||
throw Poco::Exception("Logical error. Maybe size of file " + path + " is wrong.");
|
||||
|
||||
new_chars.resize(old_size + region_name.length() + 1);
|
||||
memcpy(&new_chars[old_size], region_name.c_str(), region_name.length() + 1);
|
||||
|
||||
if (region_id > max_region_id)
|
||||
max_region_id = region_id;
|
||||
|
||||
while (region_id >= static_cast<int>(new_names_refs.size()))
|
||||
new_names_refs.resize(new_names_refs.size() * 2, StringRef("", 0));
|
||||
|
||||
new_names_refs[region_id] = StringRef(&new_chars[old_size], region_name.length());
|
||||
}
|
||||
|
||||
chars[language_id].swap(new_chars);
|
||||
names_refs[language_id].swap(new_names_refs);
|
||||
}
|
||||
|
||||
for (size_t language_id = 0; language_id < SUPPORTED_LANGUAGES_COUNT; ++language_id)
|
||||
names_refs[language_id].resize(max_region_id + 1, StringRef("", 0));
|
||||
}
|
||||
|
||||
StringRef getRegionName(RegionID_t region_id, Language language = Language::RU) const
|
||||
{
|
||||
size_t language_id = static_cast<size_t>(language);
|
||||
|
||||
if (static_cast<size_t>(region_id) > names_refs[language_id].size())
|
||||
return StringRef("", 0);
|
||||
|
||||
StringRef ref = names_refs[language_id][region_id];
|
||||
|
||||
while (ref.size == 0 && language_id != ROOT_LANGUAGE)
|
||||
{
|
||||
static const size_t FALLBACK[] = { 0, 0, 0, 0, 0, 1 };
|
||||
language_id = FALLBACK[language_id];
|
||||
ref = names_refs[language_id][region_id];
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
static Language getLanguageEnum(const std::string & language)
|
||||
{
|
||||
if (language.size() == 2)
|
||||
{
|
||||
for (size_t i = 0; i < LANGUAGE_ALIASES_COUNT; ++i)
|
||||
{
|
||||
const auto & alias = getLanguageAliases()[i];
|
||||
if (language[0] == alias.name[0] && language[1] == alias.name[1])
|
||||
return alias.lang;
|
||||
}
|
||||
}
|
||||
throw Poco::Exception("Unsupported language for region name. Supported languages are: " + dumpSupportedLanguagesNames() + ".");
|
||||
}
|
||||
|
||||
static std::string dumpSupportedLanguagesNames()
|
||||
{
|
||||
std::string res = "";
|
||||
for (size_t i = 0; i < LANGUAGE_ALIASES_COUNT; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
res += ", ";
|
||||
res += '\'';
|
||||
res += getLanguageAliases()[i].name;
|
||||
res += '\'';
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string directory;
|
||||
ModificationTimes_t file_modification_times = ModificationTimes_t(SUPPORTED_LANGUAGES_COUNT);
|
||||
Logger * log = &Logger::get("RegionsNames");
|
||||
|
||||
/// Байты имен для каждого языка, уложенные подряд, разделенные нулями
|
||||
CharsForLanguageID_t chars = CharsForLanguageID_t(SUPPORTED_LANGUAGES_COUNT);
|
||||
|
||||
/// Отображение для каждого языка из id региона в указатель на диапазон байт имени
|
||||
StringRefsForLanguageID_t names_refs = StringRefsForLanguageID_t(SUPPORTED_LANGUAGES_COUNT);
|
||||
};
|
117
dbms/include/DB/Dictionaries/Embedded/TechDataHierarchy.h
Normal file
117
dbms/include/DB/Dictionaries/Embedded/TechDataHierarchy.h
Normal file
@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <common/logger_useful.h>
|
||||
#include <common/singleton.h>
|
||||
|
||||
#include <mysqlxx/PoolWithFailover.h>
|
||||
|
||||
|
||||
/** @brief Класс, позволяющий узнавать, принадлежит ли поисковая система или операционная система
|
||||
* другой поисковой или операционной системе, соответственно.
|
||||
* Информацию об иерархии регионов загружает из БД.
|
||||
*/
|
||||
class TechDataHierarchy
|
||||
{
|
||||
private:
|
||||
Logger * log;
|
||||
|
||||
UInt8 os_parent[256];
|
||||
UInt8 se_parent[256];
|
||||
|
||||
public:
|
||||
static constexpr auto required_key = "mysql_metrica";
|
||||
|
||||
TechDataHierarchy()
|
||||
: log(&Logger::get("TechDataHierarchy"))
|
||||
{
|
||||
LOG_DEBUG(log, "Loading tech data hierarchy.");
|
||||
|
||||
memset(os_parent, 0, sizeof(os_parent));
|
||||
memset(se_parent, 0, sizeof(se_parent));
|
||||
|
||||
mysqlxx::PoolWithFailover pool(required_key);
|
||||
mysqlxx::Pool::Entry conn = pool.Get();
|
||||
|
||||
{
|
||||
mysqlxx::Query q = conn->query("SELECT Id, COALESCE(Parent_Id, 0) FROM OS2");
|
||||
LOG_TRACE(log, q.str());
|
||||
mysqlxx::UseQueryResult res = q.use();
|
||||
while (mysqlxx::Row row = res.fetch())
|
||||
{
|
||||
UInt64 child = row[0].getUInt();
|
||||
UInt64 parent = row[1].getUInt();
|
||||
|
||||
if (child > 255 || parent > 255)
|
||||
throw Poco::Exception("Too large OS id (> 255).");
|
||||
|
||||
os_parent[child] = parent;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
mysqlxx::Query q = conn->query("SELECT Id, COALESCE(ParentId, 0) FROM SearchEngines");
|
||||
LOG_TRACE(log, q.str());
|
||||
mysqlxx::UseQueryResult res = q.use();
|
||||
while (mysqlxx::Row row = res.fetch())
|
||||
{
|
||||
UInt64 child = row[0].getUInt();
|
||||
UInt64 parent = row[1].getUInt();
|
||||
|
||||
if (child > 255 || parent > 255)
|
||||
throw Poco::Exception("Too large search engine id (> 255).");
|
||||
|
||||
se_parent[child] = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Отношение "принадлежит".
|
||||
bool isOSIn(UInt8 lhs, UInt8 rhs) const
|
||||
{
|
||||
while (lhs != rhs && os_parent[lhs])
|
||||
lhs = os_parent[lhs];
|
||||
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
bool isSEIn(UInt8 lhs, UInt8 rhs) const
|
||||
{
|
||||
while (lhs != rhs && se_parent[lhs])
|
||||
lhs = se_parent[lhs];
|
||||
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
|
||||
UInt8 OSToParent(UInt8 x) const
|
||||
{
|
||||
return os_parent[x];
|
||||
}
|
||||
|
||||
UInt8 SEToParent(UInt8 x) const
|
||||
{
|
||||
return se_parent[x];
|
||||
}
|
||||
|
||||
|
||||
/// К самому верхнему предку.
|
||||
UInt8 OSToMostAncestor(UInt8 x) const
|
||||
{
|
||||
while (os_parent[x])
|
||||
x = os_parent[x];
|
||||
return x;
|
||||
}
|
||||
|
||||
UInt8 SEToMostAncestor(UInt8 x) const
|
||||
{
|
||||
while (se_parent[x])
|
||||
x = se_parent[x];
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class TechDataHierarchySingleton : public Singleton<TechDataHierarchySingleton>, public TechDataHierarchy {};
|
@ -5,6 +5,10 @@
|
||||
#include <type_traits>
|
||||
#include <array>
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#include <smmintrin.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
@ -13,6 +17,7 @@ namespace DB
|
||||
* roundToExp2 - вниз до ближайшей степени двойки;
|
||||
* roundDuration - вниз до ближайшего из: 0, 1, 10, 30, 60, 120, 180, 240, 300, 600, 1200, 1800, 3600, 7200, 18000, 36000;
|
||||
* roundAge - вниз до ближайшего из: 0, 18, 25, 35, 45.
|
||||
*
|
||||
* round(x, N) - арифметическое округление (N = 0 по умолчанию).
|
||||
* ceil(x, N) - наименьшее число, которое не меньше x (N = 0 по умолчанию).
|
||||
* floor(x, N) - наибольшее число, которое не больше x (N = 0 по умолчанию).
|
||||
@ -163,6 +168,12 @@ namespace DB
|
||||
NullScale // возвращать нулевое значение
|
||||
};
|
||||
|
||||
#if !defined(_MM_FROUND_NINT)
|
||||
#define _MM_FROUND_NINT 0
|
||||
#define _MM_FROUND_FLOOR 1
|
||||
#define _MM_FROUND_CEIL 2
|
||||
#endif
|
||||
|
||||
/** Реализация низкоуровневых функций округления для целочисленных значений.
|
||||
*/
|
||||
template<typename T, int rounding_mode, ScaleMode scale_mode, typename Enable = void>
|
||||
@ -257,10 +268,11 @@ namespace DB
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
#if defined(__x86_64__)
|
||||
template <typename T>
|
||||
class BaseFloatRoundingComputation;
|
||||
|
||||
template<>
|
||||
template <>
|
||||
class BaseFloatRoundingComputation<Float32>
|
||||
{
|
||||
public:
|
||||
@ -298,7 +310,7 @@ namespace DB
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
template <>
|
||||
class BaseFloatRoundingComputation<Float64>
|
||||
{
|
||||
public:
|
||||
@ -522,6 +534,81 @@ namespace DB
|
||||
_mm_storeu_pd(out, val);
|
||||
}
|
||||
};
|
||||
#else
|
||||
/// Реализация для ARM. Не векторизована. Не исправляет отрицательные нули.
|
||||
|
||||
template <int mode>
|
||||
float roundWithMode(float x)
|
||||
{
|
||||
if (mode == _MM_FROUND_NINT) return roundf(x);
|
||||
if (mode == _MM_FROUND_FLOOR) return floorf(x);
|
||||
if (mode == _MM_FROUND_CEIL) return ceilf(x);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
template <int mode>
|
||||
double roundWithMode(double x)
|
||||
{
|
||||
if (mode == _MM_FROUND_NINT) return round(x);
|
||||
if (mode == _MM_FROUND_FLOOR) return floor(x);
|
||||
if (mode == _MM_FROUND_CEIL) return ceil(x);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class BaseFloatRoundingComputation
|
||||
{
|
||||
public:
|
||||
using Scale = T;
|
||||
static const size_t data_count = 1;
|
||||
|
||||
static inline void prepare(size_t scale, Scale & mm_scale)
|
||||
{
|
||||
mm_scale = static_cast<T>(scale);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, int rounding_mode, ScaleMode scale_mode>
|
||||
class FloatRoundingComputation;
|
||||
|
||||
template <typename T, int rounding_mode>
|
||||
class FloatRoundingComputation<T, rounding_mode, PositiveScale>
|
||||
: public BaseFloatRoundingComputation<T>
|
||||
{
|
||||
public:
|
||||
static inline void compute(const T * __restrict in, const T & scale, T * __restrict out)
|
||||
{
|
||||
out[0] = roundWithMode<rounding_mode>(in[0] * scale) / scale;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, int rounding_mode>
|
||||
class FloatRoundingComputation<T, rounding_mode, NegativeScale>
|
||||
: public BaseFloatRoundingComputation<T>
|
||||
{
|
||||
public:
|
||||
static inline void compute(const T * __restrict in, const T & scale, T * __restrict out)
|
||||
{
|
||||
out[0] = roundWithMode<rounding_mode>(in[0] / scale) * scale;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, int rounding_mode>
|
||||
class FloatRoundingComputation<T, rounding_mode, ZeroScale>
|
||||
: public BaseFloatRoundingComputation<T>
|
||||
{
|
||||
public:
|
||||
static inline void prepare(size_t scale, T & mm_scale)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void compute(const T * __restrict in, const T & scale, T * __restrict out)
|
||||
{
|
||||
out[0] = roundWithMode<rounding_mode>(in[0]);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
/** Реализация высокоуровневых функций округления.
|
||||
*/
|
||||
@ -906,7 +993,7 @@ namespace
|
||||
|
||||
/** Выбрать подходящий алгоритм обработки в зависимости от масштаба.
|
||||
*/
|
||||
template<typename T, template<typename> class U, int rounding_mode>
|
||||
template<typename T, template <typename> class U, int rounding_mode>
|
||||
struct Dispatcher
|
||||
{
|
||||
static inline void apply(Block & block, U<T> * col, const ColumnNumbers & arguments, size_t result)
|
||||
@ -1053,9 +1140,10 @@ namespace
|
||||
typedef FunctionUnaryArithmetic<RoundToExp2Impl, NameRoundToExp2> FunctionRoundToExp2;
|
||||
typedef FunctionUnaryArithmetic<RoundDurationImpl, NameRoundDuration> FunctionRoundDuration;
|
||||
typedef FunctionUnaryArithmetic<RoundAgeImpl, NameRoundAge> FunctionRoundAge;
|
||||
|
||||
typedef FunctionRounding<NameRound, _MM_FROUND_NINT> FunctionRound;
|
||||
typedef FunctionRounding<NameCeil, _MM_FROUND_CEIL> FunctionCeil;
|
||||
typedef FunctionRounding<NameFloor, _MM_FROUND_FLOOR> FunctionFloor;
|
||||
typedef FunctionRounding<NameCeil, _MM_FROUND_CEIL> FunctionCeil;
|
||||
|
||||
|
||||
struct PositiveMonotonicity
|
||||
|
@ -3,9 +3,9 @@
|
||||
#include <thread>
|
||||
#include <common/MultiVersion.h>
|
||||
#include <common/logger_useful.h>
|
||||
#include <statdaemons/RegionsHierarchies.h>
|
||||
#include <statdaemons/TechDataHierarchy.h>
|
||||
#include <statdaemons/RegionsNames.h>
|
||||
#include <DB/Dictionaries/Embedded/RegionsHierarchies.h>
|
||||
#include <DB/Dictionaries/Embedded/TechDataHierarchy.h>
|
||||
#include <DB/Dictionaries/Embedded/RegionsNames.h>
|
||||
#include <DB/Common/setThreadName.h>
|
||||
|
||||
|
||||
|
@ -1,269 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/Storages/StorageReplicatedMergeTree.h>
|
||||
#include <DB/Storages/MergeTree/AbandonableLockInZooKeeper.h>
|
||||
#include <DB/Storages/MergeTree/ReplicatedMergeTreeQuorumEntry.h>
|
||||
#include <DB/DataStreams/IBlockOutputStream.h>
|
||||
#include <DB/IO/Operators.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int TOO_LESS_LIVE_REPLICAS;
|
||||
extern const int UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE;
|
||||
extern const int CHECKSUM_DOESNT_MATCH;
|
||||
extern const int UNEXPECTED_ZOOKEEPER_ERROR;
|
||||
extern const int NO_ZOOKEEPER;
|
||||
}
|
||||
class StorageReplicatedMergeTree;
|
||||
|
||||
|
||||
class ReplicatedMergeTreeBlockOutputStream : public IBlockOutputStream
|
||||
{
|
||||
public:
|
||||
ReplicatedMergeTreeBlockOutputStream(StorageReplicatedMergeTree & storage_, const String & insert_id_, size_t quorum_)
|
||||
: storage(storage_), insert_id(insert_id_), quorum(quorum_),
|
||||
log(&Logger::get(storage.data.getLogName() + " (Replicated OutputStream)"))
|
||||
{
|
||||
/// Значение кворума 1 имеет такой же смысл, как если он отключён.
|
||||
if (quorum == 1)
|
||||
quorum = 0;
|
||||
}
|
||||
ReplicatedMergeTreeBlockOutputStream(StorageReplicatedMergeTree & storage_, const String & insert_id_, size_t quorum_);
|
||||
|
||||
void writePrefix() override
|
||||
{
|
||||
/// TODO Можно ли здесь не блокировать структуру таблицы?
|
||||
storage.data.delayInsertIfNeeded(&storage.restarting_thread->getWakeupEvent());
|
||||
}
|
||||
|
||||
void write(const Block & block) override
|
||||
{
|
||||
auto zookeeper = storage.getZooKeeper();
|
||||
|
||||
assertSessionIsNotExpired(zookeeper);
|
||||
|
||||
/** Если запись с кворумом, то проверим, что требуемое количество реплик сейчас живо,
|
||||
* а также что для всех предыдущих кусков, для которых требуется кворум, этот кворум достигнут.
|
||||
*/
|
||||
String quorum_status_path = storage.zookeeper_path + "/quorum/status";
|
||||
if (quorum)
|
||||
{
|
||||
/// Список живых реплик. Все они регистрируют эфемерную ноду для leader_election.
|
||||
auto live_replicas = zookeeper->getChildren(storage.zookeeper_path + "/leader_election");
|
||||
|
||||
if (live_replicas.size() < quorum)
|
||||
throw Exception("Number of alive replicas ("
|
||||
+ toString(live_replicas.size()) + ") is less than requested quorum (" + toString(quorum) + ").",
|
||||
ErrorCodes::TOO_LESS_LIVE_REPLICAS);
|
||||
|
||||
/** Достигнут ли кворум для последнего куска, для которого нужен кворум?
|
||||
* Запись всех кусков с включенным кворумом линейно упорядочена.
|
||||
* Это значит, что в любой момент времени может быть только один кусок,
|
||||
* для которого нужен, но ещё не достигнут кворум.
|
||||
* Информация о таком куске будет расположена в ноде /quorum/status.
|
||||
* Если кворум достигнут, то нода удаляется.
|
||||
*/
|
||||
|
||||
String quorum_status;
|
||||
bool quorum_unsatisfied = zookeeper->tryGet(quorum_status_path, quorum_status);
|
||||
|
||||
if (quorum_unsatisfied)
|
||||
throw Exception("Quorum for previous write has not been satisfied yet. Status: " + quorum_status, ErrorCodes::UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE);
|
||||
|
||||
/// Обе проверки неявно делаются и позже (иначе был бы race condition).
|
||||
}
|
||||
|
||||
auto part_blocks = storage.writer.splitBlockIntoParts(block);
|
||||
|
||||
for (auto & current_block : part_blocks)
|
||||
{
|
||||
assertSessionIsNotExpired(zookeeper);
|
||||
|
||||
++block_index;
|
||||
String block_id = insert_id.empty() ? "" : insert_id + "__" + toString(block_index);
|
||||
String month_name = toString(DateLUT::instance().toNumYYYYMMDD(DayNum_t(current_block.min_date)) / 100);
|
||||
|
||||
AbandonableLockInZooKeeper block_number_lock = storage.allocateBlockNumber(month_name);
|
||||
|
||||
Int64 part_number = block_number_lock.getNumber();
|
||||
|
||||
MergeTreeData::MutableDataPartPtr part = storage.writer.writeTempPart(current_block, part_number);
|
||||
String part_name = ActiveDataPartSet::getPartName(part->left_date, part->right_date, part->left, part->right, part->level);
|
||||
|
||||
/// Хэш от данных.
|
||||
SipHash hash;
|
||||
part->checksums.summaryDataChecksum(hash);
|
||||
union
|
||||
{
|
||||
char bytes[16];
|
||||
UInt64 lo, hi;
|
||||
} hash_value;
|
||||
hash.get128(hash_value.bytes);
|
||||
|
||||
String checksum(hash_value.bytes, 16);
|
||||
|
||||
/// Если в запросе не указан ID, возьмем в качестве ID хеш от данных. То есть, не вставляем одинаковые данные дважды.
|
||||
/// NOTE: Если такая дедупликация не нужна, можно вместо этого оставлять block_id пустым.
|
||||
/// Можно для этого сделать настройку или синтаксис в запросе (например, ID=null).
|
||||
if (block_id.empty())
|
||||
{
|
||||
block_id = toString(hash_value.lo) + "_" + toString(hash_value.hi);
|
||||
|
||||
if (block_id.empty())
|
||||
throw Exception("Logical error: block_id is empty.", ErrorCodes::LOGICAL_ERROR);
|
||||
}
|
||||
|
||||
LOG_DEBUG(log, "Wrote block " << part_number << " with ID " << block_id << ", " << current_block.block.rows() << " rows");
|
||||
|
||||
StorageReplicatedMergeTree::LogEntry log_entry;
|
||||
log_entry.type = StorageReplicatedMergeTree::LogEntry::GET_PART;
|
||||
log_entry.create_time = time(0);
|
||||
log_entry.source_replica = storage.replica_name;
|
||||
log_entry.new_part_name = part_name;
|
||||
log_entry.quorum = quorum;
|
||||
log_entry.block_id = block_id;
|
||||
|
||||
/// Одновременно добавим информацию о куске во все нужные места в ZooKeeper и снимем block_number_lock.
|
||||
|
||||
/// Информация о блоке.
|
||||
zkutil::Ops ops;
|
||||
auto acl = zookeeper->getDefaultACL();
|
||||
|
||||
ops.push_back(
|
||||
new zkutil::Op::Create(
|
||||
storage.zookeeper_path + "/blocks/" + block_id,
|
||||
"",
|
||||
acl,
|
||||
zkutil::CreateMode::Persistent));
|
||||
ops.push_back(
|
||||
new zkutil::Op::Create(
|
||||
storage.zookeeper_path + "/blocks/" + block_id + "/checksum",
|
||||
checksum,
|
||||
acl,
|
||||
zkutil::CreateMode::Persistent));
|
||||
ops.push_back(
|
||||
new zkutil::Op::Create(
|
||||
storage.zookeeper_path + "/blocks/" + block_id + "/number",
|
||||
toString(part_number),
|
||||
acl,
|
||||
zkutil::CreateMode::Persistent));
|
||||
|
||||
/// Информация о куске, в данных реплики.
|
||||
storage.checkPartAndAddToZooKeeper(part, ops, part_name);
|
||||
|
||||
/// Лог репликации.
|
||||
ops.push_back(new zkutil::Op::Create(
|
||||
storage.zookeeper_path + "/log/log-",
|
||||
log_entry.toString(),
|
||||
acl,
|
||||
zkutil::CreateMode::PersistentSequential));
|
||||
|
||||
/// Удаление информации о том, что номер блока используется для записи.
|
||||
block_number_lock.getUnlockOps(ops);
|
||||
|
||||
/** Если нужен кворум - создание узла, в котором отслеживается кворум.
|
||||
* (Если такой узел уже существует - значит кто-то успел одновременно сделать другую кворумную запись, но для неё кворум ещё не достигнут.
|
||||
* Делать в это время следующую кворумную запись нельзя.)
|
||||
*/
|
||||
if (quorum)
|
||||
{
|
||||
ReplicatedMergeTreeQuorumEntry quorum_entry;
|
||||
quorum_entry.part_name = part_name;
|
||||
quorum_entry.required_number_of_replicas = quorum;
|
||||
quorum_entry.replicas.insert(storage.replica_name);
|
||||
|
||||
/** В данный момент, этот узел будет содержать информацию о том, что текущая реплика получила кусок.
|
||||
* Когда другие реплики будут получать этот кусок (обычным способом, обрабатывая лог репликации),
|
||||
* они будут добавлять себя в содержимое этого узла.
|
||||
* Когда в нём будет информация о quorum количестве реплик, этот узел удаляется,
|
||||
* что говорит о том, что кворум достигнут.
|
||||
*/
|
||||
|
||||
ops.push_back(
|
||||
new zkutil::Op::Create(
|
||||
quorum_status_path,
|
||||
quorum_entry.toString(),
|
||||
acl,
|
||||
zkutil::CreateMode::Persistent));
|
||||
}
|
||||
|
||||
MergeTreeData::Transaction transaction; /// Если не получится добавить кусок в ZK, снова уберем его из рабочего набора.
|
||||
storage.data.renameTempPartAndAdd(part, nullptr, &transaction);
|
||||
|
||||
try
|
||||
{
|
||||
auto code = zookeeper->tryMulti(ops);
|
||||
if (code == ZOK)
|
||||
{
|
||||
transaction.commit();
|
||||
storage.merge_selecting_event.set();
|
||||
}
|
||||
else if (code == ZNODEEXISTS)
|
||||
{
|
||||
/// Если блок с таким ID уже есть в таблице, откатим его вставку.
|
||||
String expected_checksum;
|
||||
if (!block_id.empty() && zookeeper->tryGet(
|
||||
storage.zookeeper_path + "/blocks/" + block_id + "/checksum", expected_checksum))
|
||||
{
|
||||
LOG_INFO(log, "Block with ID " << block_id << " already exists; ignoring it (removing part " << part->name << ")");
|
||||
|
||||
/// Если данные отличались от тех, что были вставлены ранее с тем же ID, бросим исключение.
|
||||
if (expected_checksum != checksum)
|
||||
{
|
||||
if (!insert_id.empty())
|
||||
throw Exception("Attempt to insert block with same ID but different checksum", ErrorCodes::CHECKSUM_DOESNT_MATCH);
|
||||
else
|
||||
throw Exception("Logical error: got ZNODEEXISTS while inserting data, block ID is derived from checksum but checksum doesn't match", ErrorCodes::LOGICAL_ERROR);
|
||||
}
|
||||
|
||||
transaction.rollback();
|
||||
}
|
||||
else if (zookeeper->exists(quorum_status_path))
|
||||
{
|
||||
transaction.rollback();
|
||||
|
||||
throw Exception("Another quorum insert has been already started", ErrorCodes::UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE);
|
||||
}
|
||||
else
|
||||
{
|
||||
/// Сюда можем попасть также, если узел с кворумом существовал, но потом быстро был удалён.
|
||||
|
||||
throw Exception("Unexpected ZNODEEXISTS while adding block " + toString(part_number) + " with ID " + block_id + ": "
|
||||
+ zkutil::ZooKeeper::error2string(code), ErrorCodes::UNEXPECTED_ZOOKEEPER_ERROR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Exception("Unexpected error while adding block " + toString(part_number) + " with ID " + block_id + ": "
|
||||
+ zkutil::ZooKeeper::error2string(code), ErrorCodes::UNEXPECTED_ZOOKEEPER_ERROR);
|
||||
}
|
||||
}
|
||||
catch (const zkutil::KeeperException & e)
|
||||
{
|
||||
/** Если потерялось соединение, и мы не знаем, применились ли изменения, нельзя удалять локальный кусок:
|
||||
* если изменения применились, в /blocks/ появился вставленный блок, и его нельзя будет вставить снова.
|
||||
*/
|
||||
if (e.code == ZOPERATIONTIMEOUT ||
|
||||
e.code == ZCONNECTIONLOSS)
|
||||
{
|
||||
transaction.commit();
|
||||
storage.enqueuePartForCheck(part->name);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
if (quorum)
|
||||
{
|
||||
/// Дожидаемся достижения кворума. TODO Настраиваемый таймаут.
|
||||
LOG_TRACE(log, "Waiting for quorum");
|
||||
zookeeper->waitForDisappear(quorum_status_path);
|
||||
LOG_TRACE(log, "Quorum satisfied");
|
||||
}
|
||||
}
|
||||
}
|
||||
void writePrefix() override;
|
||||
void write(const Block & block) override;
|
||||
|
||||
private:
|
||||
StorageReplicatedMergeTree & storage;
|
||||
@ -272,14 +24,6 @@ private:
|
||||
size_t block_index = 0;
|
||||
|
||||
Logger * log;
|
||||
|
||||
|
||||
/// Позволяет проверить, что сессия в ZooKeeper ещё жива.
|
||||
void assertSessionIsNotExpired(zkutil::ZooKeeperPtr & zookeeper)
|
||||
{
|
||||
if (zookeeper->expired())
|
||||
throw Exception("ZooKeeper session has been expired.", ErrorCodes::NO_ZOOKEEPER);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,19 @@ private:
|
||||
|
||||
using Queue = std::list<LogEntryPtr>;
|
||||
|
||||
struct ByTime
|
||||
{
|
||||
bool operator()(const LogEntryPtr & lhs, const LogEntryPtr & rhs) const
|
||||
{
|
||||
return std::forward_as_tuple(lhs.get()->create_time, lhs.get())
|
||||
< std::forward_as_tuple(rhs.get()->create_time, rhs.get());
|
||||
}
|
||||
};
|
||||
|
||||
/// Для вычисления min_unprocessed_insert_time, max_processed_insert_time, по которым вычисляется отставание реплик.
|
||||
using InsertsByTime = std::set<LogEntryPtr, ByTime>;
|
||||
|
||||
|
||||
String zookeeper_path;
|
||||
String replica_path;
|
||||
String logger_name;
|
||||
@ -33,6 +46,10 @@ private:
|
||||
*/
|
||||
Queue queue;
|
||||
|
||||
InsertsByTime inserts_by_time;
|
||||
time_t min_unprocessed_insert_time = 0;
|
||||
time_t max_processed_insert_time = 0;
|
||||
|
||||
time_t last_queue_update = 0;
|
||||
|
||||
/// Куски, которые появятся в результате действий, выполняемых прямо сейчас фоновыми потоками (этих действий нет в очереди).
|
||||
@ -71,16 +88,28 @@ private:
|
||||
*/
|
||||
bool shouldExecuteLogEntry(const LogEntry & entry, String & out_postpone_reason, MergeTreeDataMerger & merger);
|
||||
|
||||
/// После удаления элемента очереди, обновить времена insert-ов в оперативке. Выполняется под queue_mutex.
|
||||
/// Возвращает информацию, какие времена изменились - эту информацию можно передать в updateTimesInZooKeeper.
|
||||
void updateTimesOnRemoval(const LogEntryPtr & entry, bool & min_unprocessed_insert_time_changed, bool & max_processed_insert_time_changed);
|
||||
|
||||
/// Обновить времена insert-ов в ZooKeeper.
|
||||
void updateTimesInZooKeeper(zkutil::ZooKeeperPtr zookeeper, bool min_unprocessed_insert_time_changed, bool max_processed_insert_time_changed);
|
||||
|
||||
public:
|
||||
ReplicatedMergeTreeQueue() {}
|
||||
|
||||
void initialize(const String & zookeeper_path_, const String & replica_path_, const String & logger_name_,
|
||||
const MergeTreeData::DataParts & parts, zkutil::ZooKeeperPtr zookeeper);
|
||||
|
||||
/** Вставить действие в конец очереди. */
|
||||
void insert(LogEntryPtr & entry);
|
||||
/** Вставить действие в конец очереди.
|
||||
* Для восстановления битых кусков во время работы.
|
||||
* Не вставляет само действие в ZK (сделайте это самостоятельно).
|
||||
*/
|
||||
void insert(zkutil::ZooKeeperPtr zookeeper, LogEntryPtr & entry);
|
||||
|
||||
/** Удалить действие с указанным куском (в качестве new_part_name) из очереди. */
|
||||
/** Удалить действие с указанным куском (в качестве new_part_name) из очереди.
|
||||
* Вызывается для невыполнимых действий в очереди - старых потерянных кусков.
|
||||
*/
|
||||
bool remove(zkutil::ZooKeeperPtr zookeeper, const String & part_name);
|
||||
|
||||
/** Скопировать новые записи из общего лога в очередь этой реплики. Установить log_pointer в соответствующее значение.
|
||||
@ -142,6 +171,9 @@ public:
|
||||
/// Получить данные элементов очереди.
|
||||
using LogEntriesData = std::vector<ReplicatedMergeTreeLogEntryData>;
|
||||
void getEntries(LogEntriesData & res);
|
||||
|
||||
/// Получить информацию о временах insert-ов.
|
||||
void getInsertTimes(time_t & out_min_unprocessed_insert_time, time_t & out_max_processed_insert_time) const;
|
||||
};
|
||||
|
||||
|
||||
|
@ -45,12 +45,6 @@ public:
|
||||
wakeup();
|
||||
}
|
||||
|
||||
void getReplicaDelays(time_t & out_absolute_delay, time_t & out_relative_delay) const
|
||||
{
|
||||
out_absolute_delay = absolute_delay.load(std::memory_order_relaxed);
|
||||
out_relative_delay = relative_delay.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
StorageReplicatedMergeTree & storage;
|
||||
Logger * log;
|
||||
@ -62,11 +56,6 @@ private:
|
||||
|
||||
std::thread thread;
|
||||
|
||||
/// Отставание реплики.
|
||||
std::atomic<time_t> absolute_delay {};
|
||||
std::atomic<time_t> relative_delay {};
|
||||
|
||||
|
||||
void run();
|
||||
|
||||
/// Запустить или остановить фоновые потоки. Используется для частичной переинициализации при пересоздании сессии в ZooKeeper.
|
||||
@ -85,9 +74,6 @@ private:
|
||||
|
||||
/// Запретить запись в таблицу и завершить все фоновые потоки.
|
||||
void goReadOnlyPermanently();
|
||||
|
||||
/// Получить информацию об отставании реплик.
|
||||
void checkReplicationDelays(time_t & out_absolute_delay, time_t & out_relative_delay);
|
||||
};
|
||||
|
||||
|
||||
|
@ -172,7 +172,7 @@ public:
|
||||
using LogEntriesData = std::vector<ReplicatedMergeTreeLogEntryData>;
|
||||
void getQueue(LogEntriesData & res, String & replica_name);
|
||||
|
||||
void getReplicaDelays(time_t & out_absolute_delay, time_t & out_relative_delay) const;
|
||||
void getReplicaDelays(time_t & out_absolute_delay, time_t & out_relative_delay);
|
||||
|
||||
private:
|
||||
void dropUnreplicatedPartition(const Field & partition, bool detach, const Settings & settings);
|
||||
@ -193,17 +193,9 @@ private:
|
||||
zkutil::ZooKeeperPtr current_zookeeper; /// Используйте только с помощью методов ниже.
|
||||
std::mutex current_zookeeper_mutex; /// Для пересоздания сессии в фоновом потоке.
|
||||
|
||||
zkutil::ZooKeeperPtr getZooKeeper()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(current_zookeeper_mutex);
|
||||
return current_zookeeper;
|
||||
}
|
||||
|
||||
void setZooKeeper(zkutil::ZooKeeperPtr zookeeper)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(current_zookeeper_mutex);
|
||||
current_zookeeper = zookeeper;
|
||||
}
|
||||
zkutil::ZooKeeperPtr tryGetZooKeeper();
|
||||
zkutil::ZooKeeperPtr getZooKeeper();
|
||||
void setZooKeeper(zkutil::ZooKeeperPtr zookeeper);
|
||||
|
||||
/// Если true, таблица в офлайновом режиме, и в нее нельзя писать.
|
||||
bool is_readonly = false;
|
||||
@ -421,6 +413,9 @@ private:
|
||||
/** Дождаться, пока указанная реплика выполнит указанное действие из лога.
|
||||
*/
|
||||
void waitForReplicaToProcessLogEntry(const String & replica_name, const LogEntry & entry);
|
||||
|
||||
/// Кинуть исключение, если таблица readonly.
|
||||
void assertNotReadonly() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -192,8 +192,6 @@ void Compiler::compile(
|
||||
" -I /usr/share/clickhouse/headers/contrib/libcpuid/include/"
|
||||
" -I /usr/share/clickhouse/headers/libs/libcommon/include/"
|
||||
" -I /usr/share/clickhouse/headers/libs/libmysqlxx/include/"
|
||||
" -I /usr/share/clickhouse/headers/libs/libstatdaemons/include/"
|
||||
" -I /usr/share/clickhouse/headers/libs/libstats/include/"
|
||||
" " << additional_compiler_flags <<
|
||||
" -o " << so_file_path << " " << cpp_file_path
|
||||
<< " 2>&1 || echo Exit code: $?";
|
||||
|
@ -72,8 +72,12 @@ struct ContextShared
|
||||
{
|
||||
Logger * log = &Logger::get("Context"); /// Логгер.
|
||||
|
||||
mutable Poco::Mutex mutex; /// Для доступа и модификации разделяемых объектов.
|
||||
mutable Poco::Mutex external_dictionaries_mutex; /// Для доступа к внешним словарям. Отдельный мьютекс, чтобы избежать локов при обращении сервера к самому себе.
|
||||
/// Для доступа и модификации разделяемых объектов. Рекурсивный mutex.
|
||||
mutable Poco::Mutex mutex;
|
||||
/// Для доступа к внешним словарям. Отдельный мьютекс, чтобы избежать локов при обращении сервера к самому себе.
|
||||
mutable std::mutex external_dictionaries_mutex;
|
||||
/// Отдельный mutex для переинициализации zookeeper-а. Эта операция может заблокироваться на существенное время и не должна мешать остальным.
|
||||
mutable std::mutex zookeeper_mutex;
|
||||
|
||||
mutable zkutil::ZooKeeperPtr zookeeper; /// Клиент для ZooKeeper.
|
||||
|
||||
@ -726,7 +730,7 @@ const Dictionaries & Context::getDictionariesImpl(const bool throw_on_error) con
|
||||
|
||||
const ExternalDictionaries & Context::getExternalDictionariesImpl(const bool throw_on_error) const
|
||||
{
|
||||
Poco::ScopedLock<Poco::Mutex> lock(shared->external_dictionaries_mutex);
|
||||
std::lock_guard<std::mutex> lock(shared->external_dictionaries_mutex);
|
||||
|
||||
if (!shared->external_dictionaries)
|
||||
{
|
||||
@ -829,7 +833,7 @@ void Context::resetCaches() const
|
||||
|
||||
void Context::setZooKeeper(zkutil::ZooKeeperPtr zookeeper)
|
||||
{
|
||||
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
|
||||
std::lock_guard<std::mutex> lock(shared->zookeeper_mutex);
|
||||
|
||||
if (shared->zookeeper)
|
||||
throw Exception("ZooKeeper client has already been set.", ErrorCodes::LOGICAL_ERROR);
|
||||
@ -839,7 +843,7 @@ void Context::setZooKeeper(zkutil::ZooKeeperPtr zookeeper)
|
||||
|
||||
zkutil::ZooKeeperPtr Context::getZooKeeper() const
|
||||
{
|
||||
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
|
||||
std::lock_guard<std::mutex> lock(shared->zookeeper_mutex);
|
||||
|
||||
if (shared->zookeeper && shared->zookeeper->expired())
|
||||
shared->zookeeper = shared->zookeeper->startNewSession();
|
||||
|
@ -88,16 +88,20 @@ ProcessListEntry::~ProcessListEntry()
|
||||
|
||||
/// Важен порядок удаления memory_tracker-ов.
|
||||
|
||||
String user = it->user;
|
||||
String query_id = it->query_id;
|
||||
bool is_cancelled = it->is_cancelled;
|
||||
|
||||
/// Здесь удаляется memory_tracker одного запроса.
|
||||
parent.cont.erase(it);
|
||||
|
||||
ProcessList::UserToQueries::iterator user_process_list = parent.user_to_queries.find(it->user);
|
||||
ProcessList::UserToQueries::iterator user_process_list = parent.user_to_queries.find(user);
|
||||
if (user_process_list != parent.user_to_queries.end())
|
||||
{
|
||||
/// В случае, если запрос отменяется, данные о нем удаляются из мапа в момент отмены, а не здесь.
|
||||
if (!it->is_cancelled && !it->query_id.empty())
|
||||
if (!is_cancelled && !query_id.empty())
|
||||
{
|
||||
ProcessListForUser::QueryToElement::iterator element = user_process_list->second.queries.find(it->query_id);
|
||||
ProcessListForUser::QueryToElement::iterator element = user_process_list->second.queries.find(query_id);
|
||||
if (element != user_process_list->second.queries.end())
|
||||
user_process_list->second.queries.erase(element);
|
||||
}
|
||||
|
70
dbms/src/Server/MetricsTransmitter.cpp
Normal file
70
dbms/src/Server/MetricsTransmitter.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#include "MetricsTransmitter.h"
|
||||
|
||||
#include <daemon/Daemon.h>
|
||||
#include <DB/Common/setThreadName.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
MetricsTransmitter::~MetricsTransmitter()
|
||||
{
|
||||
try
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{mutex};
|
||||
quit = true;
|
||||
}
|
||||
|
||||
cond.notify_one();
|
||||
|
||||
thread.join();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
DB::tryLogCurrentException(__FUNCTION__);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MetricsTransmitter::run()
|
||||
{
|
||||
setThreadName("ProfileEventsTx");
|
||||
|
||||
const auto get_next_minute = [] {
|
||||
return std::chrono::time_point_cast<std::chrono::minutes, std::chrono::system_clock>(
|
||||
std::chrono::system_clock::now() + std::chrono::minutes(1)
|
||||
);
|
||||
};
|
||||
|
||||
std::unique_lock<std::mutex> lock{mutex};
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (cond.wait_until(lock, get_next_minute(), [this] { return quit; }))
|
||||
break;
|
||||
|
||||
transmitCounters();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MetricsTransmitter::transmitCounters()
|
||||
{
|
||||
GraphiteWriter::KeyValueVector<size_t> key_vals{};
|
||||
key_vals.reserve(ProfileEvents::END);
|
||||
|
||||
for (size_t i = 0; i < ProfileEvents::END; ++i)
|
||||
{
|
||||
const auto counter = ProfileEvents::counters[i];
|
||||
const auto counter_increment = counter - prev_counters[i];
|
||||
prev_counters[i] = counter;
|
||||
|
||||
std::string key{ProfileEvents::getDescription(static_cast<ProfileEvents::Event>(i))};
|
||||
key_vals.emplace_back(event_path_prefix + key, counter_increment);
|
||||
}
|
||||
|
||||
Daemon::instance().writeToGraphite(key_vals);
|
||||
}
|
||||
|
||||
}
|
35
dbms/src/Server/MetricsTransmitter.h
Normal file
35
dbms/src/Server/MetricsTransmitter.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <DB/Common/ProfileEvents.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
/** Automatically sends difference of ProfileEvents to Graphite at beginning of every minute
|
||||
*/
|
||||
class MetricsTransmitter
|
||||
{
|
||||
public:
|
||||
~MetricsTransmitter();
|
||||
|
||||
private:
|
||||
void run();
|
||||
void transmitCounters();
|
||||
|
||||
/// Значения счётчиков при предыдущей отправке (или нули, если ни разу не отправляли).
|
||||
decltype(ProfileEvents::counters) prev_counters{};
|
||||
|
||||
bool quit = false;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cond;
|
||||
std::thread thread {&MetricsTransmitter::run, this};
|
||||
|
||||
static constexpr auto event_path_prefix = "ClickHouse.ProfileEvents.";
|
||||
};
|
||||
|
||||
}
|
@ -12,9 +12,9 @@
|
||||
#include <DB/IO/WriteHelpers.h>
|
||||
|
||||
#include <common/DateLUT.h>
|
||||
#include <statdaemons/RegionsHierarchy.h>
|
||||
#include <statdaemons/TechDataHierarchy.h>
|
||||
#include <statdaemons/Interests.h>
|
||||
|
||||
#include <DB/Dictionaries/Embedded/RegionsHierarchy.h>
|
||||
#include <DB/Dictionaries/Embedded/TechDataHierarchy.h>
|
||||
|
||||
|
||||
/// Код в основном взят из из OLAP-server. Здесь нужен только для парсинга значений атрибутов.
|
||||
|
96
dbms/src/Server/ReplicasStatusHandler.cpp
Normal file
96
dbms/src/Server/ReplicasStatusHandler.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "ReplicasStatusHandler.h"
|
||||
|
||||
#include <DB/Interpreters/Context.h>
|
||||
#include <DB/Storages/StorageReplicatedMergeTree.h>
|
||||
#include <DB/Common/HTMLForm.h>
|
||||
|
||||
#include <Poco/Net/HTTPServerRequest.h>
|
||||
#include <Poco/Net/HTTPServerResponse.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
|
||||
ReplicasStatusHandler::ReplicasStatusHandler(Context & context_)
|
||||
: context(context_)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void ReplicasStatusHandler::handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response)
|
||||
{
|
||||
try
|
||||
{
|
||||
HTMLForm params(request);
|
||||
|
||||
/// Даже в случае, когда отставание небольшое, выводить подробную информацию об отставании.
|
||||
bool verbose = params.get("verbose", "") == "1";
|
||||
|
||||
/// Собираем набор реплицируемых таблиц.
|
||||
Databases replicated_tables;
|
||||
{
|
||||
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
||||
|
||||
for (const auto & db : context.getDatabases())
|
||||
for (const auto & table : db.second)
|
||||
if (typeid_cast<const StorageReplicatedMergeTree *>(table.second.get()))
|
||||
replicated_tables[db.first][table.first] = table.second;
|
||||
}
|
||||
|
||||
const MergeTreeSettings & settings = context.getMergeTreeSettings();
|
||||
|
||||
bool ok = true;
|
||||
std::stringstream message;
|
||||
|
||||
for (const auto & db : replicated_tables)
|
||||
{
|
||||
for (auto & table : db.second)
|
||||
{
|
||||
time_t absolute_delay = 0;
|
||||
time_t relative_delay = 0;
|
||||
|
||||
static_cast<StorageReplicatedMergeTree &>(*table.second).getReplicaDelays(absolute_delay, relative_delay);
|
||||
|
||||
if ((settings.min_absolute_delay_to_close && absolute_delay >= static_cast<time_t>(settings.min_absolute_delay_to_close))
|
||||
|| (settings.min_relative_delay_to_close && relative_delay >= static_cast<time_t>(settings.min_relative_delay_to_close)))
|
||||
ok = false;
|
||||
|
||||
message << backQuoteIfNeed(db.first) << "." << backQuoteIfNeed(table.first)
|
||||
<< ":\tAbsolute delay: " << absolute_delay << ". Relative delay: " << relative_delay << ".\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (ok && !verbose)
|
||||
{
|
||||
const char * data = "Ok.\n";
|
||||
response.sendBuffer(data, strlen(data));
|
||||
}
|
||||
else
|
||||
{
|
||||
response.send() << message.rdbuf();
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
tryLogCurrentException("ReplicasStatusHandler");
|
||||
|
||||
try
|
||||
{
|
||||
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR);
|
||||
|
||||
if (!response.sent())
|
||||
{
|
||||
/// Ещё ничего не отправляли, и даже не знаем, нужно ли сжимать ответ.
|
||||
response.send() << getCurrentExceptionMessage(false) << std::endl;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_ERROR((&Logger::get("ReplicasStatusHandler")), "Cannot send exception to client");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
24
dbms/src/Server/ReplicasStatusHandler.h
Normal file
24
dbms/src/Server/ReplicasStatusHandler.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <Poco/Net/HTTPRequestHandler.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
class Context;
|
||||
|
||||
/// Отвечает "Ok.\n", если все реплики на этом сервере не слишком сильно отстают. Иначе выводит информацию об отставании. TODO Вынести в отдельный файл.
|
||||
class ReplicasStatusHandler : public Poco::Net::HTTPRequestHandler
|
||||
{
|
||||
private:
|
||||
Context & context;
|
||||
|
||||
public:
|
||||
ReplicasStatusHandler(Context & context_);
|
||||
|
||||
void handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response);
|
||||
};
|
||||
|
||||
|
||||
}
|
@ -1,27 +1,23 @@
|
||||
#include <sys/resource.h>
|
||||
#include <sys/file.h>
|
||||
|
||||
#include <Poco/Net/HTTPServerRequest.h>
|
||||
#include <Poco/Net/DNS.h>
|
||||
#include <Poco/Util/XMLConfiguration.h>
|
||||
#include <Poco/DirectoryIterator.h>
|
||||
|
||||
#include <common/ApplicationServerExt.h>
|
||||
#include <common/ErrorHandlers.h>
|
||||
#include <common/Revision.h>
|
||||
|
||||
#include <DB/Common/ConfigProcessor.h>
|
||||
#include <ext/scope_guard.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <DB/Common/Macros.h>
|
||||
#include <DB/Common/getFQDNOrHostName.h>
|
||||
#include <DB/Common/setThreadName.h>
|
||||
|
||||
#include <DB/Interpreters/loadMetadata.h>
|
||||
#include <DB/Interpreters/ProcessList.h>
|
||||
|
||||
#include <DB/Storages/System/StorageSystemNumbers.h>
|
||||
#include <DB/Storages/System/StorageSystemTables.h>
|
||||
#include <DB/Storages/System/StorageSystemParts.h>
|
||||
@ -38,129 +34,23 @@
|
||||
#include <DB/Storages/System/StorageSystemColumns.h>
|
||||
#include <DB/Storages/System/StorageSystemFunctions.h>
|
||||
#include <DB/Storages/System/StorageSystemClusters.h>
|
||||
#include <DB/Storages/StorageReplicatedMergeTree.h>
|
||||
|
||||
#include <DB/IO/copyData.h>
|
||||
#include <DB/IO/LimitReadBuffer.h>
|
||||
#include <DB/IO/WriteBufferFromFileDescriptor.h>
|
||||
#include <DB/IO/Operators.h>
|
||||
|
||||
#include <zkutil/ZooKeeper.h>
|
||||
|
||||
#include "Server.h"
|
||||
#include "HTTPHandler.h"
|
||||
#include "ReplicasStatusHandler.h"
|
||||
#include "InterserverIOHTTPHandler.h"
|
||||
#include "OLAPHTTPHandler.h"
|
||||
#include "TCPHandler.h"
|
||||
#include "MetricsTransmitter.h"
|
||||
#include "UsersConfigReloader.h"
|
||||
#include "StatusFile.h"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/** Automatically sends difference of ProfileEvents to Graphite at beginning of every minute
|
||||
*/
|
||||
class ProfileEventsTransmitter
|
||||
{
|
||||
public:
|
||||
~ProfileEventsTransmitter()
|
||||
{
|
||||
try
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{mutex};
|
||||
quit = true;
|
||||
}
|
||||
|
||||
cond.notify_one();
|
||||
|
||||
thread.join();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
DB::tryLogCurrentException(__FUNCTION__);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void run()
|
||||
{
|
||||
setThreadName("ProfileEventsTx");
|
||||
|
||||
const auto get_next_minute = [] {
|
||||
return std::chrono::time_point_cast<std::chrono::minutes, std::chrono::system_clock>(
|
||||
std::chrono::system_clock::now() + std::chrono::minutes(1)
|
||||
);
|
||||
};
|
||||
|
||||
std::unique_lock<std::mutex> lock{mutex};
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (cond.wait_until(lock, get_next_minute(), [this] { return quit; }))
|
||||
break;
|
||||
|
||||
transmitCounters();
|
||||
}
|
||||
}
|
||||
|
||||
void transmitCounters()
|
||||
{
|
||||
GraphiteWriter::KeyValueVector<size_t> key_vals{};
|
||||
key_vals.reserve(ProfileEvents::END);
|
||||
|
||||
for (size_t i = 0; i < ProfileEvents::END; ++i)
|
||||
{
|
||||
const auto counter = ProfileEvents::counters[i];
|
||||
const auto counter_increment = counter - prev_counters[i];
|
||||
prev_counters[i] = counter;
|
||||
|
||||
std::string key{ProfileEvents::getDescription(static_cast<ProfileEvents::Event>(i))};
|
||||
key_vals.emplace_back(event_path_prefix + key, counter_increment);
|
||||
}
|
||||
|
||||
Daemon::instance().writeToGraphite(key_vals);
|
||||
}
|
||||
|
||||
/// Значения счётчиков при предыдущей отправке (или нули, если ни разу не отправляли).
|
||||
decltype(ProfileEvents::counters) prev_counters{};
|
||||
|
||||
bool quit = false;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cond;
|
||||
std::thread thread{&ProfileEventsTransmitter::run, this};
|
||||
|
||||
static constexpr auto event_path_prefix = "ClickHouse.ProfileEvents.";
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
/** Каждые две секунды проверяет, не изменился ли конфиг.
|
||||
* Когда изменился, запускает на нем ConfigProcessor и вызывает setUsersConfig у контекста.
|
||||
* NOTE: Не перезагружает конфиг, если изменились другие файлы, влияющие на обработку конфига: metrika.xml
|
||||
* и содержимое conf.d и users.d. Это можно исправить, переместив проверку времени изменения файлов в ConfigProcessor.
|
||||
*/
|
||||
class UsersConfigReloader
|
||||
{
|
||||
public:
|
||||
UsersConfigReloader(const std::string & path, Context * context);
|
||||
~UsersConfigReloader();
|
||||
private:
|
||||
std::string path;
|
||||
Context * context;
|
||||
|
||||
time_t file_modification_time;
|
||||
std::atomic<bool> quit;
|
||||
std::thread thread;
|
||||
|
||||
Logger * log;
|
||||
|
||||
void reloadIfNewer(bool force);
|
||||
void run();
|
||||
};
|
||||
|
||||
|
||||
/// Отвечает "Ok.\n". Используется для проверки живости.
|
||||
class PingRequestHandler : public Poco::Net::HTTPRequestHandler
|
||||
@ -181,75 +71,6 @@ public:
|
||||
};
|
||||
|
||||
|
||||
/// Отвечает "Ok.\n", если все реплики на этом сервере не слишком сильно отстают. Иначе выводит информацию об отставании. TODO Вынести в отдельный файл.
|
||||
class ReplicasStatusHandler : public Poco::Net::HTTPRequestHandler
|
||||
{
|
||||
private:
|
||||
Context & context;
|
||||
|
||||
public:
|
||||
ReplicasStatusHandler(Context & context_)
|
||||
: context(context_)
|
||||
{
|
||||
}
|
||||
|
||||
void handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response)
|
||||
{
|
||||
try
|
||||
{
|
||||
/// Собираем набор реплицируемых таблиц.
|
||||
Databases replicated_tables;
|
||||
{
|
||||
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
||||
|
||||
for (const auto & db : context.getDatabases())
|
||||
for (const auto & table : db.second)
|
||||
if (typeid_cast<const StorageReplicatedMergeTree *>(table.second.get()))
|
||||
replicated_tables[db.first][table.first] = table.second;
|
||||
}
|
||||
|
||||
const MergeTreeSettings & settings = context.getMergeTreeSettings();
|
||||
|
||||
bool ok = /*true*/false;
|
||||
std::stringstream message;
|
||||
|
||||
for (const auto & db : replicated_tables)
|
||||
{
|
||||
for (const auto & table : db.second)
|
||||
{
|
||||
time_t absolute_delay = 0;
|
||||
time_t relative_delay = 0;
|
||||
|
||||
static_cast<const StorageReplicatedMergeTree &>(*table.second).getReplicaDelays(absolute_delay, relative_delay);
|
||||
|
||||
if ((settings.min_absolute_delay_to_close && absolute_delay >= static_cast<time_t>(settings.min_absolute_delay_to_close))
|
||||
|| (settings.min_relative_delay_to_close && relative_delay >= static_cast<time_t>(settings.min_relative_delay_to_close)))
|
||||
ok = false;
|
||||
|
||||
message << backQuoteIfNeed(db.first) << "." << backQuoteIfNeed(table.first)
|
||||
<< "\tAbsolute delay: " << absolute_delay << ". Relative delay: " << relative_delay << ".\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (ok)
|
||||
{
|
||||
const char * data = "Ok.\n";
|
||||
response.sendBuffer(data, strlen(data));
|
||||
}
|
||||
else
|
||||
{
|
||||
response.send() << message.rdbuf();
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
/// TODO Отправлять клиенту.
|
||||
tryLogCurrentException("ReplicasStatusHandler");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// Отвечает 404 с подробным объяснением.
|
||||
class NotFoundHandler : public Poco::Net::HTTPRequestHandler
|
||||
{
|
||||
@ -299,23 +120,29 @@ public:
|
||||
|
||||
const auto & uri = request.getURI();
|
||||
|
||||
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET
|
||||
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD)
|
||||
{
|
||||
if (uri == "/" || uri == "/ping")
|
||||
return new PingRequestHandler;
|
||||
else if (0 == uri.compare(0, strlen("/replicas_status"), "/replicas_status"))
|
||||
return new ReplicasStatusHandler(*server.global_context);
|
||||
}
|
||||
|
||||
if (uri.find('?') != std::string::npos
|
||||
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
|
||||
{
|
||||
return new HandlerType(server);
|
||||
}
|
||||
else if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET
|
||||
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD)
|
||||
|
||||
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET
|
||||
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD
|
||||
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
|
||||
{
|
||||
if (uri == "/" || uri == "/ping")
|
||||
return new PingRequestHandler;
|
||||
else if (uri == "/replicas_status")
|
||||
return new ReplicasStatusHandler(*server.global_context);
|
||||
else
|
||||
return new NotFoundHandler;
|
||||
return new NotFoundHandler;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
@ -338,201 +165,6 @@ public:
|
||||
};
|
||||
|
||||
|
||||
UsersConfigReloader::UsersConfigReloader(const std::string & path_, Context * context_)
|
||||
: path(path_), context(context_), file_modification_time(0), quit(false), log(&Logger::get("UsersConfigReloader"))
|
||||
{
|
||||
/// Если путь к конфигу не абсолютный, угадаем, относительно чего он задан.
|
||||
/// Сначала поищем его рядом с основным конфигом, потом - в текущей директории.
|
||||
if (path.empty() || path[0] != '/')
|
||||
{
|
||||
std::string main_config_path = Application::instance().config().getString("config-file", "config.xml");
|
||||
std::string config_dir = Poco::Path(main_config_path).parent().toString();
|
||||
if (Poco::File(config_dir + path).exists())
|
||||
path = config_dir + path;
|
||||
}
|
||||
|
||||
reloadIfNewer(true);
|
||||
thread = std::thread(&UsersConfigReloader::run, this);
|
||||
}
|
||||
|
||||
UsersConfigReloader::~UsersConfigReloader()
|
||||
{
|
||||
try
|
||||
{
|
||||
quit = true;
|
||||
thread.join();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
tryLogCurrentException("~UsersConfigReloader");
|
||||
}
|
||||
}
|
||||
|
||||
void UsersConfigReloader::run()
|
||||
{
|
||||
setThreadName("UserConfReload");
|
||||
|
||||
while (!quit)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
reloadIfNewer(false);
|
||||
}
|
||||
}
|
||||
|
||||
void UsersConfigReloader::reloadIfNewer(bool force)
|
||||
{
|
||||
Poco::File f(path);
|
||||
if (!f.exists())
|
||||
{
|
||||
if (force)
|
||||
throw Exception("Users config not found at: " + path, ErrorCodes::FILE_DOESNT_EXIST);
|
||||
if (file_modification_time)
|
||||
{
|
||||
LOG_ERROR(log, "Users config not found at: " << path);
|
||||
file_modification_time = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
time_t new_modification_time = f.getLastModified().epochTime();
|
||||
if (!force && new_modification_time == file_modification_time)
|
||||
return;
|
||||
file_modification_time = new_modification_time;
|
||||
|
||||
LOG_DEBUG(log, "Loading users config");
|
||||
|
||||
ConfigurationPtr config;
|
||||
|
||||
try
|
||||
{
|
||||
config = ConfigProcessor(!force).loadConfig(path);
|
||||
}
|
||||
catch (Poco::Exception & e)
|
||||
{
|
||||
if (force)
|
||||
throw;
|
||||
|
||||
LOG_ERROR(log, "Error loading users config: " << e.what() << ": " << e.displayText());
|
||||
return;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
if (force)
|
||||
throw;
|
||||
|
||||
LOG_ERROR(log, "Error loading users config.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
context->setUsersConfig(config);
|
||||
}
|
||||
catch (Exception & e)
|
||||
{
|
||||
if (force)
|
||||
throw;
|
||||
|
||||
LOG_ERROR(log, "Error updating users config: " << e.what() << ": " << e.displayText() << "\n" << e.getStackTrace().toString());
|
||||
}
|
||||
catch (Poco::Exception & e)
|
||||
{
|
||||
if (force)
|
||||
throw;
|
||||
|
||||
LOG_ERROR(log, "Error updating users config: " << e.what() << ": " << e.displayText());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
if (force)
|
||||
throw;
|
||||
|
||||
LOG_ERROR(log, "Error updating users config.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Обеспечивает, что с одной директорией с данными может одновременно работать не более одного сервера.
|
||||
*/
|
||||
class StatusFile : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
StatusFile(const std::string & path_)
|
||||
: path(path_)
|
||||
{
|
||||
/// Если файл уже существует. NOTE Незначительный race condition.
|
||||
if (Poco::File(path).exists())
|
||||
{
|
||||
std::string contents;
|
||||
{
|
||||
ReadBufferFromFile in(path, 1024);
|
||||
LimitReadBuffer limit_in(in, 1024);
|
||||
WriteBufferFromString out(contents);
|
||||
copyData(limit_in, out);
|
||||
}
|
||||
|
||||
if (!contents.empty())
|
||||
LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists - unclean restart. Contents:\n" << contents);
|
||||
else
|
||||
LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists and is empty - probably unclean hardware restart.");
|
||||
}
|
||||
|
||||
fd = open(path.c_str(), O_WRONLY | O_CREAT, 0666);
|
||||
|
||||
if (-1 == fd)
|
||||
throwFromErrno("Cannot open file " + path);
|
||||
|
||||
try
|
||||
{
|
||||
int flock_ret = flock(fd, LOCK_EX | LOCK_NB);
|
||||
if (-1 == flock_ret)
|
||||
{
|
||||
if (errno == EWOULDBLOCK)
|
||||
throw Exception("Cannot lock file " + path + ". Another server instance in same directory is already running.");
|
||||
else
|
||||
throwFromErrno("Cannot lock file " + path);
|
||||
}
|
||||
|
||||
if (0 != ftruncate(fd, 0))
|
||||
throwFromErrno("Cannot ftruncate " + path);
|
||||
|
||||
if (0 != lseek(fd, 0, SEEK_SET))
|
||||
throwFromErrno("Cannot lseek " + path);
|
||||
|
||||
/// Записываем в файл информацию о текущем экземпляре сервера.
|
||||
{
|
||||
WriteBufferFromFileDescriptor out(fd, 1024);
|
||||
out
|
||||
<< "PID: " << getpid() << "\n"
|
||||
<< "Started at: " << mysqlxx::DateTime(time(0)) << "\n"
|
||||
<< "Revision: " << Revision::get() << "\n";
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
close(fd);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
~StatusFile()
|
||||
{
|
||||
char buf[128];
|
||||
|
||||
if (0 != close(fd))
|
||||
LOG_ERROR(&Logger::get("StatusFile"), "Cannot close file " << path << ", errno: "
|
||||
<< errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf)));
|
||||
|
||||
if (0 != unlink(path.c_str()))
|
||||
LOG_ERROR(&Logger::get("StatusFile"), "Cannot unlink file " << path << ", errno: "
|
||||
<< errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf)));
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string path;
|
||||
int fd = -1;
|
||||
};
|
||||
|
||||
|
||||
int Server::main(const std::vector<std::string> & args)
|
||||
{
|
||||
Logger * log = &logger();
|
||||
@ -704,8 +336,8 @@ int Server::main(const std::vector<std::string> & args)
|
||||
);
|
||||
|
||||
{
|
||||
const auto profile_events_transmitter = config().getBool("use_graphite", true)
|
||||
? std::make_unique<ProfileEventsTransmitter>()
|
||||
const auto metrics_transmitter = config().getBool("use_graphite", true)
|
||||
? std::make_unique<MetricsTransmitter>()
|
||||
: nullptr;
|
||||
|
||||
const std::string listen_host = config().getString("listen_host", "::");
|
||||
|
@ -18,7 +18,7 @@
|
||||
#include <Poco/Net/TCPServerConnection.h>
|
||||
|
||||
#include <common/logger_useful.h>
|
||||
#include <statdaemons/daemon.h>
|
||||
#include <daemon/Daemon.h>
|
||||
#include <DB/Common/HTMLForm.h>
|
||||
|
||||
#include <DB/Interpreters/Context.h>
|
||||
|
97
dbms/src/Server/StatusFile.cpp
Normal file
97
dbms/src/Server/StatusFile.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
#include "StatusFile.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/file.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <Poco/File.h>
|
||||
#include <common/logger_useful.h>
|
||||
#include <common/Revision.h>
|
||||
#include <mysqlxx/DateTime.h>
|
||||
|
||||
#include <DB/IO/copyData.h>
|
||||
#include <DB/IO/ReadBufferFromFile.h>
|
||||
#include <DB/IO/LimitReadBuffer.h>
|
||||
#include <DB/IO/WriteBufferFromString.h>
|
||||
#include <DB/IO/WriteBufferFromFileDescriptor.h>
|
||||
#include <DB/IO/Operators.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
|
||||
StatusFile::StatusFile(const std::string & path_)
|
||||
: path(path_)
|
||||
{
|
||||
/// Если файл уже существует. NOTE Незначительный race condition.
|
||||
if (Poco::File(path).exists())
|
||||
{
|
||||
std::string contents;
|
||||
{
|
||||
ReadBufferFromFile in(path, 1024);
|
||||
LimitReadBuffer limit_in(in, 1024);
|
||||
WriteBufferFromString out(contents);
|
||||
copyData(limit_in, out);
|
||||
}
|
||||
|
||||
if (!contents.empty())
|
||||
LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists - unclean restart. Contents:\n" << contents);
|
||||
else
|
||||
LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists and is empty - probably unclean hardware restart.");
|
||||
}
|
||||
|
||||
fd = open(path.c_str(), O_WRONLY | O_CREAT, 0666);
|
||||
|
||||
if (-1 == fd)
|
||||
throwFromErrno("Cannot open file " + path);
|
||||
|
||||
try
|
||||
{
|
||||
int flock_ret = flock(fd, LOCK_EX | LOCK_NB);
|
||||
if (-1 == flock_ret)
|
||||
{
|
||||
if (errno == EWOULDBLOCK)
|
||||
throw Exception("Cannot lock file " + path + ". Another server instance in same directory is already running.");
|
||||
else
|
||||
throwFromErrno("Cannot lock file " + path);
|
||||
}
|
||||
|
||||
if (0 != ftruncate(fd, 0))
|
||||
throwFromErrno("Cannot ftruncate " + path);
|
||||
|
||||
if (0 != lseek(fd, 0, SEEK_SET))
|
||||
throwFromErrno("Cannot lseek " + path);
|
||||
|
||||
/// Записываем в файл информацию о текущем экземпляре сервера.
|
||||
{
|
||||
WriteBufferFromFileDescriptor out(fd, 1024);
|
||||
out
|
||||
<< "PID: " << getpid() << "\n"
|
||||
<< "Started at: " << mysqlxx::DateTime(time(0)) << "\n"
|
||||
<< "Revision: " << Revision::get() << "\n";
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
close(fd);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StatusFile::~StatusFile()
|
||||
{
|
||||
char buf[128];
|
||||
|
||||
if (0 != close(fd))
|
||||
LOG_ERROR(&Logger::get("StatusFile"), "Cannot close file " << path << ", errno: "
|
||||
<< errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf)));
|
||||
|
||||
if (0 != unlink(path.c_str()))
|
||||
LOG_ERROR(&Logger::get("StatusFile"), "Cannot unlink file " << path << ", errno: "
|
||||
<< errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf)));
|
||||
}
|
||||
|
||||
}
|
25
dbms/src/Server/StatusFile.h
Normal file
25
dbms/src/Server/StatusFile.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
|
||||
/** Обеспечивает, что с одной директорией с данными может одновременно работать не более одного сервера.
|
||||
*/
|
||||
class StatusFile : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
StatusFile(const std::string & path_);
|
||||
~StatusFile();
|
||||
|
||||
private:
|
||||
const std::string path;
|
||||
int fd = -1;
|
||||
};
|
||||
|
||||
|
||||
}
|
135
dbms/src/Server/UsersConfigReloader.cpp
Normal file
135
dbms/src/Server/UsersConfigReloader.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
#include "UsersConfigReloader.h"
|
||||
|
||||
#include <Poco/Util/Application.h>
|
||||
#include <Poco/File.h>
|
||||
|
||||
#include <common/logger_useful.h>
|
||||
|
||||
#include <DB/Interpreters/Context.h>
|
||||
#include <DB/Common/setThreadName.h>
|
||||
#include <DB/Common/ConfigProcessor.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
namespace ErrorCodes { extern const int FILE_DOESNT_EXIST; }
|
||||
|
||||
|
||||
UsersConfigReloader::UsersConfigReloader(const std::string & path_, Context * context_)
|
||||
: path(path_), context(context_), file_modification_time(0), quit(false), log(&Logger::get("UsersConfigReloader"))
|
||||
{
|
||||
/// Если путь к конфигу не абсолютный, угадаем, относительно чего он задан.
|
||||
/// Сначала поищем его рядом с основным конфигом, потом - в текущей директории.
|
||||
if (path.empty() || path[0] != '/')
|
||||
{
|
||||
std::string main_config_path = Poco::Util::Application::instance().config().getString("config-file", "config.xml");
|
||||
std::string config_dir = Poco::Path(main_config_path).parent().toString();
|
||||
if (Poco::File(config_dir + path).exists())
|
||||
path = config_dir + path;
|
||||
}
|
||||
|
||||
reloadIfNewer(true);
|
||||
thread = std::thread(&UsersConfigReloader::run, this);
|
||||
}
|
||||
|
||||
|
||||
UsersConfigReloader::~UsersConfigReloader()
|
||||
{
|
||||
try
|
||||
{
|
||||
quit = true;
|
||||
thread.join();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
tryLogCurrentException("~UsersConfigReloader");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UsersConfigReloader::run()
|
||||
{
|
||||
setThreadName("UserConfReload");
|
||||
|
||||
while (!quit)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
reloadIfNewer(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UsersConfigReloader::reloadIfNewer(bool force)
|
||||
{
|
||||
Poco::File f(path);
|
||||
if (!f.exists())
|
||||
{
|
||||
if (force)
|
||||
throw Exception("Users config not found at: " + path, ErrorCodes::FILE_DOESNT_EXIST);
|
||||
if (file_modification_time)
|
||||
{
|
||||
LOG_ERROR(log, "Users config not found at: " << path);
|
||||
file_modification_time = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
time_t new_modification_time = f.getLastModified().epochTime();
|
||||
if (!force && new_modification_time == file_modification_time)
|
||||
return;
|
||||
file_modification_time = new_modification_time;
|
||||
|
||||
LOG_DEBUG(log, "Loading users config");
|
||||
|
||||
ConfigurationPtr config;
|
||||
|
||||
try
|
||||
{
|
||||
config = ConfigProcessor(!force).loadConfig(path);
|
||||
}
|
||||
catch (Poco::Exception & e)
|
||||
{
|
||||
if (force)
|
||||
throw;
|
||||
|
||||
LOG_ERROR(log, "Error loading users config: " << e.what() << ": " << e.displayText());
|
||||
return;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
if (force)
|
||||
throw;
|
||||
|
||||
LOG_ERROR(log, "Error loading users config.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
context->setUsersConfig(config);
|
||||
}
|
||||
catch (Exception & e)
|
||||
{
|
||||
if (force)
|
||||
throw;
|
||||
|
||||
LOG_ERROR(log, "Error updating users config: " << e.what() << ": " << e.displayText() << "\n" << e.getStackTrace().toString());
|
||||
}
|
||||
catch (Poco::Exception & e)
|
||||
{
|
||||
if (force)
|
||||
throw;
|
||||
|
||||
LOG_ERROR(log, "Error updating users config: " << e.what() << ": " << e.displayText());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
if (force)
|
||||
throw;
|
||||
|
||||
LOG_ERROR(log, "Error updating users config.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
41
dbms/src/Server/UsersConfigReloader.h
Normal file
41
dbms/src/Server/UsersConfigReloader.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <time.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
|
||||
namespace Poco { class Logger; }
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
class Context;
|
||||
|
||||
/** Каждые две секунды проверяет, не изменился ли конфиг.
|
||||
* Когда изменился, запускает на нем ConfigProcessor и вызывает setUsersConfig у контекста.
|
||||
* NOTE: Не перезагружает конфиг, если изменились другие файлы, влияющие на обработку конфига: metrika.xml
|
||||
* и содержимое conf.d и users.d. Это можно исправить, переместив проверку времени изменения файлов в ConfigProcessor.
|
||||
*/
|
||||
class UsersConfigReloader
|
||||
{
|
||||
public:
|
||||
UsersConfigReloader(const std::string & path, Context * context);
|
||||
~UsersConfigReloader();
|
||||
|
||||
private:
|
||||
std::string path;
|
||||
Context * context;
|
||||
|
||||
time_t file_modification_time;
|
||||
std::atomic<bool> quit;
|
||||
std::thread thread;
|
||||
|
||||
Poco::Logger * log;
|
||||
|
||||
void reloadIfNewer(bool force);
|
||||
void run();
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,279 @@
|
||||
#include <DB/Storages/StorageReplicatedMergeTree.h>
|
||||
#include <DB/Storages/MergeTree/AbandonableLockInZooKeeper.h>
|
||||
#include <DB/Storages/MergeTree/ReplicatedMergeTreeQuorumEntry.h>
|
||||
#include <DB/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.h>
|
||||
#include <DB/DataStreams/IBlockOutputStream.h>
|
||||
#include <DB/IO/Operators.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int TOO_LESS_LIVE_REPLICAS;
|
||||
extern const int UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE;
|
||||
extern const int CHECKSUM_DOESNT_MATCH;
|
||||
extern const int UNEXPECTED_ZOOKEEPER_ERROR;
|
||||
extern const int NO_ZOOKEEPER;
|
||||
}
|
||||
|
||||
|
||||
ReplicatedMergeTreeBlockOutputStream::ReplicatedMergeTreeBlockOutputStream(
|
||||
StorageReplicatedMergeTree & storage_, const String & insert_id_, size_t quorum_)
|
||||
: storage(storage_), insert_id(insert_id_), quorum(quorum_),
|
||||
log(&Logger::get(storage.data.getLogName() + " (Replicated OutputStream)"))
|
||||
{
|
||||
/// Значение кворума 1 имеет такой же смысл, как если он отключён.
|
||||
if (quorum == 1)
|
||||
quorum = 0;
|
||||
}
|
||||
|
||||
|
||||
void ReplicatedMergeTreeBlockOutputStream::writePrefix()
|
||||
{
|
||||
/// TODO Можно ли здесь не блокировать структуру таблицы?
|
||||
storage.data.delayInsertIfNeeded(&storage.restarting_thread->getWakeupEvent());
|
||||
}
|
||||
|
||||
|
||||
/// Позволяет проверить, что сессия в ZooKeeper ещё жива.
|
||||
static void assertSessionIsNotExpired(zkutil::ZooKeeperPtr & zookeeper)
|
||||
{
|
||||
if (!zookeeper)
|
||||
throw Exception("No ZooKeeper session.", ErrorCodes::NO_ZOOKEEPER);
|
||||
|
||||
if (zookeeper->expired())
|
||||
throw Exception("ZooKeeper session has been expired.", ErrorCodes::NO_ZOOKEEPER);
|
||||
}
|
||||
|
||||
|
||||
void ReplicatedMergeTreeBlockOutputStream::write(const Block & block)
|
||||
{
|
||||
auto zookeeper = storage.getZooKeeper();
|
||||
|
||||
assertSessionIsNotExpired(zookeeper);
|
||||
|
||||
/** Если запись с кворумом, то проверим, что требуемое количество реплик сейчас живо,
|
||||
* а также что для всех предыдущих кусков, для которых требуется кворум, этот кворум достигнут.
|
||||
*/
|
||||
String quorum_status_path = storage.zookeeper_path + "/quorum/status";
|
||||
if (quorum)
|
||||
{
|
||||
/// Список живых реплик. Все они регистрируют эфемерную ноду для leader_election.
|
||||
auto live_replicas = zookeeper->getChildren(storage.zookeeper_path + "/leader_election");
|
||||
|
||||
if (live_replicas.size() < quorum)
|
||||
throw Exception("Number of alive replicas ("
|
||||
+ toString(live_replicas.size()) + ") is less than requested quorum (" + toString(quorum) + ").",
|
||||
ErrorCodes::TOO_LESS_LIVE_REPLICAS);
|
||||
|
||||
/** Достигнут ли кворум для последнего куска, для которого нужен кворум?
|
||||
* Запись всех кусков с включенным кворумом линейно упорядочена.
|
||||
* Это значит, что в любой момент времени может быть только один кусок,
|
||||
* для которого нужен, но ещё не достигнут кворум.
|
||||
* Информация о таком куске будет расположена в ноде /quorum/status.
|
||||
* Если кворум достигнут, то нода удаляется.
|
||||
*/
|
||||
|
||||
String quorum_status;
|
||||
bool quorum_unsatisfied = zookeeper->tryGet(quorum_status_path, quorum_status);
|
||||
|
||||
if (quorum_unsatisfied)
|
||||
throw Exception("Quorum for previous write has not been satisfied yet. Status: " + quorum_status, ErrorCodes::UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE);
|
||||
|
||||
/// Обе проверки неявно делаются и позже (иначе был бы race condition).
|
||||
}
|
||||
|
||||
auto part_blocks = storage.writer.splitBlockIntoParts(block);
|
||||
|
||||
for (auto & current_block : part_blocks)
|
||||
{
|
||||
assertSessionIsNotExpired(zookeeper);
|
||||
|
||||
++block_index;
|
||||
String block_id = insert_id.empty() ? "" : insert_id + "__" + toString(block_index);
|
||||
String month_name = toString(DateLUT::instance().toNumYYYYMMDD(DayNum_t(current_block.min_date)) / 100);
|
||||
|
||||
AbandonableLockInZooKeeper block_number_lock = storage.allocateBlockNumber(month_name);
|
||||
|
||||
Int64 part_number = block_number_lock.getNumber();
|
||||
|
||||
MergeTreeData::MutableDataPartPtr part = storage.writer.writeTempPart(current_block, part_number);
|
||||
String part_name = ActiveDataPartSet::getPartName(part->left_date, part->right_date, part->left, part->right, part->level);
|
||||
|
||||
/// Хэш от данных.
|
||||
SipHash hash;
|
||||
part->checksums.summaryDataChecksum(hash);
|
||||
union
|
||||
{
|
||||
char bytes[16];
|
||||
UInt64 lo, hi;
|
||||
} hash_value;
|
||||
hash.get128(hash_value.bytes);
|
||||
|
||||
String checksum(hash_value.bytes, 16);
|
||||
|
||||
/// Если в запросе не указан ID, возьмем в качестве ID хеш от данных. То есть, не вставляем одинаковые данные дважды.
|
||||
/// NOTE: Если такая дедупликация не нужна, можно вместо этого оставлять block_id пустым.
|
||||
/// Можно для этого сделать настройку или синтаксис в запросе (например, ID=null).
|
||||
if (block_id.empty())
|
||||
{
|
||||
block_id = toString(hash_value.lo) + "_" + toString(hash_value.hi);
|
||||
|
||||
if (block_id.empty())
|
||||
throw Exception("Logical error: block_id is empty.", ErrorCodes::LOGICAL_ERROR);
|
||||
}
|
||||
|
||||
LOG_DEBUG(log, "Wrote block " << part_number << " with ID " << block_id << ", " << current_block.block.rows() << " rows");
|
||||
|
||||
StorageReplicatedMergeTree::LogEntry log_entry;
|
||||
log_entry.type = StorageReplicatedMergeTree::LogEntry::GET_PART;
|
||||
log_entry.create_time = time(0);
|
||||
log_entry.source_replica = storage.replica_name;
|
||||
log_entry.new_part_name = part_name;
|
||||
log_entry.quorum = quorum;
|
||||
log_entry.block_id = block_id;
|
||||
|
||||
/// Одновременно добавим информацию о куске во все нужные места в ZooKeeper и снимем block_number_lock.
|
||||
|
||||
/// Информация о блоке.
|
||||
zkutil::Ops ops;
|
||||
auto acl = zookeeper->getDefaultACL();
|
||||
|
||||
ops.push_back(
|
||||
new zkutil::Op::Create(
|
||||
storage.zookeeper_path + "/blocks/" + block_id,
|
||||
"",
|
||||
acl,
|
||||
zkutil::CreateMode::Persistent));
|
||||
ops.push_back(
|
||||
new zkutil::Op::Create(
|
||||
storage.zookeeper_path + "/blocks/" + block_id + "/checksum",
|
||||
checksum,
|
||||
acl,
|
||||
zkutil::CreateMode::Persistent));
|
||||
ops.push_back(
|
||||
new zkutil::Op::Create(
|
||||
storage.zookeeper_path + "/blocks/" + block_id + "/number",
|
||||
toString(part_number),
|
||||
acl,
|
||||
zkutil::CreateMode::Persistent));
|
||||
|
||||
/// Информация о куске, в данных реплики.
|
||||
storage.checkPartAndAddToZooKeeper(part, ops, part_name);
|
||||
|
||||
/// Лог репликации.
|
||||
ops.push_back(new zkutil::Op::Create(
|
||||
storage.zookeeper_path + "/log/log-",
|
||||
log_entry.toString(),
|
||||
acl,
|
||||
zkutil::CreateMode::PersistentSequential));
|
||||
|
||||
/// Удаление информации о том, что номер блока используется для записи.
|
||||
block_number_lock.getUnlockOps(ops);
|
||||
|
||||
/** Если нужен кворум - создание узла, в котором отслеживается кворум.
|
||||
* (Если такой узел уже существует - значит кто-то успел одновременно сделать другую кворумную запись, но для неё кворум ещё не достигнут.
|
||||
* Делать в это время следующую кворумную запись нельзя.)
|
||||
*/
|
||||
if (quorum)
|
||||
{
|
||||
ReplicatedMergeTreeQuorumEntry quorum_entry;
|
||||
quorum_entry.part_name = part_name;
|
||||
quorum_entry.required_number_of_replicas = quorum;
|
||||
quorum_entry.replicas.insert(storage.replica_name);
|
||||
|
||||
/** В данный момент, этот узел будет содержать информацию о том, что текущая реплика получила кусок.
|
||||
* Когда другие реплики будут получать этот кусок (обычным способом, обрабатывая лог репликации),
|
||||
* они будут добавлять себя в содержимое этого узла.
|
||||
* Когда в нём будет информация о quorum количестве реплик, этот узел удаляется,
|
||||
* что говорит о том, что кворум достигнут.
|
||||
*/
|
||||
|
||||
ops.push_back(
|
||||
new zkutil::Op::Create(
|
||||
quorum_status_path,
|
||||
quorum_entry.toString(),
|
||||
acl,
|
||||
zkutil::CreateMode::Persistent));
|
||||
}
|
||||
|
||||
MergeTreeData::Transaction transaction; /// Если не получится добавить кусок в ZK, снова уберем его из рабочего набора.
|
||||
storage.data.renameTempPartAndAdd(part, nullptr, &transaction);
|
||||
|
||||
try
|
||||
{
|
||||
auto code = zookeeper->tryMulti(ops);
|
||||
if (code == ZOK)
|
||||
{
|
||||
transaction.commit();
|
||||
storage.merge_selecting_event.set();
|
||||
}
|
||||
else if (code == ZNODEEXISTS)
|
||||
{
|
||||
/// Если блок с таким ID уже есть в таблице, откатим его вставку.
|
||||
String expected_checksum;
|
||||
if (!block_id.empty() && zookeeper->tryGet(
|
||||
storage.zookeeper_path + "/blocks/" + block_id + "/checksum", expected_checksum))
|
||||
{
|
||||
LOG_INFO(log, "Block with ID " << block_id << " already exists; ignoring it (removing part " << part->name << ")");
|
||||
|
||||
/// Если данные отличались от тех, что были вставлены ранее с тем же ID, бросим исключение.
|
||||
if (expected_checksum != checksum)
|
||||
{
|
||||
if (!insert_id.empty())
|
||||
throw Exception("Attempt to insert block with same ID but different checksum", ErrorCodes::CHECKSUM_DOESNT_MATCH);
|
||||
else
|
||||
throw Exception("Logical error: got ZNODEEXISTS while inserting data, block ID is derived from checksum but checksum doesn't match", ErrorCodes::LOGICAL_ERROR);
|
||||
}
|
||||
|
||||
transaction.rollback();
|
||||
}
|
||||
else if (zookeeper->exists(quorum_status_path))
|
||||
{
|
||||
transaction.rollback();
|
||||
|
||||
throw Exception("Another quorum insert has been already started", ErrorCodes::UNSATISFIED_QUORUM_FOR_PREVIOUS_WRITE);
|
||||
}
|
||||
else
|
||||
{
|
||||
/// Сюда можем попасть также, если узел с кворумом существовал, но потом быстро был удалён.
|
||||
|
||||
throw Exception("Unexpected ZNODEEXISTS while adding block " + toString(part_number) + " with ID " + block_id + ": "
|
||||
+ zkutil::ZooKeeper::error2string(code), ErrorCodes::UNEXPECTED_ZOOKEEPER_ERROR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Exception("Unexpected error while adding block " + toString(part_number) + " with ID " + block_id + ": "
|
||||
+ zkutil::ZooKeeper::error2string(code), ErrorCodes::UNEXPECTED_ZOOKEEPER_ERROR);
|
||||
}
|
||||
}
|
||||
catch (const zkutil::KeeperException & e)
|
||||
{
|
||||
/** Если потерялось соединение, и мы не знаем, применились ли изменения, нельзя удалять локальный кусок:
|
||||
* если изменения применились, в /blocks/ появился вставленный блок, и его нельзя будет вставить снова.
|
||||
*/
|
||||
if (e.code == ZOPERATIONTIMEOUT ||
|
||||
e.code == ZCONNECTIONLOSS)
|
||||
{
|
||||
transaction.commit();
|
||||
storage.enqueuePartForCheck(part->name);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
if (quorum)
|
||||
{
|
||||
/// Дожидаемся достижения кворума. TODO Настраиваемый таймаут.
|
||||
LOG_TRACE(log, "Waiting for quorum");
|
||||
zookeeper->waitForDisappear(quorum_status_path);
|
||||
LOG_TRACE(log, "Quorum satisfied");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -49,6 +49,8 @@ void ReplicatedMergeTreeQueue::load(zkutil::ZooKeeperPtr zookeeper)
|
||||
insertUnlocked(entry);
|
||||
}
|
||||
|
||||
updateTimesInZooKeeper(zookeeper, true, false);
|
||||
|
||||
LOG_TRACE(log, "Loaded queue");
|
||||
}
|
||||
|
||||
@ -71,13 +73,88 @@ void ReplicatedMergeTreeQueue::insertUnlocked(LogEntryPtr & entry)
|
||||
{
|
||||
virtual_parts.add(entry->new_part_name);
|
||||
queue.push_back(entry);
|
||||
|
||||
if (entry->type == LogEntry::GET_PART)
|
||||
{
|
||||
inserts_by_time.insert(entry);
|
||||
|
||||
if (entry->create_time && (!min_unprocessed_insert_time || entry->create_time < min_unprocessed_insert_time))
|
||||
min_unprocessed_insert_time = entry->create_time;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ReplicatedMergeTreeQueue::insert(LogEntryPtr & entry)
|
||||
void ReplicatedMergeTreeQueue::insert(zkutil::ZooKeeperPtr zookeeper, LogEntryPtr & entry)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
insertUnlocked(entry);
|
||||
time_t prev_min_unprocessed_insert_time;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
prev_min_unprocessed_insert_time = min_unprocessed_insert_time;
|
||||
insertUnlocked(entry);
|
||||
}
|
||||
|
||||
if (min_unprocessed_insert_time != prev_min_unprocessed_insert_time)
|
||||
updateTimesInZooKeeper(zookeeper, true, false);
|
||||
}
|
||||
|
||||
|
||||
void ReplicatedMergeTreeQueue::updateTimesOnRemoval(
|
||||
const LogEntryPtr & entry,
|
||||
bool & min_unprocessed_insert_time_changed,
|
||||
bool & max_processed_insert_time_changed)
|
||||
{
|
||||
if (entry->type != LogEntry::GET_PART)
|
||||
return;
|
||||
|
||||
inserts_by_time.erase(entry);
|
||||
|
||||
if (inserts_by_time.empty())
|
||||
{
|
||||
min_unprocessed_insert_time = 0;
|
||||
min_unprocessed_insert_time_changed = true;
|
||||
}
|
||||
else if ((*inserts_by_time.begin())->create_time > min_unprocessed_insert_time)
|
||||
{
|
||||
min_unprocessed_insert_time = (*inserts_by_time.begin())->create_time;
|
||||
min_unprocessed_insert_time_changed = true;
|
||||
}
|
||||
|
||||
if (entry->create_time > max_processed_insert_time)
|
||||
{
|
||||
max_processed_insert_time = entry->create_time;
|
||||
max_processed_insert_time_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ReplicatedMergeTreeQueue::updateTimesInZooKeeper(
|
||||
zkutil::ZooKeeperPtr zookeeper,
|
||||
bool min_unprocessed_insert_time_changed,
|
||||
bool max_processed_insert_time_changed)
|
||||
{
|
||||
/// Здесь может быть race condition (при одновременном выполнении разных remove).
|
||||
/// Считаем его несущественным (в течение небольшого времени, в ZK будет записано немного отличающееся значение времени).
|
||||
/// Также читаем значения переменных min_unprocessed_insert_time, max_processed_insert_time без синхронизации.
|
||||
zkutil::Ops ops;
|
||||
|
||||
if (min_unprocessed_insert_time_changed)
|
||||
ops.push_back(new zkutil::Op::SetData(
|
||||
replica_path + "/min_unprocessed_insert_time", toString(min_unprocessed_insert_time), -1));
|
||||
|
||||
if (max_processed_insert_time_changed)
|
||||
ops.push_back(new zkutil::Op::SetData(
|
||||
replica_path + "/max_processed_insert_time", toString(max_processed_insert_time), -1));
|
||||
|
||||
if (!ops.empty())
|
||||
{
|
||||
auto code = zookeeper->tryMulti(ops);
|
||||
|
||||
if (code != ZOK)
|
||||
LOG_ERROR(log, "Couldn't set value of nodes for insert times ("
|
||||
<< replica_path << "/min_unprocessed_insert_time, max_processed_insert_time)" << ": "
|
||||
<< zkutil::ZooKeeper::error2string(code) + ". This shouldn't happen often.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -86,44 +163,67 @@ void ReplicatedMergeTreeQueue::remove(zkutil::ZooKeeperPtr zookeeper, LogEntryPt
|
||||
auto code = zookeeper->tryRemove(replica_path + "/queue/" + entry->znode_name);
|
||||
|
||||
if (code != ZOK)
|
||||
LOG_ERROR(log, "Couldn't remove " << replica_path + "/queue/" + entry->znode_name << ": "
|
||||
<< zkutil::ZooKeeper::error2string(code) + ". This shouldn't happen often.");
|
||||
LOG_ERROR(log, "Couldn't remove " << replica_path << "/queue/" << entry->znode_name << ": "
|
||||
<< zkutil::ZooKeeper::error2string(code) << ". This shouldn't happen often.");
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
bool min_unprocessed_insert_time_changed = false;
|
||||
bool max_processed_insert_time_changed = false;
|
||||
|
||||
/// Удалим задание из очереди в оперативке.
|
||||
/// Нельзя просто обратиться по заранее сохраненному итератору, потому что задание мог успеть удалить кто-то другой.
|
||||
/// Почему просматриваем очередь с конца?
|
||||
/// - потому что задание к выполнению сначала перемещается в конец очереди, чтобы в случае неуспеха оно осталось в конце.
|
||||
for (Queue::iterator it = queue.end(); it != queue.begin();)
|
||||
{
|
||||
--it;
|
||||
if (*it == entry)
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
/// Удалим задание из очереди в оперативке.
|
||||
/// Нельзя просто обратиться по заранее сохраненному итератору, потому что задание мог успеть удалить кто-то другой.
|
||||
/// Почему просматриваем очередь с конца?
|
||||
/// - потому что задание к выполнению сначала перемещается в конец очереди, чтобы в случае неуспеха оно осталось в конце.
|
||||
for (Queue::iterator it = queue.end(); it != queue.begin();)
|
||||
{
|
||||
queue.erase(it);
|
||||
break;
|
||||
--it;
|
||||
if (*it == entry)
|
||||
{
|
||||
queue.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateTimesOnRemoval(entry, min_unprocessed_insert_time_changed, max_processed_insert_time_changed);
|
||||
}
|
||||
|
||||
updateTimesInZooKeeper(zookeeper, min_unprocessed_insert_time_changed, max_processed_insert_time_changed);
|
||||
}
|
||||
|
||||
|
||||
bool ReplicatedMergeTreeQueue::remove(zkutil::ZooKeeperPtr zookeeper, const String & part_name)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
LogEntryPtr found;
|
||||
|
||||
bool min_unprocessed_insert_time_changed = false;
|
||||
bool max_processed_insert_time_changed = false;
|
||||
|
||||
for (Queue::iterator it = queue.begin(); it != queue.end();)
|
||||
{
|
||||
if ((*it)->new_part_name == part_name)
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
for (Queue::iterator it = queue.begin(); it != queue.end();)
|
||||
{
|
||||
zookeeper->tryRemove(replica_path + "/queue/" + (*it)->znode_name); /// NOTE Может быть, стоит избежать блокировки в это время.
|
||||
queue.erase(it++);
|
||||
return true;
|
||||
if ((*it)->new_part_name == part_name)
|
||||
{
|
||||
found = *it;
|
||||
queue.erase(it++);
|
||||
updateTimesOnRemoval(found, min_unprocessed_insert_time_changed, max_processed_insert_time_changed);
|
||||
break;
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (!found)
|
||||
return false;
|
||||
|
||||
zookeeper->tryRemove(replica_path + "/queue/" + found->znode_name);
|
||||
updateTimesInZooKeeper(zookeeper, min_unprocessed_insert_time_changed, max_processed_insert_time_changed);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -156,67 +256,86 @@ bool ReplicatedMergeTreeQueue::pullLogsToQueue(zkutil::ZooKeeperPtr zookeeper, z
|
||||
std::remove_if(log_entries.begin(), log_entries.end(), [&min_log_entry](const String & entry) { return entry < min_log_entry; }),
|
||||
log_entries.end());
|
||||
|
||||
if (log_entries.empty())
|
||||
return false;
|
||||
|
||||
std::sort(log_entries.begin(), log_entries.end());
|
||||
|
||||
String last_entry = log_entries.back();
|
||||
if (0 != last_entry.compare(0, strlen("log-"), "log-"))
|
||||
throw Exception("Error in zookeeper data: unexpected node " + last_entry + " in " + zookeeper_path + "/log",
|
||||
ErrorCodes::UNEXPECTED_NODE_IN_ZOOKEEPER);
|
||||
|
||||
UInt64 last_entry_index = parse<UInt64>(last_entry.substr(strlen("log-")));
|
||||
|
||||
LOG_DEBUG(log, "Pulling " << log_entries.size() << " entries to queue: " << log_entries.front() << " - " << log_entries.back());
|
||||
|
||||
std::vector<std::pair<String, zkutil::ZooKeeper::GetFuture>> futures;
|
||||
futures.reserve(log_entries.size());
|
||||
|
||||
for (const String & entry : log_entries)
|
||||
futures.emplace_back(entry, zookeeper->asyncGet(zookeeper_path + "/log/" + entry));
|
||||
|
||||
/// Одновременно добавим все новые записи в очередь и продвинем указатель на лог.
|
||||
|
||||
zkutil::Ops ops;
|
||||
std::vector<LogEntryPtr> copied_entries;
|
||||
copied_entries.reserve(log_entries.size());
|
||||
|
||||
for (auto & future : futures)
|
||||
if (!log_entries.empty())
|
||||
{
|
||||
zkutil::ZooKeeper::ValueAndStat res = future.second.get();
|
||||
copied_entries.emplace_back(LogEntry::parse(res.value, res.stat));
|
||||
std::sort(log_entries.begin(), log_entries.end());
|
||||
|
||||
ops.push_back(new zkutil::Op::Create(
|
||||
replica_path + "/queue/queue-", res.value, zookeeper->getDefaultACL(), zkutil::CreateMode::PersistentSequential));
|
||||
}
|
||||
String last_entry = log_entries.back();
|
||||
if (0 != last_entry.compare(0, strlen("log-"), "log-"))
|
||||
throw Exception("Error in zookeeper data: unexpected node " + last_entry + " in " + zookeeper_path + "/log",
|
||||
ErrorCodes::UNEXPECTED_NODE_IN_ZOOKEEPER);
|
||||
|
||||
ops.push_back(new zkutil::Op::SetData(
|
||||
replica_path + "/log_pointer", toString(last_entry_index + 1), -1));
|
||||
UInt64 last_entry_index = parse<UInt64>(last_entry.substr(strlen("log-")));
|
||||
|
||||
auto results = zookeeper->multi(ops);
|
||||
LOG_DEBUG(log, "Pulling " << log_entries.size() << " entries to queue: " << log_entries.front() << " - " << log_entries.back());
|
||||
|
||||
/// Сейчас мы успешно обновили очередь в ZooKeeper. Обновим её в оперативке.
|
||||
std::vector<std::pair<String, zkutil::ZooKeeper::GetFuture>> futures;
|
||||
futures.reserve(log_entries.size());
|
||||
|
||||
try
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (const String & entry : log_entries)
|
||||
futures.emplace_back(entry, zookeeper->asyncGet(zookeeper_path + "/log/" + entry));
|
||||
|
||||
for (size_t i = 0, size = copied_entries.size(); i < size; ++i)
|
||||
/// Одновременно добавим все новые записи в очередь и продвинем указатель на лог.
|
||||
|
||||
zkutil::Ops ops;
|
||||
std::vector<LogEntryPtr> copied_entries;
|
||||
copied_entries.reserve(log_entries.size());
|
||||
|
||||
bool min_unprocessed_insert_time_changed = false;
|
||||
|
||||
for (auto & future : futures)
|
||||
{
|
||||
String path_created = dynamic_cast<zkutil::Op::Create &>(ops[i]).getPathCreated();
|
||||
copied_entries[i]->znode_name = path_created.substr(path_created.find_last_of('/') + 1);
|
||||
zkutil::ZooKeeper::ValueAndStat res = future.second.get();
|
||||
copied_entries.emplace_back(LogEntry::parse(res.value, res.stat));
|
||||
|
||||
insertUnlocked(copied_entries[i]);
|
||||
ops.push_back(new zkutil::Op::Create(
|
||||
replica_path + "/queue/queue-", res.value, zookeeper->getDefaultACL(), zkutil::CreateMode::PersistentSequential));
|
||||
|
||||
const auto & entry = *copied_entries.back();
|
||||
if (entry.type == LogEntry::GET_PART)
|
||||
{
|
||||
if (entry.create_time && (!min_unprocessed_insert_time || entry.create_time < min_unprocessed_insert_time))
|
||||
{
|
||||
min_unprocessed_insert_time = entry.create_time;
|
||||
min_unprocessed_insert_time_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last_queue_update = time(0);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
/// Если не удалось, то данные в оперативке некорректные. Во избежание возможной дальнейшей порчи данных в ZK, убъёмся.
|
||||
/// Попадание сюда возможно лишь в случае неизвестной логической ошибки.
|
||||
std::terminate();
|
||||
ops.push_back(new zkutil::Op::SetData(
|
||||
replica_path + "/log_pointer", toString(last_entry_index + 1), -1));
|
||||
|
||||
if (min_unprocessed_insert_time_changed)
|
||||
ops.push_back(new zkutil::Op::SetData(
|
||||
replica_path + "/min_unprocessed_insert_time", toString(min_unprocessed_insert_time), -1));
|
||||
|
||||
auto results = zookeeper->multi(ops);
|
||||
|
||||
/// Сейчас мы успешно обновили очередь в ZooKeeper. Обновим её в оперативке.
|
||||
|
||||
try
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
for (size_t i = 0, size = copied_entries.size(); i < size; ++i)
|
||||
{
|
||||
String path_created = dynamic_cast<zkutil::Op::Create &>(ops[i]).getPathCreated();
|
||||
copied_entries[i]->znode_name = path_created.substr(path_created.find_last_of('/') + 1);
|
||||
|
||||
insertUnlocked(copied_entries[i]);
|
||||
}
|
||||
|
||||
last_queue_update = time(0);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
/// Если не удалось, то данные в оперативке некорректные. Во избежание возможной дальнейшей порчи данных в ZK, убъёмся.
|
||||
/// Попадание сюда возможно лишь в случае неизвестной логической ошибки.
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
if (!copied_entries.empty())
|
||||
LOG_DEBUG(log, "Pulled " << copied_entries.size() << " entries to queue.");
|
||||
}
|
||||
|
||||
if (next_update_event)
|
||||
@ -225,9 +344,7 @@ bool ReplicatedMergeTreeQueue::pullLogsToQueue(zkutil::ZooKeeperPtr zookeeper, z
|
||||
next_update_event->set();
|
||||
}
|
||||
|
||||
LOG_DEBUG(log, "Pulled " << copied_entries.size() << " entries to queue.");
|
||||
|
||||
return true;
|
||||
return !log_entries.empty();
|
||||
}
|
||||
|
||||
|
||||
@ -279,6 +396,8 @@ void ReplicatedMergeTreeQueue::removeGetsAndMergesInRange(zkutil::ZooKeeperPtr z
|
||||
{
|
||||
Queue to_wait;
|
||||
size_t removed_entries = 0;
|
||||
bool min_unprocessed_insert_time_changed = false;
|
||||
bool max_processed_insert_time_changed = false;
|
||||
|
||||
/// Удалим из очереди операции с кусками, содержащимися в удаляемом диапазоне.
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
@ -293,6 +412,8 @@ void ReplicatedMergeTreeQueue::removeGetsAndMergesInRange(zkutil::ZooKeeperPtr z
|
||||
if (code != ZOK)
|
||||
LOG_INFO(log, "Couldn't remove " << replica_path + "/queue/" + (*it)->znode_name << ": "
|
||||
<< zkutil::ZooKeeper::error2string(code));
|
||||
|
||||
updateTimesOnRemoval(*it, min_unprocessed_insert_time_changed, max_processed_insert_time_changed);
|
||||
queue.erase(it++);
|
||||
++removed_entries;
|
||||
}
|
||||
@ -300,6 +421,8 @@ void ReplicatedMergeTreeQueue::removeGetsAndMergesInRange(zkutil::ZooKeeperPtr z
|
||||
++it;
|
||||
}
|
||||
|
||||
updateTimesInZooKeeper(zookeeper, min_unprocessed_insert_time_changed, max_processed_insert_time_changed);
|
||||
|
||||
LOG_DEBUG(log, "Removed " << removed_entries << " entries from queue. "
|
||||
"Waiting for " << to_wait.size() << " entries that are currently executing.");
|
||||
|
||||
@ -573,6 +696,13 @@ void ReplicatedMergeTreeQueue::countMerges(size_t & all_merges, size_t & big_mer
|
||||
}
|
||||
|
||||
|
||||
void ReplicatedMergeTreeQueue::getInsertTimes(time_t & out_min_unprocessed_insert_time, time_t & out_max_processed_insert_time) const
|
||||
{
|
||||
out_min_unprocessed_insert_time = min_unprocessed_insert_time;
|
||||
out_max_processed_insert_time = max_processed_insert_time;
|
||||
}
|
||||
|
||||
|
||||
String padIndex(Int64 index)
|
||||
{
|
||||
String index_str = toString(index);
|
||||
|
@ -107,22 +107,32 @@ void ReplicatedMergeTreeRestartingThread::run()
|
||||
if (current_time >= prev_time_of_check_delay + static_cast<time_t>(storage.data.settings.check_delay_period))
|
||||
{
|
||||
/// Выясняем отставания реплик.
|
||||
time_t new_absolute_delay = 0;
|
||||
time_t new_relative_delay = 0;
|
||||
time_t absolute_delay = 0;
|
||||
time_t relative_delay = 0;
|
||||
|
||||
/// TODO Ловить здесь исключение.
|
||||
checkReplicationDelays(new_absolute_delay, new_relative_delay);
|
||||
|
||||
absolute_delay.store(new_absolute_delay, std::memory_order_relaxed);
|
||||
relative_delay.store(new_relative_delay, std::memory_order_relaxed);
|
||||
bool error = false;
|
||||
try
|
||||
{
|
||||
storage.getReplicaDelays(absolute_delay, relative_delay);
|
||||
LOG_TRACE(log, "Absolute delay: " << absolute_delay << ". Relative delay: " << relative_delay << ".");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
tryLogCurrentException("__PRETTY_FUNCTION__", "Cannot get replica delays");
|
||||
error = true;
|
||||
}
|
||||
|
||||
prev_time_of_check_delay = current_time;
|
||||
|
||||
/// Уступаем лидерство, если относительное отставание больше порога.
|
||||
if (storage.is_leader_node && new_relative_delay > static_cast<time_t>(storage.data.settings.min_relative_delay_to_yield_leadership))
|
||||
if (storage.is_leader_node
|
||||
&& (error || relative_delay > static_cast<time_t>(storage.data.settings.min_relative_delay_to_yield_leadership)))
|
||||
{
|
||||
LOG_INFO(log, "Relative replica delay (" << new_relative_delay << " seconds) is bigger than threshold ("
|
||||
<< storage.data.settings.min_relative_delay_to_yield_leadership << "). Will yield leadership.");
|
||||
if (error)
|
||||
LOG_INFO(log, "Will yield leadership.");
|
||||
else
|
||||
LOG_INFO(log, "Relative replica delay (" << relative_delay << " seconds) is bigger than threshold ("
|
||||
<< storage.data.settings.min_relative_delay_to_yield_leadership << "). Will yield leadership.");
|
||||
|
||||
need_restart = true;
|
||||
continue;
|
||||
@ -374,17 +384,4 @@ void ReplicatedMergeTreeRestartingThread::goReadOnlyPermanently()
|
||||
}
|
||||
|
||||
|
||||
void ReplicatedMergeTreeRestartingThread::checkReplicationDelays(time_t & out_absolute_delay, time_t & out_relative_delay)
|
||||
{
|
||||
out_absolute_delay = 0;
|
||||
out_relative_delay = 0;
|
||||
|
||||
auto zookeeper = storage.getZooKeeper();
|
||||
|
||||
// TODO
|
||||
|
||||
LOG_TRACE(log, "Absolute delay: " << out_absolute_delay << ". Relative delay: " << out_relative_delay << ".");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,11 @@
|
||||
#include <time.h>
|
||||
#include <ext/range.hpp>
|
||||
|
||||
#include <zkutil/Types.h>
|
||||
#include <zkutil/KeeperException.h>
|
||||
|
||||
#include <DB/Core/FieldVisitors.h>
|
||||
|
||||
#include <DB/Storages/ColumnsDescription.h>
|
||||
#include <DB/Storages/StorageReplicatedMergeTree.h>
|
||||
#include <DB/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.h>
|
||||
@ -9,22 +15,27 @@
|
||||
#include <DB/Storages/MergeTree/MergeList.h>
|
||||
#include <DB/Storages/MergeTree/MergeTreeWhereOptimizer.h>
|
||||
#include <DB/Storages/MergeTree/ReplicatedMergeTreeAddress.h>
|
||||
|
||||
#include <DB/Parsers/formatAST.h>
|
||||
#include <DB/Parsers/ASTInsertQuery.h>
|
||||
|
||||
#include <DB/IO/WriteBufferFromOStream.h>
|
||||
#include <DB/IO/ReadBufferFromString.h>
|
||||
#include <DB/IO/Operators.h>
|
||||
|
||||
#include <DB/Interpreters/InterpreterAlterQuery.h>
|
||||
#include <DB/Common/VirtualColumnUtils.h>
|
||||
#include <DB/Parsers/ASTInsertQuery.h>
|
||||
|
||||
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
|
||||
#include <DB/DataStreams/RemoteBlockInputStream.h>
|
||||
#include <DB/DataStreams/NullBlockOutputStream.h>
|
||||
#include <DB/DataStreams/copyData.h>
|
||||
|
||||
#include <DB/Common/Macros.h>
|
||||
#include <DB/Common/VirtualColumnUtils.h>
|
||||
#include <DB/Common/formatReadable.h>
|
||||
#include <DB/Common/setThreadName.h>
|
||||
|
||||
#include <Poco/DirectoryIterator.h>
|
||||
#include <time.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
@ -56,6 +67,12 @@ namespace ErrorCodes
|
||||
|
||||
|
||||
const auto ERROR_SLEEP_MS = 1000;
|
||||
|
||||
/// Если ждём какого-то события с помощью watch-а, то просыпаться на всякий случай вхолостую раз в указанное время.
|
||||
const auto WAIT_FOR_NEW_LOGS_SLEEP_MS = 60 * 1000;
|
||||
const auto WAIT_FOR_ALTER_SLEEP_MS = 300 * 1000;
|
||||
const auto WAIT_FOR_REPLICA_QUEUE_MS = 10 * 1000;
|
||||
|
||||
const auto MERGE_SELECTING_SLEEP_MS = 5 * 1000;
|
||||
|
||||
const Int64 RESERVED_BLOCK_NUMBERS = 200;
|
||||
@ -98,6 +115,27 @@ const Int64 RESERVED_BLOCK_NUMBERS = 200;
|
||||
const auto MAX_AGE_OF_LOCAL_PART_THAT_WASNT_ADDED_TO_ZOOKEEPER = 5 * 60;
|
||||
|
||||
|
||||
void StorageReplicatedMergeTree::setZooKeeper(zkutil::ZooKeeperPtr zookeeper)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(current_zookeeper_mutex);
|
||||
current_zookeeper = zookeeper;
|
||||
}
|
||||
|
||||
zkutil::ZooKeeperPtr StorageReplicatedMergeTree::tryGetZooKeeper()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(current_zookeeper_mutex);
|
||||
return current_zookeeper;
|
||||
}
|
||||
|
||||
zkutil::ZooKeeperPtr StorageReplicatedMergeTree::getZooKeeper()
|
||||
{
|
||||
auto res = tryGetZooKeeper();
|
||||
if (!res)
|
||||
throw Exception("Cannot get ZooKeeper", ErrorCodes::NO_ZOOKEEPER);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
StorageReplicatedMergeTree::StorageReplicatedMergeTree(
|
||||
const String & zookeeper_path_,
|
||||
const String & replica_name_,
|
||||
@ -167,6 +205,8 @@ StorageReplicatedMergeTree::StorageReplicatedMergeTree(
|
||||
throw Exception("Can't create replicated table without ZooKeeper", ErrorCodes::NO_ZOOKEEPER);
|
||||
|
||||
/// Не активируем реплику. Она будет в режиме readonly.
|
||||
LOG_ERROR(log, "No ZooKeeper: table will be in readonly mode.");
|
||||
is_readonly = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -232,6 +272,7 @@ void StorageReplicatedMergeTree::createNewZooKeeperNodes()
|
||||
|
||||
/// Отслеживание отставания реплик.
|
||||
zookeeper->createIfNotExists(replica_path + "/min_unprocessed_insert_time", "");
|
||||
zookeeper->createIfNotExists(replica_path + "/max_processed_insert_time", "");
|
||||
}
|
||||
|
||||
|
||||
@ -264,7 +305,7 @@ StoragePtr StorageReplicatedMergeTree::create(
|
||||
|
||||
StoragePtr res_ptr = res->thisPtr();
|
||||
|
||||
if (res->getZooKeeper())
|
||||
if (res->tryGetZooKeeper())
|
||||
{
|
||||
String endpoint_name = "ReplicatedMergeTree:" + res->replica_path;
|
||||
InterserverIOEndpointPtr endpoint = new ReplicatedMergeTreePartsServer(res->data, *res);
|
||||
@ -552,7 +593,7 @@ void StorageReplicatedMergeTree::createReplica()
|
||||
zookeeper->create(replica_path + "/queue/queue-", entry, zkutil::CreateMode::PersistentSequential);
|
||||
}
|
||||
|
||||
/// Далее оно будет загружено в переменную queue в методе queue.load.
|
||||
/// Далее оно будет загружено в переменную queue в методе queue.initialize.
|
||||
|
||||
LOG_DEBUG(log, "Copied " << source_queue.size() << " queue entries");
|
||||
}
|
||||
@ -691,7 +732,7 @@ void StorageReplicatedMergeTree::checkParts(bool skip_sanity_checks)
|
||||
log_entry.new_part_name = name;
|
||||
log_entry.create_time = tryGetPartCreateTime(zookeeper, replica_path, name);
|
||||
|
||||
/// Полагаемся, что это происходит до загрузки очереди (queue.load).
|
||||
/// Полагаемся, что это происходит до загрузки очереди (queue.initialize).
|
||||
zkutil::Ops ops;
|
||||
removePartFromZooKeeper(name, ops);
|
||||
ops.push_back(new zkutil::Op::Create(
|
||||
@ -1206,7 +1247,7 @@ void StorageReplicatedMergeTree::queueUpdatingThread()
|
||||
try
|
||||
{
|
||||
pullLogsToQueue(queue_updating_event);
|
||||
queue_updating_event->wait();
|
||||
queue_updating_event->tryWait(WAIT_FOR_NEW_LOGS_SLEEP_MS);
|
||||
}
|
||||
catch (const zkutil::KeeperException & e)
|
||||
{
|
||||
@ -1214,13 +1255,11 @@ void StorageReplicatedMergeTree::queueUpdatingThread()
|
||||
restarting_thread->wakeup();
|
||||
|
||||
tryLogCurrentException(__PRETTY_FUNCTION__);
|
||||
|
||||
queue_updating_event->tryWait(ERROR_SLEEP_MS);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
tryLogCurrentException(__PRETTY_FUNCTION__);
|
||||
|
||||
queue_updating_event->tryWait(ERROR_SLEEP_MS);
|
||||
}
|
||||
}
|
||||
@ -1353,8 +1392,6 @@ void StorageReplicatedMergeTree::mergeSelectingThread()
|
||||
|
||||
try
|
||||
{
|
||||
std::lock_guard<std::mutex> merge_selecting_lock(merge_selecting_mutex);
|
||||
|
||||
if (need_pull)
|
||||
{
|
||||
/// Нужно загрузить новые записи в очередь перед тем, как выбирать куски для слияния.
|
||||
@ -1363,6 +1400,8 @@ void StorageReplicatedMergeTree::mergeSelectingThread()
|
||||
need_pull = false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> merge_selecting_lock(merge_selecting_mutex);
|
||||
|
||||
/** Сколько в очереди или в фоновом потоке мерджей крупных кусков.
|
||||
* Если их больше половины от размера пула потоков для мерджа, то можно мерджить только мелкие куски.
|
||||
*/
|
||||
@ -1677,7 +1716,7 @@ void StorageReplicatedMergeTree::alterThread()
|
||||
/// Важно, что уничтожается parts и merge_blocker перед wait-ом.
|
||||
}
|
||||
|
||||
alter_thread_event->wait();
|
||||
alter_thread_event->tryWait(WAIT_FOR_ALTER_SLEEP_MS);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@ -1726,7 +1765,7 @@ void StorageReplicatedMergeTree::removePartAndEnqueueFetch(const String & part_n
|
||||
|
||||
String path_created = dynamic_cast<zkutil::Op::Create &>(ops[0]).getPathCreated();
|
||||
log_entry->znode_name = path_created.substr(path_created.find_last_of('/') + 1);
|
||||
queue.insert(log_entry);
|
||||
queue.insert(zookeeper, log_entry);
|
||||
}
|
||||
|
||||
|
||||
@ -2289,6 +2328,7 @@ BlockInputStreams StorageReplicatedMergeTree::read(
|
||||
if (settings.select_sequential_consistency)
|
||||
{
|
||||
auto zookeeper = getZooKeeper();
|
||||
|
||||
String last_part;
|
||||
zookeeper->tryGet(zookeeper_path + "/quorum/last_part", last_part);
|
||||
|
||||
@ -2335,10 +2375,16 @@ BlockInputStreams StorageReplicatedMergeTree::read(
|
||||
}
|
||||
|
||||
|
||||
BlockOutputStreamPtr StorageReplicatedMergeTree::write(ASTPtr query, const Settings & settings)
|
||||
void StorageReplicatedMergeTree::assertNotReadonly() const
|
||||
{
|
||||
if (is_readonly)
|
||||
throw Exception("Table is in readonly mode", ErrorCodes::TABLE_IS_READ_ONLY);
|
||||
}
|
||||
|
||||
|
||||
BlockOutputStreamPtr StorageReplicatedMergeTree::write(ASTPtr query, const Settings & settings)
|
||||
{
|
||||
assertNotReadonly();
|
||||
|
||||
String insert_id;
|
||||
if (query)
|
||||
@ -2377,6 +2423,8 @@ bool StorageReplicatedMergeTree::optimize(const Settings & settings)
|
||||
void StorageReplicatedMergeTree::alter(const AlterCommands & params,
|
||||
const String & database_name, const String & table_name, Context & context)
|
||||
{
|
||||
assertNotReadonly();
|
||||
|
||||
auto zookeeper = getZooKeeper();
|
||||
const MergeTreeMergeBlocker merge_blocker{merger};
|
||||
const auto unreplicated_merge_blocker = unreplicated_merger ?
|
||||
@ -2472,7 +2520,7 @@ void StorageReplicatedMergeTree::alter(const AlterCommands & params,
|
||||
if (stat.version != replica_columns_version)
|
||||
continue;
|
||||
|
||||
alter_query_event->wait();
|
||||
alter_query_event->tryWait(WAIT_FOR_ALTER_SLEEP_MS);
|
||||
}
|
||||
|
||||
if (shutdown_called)
|
||||
@ -2538,6 +2586,8 @@ void StorageReplicatedMergeTree::dropPartition(ASTPtr query, const Field & field
|
||||
return;
|
||||
}
|
||||
|
||||
assertNotReadonly();
|
||||
|
||||
auto zookeeper = getZooKeeper();
|
||||
String month_name = MergeTreeData::getMonthName(field);
|
||||
|
||||
@ -2631,6 +2681,8 @@ void StorageReplicatedMergeTree::dropPartition(ASTPtr query, const Field & field
|
||||
|
||||
void StorageReplicatedMergeTree::attachPartition(ASTPtr query, const Field & field, bool unreplicated, bool attach_part, const Settings & settings)
|
||||
{
|
||||
assertNotReadonly();
|
||||
|
||||
auto zookeeper = getZooKeeper();
|
||||
String partition;
|
||||
|
||||
@ -2736,13 +2788,13 @@ void StorageReplicatedMergeTree::attachPartition(ASTPtr query, const Field & fie
|
||||
|
||||
void StorageReplicatedMergeTree::drop()
|
||||
{
|
||||
if (is_readonly)
|
||||
auto zookeeper = tryGetZooKeeper();
|
||||
|
||||
if (is_readonly || !zookeeper)
|
||||
throw Exception("Can't drop readonly replicated table (need to drop data in ZooKeeper as well)", ErrorCodes::TABLE_IS_READ_ONLY);
|
||||
|
||||
shutdown();
|
||||
|
||||
auto zookeeper = getZooKeeper();
|
||||
|
||||
if (zookeeper->expired())
|
||||
throw Exception("Table was not dropped because ZooKeeper session has been expired.", ErrorCodes::TABLE_WAS_NOT_DROPPED);
|
||||
|
||||
@ -2860,7 +2912,7 @@ void StorageReplicatedMergeTree::waitForReplicaToProcessLogEntry(const String &
|
||||
if (!log_pointer.empty() && parse<UInt64>(log_pointer) > log_index)
|
||||
break;
|
||||
|
||||
event->wait();
|
||||
event->tryWait(WAIT_FOR_REPLICA_QUEUE_MS);
|
||||
}
|
||||
}
|
||||
else if (0 == entry.znode_name.compare(0, strlen("queue-"), "queue-"))
|
||||
@ -2905,7 +2957,7 @@ void StorageReplicatedMergeTree::waitForReplicaToProcessLogEntry(const String &
|
||||
if (!log_pointer.empty() && parse<UInt64>(log_pointer) > log_index)
|
||||
break;
|
||||
|
||||
event->wait();
|
||||
event->tryWait(WAIT_FOR_REPLICA_QUEUE_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2952,7 +3004,7 @@ void StorageReplicatedMergeTree::waitForReplicaToProcessLogEntry(const String &
|
||||
|
||||
void StorageReplicatedMergeTree::getStatus(Status & res, bool with_zk_fields)
|
||||
{
|
||||
auto zookeeper = getZooKeeper();
|
||||
auto zookeeper = tryGetZooKeeper();
|
||||
|
||||
res.is_leader = is_leader_node;
|
||||
res.is_readonly = is_readonly;
|
||||
@ -3012,12 +3064,53 @@ void StorageReplicatedMergeTree::getQueue(LogEntriesData & res, String & replica
|
||||
}
|
||||
|
||||
|
||||
void StorageReplicatedMergeTree::getReplicaDelays(time_t & out_absolute_delay, time_t & out_relative_delay) const
|
||||
void StorageReplicatedMergeTree::getReplicaDelays(time_t & out_absolute_delay, time_t & out_relative_delay)
|
||||
{
|
||||
if (!restarting_thread)
|
||||
throw Exception("Table was shutted down or is in readonly mode.", ErrorCodes::TABLE_IS_READ_ONLY);
|
||||
assertNotReadonly();
|
||||
|
||||
restarting_thread->getReplicaDelays(out_absolute_delay, out_relative_delay);
|
||||
/** Абсолютная задержка - задержка отставания текущей реплики от реального времени.
|
||||
*/
|
||||
|
||||
time_t min_unprocessed_insert_time = 0;
|
||||
time_t max_processed_insert_time = 0;
|
||||
queue.getInsertTimes(min_unprocessed_insert_time, max_processed_insert_time);
|
||||
|
||||
time_t current_time = time(0);
|
||||
out_absolute_delay = 0;
|
||||
out_relative_delay = 0;
|
||||
|
||||
if (min_unprocessed_insert_time)
|
||||
out_absolute_delay = current_time - min_unprocessed_insert_time;
|
||||
|
||||
/** Относительная задержка - максимальная разница абсолютной задержки от какой-либо другой реплики,
|
||||
* (если эта реплика отстаёт от какой-либо другой реплики, или ноль, иначе).
|
||||
* Вычисляется только если абсолютная задержка достаточно большая.
|
||||
*/
|
||||
|
||||
if (out_absolute_delay < static_cast<time_t>(data.settings.min_relative_delay_to_yield_leadership))
|
||||
return;
|
||||
|
||||
auto zookeeper = getZooKeeper();
|
||||
|
||||
time_t max_replicas_unprocessed_insert_time = 0;
|
||||
Strings replicas = zookeeper->getChildren(zookeeper_path + "/replicas");
|
||||
|
||||
for (const auto & replica : replicas)
|
||||
{
|
||||
if (replica == replica_name)
|
||||
continue;
|
||||
|
||||
String value;
|
||||
if (!zookeeper->tryGet(zookeeper_path + "/replicas/" + replica + "/min_unprocessed_insert_time", value))
|
||||
continue;
|
||||
|
||||
time_t replica_time = value.empty() ? 0 : parse<time_t>(value);
|
||||
if (replica_time > max_replicas_unprocessed_insert_time)
|
||||
max_replicas_unprocessed_insert_time = replica_time;
|
||||
}
|
||||
|
||||
if (max_replicas_unprocessed_insert_time > min_unprocessed_insert_time)
|
||||
out_relative_delay = max_replicas_unprocessed_insert_time - min_unprocessed_insert_time;
|
||||
}
|
||||
|
||||
|
||||
|
174
libs/libdaemon/include/daemon/Daemon.h
Normal file
174
libs/libdaemon/include/daemon/Daemon.h
Normal file
@ -0,0 +1,174 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include <Poco/Process.h>
|
||||
#include <Poco/ThreadPool.h>
|
||||
#include <Poco/TaskNotification.h>
|
||||
#include <Poco/NumberFormatter.h>
|
||||
#include <Poco/Util/Application.h>
|
||||
#include <Poco/Util/ServerApplication.h>
|
||||
#include <Poco/Net/SocketAddress.h>
|
||||
#include <Poco/FileChannel.h>
|
||||
#include <Poco/SyslogChannel.h>
|
||||
|
||||
#include <common/Common.h>
|
||||
#include <common/logger_useful.h>
|
||||
|
||||
#include <daemon/GraphiteWriter.h>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <zkutil/ZooKeeperHolder.h>
|
||||
|
||||
|
||||
namespace Poco { class TaskManager; }
|
||||
|
||||
|
||||
/// \brief Базовый класс для демонов
|
||||
///
|
||||
/// \code
|
||||
/// # Список возможных опций командной строки обрабатываемых демоном:
|
||||
/// # --config-file или --config - имя файла конфигурации. По умолчанию - config.xml
|
||||
/// # --pid-file - имя PID файла. По умолчанию - pid
|
||||
/// # --log-file - имя лог файла
|
||||
/// # --error-file - имя лог файла, в который будут помещаться только ошибки
|
||||
/// # --daemon - запустить в режиме демона; если не указан - логгирование будет вестись на консоль
|
||||
/// <daemon_name> --daemon --config-file=localfile.xml --pid-file=pid.pid --log-file=log.log --errorlog-file=error.log
|
||||
/// \endcode
|
||||
///
|
||||
/// Если неперехваченное исключение выкинуто в других потоках (не Task-и), то по-умолчанию
|
||||
/// используется KillingErrorHandler, который вызывает std::terminate.
|
||||
///
|
||||
/// Кроме того, класс позволяет достаточно гибко управлять журналированием. В методе initialize() вызывается метод
|
||||
/// buildLoggers() который и строит нужные логгеры. Эта функция ожидает увидеть в конфигурации определённые теги
|
||||
/// заключённые в секции "logger".
|
||||
/// Если нужно журналирование на консоль, нужно просто не использовать тег "log" или использовать --console.
|
||||
/// Теги уровней вывода использовать можно в любом случае
|
||||
|
||||
|
||||
class Daemon : public Poco::Util::ServerApplication
|
||||
{
|
||||
public:
|
||||
Daemon();
|
||||
~Daemon();
|
||||
|
||||
/// Загружает конфигурацию и "строит" логгеры на запись в файлы
|
||||
void initialize(Poco::Util::Application &);
|
||||
|
||||
/// Читает конфигурацию
|
||||
void reloadConfiguration();
|
||||
|
||||
/// Строит необходимые логгеры
|
||||
void buildLoggers();
|
||||
|
||||
/// Определяет параметр командной строки
|
||||
void defineOptions(Poco::Util::OptionSet& _options);
|
||||
|
||||
/// Заставляет демон завершаться, если хотя бы одна задача завершилась неудачно
|
||||
void exitOnTaskError();
|
||||
|
||||
/// Возвращает TaskManager приложения
|
||||
Poco::TaskManager & getTaskManager() { return *task_manager; }
|
||||
|
||||
/// Завершение демона ("мягкое")
|
||||
void terminate();
|
||||
|
||||
/// Завершение демона ("жёсткое")
|
||||
void kill();
|
||||
|
||||
/// Получен ли сигнал на завершение?
|
||||
bool isCancelled()
|
||||
{
|
||||
return is_cancelled;
|
||||
}
|
||||
|
||||
/// Получение ссылки на экземпляр демона
|
||||
static Daemon & instance()
|
||||
{
|
||||
return dynamic_cast<Daemon &>(Poco::Util::Application::instance());
|
||||
}
|
||||
|
||||
/// Спит заданное количество секунд или до события wakeup
|
||||
void sleep(double seconds);
|
||||
|
||||
/// Разбудить
|
||||
void wakeup();
|
||||
|
||||
/// Закрыть файлы с логами. При следующей записи, будут созданы новые файлы.
|
||||
void closeLogs();
|
||||
|
||||
/// В Graphite компоненты пути(папки) разделяются точкой.
|
||||
/// У нас принят путь формата root_path.hostname_yandex_ru.key
|
||||
/// root_path по умолчанию one_min
|
||||
/// key - лучше группировать по смыслу. Например "meminfo.cached" или "meminfo.free", "meminfo.total"
|
||||
template <class T>
|
||||
void writeToGraphite(const std::string & key, const T & value, time_t timestamp = 0, const std::string & custom_root_path = "")
|
||||
{
|
||||
graphite_writer->write(key, value, timestamp, custom_root_path);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void writeToGraphite(const GraphiteWriter::KeyValueVector<T> & key_vals, time_t timestamp = 0, const std::string & custom_root_path = "")
|
||||
{
|
||||
graphite_writer->write(key_vals, timestamp, custom_root_path);
|
||||
}
|
||||
|
||||
boost::optional<size_t> getLayer() const
|
||||
{
|
||||
return layer; /// layer выставляется в классе-наследнике BaseDaemonApplication.
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/// Используется при exitOnTaskError()
|
||||
void handleNotification(Poco::TaskFailedNotification *);
|
||||
|
||||
std::unique_ptr<Poco::TaskManager> task_manager;
|
||||
|
||||
/// Создание и автоматическое удаление pid файла.
|
||||
struct PID
|
||||
{
|
||||
std::string file;
|
||||
|
||||
/// Создать объект, не создавая PID файл
|
||||
PID() {}
|
||||
|
||||
/// Создать объект, создать PID файл
|
||||
PID(const std::string & file_) { seed(file_); }
|
||||
|
||||
/// Создать PID файл
|
||||
void seed(const std::string & file_);
|
||||
|
||||
/// Удалить PID файл
|
||||
void clear();
|
||||
|
||||
~PID() { clear(); }
|
||||
};
|
||||
|
||||
PID pid;
|
||||
|
||||
/// Получен ли сигнал на завершение? Этот флаг устанавливается в BaseDaemonApplication.
|
||||
bool is_cancelled = false;
|
||||
|
||||
bool log_to_console = false;
|
||||
|
||||
/// Событие, чтобы проснуться во время ожидания
|
||||
Poco::Event wakeup_event;
|
||||
|
||||
/// Поток, в котором принимается сигнал HUP/USR1 для закрытия логов.
|
||||
Poco::Thread close_logs_thread;
|
||||
std::unique_ptr<Poco::Runnable> close_logs_listener;
|
||||
|
||||
/// Файлы с логами.
|
||||
Poco::AutoPtr<Poco::FileChannel> log_file;
|
||||
Poco::AutoPtr<Poco::FileChannel> error_log_file;
|
||||
Poco::AutoPtr<Poco::SyslogChannel> syslog_channel;
|
||||
|
||||
std::unique_ptr<GraphiteWriter> graphite_writer;
|
||||
|
||||
boost::optional<size_t> layer;
|
||||
};
|
82
libs/libdaemon/include/daemon/GraphiteWriter.h
Normal file
82
libs/libdaemon/include/daemon/GraphiteWriter.h
Normal file
@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <time.h>
|
||||
#include <Poco/Net/StreamSocket.h>
|
||||
#include <Poco/Net/SocketStream.h>
|
||||
#include <Poco/Util/Application.h>
|
||||
#include <common/logger_useful.h>
|
||||
|
||||
/// пишет в Graphite данные в формате
|
||||
/// path value timestamp\n
|
||||
/// path может иметь любую вложенность. Директории разделяются с помощью "."
|
||||
/// у нас принят следующий формат path - root_path.server_name.sub_path.key
|
||||
class GraphiteWriter
|
||||
{
|
||||
public:
|
||||
GraphiteWriter(const std::string & config_name, const std::string & sub_path = "");
|
||||
|
||||
template <typename T> using KeyValuePair = std::pair<std::string, T>;
|
||||
template <typename T> using KeyValueVector = std::vector<KeyValuePair<T>>;
|
||||
|
||||
template <typename T> void write(const std::string & key, const T & value,
|
||||
time_t timestamp = 0, const std::string & custom_root_path = "")
|
||||
{
|
||||
writeImpl(KeyValuePair<T>{ key, value }, timestamp, custom_root_path);
|
||||
}
|
||||
|
||||
template <typename T> void write(const KeyValueVector<T> & key_val_vec,
|
||||
time_t timestamp = 0, const std::string & custom_root_path = "")
|
||||
{
|
||||
writeImpl(key_val_vec, timestamp, custom_root_path);
|
||||
}
|
||||
|
||||
/// Для облачных демонов удобней использовать
|
||||
/// путь вида prefix.environment.layer.daemon_name.metrica
|
||||
static const std::string & getPerLayerPath(const std::string & prefix = "one_min");
|
||||
|
||||
/// возвращает путь root_path.server_name
|
||||
static std::string getPerServerPath(const std::string & server_name, const std::string & root_path = "one_min");
|
||||
private:
|
||||
template <typename T>
|
||||
void writeImpl(const T & data, time_t timestamp, const std::string & custom_root_path)
|
||||
{
|
||||
if (!timestamp)
|
||||
timestamp = time(0);
|
||||
|
||||
try
|
||||
{
|
||||
Poco::Net::SocketAddress socket_address(host, port);
|
||||
Poco::Net::StreamSocket socket(socket_address);
|
||||
socket.setSendTimeout(Poco::Timespan(timeout * 1000000));
|
||||
Poco::Net::SocketStream str(socket);
|
||||
|
||||
out(str, data, timestamp, custom_root_path);
|
||||
}
|
||||
catch (const Poco::Exception & e)
|
||||
{
|
||||
LOG_WARNING(&Poco::Util::Application::instance().logger(),
|
||||
"Fail to write to Graphite " << host << ":" << port << ". e.what() = " << e.what() << ", e.message() = " << e.message());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void out(std::ostream & os, const KeyValuePair<T> & key_val, time_t timestamp, const std::string & custom_root_path)
|
||||
{
|
||||
os << (custom_root_path.empty() ? root_path : custom_root_path) <<
|
||||
'.' << key_val.first << ' ' << key_val.second << ' ' << timestamp << '\n';
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void out(std::ostream & os, const KeyValueVector<T> & key_val_vec, time_t timestamp, const std::string & custom_root_path)
|
||||
{
|
||||
for (const auto & key_val : key_val_vec)
|
||||
out(os, key_val, timestamp, custom_root_path);
|
||||
}
|
||||
|
||||
std::string root_path;
|
||||
|
||||
int port;
|
||||
std::string host;
|
||||
double timeout;
|
||||
};
|
862
libs/libdaemon/src/Daemon.cpp
Normal file
862
libs/libdaemon/src/Daemon.cpp
Normal file
@ -0,0 +1,862 @@
|
||||
#include <daemon/Daemon.h>
|
||||
|
||||
#include <DB/Common/ConfigProcessor.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <cxxabi.h>
|
||||
#include <execinfo.h>
|
||||
#include <ucontext.h>
|
||||
|
||||
#include <typeinfo>
|
||||
|
||||
#include <common/logger_useful.h>
|
||||
#include <common/ErrorHandlers.h>
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <Poco/Observer.h>
|
||||
#include <Poco/Logger.h>
|
||||
#include <Poco/AutoPtr.h>
|
||||
#include <Poco/SplitterChannel.h>
|
||||
#include <Poco/Ext/LevelFilterChannel.h>
|
||||
#include <Poco/Ext/ThreadNumber.h>
|
||||
#include <Poco/FormattingChannel.h>
|
||||
#include <Poco/PatternFormatter.h>
|
||||
#include <Poco/ConsoleChannel.h>
|
||||
#include <Poco/TaskManager.h>
|
||||
#include <Poco/File.h>
|
||||
#include <Poco/Path.h>
|
||||
#include <Poco/Message.h>
|
||||
#include <Poco/Util/AbstractConfiguration.h>
|
||||
#include <Poco/Util/XMLConfiguration.h>
|
||||
#include <Poco/ScopedLock.h>
|
||||
#include <Poco/Exception.h>
|
||||
#include <Poco/ErrorHandler.h>
|
||||
#include <Poco/NumberFormatter.h>
|
||||
#include <Poco/Condition.h>
|
||||
#include <Poco/SyslogChannel.h>
|
||||
|
||||
#include <DB/Common/Exception.h>
|
||||
#include <DB/IO/WriteBufferFromFileDescriptor.h>
|
||||
#include <DB/IO/ReadBufferFromFileDescriptor.h>
|
||||
#include <DB/IO/ReadHelpers.h>
|
||||
#include <DB/IO/WriteHelpers.h>
|
||||
|
||||
#include <common/Revision.h>
|
||||
|
||||
|
||||
|
||||
using Poco::Logger;
|
||||
using Poco::AutoPtr;
|
||||
using Poco::Observer;
|
||||
using Poco::FormattingChannel;
|
||||
using Poco::SplitterChannel;
|
||||
using Poco::ConsoleChannel;
|
||||
using Poco::FileChannel;
|
||||
using Poco::Path;
|
||||
using Poco::Message;
|
||||
using Poco::Util::AbstractConfiguration;
|
||||
|
||||
|
||||
/** Для передачи информации из обработчика сигнала для обработки в другом потоке.
|
||||
* Если при получении сигнала надо делать что-нибудь серьёзное (например, вывести сообщение в лог),
|
||||
* то передать нужную информацию через pipe в другой поток и сделать там всю работу
|
||||
* - один из немногих безопасных способов сделать это.
|
||||
*/
|
||||
struct Pipe
|
||||
{
|
||||
union
|
||||
{
|
||||
int fds[2];
|
||||
struct
|
||||
{
|
||||
int read_fd;
|
||||
int write_fd;
|
||||
};
|
||||
};
|
||||
|
||||
Pipe()
|
||||
{
|
||||
if (0 != pipe(fds))
|
||||
DB::throwFromErrno("Cannot create pipe");
|
||||
}
|
||||
|
||||
~Pipe()
|
||||
{
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Pipe signal_pipe;
|
||||
|
||||
|
||||
/** Устанавливает обработчик сигнала по умолчанию, и отправляет себе сигнал sig.
|
||||
* Вызывается изнутри пользовательского обработчика сигнала, чтобы записать core dump.
|
||||
*/
|
||||
static void call_default_signal_handler(int sig)
|
||||
{
|
||||
signal(sig, SIG_DFL);
|
||||
kill(getpid(), sig);
|
||||
}
|
||||
|
||||
|
||||
typedef decltype(Poco::ThreadNumber::get()) ThreadNumber;
|
||||
static const size_t buf_size = sizeof(int) + sizeof(siginfo_t) + sizeof(ucontext_t) + sizeof(ThreadNumber);
|
||||
|
||||
|
||||
/** Обработчик сигналов HUP / USR1 */
|
||||
static void close_logs_signal_handler(int sig, siginfo_t * info, void * context)
|
||||
{
|
||||
char buf[buf_size];
|
||||
DB::WriteBufferFromFileDescriptor out(signal_pipe.write_fd, buf_size, buf);
|
||||
DB::writeBinary(sig, out);
|
||||
out.next();
|
||||
}
|
||||
|
||||
|
||||
/** Обработчик некоторых сигналов. Выводит информацию в лог (если получится).
|
||||
*/
|
||||
static void fault_signal_handler(int sig, siginfo_t * info, void * context)
|
||||
{
|
||||
char buf[buf_size];
|
||||
DB::WriteBufferFromFileDescriptor out(signal_pipe.write_fd, buf_size, buf);
|
||||
|
||||
DB::writeBinary(sig, out);
|
||||
DB::writePODBinary(*info, out);
|
||||
DB::writePODBinary(*reinterpret_cast<const ucontext_t *>(context), out);
|
||||
DB::writeBinary(Poco::ThreadNumber::get(), out);
|
||||
|
||||
out.next();
|
||||
|
||||
/// Время, за которое читающий из pipe поток должен попытаться успеть вывести в лог stack trace.
|
||||
::sleep(10);
|
||||
|
||||
call_default_signal_handler(sig);
|
||||
}
|
||||
|
||||
|
||||
static bool already_printed_stack_trace = false;
|
||||
|
||||
|
||||
/** Получает информацию через pipe.
|
||||
* При получении сигнала HUP / USR1 закрывает лог-файлы.
|
||||
* При получении информации из std::terminate, выводит её в лог.
|
||||
* При получении других сигналов, выводит информацию в лог.
|
||||
*/
|
||||
class SignalListener : public Poco::Runnable
|
||||
{
|
||||
public:
|
||||
SignalListener() : log(&Logger::get("Daemon"))
|
||||
{
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
char buf[buf_size];
|
||||
DB::ReadBufferFromFileDescriptor in(signal_pipe.read_fd, buf_size, buf);
|
||||
|
||||
while (!in.eof())
|
||||
{
|
||||
int sig = 0;
|
||||
DB::readBinary(sig, in);
|
||||
|
||||
if (sig == SIGHUP || sig == SIGUSR1)
|
||||
{
|
||||
LOG_DEBUG(log, "Received signal to close logs.");
|
||||
Daemon::instance().closeLogs();
|
||||
LOG_INFO(log, "Opened new log file after received signal.");
|
||||
}
|
||||
else if (sig == -1) /// -1 для обозначения std::terminate.
|
||||
{
|
||||
ThreadNumber thread_num;
|
||||
std::string message;
|
||||
|
||||
DB::readBinary(thread_num, in);
|
||||
DB::readBinary(message, in);
|
||||
|
||||
onTerminate(message, thread_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
siginfo_t info;
|
||||
ucontext_t context;
|
||||
ThreadNumber thread_num;
|
||||
|
||||
DB::readPODBinary(info, in);
|
||||
DB::readPODBinary(context, in);
|
||||
DB::readBinary(thread_num, in);
|
||||
|
||||
onFault(sig, info, context, thread_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Logger * log;
|
||||
|
||||
|
||||
void onTerminate(const std::string & message, ThreadNumber thread_num) const
|
||||
{
|
||||
LOG_ERROR(log, "(from thread " << thread_num << ") " << message);
|
||||
}
|
||||
|
||||
void onFault(int sig, siginfo_t & info, ucontext_t & context, ThreadNumber thread_num) const
|
||||
{
|
||||
LOG_ERROR(log, "########################################");
|
||||
LOG_ERROR(log, "(from thread " << thread_num << ") "
|
||||
<< "Received signal " << strsignal(sig) << " (" << sig << ")" << ".");
|
||||
|
||||
if (sig == SIGSEGV)
|
||||
{
|
||||
/// Выводим информацию об адресе и о причине.
|
||||
if (nullptr == info.si_addr)
|
||||
LOG_ERROR(log, "Address: NULL pointer.");
|
||||
else
|
||||
LOG_ERROR(log, "Address: " << info.si_addr
|
||||
<< (info.si_code == SEGV_ACCERR ? ". Attempted access has violated the permissions assigned to the memory area." : ""));
|
||||
}
|
||||
|
||||
if (already_printed_stack_trace)
|
||||
return;
|
||||
|
||||
void * caller_address = nullptr;
|
||||
|
||||
#if defined(__x86_64__)
|
||||
/// Get the address at the time the signal was raised from the RIP (x86-64)
|
||||
caller_address = reinterpret_cast<void *>(context.uc_mcontext.gregs[REG_RIP]);
|
||||
#elif defined(__aarch64__)
|
||||
caller_address = reinterpret_cast<void *>(context.uc_mcontext.pc);
|
||||
#endif
|
||||
|
||||
static const int max_frames = 50;
|
||||
void * frames[max_frames];
|
||||
int frames_size = backtrace(frames, max_frames);
|
||||
|
||||
if (frames_size >= 2)
|
||||
{
|
||||
/// Overwrite sigaction with caller's address
|
||||
if (caller_address && (frames_size < 3 || caller_address != frames[2]))
|
||||
frames[1] = caller_address;
|
||||
|
||||
char ** symbols = backtrace_symbols(frames, frames_size);
|
||||
|
||||
if (!symbols)
|
||||
{
|
||||
if (caller_address)
|
||||
LOG_ERROR(log, "Caller address: " << caller_address);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 1; i < frames_size; ++i)
|
||||
{
|
||||
/// Делаем demangling имён. Имя находится в скобках, до символа '+'.
|
||||
|
||||
char * name_start = nullptr;
|
||||
char * name_end = nullptr;
|
||||
char * demangled_name = nullptr;
|
||||
int status = 0;
|
||||
|
||||
if (nullptr != (name_start = strchr(symbols[i], '('))
|
||||
&& nullptr != (name_end = strchr(name_start, '+')))
|
||||
{
|
||||
++name_start;
|
||||
*name_end = '\0';
|
||||
demangled_name = abi::__cxa_demangle(name_start, 0, 0, &status);
|
||||
*name_end = '+';
|
||||
}
|
||||
|
||||
std::stringstream res;
|
||||
|
||||
res << i << ". ";
|
||||
|
||||
if (nullptr != demangled_name && 0 == status)
|
||||
{
|
||||
res.write(symbols[i], name_start - symbols[i]);
|
||||
res << demangled_name << name_end;
|
||||
}
|
||||
else
|
||||
res << symbols[i];
|
||||
|
||||
LOG_ERROR(log, res.rdbuf());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Для использования с помощью std::set_terminate.
|
||||
* Собирает чуть больше информации, чем __gnu_cxx::__verbose_terminate_handler,
|
||||
* и отправляет её в pipe. Другой поток читает из pipe и выводит её в лог.
|
||||
* См. исходники в libstdc++-v3/libsupc++/vterminate.cc
|
||||
*/
|
||||
static void terminate_handler()
|
||||
{
|
||||
static __thread bool terminating = false;
|
||||
if (terminating)
|
||||
{
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
|
||||
terminating = true;
|
||||
|
||||
/// Сюда записываем информацию для логгирования.
|
||||
std::stringstream log;
|
||||
|
||||
std::type_info * t = abi::__cxa_current_exception_type();
|
||||
if (t)
|
||||
{
|
||||
/// Note that "name" is the mangled name.
|
||||
char const * name = t->name();
|
||||
{
|
||||
int status = -1;
|
||||
char * dem = 0;
|
||||
|
||||
dem = abi::__cxa_demangle(name, 0, 0, &status);
|
||||
|
||||
log << "Terminate called after throwing an instance of " << (status == 0 ? dem : name) << std::endl;
|
||||
|
||||
if (status == 0)
|
||||
free(dem);
|
||||
}
|
||||
|
||||
already_printed_stack_trace = true;
|
||||
|
||||
/// If the exception is derived from std::exception, we can give more information.
|
||||
try
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (DB::Exception & e)
|
||||
{
|
||||
log << "Code: " << e.code() << ", e.displayText() = " << e.displayText() << ", e.what() = " << e.what() << std::endl;
|
||||
}
|
||||
catch (Poco::Exception & e)
|
||||
{
|
||||
log << "Code: " << e.code() << ", e.displayText() = " << e.displayText() << ", e.what() = " << e.what() << std::endl;
|
||||
}
|
||||
catch (const std::exception & e)
|
||||
{
|
||||
log << "what(): " << e.what() << std::endl;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
log << "Stack trace:\n\n" << StackTrace().toString() << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
log << "Terminate called without an active exception" << std::endl;
|
||||
}
|
||||
|
||||
static const size_t buf_size = 1024;
|
||||
|
||||
std::string log_message = log.str();
|
||||
if (log_message.size() > buf_size - 16)
|
||||
log_message.resize(buf_size - 16);
|
||||
|
||||
char buf[buf_size];
|
||||
DB::WriteBufferFromFileDescriptor out(signal_pipe.write_fd, buf_size, buf);
|
||||
|
||||
DB::writeBinary(-1, out);
|
||||
DB::writeBinary(Poco::ThreadNumber::get(), out);
|
||||
DB::writeBinary(log_message, out);
|
||||
out.next();
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
|
||||
static std::string createDirectory(const std::string & _path)
|
||||
{
|
||||
Poco::Path path(_path);
|
||||
std::string str;
|
||||
for(int j=0;j<path.depth();++j)
|
||||
{
|
||||
str += "/";
|
||||
str += path[j];
|
||||
|
||||
int res = ::mkdir(str.c_str(), 0700);
|
||||
if( res && (errno!=EEXIST) )
|
||||
{
|
||||
throw std::runtime_error(std::string("Can't create dir - ") + str + " - " + strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
|
||||
void Daemon::reloadConfiguration()
|
||||
{
|
||||
/** Если программа запущена не в режиме демона, и не указан параметр config-file,
|
||||
* то будем использовать параметры из файла config.xml в текущей директории,
|
||||
* но игнорировать заданные в нём параметры логгирования.
|
||||
* (Чтобы при запуске с минимумом параметров, лог выводился в консоль.)
|
||||
* При этом, параметры логгирования, заданные в командной строке, не игнорируются.
|
||||
*/
|
||||
std::string log_command_line_option = config().getString("logger.log", "");
|
||||
ConfigurationPtr processed_config = ConfigProcessor(false, true).loadConfig(config().getString("config-file", "config.xml"));
|
||||
config().add(processed_config.duplicate(), PRIO_DEFAULT, false);
|
||||
log_to_console = !config().getBool("application.runAsDaemon", false) && log_command_line_option.empty();
|
||||
}
|
||||
|
||||
|
||||
/** Форматирует по своему.
|
||||
* Некоторые детали невозможно получить, используя только Poco::PatternFormatter.
|
||||
*
|
||||
* Во-первых, используется номер потока не среди потоков Poco::Thread,
|
||||
* а среди всех потоков, для которых был получен номер (см. ThreadNumber.h)
|
||||
*
|
||||
* Во-вторых, корректно выводится локальная дата и время.
|
||||
* Poco::PatternFormatter плохо работает с локальным временем,
|
||||
* в ситуациях, когда в ближайшем будущем намечается отмена или введение daylight saving time.
|
||||
* - см. исходники Poco и http://thread.gmane.org/gmane.comp.time.tz/8883
|
||||
*
|
||||
* Также сделан чуть более эффективным (что имеет мало значения).
|
||||
*/
|
||||
class OwnPatternFormatter : public Poco::PatternFormatter
|
||||
{
|
||||
public:
|
||||
enum Options
|
||||
{
|
||||
ADD_NOTHING = 0,
|
||||
ADD_LAYER_TAG = 1 << 0
|
||||
};
|
||||
OwnPatternFormatter(const Daemon & daemon_, Options options_ = ADD_NOTHING) : Poco::PatternFormatter(""), daemon(daemon_), options(options_) {}
|
||||
|
||||
void format(const Message & msg, std::string & text) override
|
||||
{
|
||||
DB::WriteBufferFromString wb(text);
|
||||
|
||||
/// в syslog тэг идет перед сообщением и до первого пробела.
|
||||
if (options & ADD_LAYER_TAG)
|
||||
{
|
||||
boost::optional<size_t> layer = daemon.getLayer();
|
||||
if (layer)
|
||||
{
|
||||
writeCString("layer[", wb);
|
||||
DB::writeIntText(*layer, wb);
|
||||
writeCString("]: ", wb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Выведем время с точностью до миллисекунд.
|
||||
timeval tv;
|
||||
if (0 != gettimeofday(&tv, nullptr))
|
||||
DB::throwFromErrno("Cannot gettimeofday");
|
||||
|
||||
/// Поменяем разделители у даты для совместимости.
|
||||
DB::writeDateTimeText<'.', ':'>(tv.tv_sec, wb);
|
||||
|
||||
int milliseconds = tv.tv_usec / 1000;
|
||||
DB::writeChar('.', wb);
|
||||
DB::writeChar('0' + ((milliseconds / 100) % 10), wb);
|
||||
DB::writeChar('0' + ((milliseconds / 10) % 10), wb);
|
||||
DB::writeChar('0' + (milliseconds % 10), wb);
|
||||
|
||||
writeCString(" [ ", wb);
|
||||
DB::writeIntText(Poco::ThreadNumber::get(), wb);
|
||||
writeCString(" ] <", wb);
|
||||
DB::writeString(getPriorityName(static_cast<int>(msg.getPriority())), wb);
|
||||
writeCString("> ", wb);
|
||||
DB::writeString(msg.getSource(), wb);
|
||||
writeCString(": ", wb);
|
||||
DB::writeString(msg.getText(), wb);
|
||||
}
|
||||
|
||||
private:
|
||||
const Daemon & daemon;
|
||||
Options options;
|
||||
};
|
||||
|
||||
|
||||
/// Для создания и уничтожения unique_ptr, который в заголовочном файле объявлен от incomplete type.
|
||||
Daemon::Daemon() = default;
|
||||
Daemon::~Daemon() = default;
|
||||
|
||||
|
||||
void Daemon::terminate()
|
||||
{
|
||||
getTaskManager().cancelAll();
|
||||
ServerApplication::terminate();
|
||||
}
|
||||
|
||||
void Daemon::kill()
|
||||
{
|
||||
pid.clear();
|
||||
Poco::Process::kill(getpid());
|
||||
}
|
||||
|
||||
void Daemon::sleep(double seconds)
|
||||
{
|
||||
wakeup_event.reset();
|
||||
wakeup_event.tryWait(seconds * 1000);
|
||||
}
|
||||
|
||||
void Daemon::wakeup()
|
||||
{
|
||||
wakeup_event.set();
|
||||
}
|
||||
|
||||
|
||||
/// Строит необходимые логгеры
|
||||
void Daemon::buildLoggers()
|
||||
{
|
||||
/// Сменим директорию для лога
|
||||
if (config().hasProperty("logger.log") && !log_to_console)
|
||||
{
|
||||
std::string path = createDirectory(config().getString("logger.log"));
|
||||
if (config().getBool("application.runAsDaemon", false)
|
||||
&& chdir(path.c_str()) != 0)
|
||||
throw Poco::Exception("Cannot change directory to " + path);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (config().getBool("application.runAsDaemon", false)
|
||||
&& chdir("/tmp") != 0)
|
||||
throw Poco::Exception("Cannot change directory to /tmp");
|
||||
}
|
||||
|
||||
if (config().hasProperty("logger.errorlog") && !log_to_console)
|
||||
createDirectory(config().getString("logger.errorlog"));
|
||||
|
||||
if (config().hasProperty("logger.log") && !log_to_console)
|
||||
{
|
||||
std::cerr << "Should logs to " << config().getString("logger.log") << std::endl;
|
||||
|
||||
// splitter
|
||||
Poco::AutoPtr<SplitterChannel> split = new SplitterChannel;
|
||||
|
||||
// set up two channel chains
|
||||
Poco::AutoPtr<OwnPatternFormatter> pf = new OwnPatternFormatter(*this);
|
||||
pf->setProperty("times", "local");
|
||||
Poco::AutoPtr<FormattingChannel> log = new FormattingChannel(pf);
|
||||
log_file = new FileChannel;
|
||||
log_file->setProperty("path", Poco::Path(config().getString("logger.log")).absolute().toString());
|
||||
log_file->setProperty("rotation", config().getRawString("logger.size", "100M"));
|
||||
log_file->setProperty("archive", "number");
|
||||
log_file->setProperty("compress", config().getRawString("logger.compress", "true"));
|
||||
log_file->setProperty("purgeCount", config().getRawString("logger.count", "1"));
|
||||
log->setChannel(log_file);
|
||||
split->addChannel(log);
|
||||
log_file->open();
|
||||
|
||||
if (config().hasProperty("logger.errorlog"))
|
||||
{
|
||||
std::cerr << "Should error logs to " << config().getString("logger.errorlog") << std::endl;
|
||||
Poco::AutoPtr<Poco::LevelFilterChannel> level = new Poco::LevelFilterChannel;
|
||||
level->setLevel(Message::PRIO_NOTICE);
|
||||
Poco::AutoPtr<OwnPatternFormatter> pf = new OwnPatternFormatter(*this);
|
||||
pf->setProperty("times", "local");
|
||||
Poco::AutoPtr<FormattingChannel> errorlog = new FormattingChannel(pf);
|
||||
error_log_file = new FileChannel;
|
||||
error_log_file->setProperty("path", Poco::Path(config().getString("logger.errorlog")).absolute().toString());
|
||||
error_log_file->setProperty("rotation", config().getRawString("logger.size", "100M"));
|
||||
error_log_file->setProperty("archive", "number");
|
||||
error_log_file->setProperty("compress", config().getRawString("logger.compress", "true"));
|
||||
error_log_file->setProperty("purgeCount", config().getRawString("logger.count", "1"));
|
||||
errorlog->setChannel(error_log_file);
|
||||
level->setChannel(errorlog);
|
||||
split->addChannel(level);
|
||||
errorlog->open();
|
||||
}
|
||||
|
||||
if (config().getBool("logger.use_syslog", false) || config().getBool("dynamic_layer_selection", false))
|
||||
{
|
||||
Poco::AutoPtr<OwnPatternFormatter> pf = new OwnPatternFormatter(*this, OwnPatternFormatter::ADD_LAYER_TAG);
|
||||
pf->setProperty("times", "local");
|
||||
Poco::AutoPtr<FormattingChannel> log = new FormattingChannel(pf);
|
||||
syslog_channel = new Poco::SyslogChannel(commandName(), Poco::SyslogChannel::SYSLOG_CONS | Poco::SyslogChannel::SYSLOG_PID, Poco::SyslogChannel::SYSLOG_DAEMON);
|
||||
log->setChannel(syslog_channel);
|
||||
split->addChannel(log);
|
||||
syslog_channel->open();
|
||||
}
|
||||
|
||||
split->open();
|
||||
logger().close();
|
||||
logger().setChannel(split);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Выводим на консоль
|
||||
Poco::AutoPtr<ConsoleChannel> file = new ConsoleChannel;
|
||||
Poco::AutoPtr<OwnPatternFormatter> pf = new OwnPatternFormatter(*this);
|
||||
pf->setProperty("times", "local");
|
||||
Poco::AutoPtr<FormattingChannel> log = new FormattingChannel(pf);
|
||||
log->setChannel(file);
|
||||
|
||||
logger().close();
|
||||
logger().setChannel(log);
|
||||
logger().warning("Logging to console");
|
||||
}
|
||||
|
||||
// Уровни для всех
|
||||
logger().setLevel(config().getString("logger.level", "trace"));
|
||||
|
||||
// Прикрутим к корневому логгеру
|
||||
Logger::root().setLevel(logger().getLevel());
|
||||
Logger::root().setChannel(logger().getChannel());
|
||||
|
||||
// Уровни для явно указанных логгеров
|
||||
AbstractConfiguration::Keys levels;
|
||||
config().keys("logger.levels", levels);
|
||||
|
||||
if(!levels.empty())
|
||||
for(AbstractConfiguration::Keys::iterator it = levels.begin(); it != levels.end(); ++it)
|
||||
Logger::get(*it).setLevel(config().getString("logger.levels." + *it, "trace"));
|
||||
}
|
||||
|
||||
|
||||
void Daemon::closeLogs()
|
||||
{
|
||||
if (log_file)
|
||||
log_file->close();
|
||||
if (error_log_file)
|
||||
error_log_file->close();
|
||||
|
||||
if (!log_file)
|
||||
logger().warning("Logging to console but received signal to close log file (ignoring).");
|
||||
}
|
||||
|
||||
void Daemon::initialize(Application& self)
|
||||
{
|
||||
/// В случае падения - сохраняем коры
|
||||
{
|
||||
struct rlimit rlim;
|
||||
if (getrlimit(RLIMIT_CORE, &rlim))
|
||||
throw Poco::Exception("Cannot getrlimit");
|
||||
rlim.rlim_cur = 1024 * 1024 * 1024; /// 1 GiB. Если больше - они слишком долго пишутся на диск.
|
||||
if (setrlimit(RLIMIT_CORE, &rlim))
|
||||
throw Poco::Exception("Cannot setrlimit");
|
||||
}
|
||||
|
||||
task_manager.reset(new Poco::TaskManager);
|
||||
ServerApplication::initialize(self);
|
||||
|
||||
bool is_daemon = config().getBool("application.runAsDaemon", false);
|
||||
|
||||
if (is_daemon)
|
||||
{
|
||||
/** При создании pid файла и поиске конфигурации, будем интерпретировать относительные пути
|
||||
* от директории запуска программы.
|
||||
*/
|
||||
std::string path = Poco::Path(config().getString("application.path")).setFileName("").toString();
|
||||
if (0 != chdir(path.c_str()))
|
||||
throw Poco::Exception("Cannot change directory to " + path);
|
||||
}
|
||||
|
||||
/// Считаем конфигурацию
|
||||
reloadConfiguration();
|
||||
|
||||
std::string log_path = config().getString("logger.log", "");
|
||||
if (!log_path.empty())
|
||||
log_path = Poco::Path(log_path).setFileName("").toString();
|
||||
|
||||
if (is_daemon)
|
||||
{
|
||||
/** Переназначим stdout, stderr в отдельные файлы в директориях с логами.
|
||||
* Некоторые библиотеки пишут в stderr в случае ошибок или в отладочном режиме,
|
||||
* и этот вывод иногда имеет смысл смотреть даже когда программа запущена в режиме демона.
|
||||
* Делаем это до buildLoggers, чтобы ошибки во время инициализации логгера, попали в эти файлы.
|
||||
*/
|
||||
if (!log_path.empty())
|
||||
{
|
||||
std::string stdout_path = log_path + "/stdout";
|
||||
if (!freopen(stdout_path.c_str(), "a+", stdout))
|
||||
throw Poco::OpenFileException("Cannot attach stdout to " + stdout_path);
|
||||
|
||||
std::string stderr_path = log_path + "/stderr";
|
||||
if (!freopen(stderr_path.c_str(), "a+", stderr))
|
||||
throw Poco::OpenFileException("Cannot attach stderr to " + stderr_path);
|
||||
}
|
||||
|
||||
/// Создадим pid-file.
|
||||
if (is_daemon && config().has("pid"))
|
||||
pid.seed(config().getString("pid"));
|
||||
}
|
||||
|
||||
buildLoggers();
|
||||
|
||||
if (is_daemon)
|
||||
{
|
||||
/** Сменим директорию на ту, куда надо писать core файлы.
|
||||
* Делаем это после buildLoggers, чтобы не менять текущую директорию раньше.
|
||||
* Это важно, если конфиги расположены в текущей директории.
|
||||
*/
|
||||
Poco::File opt_cores = "/opt/cores";
|
||||
|
||||
std::string core_path = config().getString("core_path",
|
||||
opt_cores.exists() && opt_cores.isDirectory()
|
||||
? "/opt/cores/"
|
||||
: (!log_path.empty()
|
||||
? log_path
|
||||
: "/opt/"));
|
||||
|
||||
if (0 != chdir(core_path.c_str()))
|
||||
throw Poco::Exception("Cannot change directory to " + core_path);
|
||||
}
|
||||
|
||||
/// Ставим terminate_handler
|
||||
std::set_terminate(terminate_handler);
|
||||
|
||||
/// Ставим обработчики сигналов
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = fault_signal_handler;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
|
||||
{
|
||||
int signals[] = {SIGABRT, SIGSEGV, SIGILL, SIGBUS, SIGSYS, SIGFPE, SIGPIPE, 0};
|
||||
|
||||
if (sigemptyset(&sa.sa_mask))
|
||||
throw Poco::Exception("Cannot set signal handler.");
|
||||
|
||||
for (size_t i = 0; signals[i]; ++i)
|
||||
if (sigaddset(&sa.sa_mask, signals[i]))
|
||||
throw Poco::Exception("Cannot set signal handler.");
|
||||
|
||||
for (size_t i = 0; signals[i]; ++i)
|
||||
if (sigaction(signals[i], &sa, 0))
|
||||
throw Poco::Exception("Cannot set signal handler.");
|
||||
}
|
||||
|
||||
sa.sa_sigaction = close_logs_signal_handler;
|
||||
|
||||
{
|
||||
int signals[] = {SIGHUP, SIGUSR1, 0};
|
||||
|
||||
if (sigemptyset(&sa.sa_mask))
|
||||
throw Poco::Exception("Cannot set signal handler.");
|
||||
|
||||
for (size_t i = 0; signals[i]; ++i)
|
||||
if (sigaddset(&sa.sa_mask, signals[i]))
|
||||
throw Poco::Exception("Cannot set signal handler.");
|
||||
|
||||
for (size_t i = 0; signals[i]; ++i)
|
||||
if (sigaction(signals[i], &sa, 0))
|
||||
throw Poco::Exception("Cannot set signal handler.");
|
||||
}
|
||||
|
||||
/// Ставим ErrorHandler для потоков
|
||||
static KillingErrorHandler killing_error_handler;
|
||||
Poco::ErrorHandler::set(&killing_error_handler);
|
||||
|
||||
/// Выведем ревизию демона
|
||||
Logger::root().information("Starting daemon with revision " + Poco::NumberFormatter::format(Revision::get()));
|
||||
|
||||
close_logs_listener.reset(new SignalListener);
|
||||
close_logs_thread.start(*close_logs_listener);
|
||||
|
||||
graphite_writer.reset(new GraphiteWriter("graphite"));
|
||||
}
|
||||
|
||||
|
||||
/// Заставляет демон завершаться, если хотя бы одна задача завершилась неудачно
|
||||
void Daemon::exitOnTaskError()
|
||||
{
|
||||
Observer<Daemon, Poco::TaskFailedNotification> obs(*this, &Daemon::handleNotification);
|
||||
getTaskManager().addObserver(obs);
|
||||
}
|
||||
|
||||
/// Используется при exitOnTaskError()
|
||||
void Daemon::handleNotification(Poco::TaskFailedNotification *_tfn)
|
||||
{
|
||||
AutoPtr<Poco::TaskFailedNotification> fn(_tfn);
|
||||
Logger *lg = &(logger());
|
||||
LOG_ERROR(lg, "Task '" << fn->task()->name() << "' failed. Daemon is shutting down. Reason - " << fn->reason().displayText());
|
||||
ServerApplication::terminate();
|
||||
}
|
||||
|
||||
void Daemon::defineOptions(Poco::Util::OptionSet& _options)
|
||||
{
|
||||
Poco::Util::ServerApplication::defineOptions (_options);
|
||||
|
||||
_options.addOption(
|
||||
Poco::Util::Option ("config-file", "C", "load configuration from a given file")
|
||||
.required (false)
|
||||
.repeatable (false)
|
||||
.argument ("<file>")
|
||||
.binding("config-file")
|
||||
);
|
||||
|
||||
_options.addOption(
|
||||
Poco::Util::Option ("log-file", "L", "use given log file")
|
||||
.required (false)
|
||||
.repeatable (false)
|
||||
.argument ("<file>")
|
||||
.binding("logger.log")
|
||||
);
|
||||
|
||||
_options.addOption(
|
||||
Poco::Util::Option ("errorlog-file", "E", "use given log file for errors only")
|
||||
.required (false)
|
||||
.repeatable (false)
|
||||
.argument ("<file>")
|
||||
.binding("logger.errorlog")
|
||||
);
|
||||
|
||||
_options.addOption(
|
||||
Poco::Util::Option ("pid-file", "P", "use given pidfile")
|
||||
.required (false)
|
||||
.repeatable (false)
|
||||
.argument ("<file>")
|
||||
.binding("pid")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void Daemon::PID::seed(const std::string & file_)
|
||||
{
|
||||
/// переведём путь в абсолютный
|
||||
file = Poco::Path(file_).absolute().toString();
|
||||
|
||||
int fd = open(file.c_str(),
|
||||
O_CREAT | O_EXCL | O_WRONLY,
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
||||
|
||||
if (-1 == fd)
|
||||
{
|
||||
file.clear();
|
||||
if (EEXIST == errno)
|
||||
throw Poco::Exception("Pid file exists, should not start daemon.");
|
||||
throw Poco::CreateFileException("Cannot create pid file.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::stringstream s;
|
||||
s << getpid();
|
||||
if (static_cast<ssize_t>(s.str().size()) != write(fd, s.str().c_str(), s.str().size()))
|
||||
throw Poco::Exception("Cannot write to pid file.");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
close(fd);
|
||||
throw;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void Daemon::PID::clear()
|
||||
{
|
||||
if (!file.empty())
|
||||
{
|
||||
Poco::File(file).remove();
|
||||
file.clear();
|
||||
}
|
||||
}
|
86
libs/libdaemon/src/GraphiteWriter.cpp
Normal file
86
libs/libdaemon/src/GraphiteWriter.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
#include <daemon/GraphiteWriter.h>
|
||||
#include <daemon/Daemon.h>
|
||||
#include <Poco/Util/LayeredConfiguration.h>
|
||||
#include <Poco/Util/Application.h>
|
||||
#include <Poco/Net/DNS.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <iomanip>
|
||||
|
||||
|
||||
GraphiteWriter::GraphiteWriter(const std::string & config_name, const std::string & sub_path)
|
||||
{
|
||||
Poco::Util::LayeredConfiguration & config = Poco::Util::Application::instance().config();
|
||||
port = config.getInt(config_name + ".port", 42000);
|
||||
host = config.getString(config_name + ".host", "127.0.0.1");
|
||||
timeout = config.getDouble(config_name + ".timeout", 0.1);
|
||||
|
||||
root_path = config.getString(config_name + ".root_path", "one_min");
|
||||
if (root_path.size())
|
||||
root_path += ".";
|
||||
|
||||
/// То же, что uname -n
|
||||
std::string hostname_in_path = Poco::Net::DNS::hostName();
|
||||
|
||||
/// Заменяем точки на подчёркивания, чтобы Graphite не интерпретировал их, как разделители пути.
|
||||
std::replace(std::begin(hostname_in_path), std::end(hostname_in_path), '.', '_');
|
||||
|
||||
root_path += hostname_in_path + config.getString(config_name + ".hostname_suffix", "");
|
||||
|
||||
if (sub_path.size())
|
||||
root_path += "." + sub_path;
|
||||
}
|
||||
|
||||
static std::string getPerLayerPathImpl(const std::string prefix)
|
||||
{
|
||||
/// Угадываем имя среды по имени машинки
|
||||
/// машинки имеют имена вида example01dt.yandex.ru
|
||||
/// t - test
|
||||
/// dev - development
|
||||
/// никакого суффикса - production
|
||||
|
||||
std::stringstream path_full;
|
||||
|
||||
path_full << prefix << ".";
|
||||
|
||||
std::string hostname = Poco::Net::DNS::hostName();
|
||||
hostname = hostname.substr(0, hostname.find('.'));
|
||||
|
||||
const std::string development_suffix = "dev";
|
||||
if (hostname.back() == 't')
|
||||
path_full << "test.";
|
||||
else if (hostname.size() > development_suffix.size() &&
|
||||
hostname.substr(hostname.size() - development_suffix.size()) == development_suffix)
|
||||
path_full << "development.";
|
||||
else
|
||||
path_full << "production.";
|
||||
|
||||
const Daemon & daemon = Daemon::instance();
|
||||
|
||||
if (daemon.getLayer())
|
||||
path_full << "layer" << std::setfill('0') << std::setw(3) << *daemon.getLayer() << ".";
|
||||
|
||||
/// Когда несколько демонов запускается на одной машине
|
||||
/// к имени демона добавляется цифра.
|
||||
/// Удалим последнюю цифру
|
||||
std::locale locale;
|
||||
std::string command_name = daemon.commandName();
|
||||
if (std::isdigit(command_name.back(), locale))
|
||||
command_name = command_name.substr(0, command_name.size() - 1);
|
||||
path_full << command_name;
|
||||
|
||||
return path_full.str();
|
||||
}
|
||||
|
||||
const std::string & GraphiteWriter::getPerLayerPath(const std::string & prefix)
|
||||
{
|
||||
static std::string path = getPerLayerPathImpl(prefix); /// thread-safe statics.
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string GraphiteWriter::getPerServerPath(const std::string & server_name, const std::string & root_path)
|
||||
{
|
||||
std::string path = root_path + "." + server_name;
|
||||
std::replace(path.begin() + root_path.size() + 1, path.end(), '.', '_');
|
||||
return path;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <zkutil/ZooKeeper.h>
|
||||
#include <zkutil/ZooKeeperHolder.h>
|
||||
|
||||
namespace zkutil
|
||||
{
|
||||
@ -8,10 +8,10 @@ namespace zkutil
|
||||
class Increment
|
||||
{
|
||||
public:
|
||||
Increment(ZooKeeperPtr zk_, const std::string & path_)
|
||||
: zk(zk_), path(path_)
|
||||
Increment(ZooKeeperHolderPtr zk_holder_, const std::string & path_)
|
||||
: zookeeper_holder(zk_holder_), path(path_)
|
||||
{
|
||||
zk->createAncestors(path);
|
||||
zookeeper_holder->getZooKeeper()->createAncestors(path);
|
||||
}
|
||||
|
||||
size_t get()
|
||||
@ -23,16 +23,17 @@ public:
|
||||
zkutil::Stat stat;
|
||||
|
||||
bool success = false;
|
||||
auto zookeeper = zookeeper_holder->getZooKeeper();
|
||||
do
|
||||
{
|
||||
if (zk->tryGet(path, result_str, &stat))
|
||||
if (zookeeper->tryGet(path, result_str, &stat))
|
||||
{
|
||||
result = std::stol(result_str) + 1;
|
||||
success = zk->trySet(path, std::to_string(result), stat.version) == ZOK;
|
||||
success = zookeeper->trySet(path, std::to_string(result), stat.version) == ZOK;
|
||||
}
|
||||
else
|
||||
{
|
||||
success = zk->tryCreate(path, std::to_string(result), zkutil::CreateMode::Persistent) == ZOK;
|
||||
success = zookeeper->tryCreate(path, std::to_string(result), zkutil::CreateMode::Persistent) == ZOK;
|
||||
}
|
||||
}
|
||||
while (!success);
|
||||
@ -40,7 +41,7 @@ public:
|
||||
return result;
|
||||
}
|
||||
private:
|
||||
zkutil::ZooKeeperPtr zk;
|
||||
zkutil::ZooKeeperHolderPtr zookeeper_holder;
|
||||
std::string path;
|
||||
Logger * log = &Logger::get("zkutil::Increment");
|
||||
};
|
||||
|
@ -43,6 +43,13 @@ public:
|
||||
return code == ZINVALIDSTATE || code == ZSESSIONEXPIRED || code == ZSESSIONMOVED;
|
||||
}
|
||||
|
||||
/// любая ошибка связанная с работой сети, перевыбором мастера
|
||||
/// при этих ошибках надо либо повторить запрос повторно, либо переинициализировать сессию (см. isUnrecoverable())
|
||||
bool isHardwareError() const
|
||||
{
|
||||
return isUnrecoverable() || code == ZCONNECTIONLOSS || code == ZOPERATIONTIMEOUT;
|
||||
}
|
||||
|
||||
const int32_t code;
|
||||
|
||||
private:
|
||||
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <zkutil/ZooKeeper.h>
|
||||
#include <zkutil/ZooKeeperHolder.h>
|
||||
#include <common/logger_useful.h>
|
||||
#include <DB/Common/Exception.h>
|
||||
|
||||
@ -11,13 +11,22 @@ namespace zkutil
|
||||
public:
|
||||
/// lock_prefix - относительный путь до блокировки в ZK. Начинается со слеша
|
||||
/// lock_name - имя ноды блокировки в ZK
|
||||
Lock(zkutil::ZooKeeperPtr zk, const std::string & lock_prefix_, const std::string & lock_name_, const std::string & lock_message_ = "",
|
||||
bool create_parent_path = false) :
|
||||
zookeeper(zk), lock_path(lock_prefix_ + "/" + lock_name_), lock_message(lock_message_), log(&Logger::get("zkutil::Lock"))
|
||||
Lock(
|
||||
zkutil::ZooKeeperHolderPtr zookeeper_holder_,
|
||||
const std::string & lock_prefix_,
|
||||
const std::string & lock_name_,
|
||||
const std::string & lock_message_ = "",
|
||||
bool create_parent_path_ = false)
|
||||
:
|
||||
zookeeper_holder(zookeeper_holder_),
|
||||
lock_path(lock_prefix_ + "/" + lock_name_),
|
||||
lock_message(lock_message_),
|
||||
log(&Logger::get("zkutil::Lock"))
|
||||
{
|
||||
if (create_parent_path)
|
||||
auto zookeeper = zookeeper_holder->getZooKeeper();
|
||||
if (create_parent_path_)
|
||||
zookeeper->createAncestors(lock_prefix_);
|
||||
|
||||
|
||||
zookeeper->createIfNotExists(lock_prefix_, "");
|
||||
}
|
||||
|
||||
@ -46,9 +55,7 @@ namespace zkutil
|
||||
std::string status2String(Status status);
|
||||
|
||||
/// проверяет создана ли эфемерная нода и кто ее владелец.
|
||||
/// если мы сами создавали эфемерную ноду, то надо вызывать этот метод, чтобы убедится,
|
||||
/// что сессия с зукипером не порвалось
|
||||
Status check();
|
||||
Status tryCheck() const;
|
||||
|
||||
void unlock();
|
||||
|
||||
@ -58,11 +65,14 @@ namespace zkutil
|
||||
const std::string & getPath() { return lock_path; }
|
||||
|
||||
private:
|
||||
Status checkImpl();
|
||||
zkutil::ZooKeeperPtr zookeeper;
|
||||
zkutil::ZooKeeperHolderPtr zookeeper_holder;
|
||||
/// пока храним указатель на хендлер, никто не может переиницализировать сессию с zookeeper
|
||||
using ZooKeeperHandler = zkutil::ZooKeeperHolder::UnstorableZookeeperHandler;
|
||||
std::unique_ptr<ZooKeeperHandler> locked;
|
||||
|
||||
std::string lock_path;
|
||||
std::string lock_message;
|
||||
Logger * log;
|
||||
bool locked = false;
|
||||
|
||||
};
|
||||
}
|
||||
|
83
libs/libzkutil/include/zkutil/ZooKeeperHolder.h
Normal file
83
libs/libzkutil/include/zkutil/ZooKeeperHolder.h
Normal file
@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <zkutil/ZooKeeper.h>
|
||||
#include <mutex>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace zkutil
|
||||
{
|
||||
class Lock;
|
||||
|
||||
class ZooKeeperHolder : public boost::noncopyable
|
||||
{
|
||||
friend class zkutil::Lock;
|
||||
|
||||
protected:
|
||||
class UnstorableZookeeperHandler;
|
||||
|
||||
public:
|
||||
ZooKeeperHolder() = default;
|
||||
|
||||
/// вызывать из одного потока - не thread safe
|
||||
template <class... Args>
|
||||
void init(Args&&... args);
|
||||
/// был ли класс инициализирован
|
||||
bool isInitialized() const { return ptr != nullptr; }
|
||||
|
||||
UnstorableZookeeperHandler getZooKeeper();
|
||||
bool replaceZooKeeperSessionToNewOne();
|
||||
|
||||
bool isSessionExpired() const;
|
||||
|
||||
protected:
|
||||
/** Хендлер для подсчета количества используемых ссылок на ZooKeeper
|
||||
*
|
||||
* Запрещается переинициализировать ZooKeeper пока, хранится хотя бы один хендлер на него.
|
||||
* Большинство классов должны хранить хендлер на стеке и не хранить как член класса.
|
||||
* Т.е. хранить holder и запрашивать хендлер перед каждым использованием.
|
||||
* Поэтому класс специально объявлен, как protected.
|
||||
*
|
||||
* Исключение - классы, работающие с эфимерными нодами. Пример: zkutil::Lock
|
||||
*
|
||||
* Как использовать:
|
||||
* auto zookeeper = zookeeper_holder->getZooKeeper();
|
||||
* zookeeper->get(path);
|
||||
*/
|
||||
class UnstorableZookeeperHandler
|
||||
{
|
||||
public:
|
||||
UnstorableZookeeperHandler(ZooKeeper::Ptr zk_ptr_);
|
||||
|
||||
explicit operator bool() const { return bool(zk_ptr); }
|
||||
bool operator==(nullptr_t) const { return zk_ptr == nullptr; }
|
||||
bool operator!=(nullptr_t) const { return !(*this == nullptr); }
|
||||
|
||||
/// в случае nullptr методы разыменования кидают исключение,
|
||||
/// с более подробным текстом, чем просто nullptr
|
||||
ZooKeeper * operator->();
|
||||
const ZooKeeper * operator->() const;
|
||||
ZooKeeper & operator*();
|
||||
const ZooKeeper & operator*() const;
|
||||
|
||||
private:
|
||||
ZooKeeper::Ptr zk_ptr;
|
||||
};
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
ZooKeeper::Ptr ptr;
|
||||
|
||||
Logger * log = &Logger::get("ZooKeeperHolder");
|
||||
|
||||
static std::string nullptr_exception_message;
|
||||
};
|
||||
|
||||
template <class... Args>
|
||||
void ZooKeeperHolder::init(Args&&... args)
|
||||
{
|
||||
ptr = std::make_shared<ZooKeeper>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
using ZooKeeperHolderPtr = std::shared_ptr<ZooKeeperHolder>;
|
||||
|
||||
}; /*namespace zkutil*/
|
@ -4,12 +4,15 @@ using namespace zkutil;
|
||||
|
||||
bool Lock::tryLock()
|
||||
{
|
||||
auto zookeeper = zookeeper_holder->getZooKeeper();
|
||||
if (locked)
|
||||
{
|
||||
/// проверим, что нода создана и я ее владелец
|
||||
check();
|
||||
if (tryCheck() != Status::LOCKED_BY_ME)
|
||||
locked.reset(nullptr);
|
||||
}
|
||||
else
|
||||
|
||||
if (!locked)
|
||||
{
|
||||
size_t attempt;
|
||||
std::string dummy;
|
||||
@ -18,80 +21,92 @@ bool Lock::tryLock()
|
||||
if (code == ZNODEEXISTS)
|
||||
{
|
||||
if (attempt == 0)
|
||||
locked = false;
|
||||
locked.reset(nullptr);
|
||||
else
|
||||
{
|
||||
zkutil::Stat stat;
|
||||
zookeeper->get(lock_path, &stat);
|
||||
if (stat.ephemeralOwner == zookeeper->getClientID())
|
||||
locked = true;
|
||||
locked.reset(new ZooKeeperHandler(zookeeper));
|
||||
else
|
||||
locked = false;
|
||||
locked.reset(nullptr);
|
||||
}
|
||||
}
|
||||
else if (code == ZOK)
|
||||
{
|
||||
locked = true;
|
||||
locked.reset(new ZooKeeperHandler(zookeeper));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw zkutil::KeeperException(code);
|
||||
}
|
||||
}
|
||||
return locked;
|
||||
return bool(locked);
|
||||
}
|
||||
|
||||
void Lock::unlock()
|
||||
{
|
||||
auto zookeeper = zookeeper_holder->getZooKeeper();
|
||||
|
||||
if (locked)
|
||||
{
|
||||
/// проверим, что до сих пор мы владельцы ноды
|
||||
check();
|
||||
|
||||
size_t attempt;
|
||||
int32_t code = zookeeper->tryRemoveWithRetries(lock_path, -1, &attempt);
|
||||
if (attempt)
|
||||
try
|
||||
{
|
||||
if (code != ZOK)
|
||||
throw zkutil::KeeperException(code);
|
||||
if (tryCheck() == Status::LOCKED_BY_ME)
|
||||
{
|
||||
size_t attempt;
|
||||
int32_t code = zookeeper->tryRemoveWithRetries(lock_path, -1, &attempt);
|
||||
if (attempt)
|
||||
{
|
||||
if (code != ZOK)
|
||||
throw zkutil::KeeperException(code);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (code == ZNONODE)
|
||||
LOG_ERROR(log, "Node " << lock_path << " has been already removed. Probably due to network error.");
|
||||
else if (code != ZOK)
|
||||
throw zkutil::KeeperException(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (const zkutil::KeeperException & e)
|
||||
{
|
||||
if (code == ZNONODE)
|
||||
LOG_ERROR(log, "Node " << lock_path << " has been already removed. Probably due to network error.");
|
||||
else if (code != ZOK)
|
||||
throw zkutil::KeeperException(code);
|
||||
/// если сессия находится в невостанавливаемом состоянии, то эфимерные ноды нам больше не принадлежат
|
||||
/// и лок через таймаут будет отпущен
|
||||
if (!e.isUnrecoverable())
|
||||
throw;
|
||||
}
|
||||
locked = false;
|
||||
locked.reset(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
Lock::Status Lock::check()
|
||||
{
|
||||
Status status = checkImpl();
|
||||
if ((locked && status != LOCKED_BY_ME) || (!locked && (status != UNLOCKED && status != LOCKED_BY_OTHER)))
|
||||
throw zkutil::KeeperException(std::string("Incompability of local state and state in zookeeper. Local is ") + (locked ? "locked" : "unlocked") + ". Zookeeper state is " + status2String(status));
|
||||
return status;
|
||||
}
|
||||
|
||||
Lock::Status Lock::checkImpl()
|
||||
Lock::Status Lock::tryCheck() const
|
||||
{
|
||||
auto zookeeper = zookeeper_holder->getZooKeeper();
|
||||
|
||||
Status lock_status;
|
||||
Stat stat;
|
||||
std::string dummy;
|
||||
bool result = zookeeper->tryGet(lock_path, dummy, &stat);
|
||||
if (!result)
|
||||
return UNLOCKED;
|
||||
lock_status = UNLOCKED;
|
||||
else
|
||||
{
|
||||
if (stat.ephemeralOwner == zookeeper->getClientID())
|
||||
{
|
||||
return LOCKED_BY_ME;
|
||||
lock_status = LOCKED_BY_ME;
|
||||
}
|
||||
else
|
||||
{
|
||||
return LOCKED_BY_OTHER;
|
||||
lock_status = LOCKED_BY_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
if (locked && lock_status != LOCKED_BY_ME)
|
||||
LOG_WARNING(log, "Lock is lost. It is normal if session was reinitialized. Path: " << lock_path << "/" << lock_message);
|
||||
|
||||
return lock_status;
|
||||
}
|
||||
|
||||
std::string Lock::status2String(Status status)
|
||||
@ -101,3 +116,4 @@ std::string Lock::status2String(Status status)
|
||||
static const char * names[] = {"Unlocked", "Locked by me", "Locked by other"};
|
||||
return names[status];
|
||||
}
|
||||
|
||||
|
69
libs/libzkutil/src/ZooKeeperHolder.cpp
Normal file
69
libs/libzkutil/src/ZooKeeperHolder.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
#include <zkutil/ZooKeeperHolder.h>
|
||||
|
||||
using namespace zkutil;
|
||||
|
||||
ZooKeeperHolder::UnstorableZookeeperHandler ZooKeeperHolder::getZooKeeper()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
return UnstorableZookeeperHandler(ptr);
|
||||
}
|
||||
|
||||
bool ZooKeeperHolder::replaceZooKeeperSessionToNewOne()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
|
||||
if (ptr.unique())
|
||||
{
|
||||
ptr = ptr->startNewSession();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR(log, "replaceZooKeeperSessionToNewOne(): Fail to replace zookeeper session to new one because handlers for old zookeeper session still exists.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ZooKeeperHolder::isSessionExpired() const
|
||||
{
|
||||
return ptr ? ptr->expired() : false;
|
||||
}
|
||||
|
||||
|
||||
std::string ZooKeeperHolder::nullptr_exception_message =
|
||||
"UnstorableZookeeperHandler::zk_ptr is nullptr. "
|
||||
"ZooKeeperHolder should be initialized before sending any request to ZooKeeper";
|
||||
|
||||
ZooKeeperHolder::UnstorableZookeeperHandler::UnstorableZookeeperHandler(ZooKeeper::Ptr zk_ptr_)
|
||||
: zk_ptr(zk_ptr_)
|
||||
{
|
||||
}
|
||||
|
||||
ZooKeeper * ZooKeeperHolder::UnstorableZookeeperHandler::operator->()
|
||||
{
|
||||
if (zk_ptr == nullptr)
|
||||
throw DB::Exception(nullptr_exception_message);
|
||||
|
||||
return zk_ptr.get();
|
||||
}
|
||||
|
||||
const ZooKeeper * ZooKeeperHolder::UnstorableZookeeperHandler::operator->() const
|
||||
{
|
||||
if (zk_ptr == nullptr)
|
||||
throw DB::Exception(nullptr_exception_message);
|
||||
return zk_ptr.get();
|
||||
}
|
||||
|
||||
ZooKeeper & ZooKeeperHolder::UnstorableZookeeperHandler::operator*()
|
||||
{
|
||||
if (zk_ptr == nullptr)
|
||||
throw DB::Exception(nullptr_exception_message);
|
||||
return *zk_ptr;
|
||||
}
|
||||
|
||||
const ZooKeeper & ZooKeeperHolder::UnstorableZookeeperHandler::operator*() const
|
||||
{
|
||||
if (zk_ptr == nullptr)
|
||||
throw DB::Exception(nullptr_exception_message);
|
||||
return *zk_ptr;
|
||||
}
|
@ -6,10 +6,13 @@ int main()
|
||||
|
||||
try
|
||||
{
|
||||
zkutil::Lock l(std::make_shared<zkutil::ZooKeeper>("localhost:2181"), "/test", "test_lock");
|
||||
std::cout << "check " << l.check() << std::endl;
|
||||
auto zookeeper_holder = std::make_shared<zkutil::ZooKeeperHolder>();
|
||||
zookeeper_holder->init("localhost:2181");
|
||||
|
||||
zkutil::Lock l(zookeeper_holder, "/test", "test_lock");
|
||||
std::cout << "check " << l.tryCheck() << std::endl;
|
||||
std::cout << "lock tryLock() " << l.tryLock() << std::endl;
|
||||
std::cout << "check " << l.check() << std::endl;
|
||||
std::cout << "check " << l.tryCheck() << std::endl;
|
||||
}
|
||||
catch (const Poco::Exception & e)
|
||||
{
|
||||
|
33
libs/libzkutil/src/tests/zkutil_zookeeper_holder.cpp
Normal file
33
libs/libzkutil/src/tests/zkutil_zookeeper_holder.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include <zkutil/ZooKeeperHolder.h>
|
||||
#include <iostream>
|
||||
#include <statdaemons/Test.h>
|
||||
|
||||
#include <Poco/Util/Application.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
Test::initLogger();
|
||||
|
||||
zkutil::ZooKeeperHolder zk_holder;
|
||||
zk_holder.init("localhost:2181");
|
||||
|
||||
{
|
||||
auto zk_handler = zk_holder.getZooKeeper();
|
||||
if (zk_handler)
|
||||
{
|
||||
bool started_new_session = zk_holder.replaceZooKeeperSessionToNewOne();
|
||||
std::cerr << "Started new session: " << started_new_session << "\n";
|
||||
std::cerr << "get / " << zk_handler->get("/") << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
bool started_new_session = zk_holder.replaceZooKeeperSessionToNewOne();
|
||||
std::cerr << "Started new session: " << started_new_session << "\n";
|
||||
auto zk_handler = zk_holder.getZooKeeper();
|
||||
if (zk_handler != nullptr)
|
||||
std::cerr << "get / " << zk_handler->get("/") << "\n";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user