Это вторая часть сообщения в блоге, посвященная основам Apache HBase. Первую часть можно найти здесь.

Эта глава будет посвящена темам администрирования HBase, например Кластерная архитектура HBase, репликация, формат хранения данных и т. Д. Это будет полезно системным администраторам, а также разработчикам, которые хотят знать, как HBase работает внутри.

Архитектура HBase

Мы начнем с компонентов, которые есть в кластере HBase, и с того, как они взаимодействуют друг с другом.

Кластер HBase состоит из нескольких Master серверов и множества RegionServers.

HBase работает поверх Apache Hadoop (в основном требуется только HDFS, где хранятся данные) и Apache Zookeeper. Кластер Apache Zookeeper используется для обнаружения отказов узлов HBase и хранит распределенную конфигурацию кластера HBase (подробнее в следующих разделах).

На следующей диаграмме показан типичный кластер HBase и то, как его компоненты взаимодействуют друг с другом:

Мастер

Мастер отвечает за следующие задачи:

  • мониторинг RegionServers (обнаружение сбоев через Zookeeper)
  • присвоение регионов RegionServers
  • балансировка региональной нагрузки между RegionServers
  • обработка метаданных кластера (например, создание / изменение / удаление таблицы / CF и т. д.)

Кластер HBase обычно состоит из нескольких мастеров, один из которых является активным, а другой - резервным. Когда все главные экземпляры работают, каждый запускает выборы лидера (с помощью Zookeeper), чтобы стать активным мастером. Затем один экземпляр побеждает на выборах, другие переключаются в состояние «наблюдатель» и ждут, пока активный мастер откажется (и начнет новый раунд выборов).

RegionServer

Другой компонент кластера HBase - это RegionServer. Вы можете думать об этом как о рабочем узле, который отвечает за обслуживание клиентских запросов и управление областями данных.
Давайте поговорим о регионах. Как мы уже знаем, таблицы в HBase состоят из строк, которые идентифицируются ключом. Строки сортируются по ключу в структурах данных внутри HBase. Регион - это группа непрерывных строк, определяемая начальным и конечным ключами строк, которые ей принадлежат.
На сервере RegionServer размещается несколько регионов с разными таблицами. Важно отметить, что регионы одной и той же таблицы могут размещаться на разных серверах, например данные таблицы распределяются по кластеру. Но каждый регион одновременно управляется только одним RegionServer (это гарантирует, что мутация строки является атомарной, см. Раздел ACID в первой главе).

Когда RegionServer выходит из строя, Master переназначает все регионы на другой RegionServer. Поскольку все данные региона хранятся в HDFS, Master может безопасно назначить регион любому действующему серверу. Обычно RegionServer и HDFS DataNode размещаются на одном хосте. Но когда регион будет назначен, данные, принадлежащие этому региону, будут «нелокальными» для ответственного RegionServer, потому что размещенный вместе DataNode может не содержать реплики для данных региона. Это может повлиять на производительность.

Но ситуация не так уж и плоха. В течение этого времени RegionServer будет выполнять сжатие файлов, и сжатые файлы будут записаны на локальном DataNode: когда RegionServer сохранит данные в HDFS, он запишет первую реплику на том же хосте, что и RegionServer, и другие реплики на удаленных DataNode. С этого момента данные будут локальными для RegionServer, что улучшит производительность. Подробнее о местонахождении данных читайте в документации.

Другие важные обязанности RegionServer:

Взаимодействие клиент-кластер

В этом разделе мы получаем общий обзор того, как клиенты подключаются к кластеру HBase и взаимодействуют с ним. На следующей упрощенной диаграмме показано, как клиент взаимодействует с кластером HBase:

Клиенту требуется строка подключения кворума Zookeeper (которая содержит все серверы кворума Zookeeper, например, «server1: port,…, serverN: port») и базовый znode, который используется кластером HBase (см. Свойство zookeeper.znode.parent server). Он будет использоваться для подключения к кворуму Zookeeper и чтения местоположения системной таблицы hbase: meta (которой сейчас управляет RegionServer). Затем он подключается к этому серверу RegionServer и считывает содержимое hbase: meta в расположение регионов кеширования. hbase: meta table содержит метаданные всех регионов всех таблиц, управляемых кластером. Используя кэшированные метаданные региона, клиент может найти RegionServer, который может обрабатывать запрос для конкретной строки.
Но данные в этом кэше могут стать недействительными, например, когда Мастер переназначает регионы между RegionServers. В этом случае клиент запросит RegionServer, который уже отказался от обслуживания региона, и ответит ошибкой NotServingRegion. При получении ошибки NotServingRegion клиент аннулирует мета-кеш hbase: и повторяет запрос к новому серверу RegionServer.
Как вы можете видеть, для типичных запросов на манипулирование данными клиент не взаимодействует с мастером и не зависит от него. доступность. Но для запросов API администрирования (создание таблицы / CF / пространства имен, запуск балансировки нагрузки, уплотнение области и т. Д.) Требуется соединение с активным HBase Master.

Представление данных на диске

В этом разделе мы обсудим, как HBase хранит данные на диске.
Как мы уже знаем, RegionServer отвечает за управление данными в кластере HBase. Для каждого семейства столбцов в каждом регионе RegionServer создает так называемое Store. Хранилище состоит из MemStore и коллекции StoreFiles на диске (HFiles).
MemStore - это структура данных в памяти, реализованная с помощью списка пропуска. Он содержит ячейки или пары "ключ-значение", которые представляют последние изменения данных. Все запросы на размещение и удаление, обслуживаемые RegionServer, применяются к MemStore, а также записываются в WAL для обеспечения надежности. MemStore имеет настраиваемый максимальный размер, который по умолчанию составляет 128 МБ. Когда MemStore достигнет этого предела, он будет сброшен на диск (в формате HFile), а RegionServer создаст новый пустой MemStore.
HFile - это формат файла, основанный на SSTables, описанном в статье BigTable. HFile состоит из последовательности блоков разного типа размером 64 КБ (настраиваемое значение). Блоки могут быть разных типов:

  • блок данных (фактически содержит ячейки данных)
  • индексный блок (есть подтипы для данных, мета и цветение)
  • блок фильтра Блума (содержит фильтр Блума)
  • блок информации о файле (блок с картой ключ-значение метаданных)
  • мета-блок

В следующих разделах я кратко опишу каждый тип блоков. Подробное описание формата HFile можно найти в документации.

Блоки данных

Каждый блок данных состоит из структур данных KeyValue. Ключевые значения внутри HFile сортируются в соответствии со следующим правилом: сначала по строке, затем по ColumnFamily, затем по квалификатору столбца и, наконец, по метке времени (отсортированы в обратном порядке, поэтому сначала возвращаются самые новые записи).

На следующем рисунке показано представление KeyValue на диске (синяя часть - это расширенный вид ключа):

KeyValue никогда не пересекает границы блока, например если его размер больше размера блока, он будет записан в один блок.

Как вы уже знаете, строка HBase состоит из множества ячеек, которые представлены на диске как KeyValues. Как правило, ячейки одной строки содержат много полей, содержащих одинаковые данные (чаще всего это ключ строки и семейство столбцов). Чтобы уменьшить использование диска, HBase имеет возможность включить кодирование / сжатие данных. Подробнее о том, какой алгоритм сжатия / кодирования выбрать, читайте в разделе Сжатие и кодирование блоков данных в HBase в официальной документации.

Индексные блоки

Индексные блоки внутри HFile содержат индексную структуру. Он обеспечивает быстрый двоичный поиск по ключу для поиска блоков, содержащих определенную строку.

Блоки фильтра Блума

Блоки фильтра Блума содержат патроны фильтра Блума. Фильтр Блума - это структура данных, которая предназначена для прогнозирования того, является ли данный элемент членом набора данных.
Когда HBase пытается выполнить запрос Get для строки, он использует фильтры Блума, чтобы определить, присутствует ли строка в этом HFile. . В противном случае HBase пропускает весь HFile и продолжает сканировать другие файлы. Но важно отметить, что Bloom Filters - это вероятностная структура, которая может получать «ложные срабатывания», например он может сказать, что строка содержится в HFile, но на самом деле это не так. В этом случае HBase должен выполнить дополнительные чтения HFile, чтобы убедиться, что строка присутствует в файле.

Сегодня (в версиях до HBase 2.2) фильтр Блума используется только для операций Get и не поддерживает сканирование. Но недавно я нашел HBASE-20636, который восполнит этот пробел. Будет добавлена ​​поддержка префиксных фильтров Блума. Когда ваши клавиши запуска и остановки сканирования имеют общий префикс, сканирование будет использовать фильтр Блума для фильтрации файлов, которые не содержат строк с этим префиксом. Согласно заявке, эта функция запланирована в HBase 2.2.0.

Репликация региона

HBase обеспечивает строго согласованное чтение и запись. Сильная согласованность достигается тем, что каждый регион управляется одним RegionServer. Но в случае отказа RegionServer все его регионы будут недоступны в течение некоторого времени. Это время определяется таймаутом сеанса Zookeeper, по умолчанию 90 секунд (см. Документацию). Это значение тайм-аута можно уменьшить, чтобы сократить время до восстановления (TTR). Но это может привести к ложным сбоям, вызванным временными проблемами сети, Java GC и т. Д., Что может привести к чрезмерному переназначению регионов и, как следствие, к неправильному балансу регионов на серверах RegionServers.
Функция, известная как репликация регионов, предназначена для частичного преодоления этого ограничения. По умолчанию в каждом регионе есть только одна реплика. Когда коэффициент репликации увеличивается до 2 или более, регион будет назначен нескольким RegionServers. Одна из этих реплик является основной, которая принимает записи и чтение в этот регион. Другие реплики являются вторичными, они могут обрабатывать только запросы на чтение.
Репликация HBase - это асинхронный процесс, и для распространения новых записей на вторичные реплики может потребоваться некоторое время. Поскольку видимость изменений может быть отложена, у клиента есть два варианта:

  • сильное последовательное чтение
  • согласованность временной шкалы. Сильно согласованное чтение проходит через первичную реплику и позволяет увидеть все изменения. При чтении временной шкалы в ответ можно увидеть устаревшие данные. Чтение временной шкалы начинается с запроса первичной реплики, но после короткого интервала (настраиваемый параметр) клиент отправляет следующий запрос вторичной реплике. Клиент увидит ответ, который сначала будет содержать данные из реплики.

Пример чтения хронологии

Предположим, что у нас есть 3 RegionServer (RS1, RS2, RS3), 1 клиент только для записи (CW1) и 2 только для чтения (CR1 и CR2). RS1 размещает первичную реплику, RS2 и RS3 - вторичные хосты (RS2, RS3 - приемники репликации). Клиент CW1 выполняет 3 операции записи W1, W2, W3 одну за другой с некоторыми временными задержками между ними. Клиент CR1 читает только из RS1 и CR2 использует чтение временной шкалы и может читать с любого сервера.
Как вы можете видеть на картинке, CR1 всегда читает последнее значение W3, записанное W1. CR2 выполняет 3 последовательных чтения с соблюдением временной шкалы:

  1. Первое чтение - получить ответ от первичной реплики и увидеть последнее записанное значение W3.
  2. Второе чтение идет на сервер RS2, который видит W2 как последнее значение.
  3. И третья операция чтения получает ответ от RS3, который видит W1 как последнее записанное значение.

Как видите, 3 последовательные операции чтения с согласованностью временной шкалы могут возвращать разные результаты в течение времени. Приложение должно быть готово к такому поведению для обработки устаревших чтений.

Предупреждения о репликации

При репликации есть несколько предостережений, с которыми вам следует подготовиться.

  • увеличенное использование памяти: реплики регионов имеют собственные хранилища памяти.
  • несвежее читает
  • дополнительный сетевой трафик для репликации данных
  • поскольку детали реализации, реплики могут видеть частичные обновления (только для запросов семейств между столбцами)

Подробнее о репликации см. В документации.

Случаи применения

Это последний раздел в этом посте. На данный момент мы знаем, как работает HBase, и готовы описать некоторые возможные варианты использования HBase. В примерах есть описание домена и подробное объяснение того, как мы храним данные внутри HBase.

Пример 1: агрегирование данных в реальном времени

Предположим, у нас есть рекламная платформа, которая показывает рекламу на веб-сайтах и ​​/ или в мобильных приложениях. Рекламодатели запускают ИТ-кампании, и такие платформы собирают информацию о рекламных событиях, например, когда было показано объявление (событие показа) или когда пользователь нажимает на баннер объявления.
Предположим, события показа и клика записываются в Apache Kafka службой, которая Лови. И теперь нам как команде, которая готовит отчеты, необходимо собрать все события в некоторую агрегированную форму. Эти агрегаты могут использоваться в качестве строительных блоков для отчетов на основе времени, где пользователь хочет получить отчет с подсчетом показов и кликов по какой-либо кампании в выбранном временном диапазоне. Результирующие данные в отчете должны быть детализированы по часам, например пользователь хочет видеть изменение счетчика каждый час:

Эту задачу можно решить с помощью HBase.

Фактически, наш отчет состоит из двух подотчетов: количество показов и количество кликов. Отметим каждый подтип в соответствии с его типом события: EventType.IMPRESSION и EventType.CLICK.
И теперь у нас есть поток событий разных типов, из которых мы опрашиваем Кафка. Поскольку у нас большое количество событий, мы решили запустить несколько экземпляров нашей службы отчетов. Каждая служба получает пакет событий (Kafka возвращает набор записей по каждой операции опроса) и группирует их по [ID кампании, типу события, метке времени] и вычисляет счетчик для каждой группы. Метка времени в каждой группе - это усечение исходной метки времени события до начала часа, например, если ts = «2018–12–15 14:23:45», то усеченное значение будет «2018–12–15 14:00:00. ». Это усечение позволяет сгруппировать одно и то же событие, которое относится к одному и тому же часу.
Теперь нам нужно сохранить новое значение счетчика. Мы будем использовать следующую схему для хранения данных в HBase:

  • Ключ строки: [CampaignID]_[TruncatedTimestamp] с сохраненной временной меткой и длинным значением (в секундах).
  • Столбцы: [EventType]
  • Значение: текущее значение счетчика событий

Поскольку у нас есть несколько экземпляров, которые могут одновременно изменять значение счетчика, мы не можем просто ввести новое значение счетчика. Мы будем использовать операцию HBase Increment для атомарного увеличения текущего значения счетчика.
Один интересный нюанс в том, как мы собираем и записываем данные в HBase. Как видите, мы опрашиваем данные из Kafka партиями и выполняем предварительную агрегацию, группируя события по [ID кампании, типу события, метке времени].
Reader может предложить другое более простое решение, которое не объединяет данные о сервисе сторону, но отправьте кучу приращений по одному для каждого [идентификатор кампании, тип события, временная метка]. Это решение будет работать, но повлияет на производительность, поскольку операция приращения использует какой-то CAS на стороне сервера. Когда все экземпляры службы отправят пакет операций приращения, HBase попытается применить приращения из нескольких клиентских запросов к той же ячейке. Это создаст конкуренцию на стороне сервера и повлияет на время выполнения запроса. Вот почему мы используем предварительную агрегацию на стороне сервиса и сокращаем количество приращений, выполняемых на стороне HBase.

Пример 2: Хранение образов файловой системы

Предположим, мы пытаемся реализовать хранилище документов / файлов высокого уровня для неопытных пользователей, которые будут взаимодействовать с ним через веб-интерфейс. Первая идея, которую вы можете себе представить, - хранить содержимое файла внутри HBase, потому что он может хранить необработанные двоичные данные в ячейках. Но на самом деле HBase не предназначен для хранения больших двоичных объектов.
В документации мы можем найти раздел с названием «Хранение объектов среднего размера (MOB)», который начинается со следующего: «Данные бывают разных размеров, а сохранение все ваши данные в HBase, включая двоичные данные, такие как изображения и документы, идеальны ». Отлично, это может нам помочь. Но если мы прочитаем весь раздел, мы поймем, что эта функция сосредоточена на больших двоичных объектах размером от 100 КБ до 10 МБ. Для упрощения и предположим, что в нашей системе не может быть файлов размером более 10 МБ.
Типичная файловая система содержит образ файловой системы (иерархия файловой системы, дерево каталогов и файлов) и фактическое содержимое файлов (двоичные данные). Используя функции MOB HBase, мы можем легко хранить содержимое файла внутри ячеек. Теперь нам нужно определить, как мы будем хранить изображение FS.
Изображение FS, а также двоичные данные файлов должны храниться в течение длительного времени, чтобы предотвратить потерю данных. Также нам нужен быстрый доступ к содержимому каталога списков. И снова HBase отвечает этим требованиям, поскольку обеспечивает надежное хранилище и быстрый доступ к ключу и значению.
Образ ФС можно представить в виде дерева с узлами разных типов. Для простоты предположим, что мы должны поддерживать только файлы и каталоги. Как и любая типичная файловая система, наша FS также определяет корневой узел, который мы помечаем как «/». Это особый тип узла, и любой другой узел дерева FS является его преемником.

Теперь мы начинаем проектировать, как мы будем хранить изображение FS в HBase.
Как мы определили ранее, у нас есть 2 типа узлов, каталог и файлы и 1 специальный корневой узел. Каждый узел будет представлен в виде строки в таблице HBase. Ключ строки будет содержать уникальный идентификатор узла (например, GUID), сгенерированный при создании узла. Мы не можем использовать имя узла (которое является именем файла или каталога), потому что оно не уникально.
Каждая строка в HBase будет иметь 2 семейства столбцов: одно для метаданных и одно для содержимого файла (семейство столбцов MOB enabled). Метаданные будут содержать:

  • тип узла (ФАЙЛ или КАТАЛОГ)
  • родительский узел (идентификатор узла родительского каталога)
  • список дочерних узлов (только для узлов каталога)
  • другая типичная информация о файловой системе (отметка времени создания / изменения, владелец, права доступа и т. д.)

Мы должны определить операции, которые будут поддерживаться нашей файловой системой:

  1. Создать файл или каталог
  2. Прочитать содержимое файла
  3. Записать содержимое файла
  4. Список содержимого каталога

Отсюда мы начнем кодирование и продемонстрируем, как каждую операцию можно реализовать с помощью клиентского API HBase. Этот пример написан не для оптимальной производительности, а также не содержит исчерпывающих проверок для предотвращения всех типов ошибок.

Создать файл с содержанием

Список содержимого каталога

Резюме

HBase - очень зрелый проект с открытым исходным кодом и широким набором функций. У него большое сообщество, сильный список приверженцев (Alibaba, Cloudera, Hortonworks, Salesforce и т. Д.). Проект постоянно развивается и расширяется за счет новых функций, таких как SQL от Apache Phoenix, распределенные транзакции от Apache Omid / Apache Tephra.
Как мы видим, HBase имеет множество приложений в разных областях: хранение метрик (см. Проект OpenTSDB), рекламные данные, метаданные файловой системы и многое другое, что мы можем изучить.

использованная литература

  1. Https://hbase.apache.org/book.html
  2. Https://hadoop.apache.org/
  3. Https://zookeeper.apache.org
  4. Бумага BigTable: https://ai.google/research/pubs/pub27898
  5. Https://hadmin.io