Статьи

Изучение Swift, программного обеспечения для службы хранения объектов OpenStack


Swift — это программное обеспечение
службы
хранения объектов OpenStack .

Этот сервис предоставляет простой сервис хранения для приложений, использующих интерфейсы RESTful , обеспечивая максимальную доступность данных и емкость хранилища.

Здесь я объясню, как работают некоторые части хранилища и репликации в Swift, и покажу некоторые из его текущих ограничений.

Если вы не знаете Swift и хотите сначала прочитать более «поверхностный» обзор, вы можете прочитать обзор Swift Tech Джона Дикинсона .

Как работает хранилище Swift

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

Но в Swift встроены механизмы, позволяющие минимизировать потенциальное окно несоответствия данных: они отвечают за репликацию и согласованность данных.

Официальная документация Swift объясняет внутреннее хранилище в некотором роде, но я собираюсь написать свое собственное объяснение здесь об этом.

Последовательное хеширование

Swift использует принцип последовательного хеширования . Он строит то, что называется кольцом . Кольцо представляет пространство всех возможных вычисленных значений хэша, разделенных на эквивалентные части. Каждая часть этого пространства называется разделом . Это реализует разбиение (из теоремы CAP ).

Следующая схема (украденная из проекта Riak ) прекрасно показывает принцип:

Последовательное кольцо для перемешивания

В простом мире, если вы хотите хранить некоторые объекты и распределять их по 4 узлам, вы разделите свое хеш-пространство на 4. У вас будет 4 раздела, и вычисление хеша (объекта) по модулю 4 скажет вам, где хранить ваш объект. : на узле 0, 1, 2 или 3.

Но так как вы хотите иметь возможность расширить кластер хранения на большее количество узлов, не нарушая целое отображение хешей и не перемещая все вокруг, вам нужно создать гораздо больше разделов. Допустим, мы собираемся построить 2 10 разделов. Поскольку у нас есть 4 узла, каждый узел будет иметь 2 10  ÷ 4 = 256 разделов. Если мы когда-нибудь захотим добавить 5- й узел, это легко: нам просто нужно перебалансировать разделы и переместить 1-4 раздела из каждого узла в этот 5- й узел. Это означает, что все наши узлы будут иметь 2 10  5 5 204 разделов. Мы также можем определить вес для каждого узла, чтобы некоторые узлы могли получить больше разделов, чем другие.

Имея 2 10 разделов, мы можем иметь до 2 10 узлов в нашем кластере. Yeepee.

Для справки, Грегори Холт, один из авторов Swift, также написал пояснительный пост о ринге .

Конкретно, при создании одного кольца Swift вам нужно будет сказать, сколько разделов вы хотите, и это то, что на самом деле означает это значение.

Дублирование данных

Теперь, чтобы гарантировать доступность (как видно из теоремы CAP ), мы также хотим хранить реплики наших объектов. По умолчанию Swift хранит 3 копии каждого объекта, но это настраивается.

В этом случае нам нужно хранить каждый определенный выше раздел не только на 1 узле, но и на 2 других. Так что Swift добавляет еще одну концепцию: зоны. Зона — это изолированное пространство, которое не зависит от другой зоны, поэтому в случае сбоя в зоне другие зоны по-прежнему доступны. Конкретно, зоной может быть диск, сервер или целый шкаф, в зависимости от размера вашего кластера. В любом случае, вам решать выбрать.

Следовательно, каждый раздел должен отображаться не только на 1 хост, а на N хостов. Поэтому каждый узел будет хранить это количество разделов:

number of partition stored on one node = number of replicas × total number of partitions ÷ number of node

Примеры:


Разобьем кольцо на 2
10 = 1024 разбиения. У нас есть 3 узла. Мы хотим 3 копии данных.

→ Каждый узел будет хранить копию всего пространства разделов: 3 × 2
10  ÷ 3 = 2
10  = 1024 разделов.


Разобьем кольцо на 2
11 = 2048 перегородок. У нас есть 5 узлов. Мы хотим 3 копии данных.

→ Каждый узел будет хранить 2
11  × 3 ÷ 5 ≈ 1129 разделов.


Разобьем кольцо на 2
11 = 2048 перегородок. У нас есть 6 узлов. Мы хотим 3 копии данных.

→ Каждый узел будет хранить 2
11  × 3 ÷ 6 = 1024 разделов.

Три кольца, чтобы управлять ими всеми

В Swift есть 3 категории вещей для хранения: учетная запись , контейнер и объекты .

Счет является то , что вы ожидали бы , что это будет, учетная запись пользователя. Аккаунт содержит контейнеры (эквивалент корзин Amazon S3). Каждый контейнер может содержать определяемый пользователем ключ и значения (как хеш-таблица или словарь): значения — это то, что Swift вызывает объекты .

Swift хочет, чтобы вы построили 3 разных независимых кольца, чтобы хранить 3 вида вещей ( учетные записи , контейнеры и объекты ).

Внутри две первые категории хранятся как базы данных SQLite , тогда как последняя хранится в обычных файлах.

Обратите внимание, что эти 3 кольца могут храниться и управляться на 3 совершенно разных серверах.

Схема хранения Swift

Репликация данных

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

Когда вы помещаете что-либо в одно из 3 колец (будучи учетной записью, контейнером или объектом), оно загружается во все зоны, отвечающие за раздел кольца, которому принадлежит объект. За загрузку в разные зоны отвечает демон swift-proxy .

Схема Swift Proxy

Но если одна из зон выходит из строя, вы не можете загрузить все свои копии во всех зонах во время загрузки. Таким образом, вам нужен механизм, чтобы убедиться, что сбойная зона в какой-то момент восстановится до правильного состояния.

Это роль процессов swift- {container, account, object} -replicator . Эти процессы выполняются в каждой части узла зоны и реплицируют их содержимое на узлы других зон.

При запуске они просматривают все содержимое всех разделов всей файловой системы и для каждого раздела отправляют специальный HTTP-запрос REPLICATE всем остальным зонам, ответственным за этот раздел. Другая зона отвечает информацией о локальном состоянии раздела. Это позволяет процессу репликатора принимать решение о том, имеет ли удаленная зона актуальную версию раздела.

В случае учетной записи и контейнеров, она не проверяет на уровне раздела, а проверяет каждую учетную запись / контейнер, содержащийся внутри каждого раздела.

Если что-то не обновлено, оно будет отправлено с помощью rsync процессом репликатора. Вот почему вы прочтете, что обновления репликации «основаны на push» в документации Swift.

# Pseudo code describing replication process for accounts
# The principle is exactly the same for containers
for account in accounts:
    # Determine the partition used to store this account
    partition = hash(account) % number_of_partitions
    # The number of zone is the number of replicas configured
    for zone in partition.get_zones_storing_this_partition():
        # Send a HTTP REPLICATE command to the remote swift-account-server process
        version_of_account = zone.send_HTTP_REPLICATE_for(account):
        if version_of_account < account.version()
            account.sync_to(zone)

Этот процесс репликации равен O (номер учетной записи × количество реплик) . Чем больше будет увеличено количество ваших учетных записей и чем больше вам понадобятся реплики для ваших данных, тем больше будет время репликации для ваших учетных записей. То же правило применяется к контейнерам.

# Pseudo code describing replication process for objects
for partition in partitions_storing_objects:
    # The number of zone is the number of replicas configured
    for zone in partition.get_zones_storing_this_partition():
        # Send a HTTP REPLICATE command to the remote swift-object-server process
        verion_of_partition = zone.send_HTTP_REPLICATE_for(partition):
        if version_of_partition < partition.version()
            # Use rsync to synchronize the whole partition
            # and all its objects
            partition.rsync_to(zone)

Этот процесс репликации O (количество разделов объектов × количество реплик) . Чем больше будет увеличено количество разделов объектов, и чем больше вам понадобятся реплики для ваших данных, тем больше будет время репликации для ваших объектов.

Я думаю, что это важно знать при принятии решения о том, как построить свою архитектуру Swift. Выберите правильное число количество реплик, разделов и узлов.

Узкие места в процессе репликации

обезьяна

Доступ к файлам

Проблема, как вы уже догадались, заключается в том, что для репликации она проходит через все проклятые объекты , являющиеся учетными записями, контейнерами или хэш-файлами раздела объекта. Это означает, что нужно открыть и прочитать (часть) каждый файл, который хранит ваш узел, чтобы проверить, нужно или нет реплицировать данные!

Для репликации учетных записей и контейнеров это делается по умолчанию каждые 30 секунд, но, вероятно, это займет более 30 секунд, как только вы достигнете около 12 000 контейнеров на узле (см. Измерения ниже). Таким образом , вы будете в конечном итоге проверки согласованности счетов и контейнеров на каждом узле все все время , используя , очевидно , много процессорного времени.

Для справки, Алекс Ян также сделал анализ этой же проблемы.

TCP соединения

В худшем случае HTTP-соединения, используемые для отправки команд REPLICATE , не объединяются в пул: новое TCP-соединение устанавливается каждый раз, когда что-то проверяется на предмет того же, что хранится в удаленной зоне.

Вот почему вы увидите в руководстве по развертыванию Swift следующие строки, перечисленные в разделе «Общая настройка системы» :

# disable TIME_WAIT.. wait..
net.ipv4.tcp_tw_recycle=1
net.ipv4.tcp_tw_reuse=1

# double amount of allowed conntrack
net.ipv4.netfilter.ip_conntrack_max = 262144

По моему скромному мнению, это скорее уродливый взлом, чем тюнинг. Если вы не активируете это и у вас будет много контейнеров на вашем узле, вы скоро получите тысячи соединений в состоянии TIME_WAIT , и вы действительно рискуете перегрузить модуль conntrack IP.

Удаление контейнера

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

Единственный способ окончательно удалить контейнер — пометить учетную запись как удаленную . Таким образом, swift-account-reaper удалит все свои контейнеры и, наконец, учетную запись.

измерение

На довольно большом сервере я измерил репликацию со скоростью около 350 (учетная запись, контейнер, объектные разделы) / секунду, что может быть реальной проблемой, если вы решили создать большое количество разделов и у вас есть низкий номер_от_узла ⁄ число_от_отражений .

Например, параметры по умолчанию запускают репликацию контейнера каждые 30 секунд. Чтобы проверить состояние репликации 12 000 контейнеров, хранящихся на одном узле со скоростью 350 контейнеров в секунду, вам потребуется около 34 секунд для этого. В конце концов, вы никогда не прекратите проверять репликацию ваших контейнеров, и чем больше у вас будет контейнеров, тем больше будет увеличиваться окно несоответствия .

Вывод

Пока часть кода не будет исправлена ​​(пул HTTP-соединений, вероятно, является «самым простым»), я настоятельно рекомендую правильно выбрать различные параметры Swift для вашей настройки. Оптимизация процесса репликации состоит в том, чтобы иметь минимальное количество разделов на узел, что может быть сделано:

  • уменьшение количества разделов
  • уменьшение количества реплик
  • увеличение количества узлов

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