Надежность

При разработке крупной системы рано или поздно встает вопрос её надежности. Надежности во всех смыслах этого понятия. Некоторые программы изначально создаются с большой степенью надежности, в других начинают реализовывать что-то после того, как клиенты теряют важные данные или терпение и уходят, а в большинстве программ вообще нет средств обеспечения надежности работы.
В этой статье я опишу, что такое надежность программ, зачем и кому она нужна, и как её можно реализовать, например, на C++ для Windows.

Итак, что же такое “надежность” и каким приложениям она нужна?
Большинство людей воспринимает надежность, как синоним “времени бесперебойной работы до первого глюка” - есть такая метрика. Для разных систем требования к этой метрике разные, например, если игра работает 5-6 часов в среднем без падений, то обычно этого уже более чем достаточно для того, чтобы признать ее качественной. Если же медицинский прибор перезагружается раз в неделю - это уже может быть критичным. Ну, а для ракеты, летящей к Юпитеру, даже 1 глюк за 2 года критичен.
Все мы в принципе умеем достигать требуемого уровня качества программы в виде этой метрики - для игр это сделать проще, для ракет сложнее, но все это делают и никакого таинства тут нет.

Но в погоне за метрикой многие забывают один ключевой момент - сколько бы часов ваша программа не наработала бесперебойно, в итоге она всё же заглючит. Упадет или зависнет, или испортит данные, или начнет делать что-то неожиданное и т.п. Нет исключений из этого правила. Глючат и спутники, и марсоходы, хотя и пишутся по супер дорогим и строгим стандартам НАСА.
И настоящая система “надежности” включается именно в этот момент - в момент возникновения нештатной ситуации. Все, что было до этого - это работа в штатном режиме, который вы можете контролировать, и которым вы можете управлять. А все, что просходит после глюка - это область чистого искусства, когда вы должны предугадать и среагировать на что-то, о чем вы не имеете никакого понятия (ведь если бы имели - это было бы уже исправлено, так?).
Если вы достигнете хорошей метрики “часов до первого глюка”, но не создадите никакой системы восстановления после того, как глюк случился - клиенты вас проклянут!



Итак, спрошу опять - что же такое “надежность”?
“Надежность” - это умение программы самовостанавливаться и восстанавливать утерянные данные после исключительных ситуаций.
Всё просто. Как бы долго программа не работала без падений - это не стоит и копейки, если в случае падения уничтожается всё, что было создано за несколько часов или, хуже - дней.
Вспомните злополучный пакет MS Office. Тот же MS Word или Outlook. Сложно найти более сложную и глючную программу, но кого это волнует? Word падает у меня на машине несколько раз за день, но каждый раз он автоматически рестартует и восстанавливает то, что у меня было в документе перед падением! Теперь даже Ctrl+S жать постоянно не надо! Это и есть истинная надежность, даже если Word не может проработать и пары часов без падения. И то же самое касается всех программ в пакете MS Office - Microsoft вложился в повышение надежности Office и вложился именно в “правильную надежность”, а не в “число часов наработки на отказ”. К тому же у них многие проблемы создаются сторонними плагинами или даже сторонними приложениями типа антивирусов, так что они и наработку на отказ-то контролировать до конца не могут.


Я уже писал, что может быть повреждено в программе в случае падения - это данные и алгоритмы работы.
С данными все понятно, а как могут измениться алгоритмы работы, если программы уже нет? Если программа состоит из одного выполняемого модуля, то тут проблема только одна - она исчезнет и пользователь будет разочарован. Представьте, что MS Word не показывал бы окно с галочкой “Перезапустить”, а просто бы исчезал - это был бы негативный опыт.
Алгоритмы работы же могут измениться (и изменятся, будьте уверены!), если у вас программа содержит несколько исполняемых модулей или хотя бы потоков. Если один из потоков завис или вылетел, но приложение еще не закрылось (например, работает Windows error reporting), то остальные потоки продолжают работать и на 100% уверенно могу сказать, что никто и никогда не тестировал их в этом режиме. Они будут предполагать, что зависший поток работает и полагаться на него и тем самым работа всех алгоритмов изменится - что-то еще зависнет, где-то сработают неожиданные таймауты, а где-то могут и испортиться пользовательские данные.
Если же у вас несколько запущенных параллельно процессов общаются друг с другом (классический вариант - это когда для улучшения безопасности, один процесс работает в user аккаунте, а второй в system или admin), то в случае падения одного из них ситуация запутывается еще сильнее. Невозможно правильно реализовать и протестировать все комбинации, когда в любой из моментов одно из запушенных приложений вдруг исчезает.


Какие же есть способы восстановления после сбоя?
С проблемой повреждения данных можно бороться по-разному. Если поставить себе задачу, что больше 5 минут работы не должно быть потеряно, то сохранение раз в 5 минут состояния программы во временный файл будет достаточно. В случае падения можно из него восстановить данные.
Если вообще нельзя терять данные, то можно сохранять полное состояние по-прежнему раз в 5 минут, а в промежутках сохранять небольшое инкрементальное состояние, так что по одному большому файлу и набору инкрементальных можно будет восстановить все данные.
Если же вам нельзя сохранять постоянно состояние или сохранение занимает слишком много времени (а 1 секунда для некоторых приложений, например, для игр - это уже много), то поможет простой трюк:
Поставьте Unhandled exception filter (SetUnhandledExceptionFilter) и ловите момент возникновения ошибки. Не пытайтесь восстановиться или проигнорировать ошибку - уже что-то пошло не так и вы не знаете что именно, так что дайте программе упасть. Но перед этим вызовите сохранения ее состояния.
Тут в вас должны проснуться все ваши теоретические знания и закричать “Но ведь так нельзя! Состояние программы неопределено в этот момент!”. Да, неопределено, теория права. Но практика показывает, что в 99.9% случаев, ошибки происходят в алгоритмах, а не содержатся в данных. Так что сохранение данных в случае падения обычно завершается успешно.
Например, все, кто играет в игры, сталкивается с падениями, когда ты вдруг осознаешь, что не сохранялся час или два. И жуть как не хочется играть после этого. А ведь если бы программист игры просто вызвал SetUnhandledExceptionFilter и вызвал бы save для игры в случае падения да еще и игру бы перезапустил автоматически - 99% игроков бы на всех форумах тащились от этой фичи даже если игра бы падала раз в час.
Хочу обратить внимание, что SetUnhandledExceptionFilter практически не работает под Vista и Win7. Там всегда падение перехватывает Error Reporting. Но в WinAPI добавился новый API в этих системах - Application Recovery and Restart Functions. Метод RegisterApplicationRecoveryCallback оттуда - это практически полный аналог SetUnhandledExceptionFilter и всегда вызывается. Используйте и SetUnhandledExceptionFilter и RegisterApplicationRecoveryCallback для надежного восстановления на всех версиях Windows.


Второй важной задачей при сбое является автоматический перезапуск приложения.
Да, не всегда это нужно. Если приложение содержит интерфейс и постоянно взаимодействует с пользователем, то можно спросить о рестарте у него. Как это часто делают игры или тот же пакет MS Office. Они показывают окно с ошибкой и галочкой “Перезапустить”.
Если же ваше приложение работает в фоне, то вы даже и не захотите, чтобы пользователь увидел о проблеме. Если вы сможете незаметно для него перезапуститься и при этом не потерять никаких данных - это идеальный вариант.
Обычно невозможно перезапуститься из калбэка, который вызывается в случае падения (котогрый вы зарегистрировали через SetUnhandledExceptionFilter или RegisterApplicationRecoveryCallback). Если вы запускаете новую копию приложения в этот момент, то столкнетесь с ситуацией, что у вас некоторое время будет запущено 2 одинаковых приложения. А значит начнутся проблемы с доступом к файлам, именованных хэндлам и т.п. И помните - остальные потоки, кроме зависшего, могут продолжать работать, так что обеспечить надежную работу в этом режиме будет сложно.
Так что лучший способ перезапуска изнутри recovery callback - это запуск специального приложения, которое будет просто ждать завершения вашего приложения и потом запускать его опять. Написать и отладить такое специальное приложение - это дело нескольких часов и все проблемы с перезапуском решены. Если будете его писать, то советую ждать завершения приложения не по имени, а по Process ID, иначе будут проблемы с похожими именами и множественными залогиненными пользователями. Также предусмотрите возможность задания командной строки для запускаемого приложения.


Итак, главная проблема в обеспечении надежности приложения - это возможность автоматического восстановления. Сохраняйте состояние и автоматически перезапускайтесь. Не пытайтесь восстановиться без перезапуска, если произошла критическая ошибка - гораздо надежнее перезапуститься и загрузить сохраненное состояние.
Вы можете надстроить еще кучу небольших улучшений на эту систему, вроде автоматического создания бэкап файлов при каждом открытии файла или отсылке важных данных в Cloud и т.п - этим вы позволите пользователю восстановиться даже сработал этот 0.1% и ваша система сохранения состояния дала сбой.
Не так даже важно, в 99% случаев вы сможете восстановиться или в 99.9%: Главное - чтобы у вас такая система самовосстановления была.



Похожие записи:
Reproduce first debugging (отладка через повторение)
Software Engineering is Dead?!
Исключения и goto
Application verifier - простая бесплатная мощь

14 комментариев к Надежность

  • Pilgrim

    Спасибо за информацию. Я, например, использую SetUnhandledExceptionFilter, но не знал, что на Vista/Win7 он не работает и надо использовать новый API.

  • Юра

    У меня была ситуация с аутлуком, когда я его запускаю, он долго думает, потом падает, предлагает себя перезапустить, и опять думает-падает и т.п. Как быть пользователю в таком случае?

    И опять с тем же аутлуком, я запускаю его, он говорит, что файл почты неправильный или отсутствует. И падает\перезапускается. Что мне, пользователю, сделать в таком случае?

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

    • Согласен.
      Как раз ваш вариант доказывает, что в Outlook надежности было уделено недостаточно внимания - не предусмотрена ситуация с повреждением файла почты.
      А могли бы делать его бэкап копию раз в неделю, например, и предлагать восстановиться из бэкапа если что-то пошло не так - всяко лучше потерять изменения за пару дней, чем за несколько лет работы.

  • flexcreator

    “Хочу обратить внимание, что SetUnhandledExceptionFilter практически не работает под Vista и Win7. Там всегда падение перехватывает Error Reporting. Но в WinAPI добавился новый API в этих системах - Application Recovery and Restart Functions.”

    Огромное спасибо! На днях планировал прикрутить SetUnhandledExceptionFilter, но на стационарном у меня Vista, а ноутбуке - 7. Я бы долго разбирался, почему ничего не работает :-D

  • Спасибо, полезные мысли навеяли

  • Классная статья. Особенно интересно для необслуживаемых или встраиваемых систем, хотя там традиционно виндовс не используется.

  • Хм. половина комментария про дядю Диму куда-то исчезла.

    • Хм. половина комментария про дядю Диму куда-то исчезла.

      Это я извиняюсь за свой блог - комментарии иногда режутся, если через OpenID их постить и они начинаются с большой буквы И. Какой-то баг в плагине OpenID

  • dDIMA

    Класс, спасибо, Олег.
    Подписываюсь под каждым словом. Но кроме Exception Filters я бы еще добавил, что надежность программы очень хорошо повышается при стековом подходе к разработке. Я у себя в ЖЖ приводил такие примеры, и хотя они касались оперативной памяти, они легко масштабируются и на этот уровень.
    Довольно много операций, которые выполняет пользователь, по своей структуре являются стековыми. Например, Document.Print или Document.Save или Tools.Options. После выполнения этих операций система должна вернуться в исходное состояние. В этом разрезе не важно, чем именно окончился Print - успехом, неуспехом, ошибкой и т.п. - система должна вернуться в исходное состояние. Конечно, если это именно exception уровня ОС - тут ничего не поделаешь. А вот остальные ошибки, какими бы невероятными они не были (включая и исключения C++ и неожиданные коды возврата API и т.п.), должны быть локализованы, обработаны и возвращены в “ядро” приложения точно также, как если бы данная операция завершилась успешно.
    Ну и все же надежность в смысле бесперебойной работы - тоже важна. Если бы ворд имел супер систему рестарта, но падал каждые 5 минут - это тоже плохо :).

    • Стэковый подход к функциональности очень хорош… но в теории.
      А на практике как его реализовать? После вызова каждой функции-фичи проверять состояние системы и то, что оно не изменилось? Может быть сложно и уж точно невозможно, если в системе используется много потоков или процессов, а задача не мгновенная, а протяженная во времени.
      Для простых же операций - да, это идеальное было бы решение. Или для тех ситуаций, когда состояние программы можно описать числом или строкой - чтобы можно было отслеживать, что состояние после вызова не изменилось.

      Даже для игр такую строку или число будет непросто сделать, хотя и возможно - я примерно такое делал для отладки синхронизации в сетевой игре. На каждом клиенте каждый цикл создавалась строка состояния клиента, куда входили все позиции юнитов и счетчики и имена т.п. и потом они сравнивались на след. цикле сетевом. Если эту систему развить, то можно было бы и после вызова некоторых функций проверять, что состояние не изменилось…

  • Так-то оно так, да не совсем.

    1 Если вы сделали ошибку в программе, где гарантия того, что вы не сделаете ошибку в обработчике ошибки ошибки в программе?

    2 Если программа упала, то перезапускать её может быть уже никому не интересно. Представьте пришли люди на представление, заплатили денег. Тут посреди представления падает софт. Его, конечно, перезапустят, но пока это сделают, уйдет много времени, из-за этого съедет всё расписание и т.п. Это очень плохо.

    3 Здесь прозвучала очень здравая мысль: если программа упадет, хорошо бы, чтобы разрушения от неё были не очень серьезными: конфиги не портились, данные не терялись — это разумно. Надо ли писать в Unhandled exception filter что-то более сложное, чем запись минидампа с просьбой отослать его разработчикам… сложный вопрос. Я думаю для разных программы по-разному.

  • Urich

    SetUnhandledExceptionFilter в Win7 работает, если отключить UAC, или что то еще.:) Короче добица его правильной работы можно. Но проблемы с ней испытал когда запустил свой сервис под 2008 сервером.

  • Интернет-корпорация Google и NORAD Северной Америки) начали отслеживать перемещения Санты-Клауса в канун Рождества.

Ответить

 

 

 

Вы можете использовать эти HTML тэги

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>