понедельник, 14 ноября 2016 г.

Памятка ынтырпрайз кодера

После прочтения “Release It!” захотелось сохранить ряд мыслей по поводу разработки распределенного ынтырпрайз софта. То, о чем нужно думать, что нужно подпилить, о чем нужно не забыть и т.п.

1. Все что может отвалиться, обязательно отвалится

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

2. Падайте как можно раньше (fail fast)

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

Поэтому, если система перегружена, то лучше сразу же отбивать запрос с явным «не могу», а не позволять всем юзерам страдать, обрабатывая все запросы, но очень медленно.

3. Используйте паттерн circuit breaker

Любая очередь на обработку может быть переполнена. Это может быть локальная очередь для записи в лог или поток входящих запросов. Любая очередь должна иметь возможность сказать «горшочек – не вари», тем или иным способом.

Например, BlockingCollection в .NET будет блокировать все запросы по добавлению элементов в, а в случае распределенных систем есть вариант просто отбивать запрос, возвращая статус 429 – Too Many Requests.

В этом плане поможет паттерн Circuit Breaker, который может позволяет даже не обращаться к удаленному сервису, если тот не доступен или перегружен. Идея паттерна в том, что обращение к удаленному сервису (или к части собственной системы) «декорируется» с помощью объекта, который может находиться в одном из двух состояний: «открыт» - защищаемый компонент работает успешно или «закрыт» - защищаемый компонент недоступен или перегружен.

Если circuit breaker находится в состоянии «закрыт», то никаких обращений к защищаемому компоненту не будет, а вместо этого будет сразу же возвращена ошибка. При этом переход в «закрытое» состояние осуществляется после, например, N подряд ошибок, а разблокировка происходит, когда компонент возвращается в рабочее состояние (для этого нужно в закрытом состоянии иногда обращаться к защищаемому компоненту для проверки работоспособности).

4. Кэшируйте вменяемо

В бытность моей работы над AppInsights нашими коллегами было добавлено кэширование. Что хорошо. Плохо было то, что кэширование не было прозрачным. Вместо того, чтобы кэшировать результаты реальных запросов, по старту сервиса кэш прогревался и все запросы шли к нему. В результате, старт сервиса перегружал бэк-енд, а наличие бага в логике синхронизации привело к тому, что кэш для новых пользователей не обновлялся несколько дней.

Любой кэш должен поддерживать стратегию инвалидации, без которой он превращается в один большой memory leak. Это могут быть слабые ссылки, это может быть тайм-аут неактивности.

К тому же, кэш никогда не должен приводить к проблемам корректности (как в приведенном ранее случае). Неработоспособность кэша должна привести к падению производительности и только.

Хорошим и вменяемым примером является паттерн Cache Aside Pattern.

5. Логируйте все удаленные взаимодействия

Есть ряд вещей, которые будут очень полезны при разборе полетов. Например, важно знать, какая часть системы отпала; сколько времени занимает каждый этап взаимодействия (авторизация, отправка запроса, резолв имен и т.п.), и что именно происходит при неудачном запросе к серверу, работает ли кэширование и сколько в кэше записей.

При этом логировать желательно в формате удобном для девелоперов, дев-опсов и автоматизации.

6. Настройте логи правильно

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

Переполнение стека обычно проявляется достаточно просто: приложение крэшится, а вот нехватка памяти и места на системном диске могут проявляться самым необычным образом. Странные ошибки при загрузке сборок, непонятные вещи при сериализации/десериализации (когда сериализатор пытается сохранить сгенерированную сборку на диск) и т.п.

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

7. Сделайте из своей системы серый ящик

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

Существует ряд стандартных и не очень подходов, чтобы сделать сервис более прозрачным.

Телеметрия. Самописная или что-то типа Application Insights позволяет собирать ключевую статистику. Это может быть статистика использования (usage patterns), средняя длительность загрузки/количества запросов или стандартные показатели, типа perf counter-ов винды.

Performance Counters. Любая система собирает сотни счетчиков, начиная от операций ввода-вывода, заканчивая количеством сборок мусора разных поколений. К тому же, стоит добавить свои счетчики для наиболее ключевых показателей. Разные инструменты (типа AppInsights) умеют собирать счетчики, да и винда позволяет увидеть их удаленно (хотя лучше, чтобы смотрели на них дев-опсы, а не девы).

Мониторинг. Тот же AppInsights умеет уведомлять всех и вся при возникновении некоторого события. При этом «событием» может быть превышение показателем некоторой границы (уменьшение места на диске, превышение средней загрузки CPU и т.п.), наличие или отсутствие некоторых вызовов.

Подобные системы не всегда удобны в настройке, но однозначно помогут избежать проблем путем превентивного реагирования.

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

Это далеко не полный список приемов, но что-то, от чего можно отталкиваться.

20 комментариев:

  1. Забавно сравнивать коаны ынтырпрайз софта и клиентского софта (как например наш Скайп). Общесофтовые ценности совпадают конечно, но ключевые разнятся довольно сильно.

    ОтветитьУдалить
    Ответы
    1. Кстати, а можно тут пожаловаться на юзабилити скайпа (преимущественно, чата)? :)

      Удалить
    2. @zloy den: пожаловаться можно. Даже я (как и Юрий) сможем перенаправить критику (желательно конструктивную) команде.

      А можно мне на мыло - seteplia собак мелкософт дот ком.

      Удалить
    3. Порадовало тихое удаление скайпа из винфон10 с последующей заменой его на скайп-превью (которое по названию и не приложение вроде, а так)...

      Удалить
  2. По поводу первого пункта, мне как-то рассказывал препод из политехнического института - как только от устройства отвалится всё то, что плохо спроектировано, останется то, что спроектировано и сделано хорошо!

    И как пример показал домашние тапочки, которых было семь одинаковых пар и у всех одинаково начала отваливаться верхняя войлочная часть =)))

    ОтветитьУдалить
    Ответы
    1. Мне кажется, что к распределенным системам данный тезис не относится. Там иногда что-то отпадает, а потом восстанавливается:)

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

      Удалить
  3. Краткое собрание тезисов текущего HL++2016 ))

    ОтветитьУдалить
  4. Хочу уточнить по поводу паттерна CircuitBreaker. Аналогия взята из электрической цепи. Когда цепь замкнута – всё работает в штатном режиме, запросы идут к внешнему сервису. В случае сбоев CircuitBreaker размыкает цепь (отсюда его название). Поэтому правильнее перевести состояния как раз с точностью до наоборот: "Закрыт" – цепь замкнута, всё ок, "Открыт" – цепь разомкнута.

    ОтветитьУдалить
  5. Если система состоит из нескольких компонент, то в продакшене окажется несовместимый набор.

    ОтветитьУдалить
    Ответы
    1. Да, versioning hell - это еще одна проблема, но, ИМО, по этой теме можно книгу написать, тут тезисно сложно что-то сказать:)

      Удалить
    2. Вот это реально проблема.
      Сюда добавить версию базы, версию данных. и т.п.
      релизы без даунтайма....
      Если вдруг кто знает какие полезные книжки/ссылки почитать на тему как с этим зоопарком бороться - посоветуйте. Буду очень признателен.

      Удалить
    3. В Release It! есть об этом, но совсем чутка. Других источников я не знаю:(

      Удалить
  6. Вот по первому пункту +100500.
    Особенно если это какие-то внешние интеграции с казалось бы серьёзными компаниями. Там будет ад и черви.
    Вплоть до того что придётся городить свою систему поллинга упавших запросов.
    А вообще, это довольно суровый ынтырпрайз описан, обычно всё куда проще и скромнее.

    ОтветитьУдалить
    Ответы
    1. Ну, часть из этих вещей будут присутствовать даже в простом ынтырпрайзе, а часть, да, в более суровом.

      Бывает даже связка из пары wcf service - wcf client доставляют столько неприятностей, что без правильного логирования ничего не разобрать. Да и восстановления состояния в этом случае может быть не таким и простым делом, если каждый из компонентов должен работать 24/7.

      Удалить
  7. Кстати, Сергей, всё хотел задать вопрос не по теме.
    А как всё же заставить себя обучаться методично?
    Потому у меня что классическая ситуация:
    Начал изучать что-то одно (допустим, тонкости сишарпа).
    Попалась какая-то интересная статья по функциональному программированию, переключился на неё.
    Потом снова попробовал вернуться к сишарпу, но тут работы навалилось и сил больше никаких вообще нет ни на что.
    Через месяц более-менее отошел, но решил посмотреть что-нибудь на тему NoSQL и с чем его едят.
    И т.д. и т.п.
    Т.е. прогресс обучения вроде бы идёт, но вектор какой-то такой размазанный, что аж грустно. За это время можно было бы стать совсем уж крутым специалистом в какой-нибудь области (тот же хаскелл выучить не на уровне "кое-как могу что-то написать", а на уровне специалиста). А в итоге получается то что получается

    ОтветитьУдалить
    Ответы
    1. У меня такое тоже происходит, но я стараюсь бороться способом, похожим на управление аджайл проектом: не изменять требования в процессе итерации:).

      Это значит, что если я сейчас (в течении этого тайм-слота) читаю про проджект менеджмент, то все другое я просто складирую в бэк-лог. Если что-то проскакивает простое и интересное, то я отвлекаюсь с риском перепрыгнуть на другую тему, но это происходит не так часто.

      Поэтому у меня есть one note страничка, где в некотором хаотическом виде расбросаны материалы по разным темам, типа performance, no sql, etc, к которым я надеюсь вернуться позже.

      Я не то, чтобы часто к ним возращаюсь, но этот подход помогает не отвлекаться слишком сильно.

      Но вообще, у меня это тоже происходит:)

      Плюс план для блоггинга и план по чтению книг на год немного помогают держаться в фокусе.

      Удалить
  8. > лучше не писать на системный диск

    В общем случае у приложения просто нет прав писать куда-то, кроме пользовательской папки и темпа (да, на доступ в системный журнал тоже может не быть прав). Это кстати тоже к вопросам разработки enterprise софта. Почему-то считается, что у сервиса обязательно будут права на все, что захочется - от записи в AD до запуска произвольных процессов. В реальности же админу клиента может не быть никакого дела до того, что ваш сервис хочет жить красиво, будет применена дефолтная политика безопасности, и дальше могут начаться интересные спецэффекты :)

    ОтветитьУдалить
    Ответы
    1. Саня, с этим я согласен, но в этом случае нужно обязательно ограничить засирание системного диска определенным лимитом. Без этого будет плохо.

      И, кстати, желательно дать юзеру/админу/дев-опсу возможность перенаправить положение логов, если он знает, где есть доступ для записи.

      Удалить
  9. Я бы добавил еще один пункт - дублирование внешних сервисов.
    Например возьмем платежную систему.

    С технологической точки зрения:
    Если один сервис упал - переключаемся на другой, и клиенты по-прежнему могут оплачивать заказы.

    С точки зрения бизнеса:
    1. Если один из сервисов в течение долгого времени не может решить проблему из-за которой теряются деньги и клиенты - отключаем его. И дальше происходит чудо. Сервис который в течение недели не мог решить проблему, решает ее за 2 часа.
    2. Возможность гибко регулировать посылку трафика (и денег) на внешний сервис помогает выбивать более выгодные условия договора (снижать комиссию платежной системы).

    ОтветитьУдалить