Автор: Sergey Teplyakov

После прочтения 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 и т.п.), наличие или отсутствие некоторых вызовов.

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

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

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

Помогла статья? Оцените её!
0 из 5. Общее количество голосов - 0
 

You have no rights to post comments

Дмитрий Крикунов

Публикую статьи, обучающие курсы и новости по программированию: алгоритмам, языкам (С++, Java), параллельному программированию, паттернам и библиотекам (Qt, boost).