Reset от всех бед

7 июнь, 2005 - 23:00Сергей Митилино

Проблема надежности программного обеспечения кажется вечной: она возникла полвека назад и продолжает оставаться актуальной. Но, похоже, Джордж Кэндиа (George Candea), исследователь из Стэнфордского университета, нашел панацею от зловещих «софтверных багов». Долгожданное лекарство на данный момент включает в себя два компонента – программные предохранители и механизм микроперезагрузок.

И вновь продолжается...

Reset от всех бед

Как вы лечите свою ОС от сбоев? Как вы решаете проблему утечки ресурсов, которая приводит к засорению памяти и винчестера? Как, наконец, реагируете на зависание компьютера? Прочитав последнюю фразу, опытный пользователь сразу вспомнит о кнопке Reset и, по мнению специалистов, правильно сделает. Многие ошибки, возникающие при работе ПО, исправляются сами собой после его реинициализации.

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

Наконец, существует такое понятие, как «старение программного обеспечения» (software aging) – накопление ошибок и «мусора», приводящее со временем к снижению эффективности работы системы. Согласно последним исследованиям IBM, чтобы уменьшить значение этого эффекта, многие крупнейшие финансовые корпорации США на регулярной основе несколько раз в день перезагружают все свои серверы.

К сожалению, на перезагрузку уходит время. Немного улучшить ситуацию удается путем ее усечения до аварийной перезагрузки (crash reboot) – реинициализации ПО без корректного завершения работы (shutdown). По данным Кэндиа, на опытной машине перезагрузка RedHat 8 после сбоя (например, внезапного отключения питания) занимает всего 74 с, но если сделать это корректно, ее длительность увеличивается до 104 с. Та же тенденция прослеживается для WinXP и сервера Java-приложений JBoss 3.0.

Руководства пользователя предупреждают о возможном нарушении работоспособности ОС после аварийной остановки, однако в реальности такое происходит крайне редко. Так почему бы не довести идею до логического конца и не писать программы, для которых аварийная остановка является единственным способом завершения работы (crash-only software)? Кэндиа пишет: «Строить систему, которая гарантирует отсутствие сбоев, непрактично. Поскольку сбоев невозможно избежать, программы по меньшей мере должны быть к ним готовы».

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

Единственное препятствие – оптимизация программ на скорость выполнения. Пытаясь максимально ускорить ПО, разработчики ввели в обиход такие вместилища информации, как «кэш», «буфер» и аналогичные им, которые не способны пережить аварийной остановки. Чтобы избежать «гибели» данных вместе с кодом, их надо разделить. Сведения о текущих процессах внутри программы следует записывать в специально выделенные хранилища состояний (SSM – State Services Manager), а саму программу – разбить на отдельные изолированные модули. Таким образом, перезагрузка выполняется для каждого модуля в отдельности и не влияет на функционирование системы в целом. Джордж Кэндиа нарек данную концепцию «микроперезагрузкой» (µRB, microreboot).

Если вы полагаете, что написание программ, готовых к «внезапной смерти», является чем-то ирреальным и что реализация концепции существенно повлияет на производительность, то советую познакомиться с Microsoft OneNote. Не знаю, известен ли вам этот факт, но OneNote не требует грамотного завершения работы и у нее нет функции Save (Сохранить). Проведите такой эксперимент: скопируйте большой фрагмент текста, с изображениями и гиперссылками, в OneNote (из Internet Explorer или MS Word) и, как только процесс вставки будет успешно завершен, «убейте» программу с помощью Task Manager. Вновь открыв OneNote, вы обнаружите на своем экране, кроме раздела, в который была произведена вставка, еще и сам скопированный текст.

А чтобы убедиться в действенности механизма микроперезагрузок, достаточно пару раз столкнуться со сбоями в пользовательском интерфейсе Win XP. Я многократно наблюдал ситуацию, когда после аварийной остановки процесса EXPLORER операционная система самостоятельно перезагружала его. Это занимает считаные секунды, и зависший компьютер оживает сам, без вмешательства извне.

Необходимые условия

Чудес не бывает, и Кэндиа сформулировал ряд достаточно сложно выполняемых и строгих правил для построения систем, готовых к микроперезагрузкам. Главное требование – четкое разбиение на небольшие, быстро инициализирующиеся модули, которые не имеют прямых перекрестных ссылок и не используют внутренние ресурсы друг друга напрямую. Ведь после микроперезагрузки все ссылки на модуль утратят свою актуальность, поэтому при наличии зависимостей придется перезагружать сразу конгломерат модулей. Хорошими механизмами для разбиения на подсистемы представляются идеологии изоляционных ядер, виртуальных машин, процессов в ОС.

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

К хранилищам состояний выдвигаются те же требования, что и к коду. Примером такой подсистемы является СУБД Postgres, дописывающая все изменения в конец файла данных, вместо того чтобы изменять его. Интерфейс и возможности хранилища должны максимально точно соответствовать нуждам приложения. Если требующаяся функциональность не будет обеспечена, то придется дополнить программу внутренними процедурами управления данными о состоянии, что снова сделает их уязвимыми в случае сбоя. Тому, как этого добиться, нужно поучиться у авторов Berkeley DB, снабженной четырьмя интерфейсами, реализующими различные концепции представления данных.

Если система готова к сбою/перезагрузке любого своего компонента, ей не следует рассчитывать на какие-либо «предупредительные выстрелы и белые флаги» со стороны поврежденного кода. Кэндиа предлагает взять на вооружение механизмы, хорошо отработанные в Интернете, где все запросы и сообщения имеют ограниченное время ожидания ответа и жизни. Если оно истекло, запрос или сообщение считаются необработанными и могут быть сгенерированы повторно. Также запросы должны содержать в себе описание, позволяющее продолжать их обработку с момента, непосредственно предшествовавшего сбою.

Одним из важнейших элементов программной среды crash-only станут менеджеры ресурсов. Они будут следить за выделением памяти, дискового пространства и т. д. Конечно, не обязательно, чтобы модули имели доступ к подсистемам компьютера напрямую, в обход менеджеров. Ресурсы следует выделять только «на правах аренды»: программы должны периодически подтверждать свое право на их использование. Как только срок «аренды» истекает и модуль-владелец не в состоянии заявить о себе, зарезервированные мощности освобождаются.

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

На практике

Кэндиа сумел заинтересовать своих коллег по Стэнфорду, и в июне 2004 г. увидела свет статья, описывающая первую практическую реализацию принципа программы crash-only. Наиболее эффективна данная концепция в среде интернет-приложений: они удовлетворяют требованию, согласно которому, запросы, обрабатываемые системой, должны быть максимально компактны и обособленны. Группа решила модифицировать сервер Java-приложений JBoss.

Архитектура среды Java 2 Enterprise Edition (J2EE) отлично подходит для демонстрации концепции микроперезагрузок. Приложение разбивается на ряд независимых модулей Enterprise Java Beans (EJB), которые запускаются внутри индивидуальных контейнеров. Исследователи добавили метод microreboot и подключили внешнюю подсистему хранения сессий, записывающую историю обработки пользовательских запросов в течение одной сессии. Состояние модулей отслеживал механизм, встроенный в J2EE, – Container-Managed Persistence. Несколько EJB не удалось полностью отделить один от другого, поэтому пришлось объединить их в перезагружаемые целиком составные архивы JAR.

В роли подопытного животного выступил бесплатный экспериментальный сервер RUBiS (Rice University Bidding System), эмулирующий функциональность онлайнового аукциона eBay. Его программный код насчитывает 26 тыс. строк, размещенных в 582 файлах.

Ошибки генерировались искусственным образом по специальной методике, базировавшейся на опросе ряда независимых экспертов в области программирования на Java. Нагрузка эмулировалась с помощью специального ПО. Систему инсталлировали на трех компьютерах, на одном из них располагался сервлет аукциона (Athlon 2600 XP, 1,5 GB ОЗУ), на двух других – данные, хранящиеся в MySQL и подсистеме SSM (Pentium 4 2,8 GHz, 1 GB ОЗУ). Виртуальных клиентов разместили на четырехпроцессорном сервере с Pentium III 550 MHz и 1 GB ОЗУ. Для испытаний выбрали операционную систему Linux с ядром версии 2.4.22.

Клиентская машина детектировала ошибки и сбои сервера, как и любой другой стандартный пользователь Интернета, – по времени выполнения запроса и сообщениям об ошибках вроде «404 – страница не найдена», «500 – внутренняя ошибка сервера» и т. д. Запросы объединили в пакеты, по завершении которых задача считалась решенной. Например, регистрация нового лота заключается в загрузке титульной страницы аукциона, загрузки страницы подраздела и вызова функции «добавить новый элемент». Если клиент сталкивался с ошибкой, то он определенное число раз пытался выполнить эту операцию и после установленного количества повторов сообщал серверу о зарегистрированном сбое.

Модернизированный JBoss-сервер выявлял вероятные модули-виновники сбоя, исходя из типа запроса. Для каждого из компонентов велась статистика, и как только несколько различных запросов, которые задействуют модуль, завершались неудачей – потенциального «вредителя» перезагружали.

Изучение практической реализации микроперезагрузок начали со сравнения полных перезагрузок системы и фрагментарных. JBoss имеет механизм горячей реинициализации приложений, поэтому провести корректный эксперимент не составило большого труда. Микроперезагрузки доказали свое превосходство, по крайней мере по чистой скорости выполнения. Перезапуск сервера JBoss в среднем занимал 51 с, RUBiS стартовал быстрее – 11 с, но микроперезагрузка, к примеру, модуля SB_BuyNow потребовала всего 603 мс, или 0,6 с.

Кроме того, измерялось время выдачи ответа на запрос. Вследствие полной перезагрузки длительность обработки запроса увеличивалась до 8 с и более, что (если верить психологам) заметно ухудшает взаимодействие человека и машины. Микроперезагрузки же никогда не вызывали задержек, превышающих 2 с, а это граница, отделяющая незначительную паузу от раздражающего пользователя «торможения».

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

Изучая благотворное влияние микроперезагрузок, ученые попутно открыли эффект «рикошета» в традиционных конфигурациях, включающих кластер из двух серверов и балансировщик нагрузки, контролирующий лишь время выполнения запросов. Когда один из серверов после сбоя перестает обрабатывать запросы, балансировщик перенаправляет всю его нагрузку на другой сервер и перезагружает неработающий компьютер. Оставшийся узел оказывается перегружен и вскоре скорость обслуживания запросов заметно снижается. Балансировщик нагрузки полагает, что на этом сервере сбои и, как только первый компьютер реинициализируется, перенаправляет нагрузку обратно, а второй узел «ставит на перезагрузку». И далее по циклу. Причем, когда исследователи обратились к профессионалам в области построения коммерческих кластеров с вопросом, анализируются ли такие ситуации в коммерческих проектах, те дали отрицательный ответ. Микроперезагрузки никогда не приведут к подобным резонансам.

Общее время неработоспособности системы состоит из периода обнаружения сбоя, времени выявления сбойного модуля и времени перезагрузки. Чем быстрее работает алгоритм обнаружения, тем больше ошибочных срабатываний. Механизм микроперезагрузок позволяет программе стать устойчивой как к медлительности анализа нештатных ситуаций, так и множеству ложных сбоев. Чтобы опустить показатель доступности системы, основанной на микроперезагрузках, до уровня программы стандартной архитектуры, необходимо увеличить время выявления сбоя на 53 с. То же самое касается процента ложных сбоев. При одинаковой доступности система, основанная на микроперезагрузках, выдерживает до 97,2% ошибочных срабатываний, в то время как обычная не должна допустить ни единого промаха.

Рассматривая результаты тестов, надо учитывать низкую эффективность алгоритма выявления сбойных модулей, разработанного участниками эксперимента. Они не ставили перед собой задачи достичь здесь каких-либо высот, и поэтому в ходе измерений их система выдавала до 60% ложных диагнозов (по сведениям некоторых специалистов, у eBay этот показатель равен 24%). Но несмотря на все эффективность методики микроперезагрузок сомнений не вызывает.

Но иногда важно не быстро ликвидировать последствия, а предотвратить катастрофу. Как уже упоминалось, существует явление старения приложений. Исследователи предлагают два способа борьбы с ним – микроперезагрузки по расписанию и микроперезагрузки по триггерам. Поскольку характер работы модулей можно заранее изучить, то несложно предсказать вероятный период до следующего сбоя вследствие уменьшения доступных ресурсов и накопления программного «мусора». Анализ позволит заранее составить расписание, согласно которому будут перегружаться состарившиеся компоненты системы. Ведь многие компании сегодня поступают именно так, но они реинициализируют всю систему в целом, что, естественно, не лучшим образом сказывается на средних показателях ее производительности.

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

Завершая свой отчет, ученые решили выяснить, как сильно влияют на производительность все привнесенные ими дополнительные элементы. Они выключили искусственную генерацию ошибок и заставили полчаса соревноваться в задаче одновременного обслуживания 350 клиентов три сервера: первый – не модифицированный, второй – с поддержкой подсистемы хранения текущего состояния (SSM), а третий – с полной реализацией микроперезагрузок. Увы, согласно беспристрастным измерениям, латентность обработки запросов выросла приблизительно вдвое. Такова цена параноидального состояния программы, каждый момент ожидающей внезапной перезагрузки. Но – и очень важное «но». Средняя пропускная способность всех трех испытуемых оказалась практически равной (44,8, 44,4 и 44,5 запроса в секунду, соответственно).

Учет и контроль

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

Исследователь приводит массу широко известных примеров, когда неверные запросы приводили к сбоям. В частности, большинство современных интернет-«червей» основываются как раз на эксплуатации несовершенства интерфейсов операционных систем и прочих серверных программ. Неверная реакция на переполнение буфера SQL Server позволила эпидемии «червя» SQL Slammer приобрести невиданные масштабы, а почтовый сервер Mailman можно взломать, послав ему некорректно сформатированное электронное письмо. Наконец, кластер, поддерживающий CNN.com, «упал» вследствие резкого увеличения количества запросов 11 сентября. В момент сбоя скорость поступления обращений достигала всего 3800 в секунду, но спустя несколько часов, когда работоспособность системы восстановили, она успешно обслуживала до 30 тыс. запросов в секунду. Проблема заключалась в слишком быстром нарастании количества запросов.

Корень зла в интерфейсах, считает Кэндиа, и проблемы будут возникать до тех пор, пока существует повторное использование компонентов. Его слова подтверждает тот факт, что 56% ошибок, появляющихся из-за интерфейсных связей в одной из серверных операционных систем IBM, связаны именно с применением ранее написанного кода в новых комбинациях.

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

Здесь уместна аналогия с электрическими сетями. Только благодаря наличию предохранителей, не позволяющих току в электрической цепи превышать допустимое значение, использование электрических приборов стало практичным. Ущерб от пожаров и ненадежности сетей без предохранителей превзошел бы положительный эффект. Подобно электрическим, их программные аналоги должны оберегать модули от входных данных, не соответствующих стандарту. Не важно, что это будет: неправильная последовательность символов, некорректный диапазон числовых значений, превышение объемов данных или слишком высокая скорость поступления запросов – фильтр на входе станет отсекать данные и запросы, способные нарушить логику функционирования защищаемого кода. Не стоит стремиться к невозможному и писать универсальные программы, которым по плечу любые ситуации, – следует обеспечить надежную, контролируемую среду исполнения.

Исследователь предлагает начать реализацию концепции с оснащения «предохранителями» уже существующих программных модулей. В продукты, которые создаются сейчас, изначально можно внести необходимые изменения. Создание дополнительной стандартной инфраструктуры по контролю входных параметров и выходных данных будет иметь смысл только в случае модификации старого кода, переписывать который уже нет резона, подчеркивает ученый.

Джордж Кэндиа собирается революционизировать методологию разработки ПО. Ему уже удалось совершить маленькую революцию – в сознании программистов. Обе его главные концепции отличаются нетрадиционностью подхода, хотя в логике ему нельзя отказать. И самое главное, слова Кэндиа не расходятся с делом – за теорией сразу последовала реализация ее на практике, причем, судя по отчету, успешная. Но в его выкладках еще немало белых пятен, так что есть достаточно аспектов, которые заслуживают более подробного рассмотрения. Впрочем, если вы программист и заинтересованы в том, чтобы ваши продукты «глючили» меньше, загляните на персональную Web-страничку Джорджа: www.stanford.edu/~candea.