У нас поменялся адрес RSS-фида. Пожалуйста, проверьте его в своих читалках. Кстати, у нас есть ещё и твиттер: @suckless666.
У нас поменялся адрес RSS-фида. Пожалуйста, проверьте его в своих читалках. Кстати, у нас есть ещё и твиттер: @suckless666.
Облачные вычисления — самый модный и порванный баззворд в сфере IT за последние несколько лет. Даже агенство Gartner в своём ежегодном отчёте второй год подряд упоминает этот термин, обозначая одну из стратегических технологий. При всём ажиотаже, нет ничего плохого в том, чтобы отделить зёрна от плевел, и создать свою маленькую и уютную «тучку».
Итак, давайте понимать облачные вычисления как технологию предоставления ресурсов по требованию пользователя, контролирующую весь их жизненный цикл: создание, использование, модификацию, учёт и уничтожение.
Часто к «облакам» навешивают ярлык какой-либо бизнес-модели: IaaS, PaaS, SaaS, etc. Я расскажу про развёртывание полноценного IaaS-облака в совершенно реальном месте — Институте математики и механики УрО РАН.
Так сложилось, что нам понадобилась инфраструктура, позволяющая быстро запустить пачку виртуальных машин, а также быстро её удалить и развернуть пачку других виртуальных машин. Ещё в нашем ЦОД часто ломаются работающие сервера и нередко покупаются новые. Встал вопрос о выборе связующего ПО.
Мы долго выбирали между модным нынче OpenStack и менее знаменитой OpenNebula.
Главной причиной отказа от OpenStack было то, что мне он показался довольно неудачной попыткой слепить нечто рабочее из обломков существующих «облачных» решений. В итоге вместо унифицированного инструментария мы имеем дело с разрозненными останками разношёрстных «облачных» решений, что сильно затрудняет конфигурацию, поддержку и повседневную работу с системой.
Было решено использовать OpenNebula, и сейчас видно, что решение оказалось правильным.
Ядро OpenNebula написано на C++ и занимается исключительно управлением состоянием системы. Вокруг ядра имеется множество утилит, написанных на Ruby и Shell, которые решают различные задачи: управление пользователями, контроль виртуальных машин, хранилища образов, и т. д.
OpenNebula разделяет понятие управляющей машины и узла «облачного» кластера. Очевидно, управляющая машина отдаёт узлам кластера команды на выполнение (например, запустить VM под гипервизором Xen), а сами узлы всего лишь выполняют полученные команды. Для обмена данными OpenNebula использует протокол SSH с перенаправлением потоков ввода-вывода и авторизацией при помощи секретных ключей. Программное обеспечение OpenNebula ставится только на управляющие машины.
Сегодня OpenNebula поддерживает такие гипервизоры, как KVM, Xen и Hyper-V. В качестве системы хранения образов виртуальных машин поддерживается NFS, MooseFS, а также тупое пробрасывание образов через SCP. Увы, поддержка клёвого хранилища OpenStack Swift отсутствует, потому что все операции в OpenNebula выполняются при помощи библиотеки libvirt, которая пока не способна работать со Swift.
Мы развернули свою «тучку» из остатков старого вычислительного кластера, умеющих аппаратную виртуализацию. В качестве гипервизора выбрали модный KVM, а данные решено было хранить в файловой системе NFS на имеющемся NAS от EMC².
Можно представить структуру «облака» графически:

После решения оргвопросов:
Таким образом, получилась полноценная, простая в использовании и масштабируемая «облачная» инфраструктура с поддержкой живых миграций виртуальных машин, мониторингом состояния кластера, наличием мер по обеспечению отказоустойчивости, а также многими другими радостями, о которых можно прочесть в документации.
Как я уже говорил, всё вышеперечисленное достигается благодаря тому, что система не переусложнена, все виртуальные машины хранятся на независимом NFS-томе, а взаимодействие управляющей машины с узлами кластера ведётся через надёжный SSH:

Напоследок, приложу скриншот Sunstone. Как видно, прямо сейчас у нас практически нет нагрузки, и балансировщик запихал две VM на третий сервер. В случае выхода его из строя, виртуальные машины мгновенно перенесутся на другие узлы и продолжат свою работу без каких-либо заметных проблем:

На сегодняшний день скорость развития рынка Web-приложений крайне велика. Относительно новым и пока не до конца сформировавшимся рынком является рынок приложений в социальных сетях. Аудитория социальных сетей растёт. Количество денег, которые тратят пользователи, увеличивается. Интерес к получению дохода путём создания приложений, очевиден. Самой популярной и динамично развивающейся социальной сетью в России является В Контакте. Само собой, она предоставляет богатый API, функциональность которого расширяется практически каждый месяц.
Сегодня в Интернете полно библиотек, которые позволяют как-то работать со вконтактом. Часть из них парсит страницы, другая работает напрямую с API. Первые сразу отпадают, если вы хотите заработать на своём приложении, а вторые часто забрасываются авторами или не справляются с темпами развития социальной сети.
Примеры:
Поскольку так сложилось, что на данный момент не существует функциональной и корректной API-обёртки В Контакте для Ruby, я представляю собственное решение — vk-ruby.
Я постарался предусмотреть и избежать многих узких мест, поэтому vk-ruby:
Что нужно, чтобы начать работать с API В Контакте?
Cоздадим приложение необходимого типа:
serverside = VK::Serverside.new :app_id => APP_ID, :app_secret => APP_SECRET
# или
standalone = VK::Standalone.new :app_id => APP_ID
# или
secure = VK::Secure.new :app_id => APP_ID, :app_secret => APP_SECRET
Здесь и далее, константы APP_ID и APP_SECRET — ключи, которые вы получите при регистрации приложения. Далее необходимо авторизоваться.
serverside.authorize(CODE)
# или
standalone.authorize(CODE)
# или
secure.authorize
Информация о параметре CODE приведена в документации о способах авторизации. Кстати, теперь мы можем «дёргать» необходимые методы.
Например, посмотрим, установил ли пользователь наше приложение:
serverside.isAppUser # => 1
Установил. Попробуем выполнить поиск по аудиозаписям:
tracks = serverside.audio.search(:q => 'Rammstein')
unread.shift # => количество треков
tracks.map do |track|
track['title']
end # => [ 'Mutter', 'Bang Bang', 'Du Hast', ... ]
Standalone-приложениям предлагается поддержка расширенных методов. Например, доступ к личным сообщениям пользователя:
unread = standalone.messages.get :filters => '1'
puts unread.shift # => количество сообщений
unread.map { |msg| msg['title'] } # => [ 'заголовок1', 'заголовок2', ... ]
Secure-приложения получают доступ как к обычным и расширенным методам API, так и к административным методам. Например, возможность отправлять пользователям уведомления.
secure.secure.sendNotification(
:message => 'Привет!',
:timestamp => Time.now.to_i,
:random => rand(1000)
)
Легко заметить, код получается довольно простым и аккуратным. Всё что нужно для работы — это ознакомится со списком методов API.
Конечно, vk-ruby легко интегрировать в любое приложение. Ruby on Rails, Sinatra, Padrino — не имеет значения, хоть в другой gem. В качестве небольшого примера я написал простое Web-приложение на микрофреймворке Sinatra.
Огромным камнем в огороде вконтакта является способ авторизации клиентских приложений, который предполагает встраивание браузера и «ручное» заполнение формы авторизации. Это действительно большая проблема, которая не решилась даже с переходом В Контакте на OAuth 2.0. В качестве частичного решения, я написал утилиту vk-console, которая авторизуется в системе, выполняя парсинг страниц.
Я старался сделать данный инструмент простым и удобным, но как и любому проекту поддержка будет не лишней! Если знаете как его улучшить, нашли баг или ещё что то, то пишите, форкайте — буду очень рад.
Сложные системы состоят из множества компонент, между которыми возникает необходимость организовывать взаимодействие, часто сводящееся к передаче сообщений. Разные команды разработчиков решали эту задачу по-разному. Итак, как же в рамках экосистемы Ruby можно решить эту задачу?
ØMQ — очень эффективная и навороченная библиотека для организации обмена сообщениями, не привязанная к конкретному транспортному уровню. Внутри одного процесса можно устроить обмен сообщениями между потоками поверх UNIX-сокетов или внутрипроцессового IPC, а удалённые приложения могут «разговаривать» друг с другом поверх протоколов TCP или UDP. Более того, ØMQ ориентирована на верхний уровень модели OSI, поэтому даже сообщение в полмегабайта переданное из пункта А в пункт Б придёт в целости и сохранности.
Внутри ØMQ реализованы традиционные паттерны построения сетевых приложений: парадигма «запрос-ответ», «издатель-подписчик», механизмы маршрутизации сообщений, построения отказоустойчивых систем и многое другое, описанное в гайде.
Итак, главное для нас — сообщения. Сообщениями обмениваются изолированные приложения при помощи специальных сокетов в контексте ØMQ.
Рассмотрим более-менее прикладную задачу. Пусть имеется Web-приложение, при помощи которого пользователи обрабатывают что-то большое. Это самое «что-то большое» должно обрабатываться и отдаваться клиентам, не вынуждая систему отказывать им в обслуживании из-за высокой нагруженности. Логично разделить приложение на две части (как показано на рисунке):

pub.sock и pull.sock.Возьмём Sinatra и биндинги к ØMQ. Кстати, из-за особенностей реализации потоков в MRI, следует использовать версию Ruby 1.9 и неофициальный гем ffi-rzmq. Официальный гем zmq «подвешивает» интерпретатор при завершении работы.
В качестве нагрузки будем вычислять CRC32-хэш переданной строки и вызывать sleep() на несколько секунд внутри бэкэнда, чтобы дать видимость сложной вычислительной задачи. Обмениваться данными будем в формате JSON при помощи библиотеки Yajl.
Итак, код бэкэнда. Вообще, я старался комментировать код, хотя проблем при чтении быть не должно: всё очень просто.
loop do
begin
logger.info(recv = pull.recv_string)
data = Yajl.load(recv).first
# imagine that we're processing this piece of crap
crc32 = Zlib.crc32(data)
sleep 2
rep = [ data, crc32 ].to_json
pub.send_string rep
logger.info(rep)
rescue Interrupt
break
end
end
Теперь код фронтэнда. Это вполне себе обычное приложение на микрофреймворке Sinatra.
get '/' do
# index template should be saved at views/index.erb
erb :index
end
post '/' do
data = params['data'] || ''
if data.strip.empty?
return redirect '/'
end
$queries[data] = nil
# send query to backend
push.send_string [data].to_json
redirect '/'
end
Кстати, не забываем положить файл с ERb-шаблоном страницы по адресу views/index.erb относительно остальных файлов.
Итак, для начала запускаем backend.rb, который забивает на себя UNIX-сокеты и готовится обрабатывать данные. После этого запускаем frontend.rb и заходим браузером по адресу http://localhost:4567.
Как видно, если добавить в очередь несколько заявок подряд, то они выполнятся последовательно. Таким образом, всю работу по доставке данных от фронтэнда к бэкэнду и обратно выполнила ØMQ, за что ей большое спасибо. У меня это выглядит примерно вот так:

В качестве домашнего задания можно прочесть гайд и попробовать добавить к фронтэнду возможность работы с несколькими бэкэндами, балансировки нагрузки между ними, в результате получив функциональный аналог Gearman.
Приведённый в статье пример может показаться слишком простым. Это действительно так, и он не показывает даже верхушку айсберга возможностей ØMQ, но исключительно базовые вещи. В качестве материала для дальнейшего чтения могу категорически порекомендовать две шикарные статьи Ilya Grigorik [1,2].
Посмотрим, насколько всё плохо в Ruby с параллелизмом: юзабельны ли треды? Возможно ли вообще в Ruby параллельное программирование? Опыты проведём на огромном компьютере Fujitsu-Siemens PRIMEPOWER 850 с установленным Sun Solaris 9.
Будем одновременно считать суммы первых N членов разложения нескольких S сходящихся числовых рядов, затем эти суммы сложим.
Естественно, такая задача запросто решается при помощи парадигмы MapReduce. Для работы у нас есть восемь процессоров SPARC64 по тысяче мегагерц каждый и тридцать два гигабайта оперативной памяти на всю систему.
Физического, математического или глубинного смысла здесь искать не стоит: надо просто чем-то нагрузить вычислитель, чтобы время на выделение нового процесса или треда было пренебрежимо мало по сранению со временем решения задачи в этом процессе или треде.
Сравним производительность работы в трёх случаях:
Для этого дела я написал достаточно простой бенчмарк: parallel-bench.rb.
Вместо того, чтобы обсуждать код, лучше поговорить сразу о результатах измерений. Для пущей наглядности я представил их в виде гистограммы. Чем короче столбик, тем лучше результат.

Легко сделать выводы.
CPU=1 вариант с процессами оказался самым медленным? Дело в том, что после каждого этапа вычислений, создаётся новый процесс вычисления следующего члена числового ряда и убивается отработавший. Куча дополнительного времени тратися впустую.Итак, старый суперкомпьютер подтвердил очевидную вещь: преимущества параллельного программирования заметны лишь в том случае, когда время решения каждого этапа исходной задачи несопоставимо больше, чем временные затраты на обеспечение параллелизма. Когда такое условие выполняется, игра чертовски стоит свеч!
К сожалению, выбросить GIL из MRI не представляется возможным. Сегодня все мечты о «настоящем параллелизме» возлагаются на JRuby, а завтра — на Rubinius.
Библиотека ImageMagick применяется практически в любом Web-приложении (на языке Ruby) для обработки и композитинга изображений, рисования различных графических примитивов, и так далее.
Если говорить о применении этой библиотеки в экосистеме Ruby, то сразу на ум приходит гем RMagick, который мало того, что имеет просто невероятно неудобное API, так его использование обходится дороже, чем тупой фоновый запуск отдельных программ, входящих в пакет ImageMagick. Плюс, иногда его требовательность к версиям установленных в системе библиотек способна поразить даже самых лояльных разработчиков.
Ясен перец, что порой возникают задачи, где вся такая мощь и тяжесть ImageMagick не требуется и можно обойтись более гуманными средствами.
Думаю, большинству PHP-разработчиков хорошо знакома достаточно милая библиотека GD2. Среди Ruby-разработчиков она не пользуется большой популярностью, и это весьма грустно, ведь она способна достойно решать основные задачи работы с растровой графикой.
Существует гем gd2-ffij. Последняя его версия вышла в 2010 году, но осмотр кода показал, что развитие библиотеки остановилось отнюдь не потому, что всё очень плачевно. Дело в том, что требуемые от библиотеки задачи решены в необходимом и достаточном виде.
Легко показать, что вывести какое-нибудь изображение с помощью GD2 не так уж тяжело. Я тут накидал простенькое приложение на Sinatra, которое рисует очень красивые и приятные глазу QR-коды с заданным текстом: https://github.com/eveel/quoreux :)
Код, который рендерит и сохраняет результирующее изображение выглядит достаточно неплохо:
image.draw do |canvas|
qr.modules.each_index do |y|
qr.modules.each_index do |x|
canvas.color = qr.is_dark(y, x) ? dark : bright
canvas.rectangle(x * settings.cell_width,
y * settings.cell_width,
(x + 1) * settings.cell_width,
(y + 1) * settings.cell_width, true)
end
end
image.export image_path
end
Результат работы всего приложения выглядит примерно так:

Как видно, задача решена легко, с минимальными затратами ресурсов системы и человеческого времени, а значит, что иногда лучше думать, чем хвататься за ImageMagick.
Меня огорчает лишь слабая поддержка GD2 со стороны Ruby-сообщества. Интересно, с чем это связано?
Мы знаем, что λ-исчисление лишено поддержки рекурсии. Также мы знаем, что использование λ-функций часто помогает сделать запись программы красивее и лаконичнее. В любом случае, возникают ситуации, когда достижению этой благородной цели мешает невозможность сделать рекурсивный вызов λ-функции.
Здесь нам поможет Y-комбинатор.
Итак, я не в состоянии дать определение, выводы и основы комбинаторной логики, потому что Саклесс ни разу не об этом. Несмотря на это, следует вкратце рассказать об этом комбинаторе.
Комбинатор Y помимо его названия с латинской (или какой там?) буквой “Y” называют «парадоксальным», благодаря его связи с самоотносимостью. Это свойство самоотносимости позволяет нам реализовать рекурсию над лямбда-исчислением. Мило, не правда ли?
Итак, реализовать это можно как-то так:
def Y
lambda { |f| f.call(f) }.call(
lambda do |g|
yield(lambda { |*n| g.call(g).call(*n) })
end)
end
Раньше для реализации функции рекурсивного подсчёта факториала целого числа приходилось писать отдельный метод. Как правило, в таких случаях декларация отдельного метода выглядит не так изящно, как запись λ-функции:
fact = Y { |this|
lambda do |n|
(n > 1) ? n * this.call(n - 1) : 1
end
}
p fact.call(5) # => 120
Вуаля! Результат одинаков, но давайте посмотрим, как дела с производительностью? Для этого закатаем простой бенчмарк: объявим метод вычисления факториала, объявим метод создания Y-комбинатора, объявим λ-функцию вычисления факториала, а потом погоняем всё это дело на разных n.
Результат несколько огорчает. При использовании λ-функций мы гораздо раньше напарываемся на ограничение уровня стека, чем при использовании методов. Это довольно печально, хотя на этом проблемы не заканчиваются.
Уже на маленьких n временные затраты (real) на вызовы λ-функции превышают тупой вызов метода. С увеличением n разрыв идёт уже не на один, а на два порядка, то есть вариант с Y-комбинатором медленнее в сто раз! Как решить эту проблему?
На хабре в аналогичной статье человек предлагает решать эту проблему на Python при помощи промежуточного кэширования. Думаю, это поможет, но в таком случае становится несколько страшно смотреть на реализацию этого дела.
Y-комбинатор позволяет избавить себя от лишнего объявления метода при использовании функции, требующей рекурсии.
В случае обработки деревьев, списков или чего-то в этом роде, этот приём позволяет сделать код красивым, чистым и шелковистым. К сожалению, придётся несколько пожертвовать производительностью, но в любом случае это лучше, чем превращать код в лапшу. И это хорошо.