Elliptics від Яндекса. Як з його допомогою створити своє відмовостійке сховище

    Добрий день, дорогі читачі!
 
У минулих статтях я в загальних рисах розповідав про наше відкрите відмовостійке сховище Elliptics, а також трохи опускався в деталі . Сьогодні ж я вам наочно розповім і покажу, як використовувати Elliptics на прикладі створення своєї власної відмовостійкій ХабраМузикі.
 
 
 
ХабраМузика — це ваше особисте сховище музики з підтримкою регіональності, реплицирования даних, мінімальним навантаженням на диск і мережа, а також простим HTTP API, який можна використовувати в будь-якому вашому додатку або на особистому сайті.
 
Під катом — покрокові подробиці.
 
 Розвертаємо Elliptics
Насамперед нам потрібно поставити Elliptics. Для цього ми збираємо пакети в нашому репозиторії під такі распространненость серверні системи, як:
 
 
     
  • Debian Wheezy;
  •  
  • Ubuntu Precise 12.04;
  •  
  • Ubuntu Trusty 14.04;
  •  
  • RedHat 6 / CentOS 6.
  •  
Для інших дистрибутивів ви можете зібрати систему самостійно з початкових кодів, які розташовані на нашому GitHub-акаунті .
 
Для подальшої роботи необхідно поставити пакети:
 
 
$ sudo apt-get install elliptics elliptics-client rift

або
 
 
$ sudo yum install elliptics elliptics-client rift

 
Якщо у вас є бажання спробувати в справі API для C + +, то також слід поставити пакет
elliptics-dev
або
elliptics-client-devel
залежно від дистрибутива.
 
Відмінно, пакети поставлені. Тепер прийшла пора запускати свій кластер. В рамках даного туторіал ми будемо запускати всі сервера на одній машині. У реальному ж ситуації їх варто розносити на різні машини і дата-центри. На GitHub розташовані всі конфігураційні файли, які знадобляться для нашого туторіал.
 
Для початку ознайомимося з прикладом конфігураційного файлу
node-1.json
:
 
 
{
        "loggers": {
                "type": "/srv/elliptics/1/log.txt",
                "level": 4
        },
        "options": {
                "join": true,
                "flags": 4,
                "remote": [
                        "autodiscovery:224.0.0.5:1025:2"
                ],
                "address": [
                        "localhost:1025:2"
                ],
                "wait_timeout": 60,
                "check_timeout": 60,
                "io_thread_num": 16,
                "nonblocking_io_thread_num": 16,
                "net_thread_num": 4,
                "daemon": true,
                "auth_cookie": "<-Don't forget to change it->",
                "cache": {
                        "size": 1073741824
                },
                "indexes_shard_count": 16,
                "monitor_port": 20000
        },
        "backends": [
                {
                        "type": "blob",
                        "group": 1,
                        "history": "/srv/elliptics/1/history/",
                        "data": "/srv/elliptics/1/blob/data",
                        "sync": "-1",
                        "blob_flags": "129",
                        "blob_size": "10G",
                        "records_in_blob": "1000000"
                }
        ]
}

Щоб запустити Elliptics з цим конфігураційним файлом, треба створити такі каталоги:
 
     
  • /srv/elliptics/1/
    — тут в файл log.txt будуть писатися логи з рівнем Debug;
  •  
  • /srv/elliptics/1/blob/
    — тут будуть лежати Блоб з даними;
  •  
  • /srv/elliptics/1/history/
    — тут буде лежати файлик з початками інтервалів з DHT-кільця.
  •  
Детально про решту зазначених та невказаних полях можна прочитати в нашій документації .
 
Для запуску досить виконати:
 
 
$ dnet_ioserv -c node-1.json

Але ж ми хочемо побудувати відмовостійкий кластер. Для цього треба підняти ще хоча б одну машину в другій групі. В її конфіге потрібно буде вказати іншу адресу, а також інші remote-адреси. У результаті, поки використовуються конфігураційні файли
node-1.json, node-2.json
для зберігання даних в 2 копіях.
 
Створимо каталоги, потрібні для перших двох нод, і запустимо кожну з них:
 
 
$ for id in `seq 1 2`; do mkdir /srv/elliptics/$id/{history,blob} -p; done
$ for id in `seq 1 2`; do dnet_ioserv -c node-$id.json; done

 Знайомимося з C + + / Python API
 
А тепер, коли сервера у нас запущені, приступимо до знайомства з низькорівневими C + + і Python API. Для цього кожна дія я буду показувати відразу на двох мовах з поясненнями.
 
 Підключення до серверів
Спершу потрібно створити клієнтську ноду для підключення до серверів Elliptics.
 
С + +:
 
 
#include <elliptics/session.hpp>

using namespace ioremap::elliptics;

file_logger logger(“/dev/stderr”, DNET_LOG_ERROR);
node n(file_logger);

n.add_remote("localhost", 1025); 
n.add_remote("localhost", 1026);

session sess(n);
sess.set_groups({ 1, 2, 3 });

Python:
 
 
import elliptics
log = elliptics.Logger(“/dev/stderr”, elliptics.log_level.debug)
node = elliptics.Node(log)

node.add_remote(“localhost”, 1025)
node.add_remote(“localhost”, 1026)

sess = elliptics.Session(node)
sess.group = [ 1, 2, 3 ]

 Записуємо дані в Elliptics
Запишемо в Elliptics трохи даних:
 
C + +:
 
 
auto async_result = sess.write_data(std::string("test_key"), "some data", offset);

Python:
 
 
async_result = sess.write_data(“test_key”, “some_data”, offset)

Вищенаведений код відправить запит запису даних на сервери Elliptics. Однак після цього потрібно дочекатися відповідей і обробити їх. Багато команд посилають по кілька запитів на сервера Elliptics'а. Так, наприклад, запис даних шле запити одночасно в кожну групу за запитом. На кожен запит приходить відповідь, з якого можна дізнатися як деякі загальні для всіх команд відомості (наприклад, адреса сервера, що обробив запит, або код результату виконання команди), так і специфічну для даної команди інформацію (таку, як шлях до блоб, в який було зроблено запис, з координатами усередині нього).
 
 Обробка відповіді від сервера
Існує кілька варіантів обробки результату виконання команди. Їх варто розглянути докладніше.
 
 

Синхронний запит

C + +:
 
 
try {
    async_result.wait();
} catch (error &e) {
	std::cerr << “Error while write execution: “ << e.message();
	return;
}
auto sync_result = async_result.get();
for (write_result_entry entry : async_result.get()) {
    // Process entry
    std::cerr << "Received answer from: " << dnet_server_convert_dnet_addr(result.address()) << "status: " << result.status() << std::endl;
}

Python:
 
 
try:
        async_result.wait()
except Exception as e:
        print "Write failed: ", e

for entry in async_result.get():
        print("Reply from {}, status: {}".format(entry.address, entry.error.code))

Від кидання винятків можна позбутися, викликавши у session метод set_exceptions_policy, який присутній як в C + +, так і в Python API. У такому випадку потрібно робити ручну перевірку на помилки:
 
C + +:
 
 
if (async_result.error()) {
    // Логика обработки ошибки
}

Python:
 
 
if (async_result.error) {
    // Логика обработки ошибки
}

 

Ітеріруемий запит

Іноді сумарний обсяг даних від сервера може бути занадто великим, щоб разом помістити його в пам'ять. Також може матися вимога обробки даних по мірі їх надходження. У такому випадку можна скористатися ітерірованіем: тіло циклу буде виконуватися на кожну відповідь, що прийшов від сервера, по мірі їх виступів. Між пакетами потік користувача буде спати.
 
C + +:
 
 
for (auto entry : async_result) {
}

Python:
 
 
for entry in async_result:
   pass

 

Асинхронний запит

Також, як тільки відповідь буде цілком отриманий, можна виконати свій код в потоці Elliptics'а.
 
C + +:
 
 
async_result.connect([] (const sync_write_result &result, const error_info &error) {
    if (error) {
        // Обработка ошибки
        return;
    }
    
    for (auto entry : result) {
        // Обработка ответа
    }
});

 
Python:
 
 
def handler(results, error):
    if error.code != 0:
        // Обработка ошибки
        return
    for entry in results:
        // Обработка ответа
        pass
async_result.connect(handler)

 

Асинхронний ітеративний запит

Цей спосіб рекомендується використовувати, якщо даних від сервера очікується багато, їх хочеться обробляти по мірі надходження і не хочеться блокувати жоден з потоків для очікування даних. Перший callback буде викликатися на кожен отриманий від сервера відповідь, а останній викличеться після отримання останньої відповіді. Причому єдиним аргументом буде інформація про можливу помилку, якщо підсумковий код відповіді не нульовий.
 
C + +
 
 
async_result.connect([] (const write_result_entry &entry) {
    // Обработка ответа
}, [] (const error_info &error) {
    if (error) {
        // Обработка ошибки
    }
});

Python:
 
 
def result_handler(result, error):
    // Обработка ответа
    pass
def error_handler(results, error):
    if error.code != 0:
        // Обработка ошибки
        pass
async_result.connect(result_handler, error_handler)

 Читання даних
Для читання даних досить виконати нижченаведений код, а потім обробити відповідь так само, як у випадку write'а:
 
C + +:
 
 
auto async_result = sess.read_data(std::string("test_key"), offset, size);

Python:
 
 
async_result = sess.read_data(“test_key”, offset, size)

При цьому варто пам'ятати, що на читання сервер міг не знайти файл з першого разу. У такому випадку кількість відповідей може бути більше одного, причому всі, крім останнього, будуть з негативним кодом помилки.
 
 Видалення даних
Видаляти дані також просто.
 
C + +:
 
 
auto async_result = sess.remove_data(std::string("test_key"));

Python:
 
 
async_result = sess.remove_data(“test_key”)

 І багато іншого
доступно в нашої документації .
 
 HTTP API
 
Для роботи з HTTP API ми рекомендуємо використовувати Rift . Докладна документація доступна тут . Rift заснований на нашому високопродуктивному асинхронному HTTP-сервері TheVoid , спеціально спроектованому, щоб працювати в якості HTTP-backend'а для Nginx.
 
Rift має підтримку двох основних режимів роботи — з підтримкою Бакета і без. У цьому пості ми будемо розглядати режим з Бакета як більш гнучкий і, звичайно ж, складний. Бакета дозволяють розділяти файли і права різних користувачів. Наприклад, можна дати одному користувачеві право писати в Бакет, а інший буде мати можливість з нього тільки читати.
 
Як приклад розглянемо спосіб створення свого сервісу по прослуховуванню музики.
 
Для початку нам потрібно підняти ще як мінімум один сервер Elliptics для зберігання такої мета-інформації Rift'а, як Бакета.
 
 
$ dnet_ioserv -c node-3.json

Тепер треба налаштувати і підняти Rift, його конфіг приведений у GitHub-репозиторії під ім'ям
rift.json
, розглянемо його ближче:
 
 
{
    "endpoints": [
        "0.0.0.0:8080"
    ],
    "backlog": 128,
    "threads": 2,
    "buffer_size": 65536,
    "logger": {
        "file": "/srv/rift/log.txt",
        "level": 4
    },
    "daemon": {
        "fork": true,
        "uid": 1000
    },
    "monitor-port": 21000,
    "application": {
        "remotes": [
            "localhost:1025:2",
            "localhost:1026:2",
            "localhost:1027:2"
        ],
        "groups": [],
        "metadata-groups": [
            3
        ],
        "bucket": {
            "timeout": 60
        },
        "read-timeout": 10,
        "write-timeout": 16,
        "redirect-port": 8081,
        "path-prefix": "/srv/elliptics/"
    }
}

Конфігураційний файл можна розбити на дві секції. Перша відповідає за конфігурування HTTP-сервера в цілому:
 
     
  • "endpoints" — список портів і unix-сокетів, які слухає сервер на вхідні з'єднання;
  •  
  • "logger" — конфігурація системи логгірованія;
  •  
  • "daemon" — опис демонізації сервера.
  •  
Друга секція — "application" — відповідає за конфігурацію Rift'а:
 
     
  • "remotes" — список вузлів Elliptics'а;
  •  
  • "metadata-groups" — список груп, в яких зберігається вся мета-інформація, наприклад, Бакета;
  •  
  • "redirect-port" — порт, який буде слухати Nginx на машинах з даними;
  •  
  • "path-prefix" — початкова частина шляху до блоб на машинах з даними.
  •  
Детально про всі параметри конфігураційного файлу можна прочитати тут .
 
 
$ rift_server -c rift.json

Всі операції в Rift з активованими Бакета вимагають авторизації. Алгоритм формування підпису описаний у документації. Тут же я буду використовувати вкрай простий http-клієнт на Python, який всі запити підписуватиме належним чином. Він лежить в архіві і називається
http_auth.py
.
 
Створимо Бакет, в який ми будемо заливати нашу музику. Причому хотілося б, щоб правом затоки музики володів тільки адміністратор ресурсу, тобто ми, а слухати міг хто завгодно. Розглянемо файл конфігурації Бакета:
 
 
{
    "groups": [
        1, 2
    ],
    "acl": [
        {
            "user": "admin",
            "token": "favorite_music",
            "flags": 6
        },
        {
            "user": "*",
            "token": "very_secret_token",
            "flags": 1
        }
    ]
}

Перше поле "groups" каже, що дані будуть писатися в двох копіях, по копії в групах 1 і 2. Детальніше про групи можна прочитати в попередніх статтях .
Друге поле "acl" містить права доступу для різних користувачів, в даному прикладі для кожного користувача були вказані 3 поля:
 
     
  • "user" — публічний id користувача;
  •  
  • "token" — секретний токен, по якому відбувається авторизація;
  •  
  • "flags" — опис прав користувача, на даний момент доступні наступні прапори і їх бітові комбінації:
     
       
    • 1 — для даного користувача не потрібно авторизація;
    •  
    • 2 — даний користувач має право для запису даних у Бакет;

    •  
    • 4 — даний користувач має право для зміни даних самого Бакета (список груп, acl і т.д.).
    •  
  •  
Правами для запису нових файлів і зміни Бакета володіє тільки "admin", так само вказано його секретний ключ, за допомогою якого відбуватиметься авторизація. А ось правом читання володіє будь-який зовнішній користувач без необхідності авторизації. Створимо тепер Бакет
music
в директорії
all
:
 
 
$ ./http_auth.py http://localhost:8080/update-bucket/all/music --file update-bucket.json
200

Директорія — це особлива сутність, яка дозволяє, наприклад, знайти все що містяться в ній Бакета. У створений Бакет заллємо трохи музики по ключу
example.mp3
:
 
 
$ ./http_auth.py http://localhost:8080/upload/music/example.mp3?user=admin --file example.mp3 --token favorite_music
200

Після цього можна слухати музику прямо з будь-якого програвача:
 
 
$ mplayer http://localhost:8080/get/music/example.mp3

Ручка
/get
підтримує заголовки Last-Modified і Range для більш ефективного управління трафіком, але це все одно не саме раціональне використання ресурсів. Набагато ефективніше було б Стрім дані за допомогою Nginx'а з найближчих до користувача фізичних машин, на яких даний файл зберігається. Це досягається за рахунок підтримки регіональності в Elliptics.
 
Для цього нам потрібен Nginx з нашим eblob-модулем. Його можна знайти на GitHub'е .
 
Зібрати його можна з використанням будь-якого відповідного Nginx'а (підтримуються версії по 1.7.х) приблизно таким чином:
 
 
$ git clone https://github.com/toshic/nginx_patches.git
$ wget 'http://nginx.org/download/nginx-1.7.0.tar.gz'
$ tar -xzvf nginx-1.7.0.tar.gz
$ cd nginx-1.7.0
$ ./configure --prefix=/opt/nginx --add-module=../nginx_patches/nginx-eblob
$ make
$ sudo checkinstall # :)

Для активації eblob-модуля досить додати блок
“eblob;”
в
location
-секцію. Таким чином, мінімальний конфігураційний файл виглядає так:
 
 
worker_processes  8;
 
events {
    worker_connections  1024;
}
 
http {
    sendfile on;
 
    server {
        listen       8081;
        server_name  localhost;
 
        location / {
            root   /var/srw;
            eblob;
        }
    }
}

Зрозуміло, для бойового оточення Nginx слід налаштовувати не настільки наївно.
 
Відмінно, тепер, коли nginx налаштований і запущений, можна Стрім музику безпосередньо з нього:
 
 
$ mplayer -q http://localhost:8080/redirect/music/example.mp3

За даним запитом сервер згенерує url спеціального виду, одержавши який Nginx зможе віддавати музику прямо з blob'ов, в який Elliptics зберігає свої дані.
 
Набагато докладніше про Rift можна почитати в нашій документації . Там же є докладний опис всіх доступних методів.
 
 Замість висновку
Про це та багато іншого можна почитати в нашій документації на doc.reverbrain.com :
 Нагадую, що всі надані в даній статті проекти відкриті і розташовані на GitHub: https://github.com/reverbrain .
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.