Добрый день! Меня зовут Сунцов Илья, я работаю старшим тестировщиком в GridGain Systems.
В ближайшие полчаса я расскажу немного теории о распределенных системах, о, навеное,
новом для вас языке программирования - Clojure и покажу примеры реальных тестов, написанных с
помощью одного из модулей Clojure - Jepsen-а.
Когда берешь новую и технически сложную тему, довольно трудно представить себе тех,
кто прийдет тебя послушать. Это довольно важно, ведь
В зависимости от этого ты пишешь аннотацию к презентации, планируешь свой доклад.
У меня есть какое-то смутное представление на этот счет и я бы хотел для начала подтвердить или опровергнуть свои предположения.
Давайте так
Кто умеет кодить на Python или Java или Си или может вообще на Lisp?
Кто занимается тестированием какой-то распределенной системы? Или может быть приложения, которое работает поверх MongoDB, Apache Ignite, Redis или Cassandra? Иои что хотя бы слышал про эти системы?
Кому нравится ломать головы над новыми сложными задачами?
Отлично) Тогда идем дальше!
Распределенные системы.... Давайте подумаем что же это такое? Ммммм... Наверное это система для которой пространственное отношение элементов (или групп элементов) играют существенную роль с точки зрения функционирования системы Понятно, что узлы этой системы взаимодействуют как-то между собой и с какими-то клиенскими приложениями. И эти системы нас не интересовали бы вовсе, если бы они не ломались. Таки где-же они обычно ломаются?
Посмотрим на основные характеристики РС.
На слайде вы можете увидеть такую вот диаграмму, которая наглядно иллюстрирует CAP теорему. Она гласит, что создать целостную и доступную систему можно только пренебрегнув устойчивостью к нарушению связи мд узлами.
В реальном же мире два выходы. CP либо AP, то есть смещаемся либо в стороно целостности пожертвовав доступностью или в сторону доступности, пожертвовав целостностью.
Нам не очень важно, насколько она сейчас актуальна - реальный мир отличается от теории – нам важно посмотреть вот на эти самые кружочки. Будем считать их основными характеристиками распределенных систем.
Начнем с целостности.
А что вообще такое целостность? В CAP теореме говорится о самом строгом виде целостности – линеаризуемости то есть если Существует непротиворечивая история последовательных операций, то данные целостны. Или если операция B началась после операции A, то B должна увидеть систему на момент окончания A или в более новом состоянии. То есть если A завершилась, то следующая операция не может видеть то, что было до A.
Представим, у нас есть какой-то регистр. Это то что мы можем прочитать, только то что мы до этого туда записали. У нас есть один читатель-писатель. Мы всё туда читаем-пишем, ничего сложного. Даже если у нас несколько читателей и писателей, тоже ничего сложного.
Но как только мы перемещаем с картинки на предыдущем слайде в реальный мир, эта картинка выглядит немножко по-другому, потому что у нас появляются сетевые задержки. Мы точно не знаем, когда именно случилась запись между w и w1. То же самое с чтениями. С точки зрения истории у нас может возникнуть 3 варианта развития ситуации.
Read a, write b, read b
Read a, read a, write b
Read a, read b, write b
Последний вариант – когда у нас например запись произошла в транзакции или когда мы файтически положили данные в регистр, но операция почему-то завершилась позже.
То есть есть варианты развития ситуации, котда вот та непротиворечивость, о которой говорит линеаризуемость нарушается.
Когда мы говорим о нарушении непротиворечивости, то имеем ввиду такие штуки, как :
- Lost updates
- Dirty read
- Stalen read
Потерянные обновления. Такая штука происходит, когда две транзакции изменяют один и тот же объект независимо друг от друга.
В итоге, кто последний, тот и закоммитил свои изменения.
Грязное чтение - еще один вид аномального чтения. Происходит такая ситуация, когда, транзакция пишет данные и до того момента, как произошел коммит какая-то другая операция может прочитать эти данные. Вроде все ок, но! Та самая первая транзакция может откатиться и данные останутся такими, какими были до этой транзакции.
Чтение устаревших данных – это когда идет транзакция и кто-то в какой-то момент времени во время ее выполнения прочитал данные, которые потом в ходе транзакции были изменены и транзакция успешно завершилась.
С доступностью все довольно просто. Если в какой-то момент работы с распеделенной системой она перестает отвечать, то это не есть хорошо. Это может происходить из-за большого количества данных приходящих в систему или когда система распадается на части и принимается решение одну из частей погасить.
Как раз об этом дальше
Осталась последняя характеристика - partition tolerance то есть устойчивость к нарушению между какими-то частями системы. Это не тогда, когда, скажем у нас в системе 100 узлов и мы просто убили один из них. В этой ситуации наверное, где-то на других узлах есть бэкапы данных, которые хранились на этом одном узле. Осталось 99 и мы продолжаем работать. Поднимут 100-й узел - превосходно, нет - ну не судьба, у нас же ж наверняка есть бекапы этих данных.
Разделение на части это когда у нас, скажем, умер свитч и одна половина узлов перестала видеть другую. Каждая половина работает самостоятельно, как самодостаточная система. Принимает и обрабатывает запросы от клиентов. Эта ситуация называется split brain и плоха она тем, что когда починят работу сети, возникнет огромное количество конфликтов данных, которые будет трудно разрешить и мы потеряем целостность.
В этой ситуации обычно определяется какая часть больше или важнее и отключается вторая.
Ок, теперь мы в общих чертах представляем себе особенности РС. Подумаем о том, как их тестировать.
Unit тесты – первое, что приходит на ум, Разработчики пишут тесты (или нет), пишут код, все это работает на ТС, Jenkins – отлично!
Performance тесты – производительность довольно важдна для таких систем как mongo db, cassandra, apache ignite. У многих продуктов есть свои тулзы для тестирования производительности. Они же зачастую помогают проводить fault tolerance тесты, это когда в гриде начинают умирать и снова подниматься отдельный узлы.
Что же мы можем сказать про функциональное тестирование? Наверняка можно создать какие-то автотесты, которые будут разворачивать готовую систему (не модуль) и выполнять какие-то операции с узлами системы, моделировать, например разрыв связи в гриде, а можно уже воспользоваться готовыми инструментами. И я имею ввиду в первую очередь Jepsen.
Jepsen привлек мое к себе внимание, когда я заметил, что на разных сайтах проскакивала информация о том, что условные Cassandra, DynamoDB и тп, при непосредственном участии некоего Кайла Кингсберри обложили свою систему jepsen тестами, прогоняли их на протяжении нескольких недель/месяцев, нашли такие-то баги и обязательно закроют их в следующем релизе.
О, подумал я, так это крутая штука этот ваш jepsen. И если твоя РС успешно прошла это тестировние, то ты прям молодец.
Пару слов о том что это такое. Jepsen это модуль языка Clojure.
Принцип работы довольно простой. Он имитирует сетевые ошибки, генерирует случайные операции к вашей распределённой системе. Смотрит каким образом эти операции были применены к вашей распределённой системе и к эталонному поведению, к модели этой распределённой системы, и есть ли с этим проблемы
Вот на слайде генератор, которые забрасывает систему обычно из 5-ти узлов данными, ниже nemesis который не сидит на каком-то узле и генерит какие-то сетевые проблемы. Ну и для простоты тут изображен лог, в который воркеры – клиенты – пишут информацию о том, что происходит.
Пока о джепсене все.
Clojure очень похож на Lisp, только под JVM.
Про Lisp есть много шуток, как например все видели этот чудесный коммикс о том, как спасти принцессу используя разные языки программирования?.
Ну там, где чуваку, который вооружился PHP, совсем грустно.
Там есть часть и про лисп) Тут чуваку чуть менее грустно, но все же непросто. Это, скажем так, новый уровень абстакции) Жаль, что многие знают о Lisp только то, что там много скобочек и каких-то e-expressions.
Прежде чем перейти к примерам, я хочу пару слов сказать о среде разработки, где можно попробовать запустить те самые примеры, и на основе их сваять что-то свое.
Для тех чьему сердцу дорог терминал можно сделать вот так. Смотрите на слайд.
Первая строка – качаем leineinegen – это такая крутая штука без которой трулно выжить в мире clojure – он автоматизирует запуск, сборку, подтягивает зависимости – похож на maven.
Потом закидывем его в /usr/bin и запускаем.
После того, как вы запустите lein repl у вас откроется repl это что-то типа интерактивного режима в python, то есть когда вы просто печатаете в терминале python и жмете enter.
То есть вы не устанавливая ничего больше можете попробовать написать какой-то код на clojure.
Для тех, кому больше нравится работать в IDE я бы порекомендовал выбрать связку Intelij IDEA от JetBrains и плагин к ней - cursive.
И idea и cursive можно использовать бесплатно. Idea – community edition, а cursive можно зарегистрировать как плагин для некоммерческой разработки.
Очень важный момент – idea последней версии не подружится с cursive. У меня все завелось с той версией, которую вы можете видеть на слайде.
Перейдем к примерам кода!
Как водится, начнем с Hello world.
Как вы видите, действительно, много скобочек)
В первой строке определяется пространство имен. Это что-то типа package в java.
Посмотрим на самое важное в этом годе – функцию, которая начинается с defn имеет имя hello world и принимает на вход имя параметер nword и внутри ее функция format форматирует строк и потом println печатает то., что получилось.
Посмледней строкой эта самая функция вызывается.
Аналог обычного цикда for. От 10 до 1 с шагом 2 печатаем все числа.
Также определяестся функция. Внутри нее loop этот самый цикл. When это аналог if, когда не будет ветки else.
Recur в этом случае просто сделает x=x-2
Clojure прекрасно дружит с java. Вот таким нехитрым способом можно создавать Java объекты в Clojure.
Вот еще один пример. Здесь создаем стек, кладем туда два элемента и потом берем первый элемент из стека.
Проверка условия – стандартный if.
Тут у нас есть ветка else, она не отражена как таковая, но она есть) Это вторая строка внутри if. То есть если 2 равно 2, то пишем что числа равны, если нет, то пишем, что не равны.
Для какого-то реального тестирования я выбрал Apache Ignite. Это open source распределенная, высокопроизводительная платформа для вычислений в оперативной памяти (In-memory).
Это middleware то есть Ignite может встать например между базой данных и пользовательским приложением. В таком кейсе приложение будет общаться не с базой данных, а с AI, который в свою очередь будет доставать и держать данные из бд в оперативной памяти, что позволит повысить производительность работы приложения
Для тестов нам понадобится несколько хостов. Для этой цели хорошо подходит AWS. В качестве ОС для тестов предлагается выбрать CentOS, но для кого милей Ubuntu при помощи пары ударов в бубен Jepsen легко можно подружить и с этим дистрибутивом.
Также не стоит забывать о том, что jepsen тесты можно включить в регрессивонную сьюту и запускать при помощи TC или Jenkins в docker.
Еще одна штука, о которой хотелось бы сказать это salticid.
Названа она в честь вида пауков скакунов, один из них представлен на слайде.
И не спроста с помощью нее можно вот так же прыгать по хостам нашего тестового стенда и настраивать какие-то штуки, что-то конфигурить.
Эта тулза написана Кайлом же, уже на Руби, что легче (или нет).
На слайде вы увидите ссылку на гитхаб репозиторий. На следующем слайде простой пример использования salticid-а.
Первая строка – установка салтицида
Далее идет пример кода на ruby, где определяется таска, которая выводит список файлов на удаленном хосте
Под цифрой три строка запуска этого кода
Класссно, братиш, скажете вы! А ты слышал про ssh? Зачем ради ls -l такой огород городить? Ради ls – да, не стоит. Salticid круто применять для рутинной настройки системы.
Внизу слайда опять же ссылка на гитхаб, в доке к салтициду много информации о том, как его можно использовать.
Теперь давайте немного поговорим о Clojure – языке, на котором написан jepsen.
Создаем чистый проект при помощи lein.
Также я настоятельно рекомендую выкачать гитхаб репо уже с готовыми тестами и исходным кодом самого jepsen.
Когда вы создадите проект получится нечто такое. Совсем нестрашная структура каталога – все как в java – код в src, тесты в test
Помимо вашего кода, под рукой у вас также будет еще куча примеров готовых тестов. Вот на слайде приведен список того, что есть.
Postgres,
mongodb,
RabbitMQ – штука для обмена сообщениями внутри системы, которая написана на erlang.
Zookeeper – иерархическое key/value хранилище, которое используется для обеспечения распределенной службы конфигураций, синхронизаций для больших распределенных систем
Aerospike – какая-то распределенная nosql штуковина.
Помимо вашего кода, под рукой у вас также будет еще куча примеров готовых тестов. Вот на слайде приведен список того, что есть.
Postgres,
mongodb,
RabbitMQ – штука для обмена сообщениями внутри системы, которая написана на erlang.
Zookeeper – иерархическое key/value хранилище, которое используется для обеспечения распределенной службы конфигураций, синхронизаций для больших распределенных систем
Aerospike – какая-то распределенная nosql штуковина.
Теперь конкретно про AI.
Все функциии нет смысла рассматривать на одном из следующих слайдов есть ссылка на github репозиторий. Там можно поглядеть весь код.
Вот так он устанваливается. Я убрал со слайда пути, чтобы все влезло. По сути все проивходит очень просто – идем в /tmp диреторию и туда wgetом с официального сайта AI выкачиваем бинарную сборку, распаковыввем и создаем внутри распакованной директории jepsen директорию, куда впоследствии положим конфиг.
Теперь давайте запустим серверные ноды нащей распределенной системы. Это делается очень просто – идем в бин директорию, там есть такой вот скрипт ignite.sh даем ему на вход конфигурационный файл и вывод отправляем в node.log файл. Огооонь! Что, еще нужно для хорошего теста – клиенты, ну или воркеры, в терминологии jepsen, которые будут делать что-то с нашей распределенной системой.
Все, что мы делали до этого это по сути shell из clojure. Настало время чего-то похардкорнее
Узрииииите! Это хардкор)
На самом деле тут все просто. Стартуем клиента и в зависимости от операции либо кладем знвчение в кэш либо забираем его оттуда.
Здесь я записал небольшое видео того, как все хорошо)
Тут вы видита как проходят подготовительные какие-то действия, связанные со скачаиванием дистрибутивом, настройкой конфигов и запуском нод
Подведем итоги.
Jepsen классная штука, которая не потребудет значительных усилий для настройки окружения, но минус и главная прелесть этой штуки в том, что тесты нужно писать на Clojure. С одной стороны это сложно. Ломается мозг, новый язык, но с другой – это хорошая тренировка для ума, по прошествии недели даже еду на тарелке начинаешь выкладывать от центра к краям.