Обложка канала

.NET Разработчик. Страница 32

Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин

  • .NET Разработчик

    День 1040. #юмор
  • .NET Разработчик

    День 1039. Асинхронный Обмен Сообщениями. Окончание 1. Основы распределённой архитектуры 2. Перманентные очереди Примеры устойчивых очередей 3. Бэкенд-сервисы 4. Получение результатов 5. Прочее Здесь собраны различные прочие соображения по поводу асинхронного обмена сообщениями. Очереди мёртвых сообщений При проектировании системы вам необходимо решить, как обрабатывать сообщения очереди, которые не могут быть обработаны. Обычно делают какую-то «очередь недоставленных сообщений» для хранения этих «мёртвых» сообщений и настраивают оповещения разработчиков для этой очереди. Многие облачные очереди автоматически сделают это за вас. Только не забудьте настроить оповещения! Управление версиями Сообщения в очереди можно считать своего рода объектами передачи данных (DTO). Они действуют как мост между двумя процессами: HTTP-приложением и бэкенд-обработчиком. Как и остальная система, DTO со временем будут меняться, и к этому лучше быть готовым. Возможно указывать версии в самом имени очереди, либо в самих DTO. Версии вводятся для того, чтобы старый потребитель не мог обрабатывать новые типы сообщении в очереди. Сервисы разных провайдеров Вполне возможно масштабировать, например, функции Azure на основе RabbitMQ или подключить бэкенд-сервис в Docker, размещённый в Google Cloud, к очереди Amazon SQS. Хотя иногда при этом возникают дополнительные расходы, а иногда вам нужно написать плагин, чтобы ваш бэкенд-сервис использовал ваш тип очереди для масштабирования. Готовые решения Существуют готовые комплексные решения для асинхронного обмена сообщениями. Примерами являются Hangfire (.NET) и Delayed Job (Ruby). Их универсальность означает, что их легче настроить, но и то, что они менее гибкие. То, что создали разработчики этого решения, может сильно отличаться от того, что нужно вашему приложению. В частности, вам необходимо изучить: 1) Используется ли перманентная очередь? В противном случае лучше даже не рассматривать это. Всё, что использует Redis в его конфигурации по умолчанию, не должно использоваться. Следует настроить Redis на перманентное хранение в файле только для добавления (Append Only File) с синхронизацией этого файла для каждой команды. Hangfire и Delayed Job используют базу данных для очереди, что нормально (при условии, что у вас уже есть база данных), но не идеально (теперь ваш сервер базы данных должен иметь дело с сообщениями очереди, помимо обычных данных). 2) Как сериализуются сообщения: будет ли сериализация обратно совместима при обновлении библиотеки? До недавнего времени Hangfire не поддерживал скользящие обновления из-за способа сериализации заданий. Приложения должны были полностью отключаться перед развёртыванием обновления Hangfire, а если требовался откат, они также должны были полностью отключаться перед выполнением отката. 3) Как сериализуются задания: как может изменяться код? Например, .NET довольно специфичен в сериализации делегатов методов, и даже добавление параметра (со значением по умолчанию) может вызвать сбой. Любое подобное изменение потребует двух обновлений вместо одного: первое добавит новую перегрузку, а после завершения всех старых заданий можно развернуть второе обновление, чтобы удалить старую перегрузку. 4) Как обрабатываются ошибки? Большинство универсальных решений автоматически повторяют попытку. По умолчанию Hangfire оставляет эти сообщения в состоянии «failed» (и вы должны создать какое-то уведомление об этом), тогда как Delayed Job удаляет их! Просто будьте бдительны. Не добавляйте бездумно готовое решение в свою архитектуру. Хорошо продуманная и правильная реализация асинхронного обмена сообщениями почти всегда является лучшим вариантом. Источник: blog.stephencleary.com/2021/02…ons.html
  • .NET Разработчик

    День 1038. Асинхронный Обмен Сообщениями. Продолжение 1. Основы распределённой архитектуры 2. Перманентные очереди Примеры устойчивых очередей 3. Бэкенд-сервисы 4. Получение результатов Важно отметить, что существует множество сценариев, которые не требуют явной отправки результатов. Например, отправка e-mail. Исходному клиенту не нужно получать уведомление о том, что письмо действительно было отправлено. Другой случай - когда конечный пользователь обновляет страницу. Он рано или поздно увидит результаты. Поэтому первый вопрос, который следует задать: действительно ли получение результатов необходимо? Рассмотрим варианты получения результатов для REST API. Опрос Клиент может опрашивать конечную точку «статуса», пока не будут доступны результаты, а затем получить окончательный результат (или ошибку). К сожалению, вариантов реализации этого шаблона очень много. Единственное принятое соглашение: для вызова, который инициирует асинхронный обмен сообщениями, HTTP-приложение должно поместить сообщение в перманентную очередь, а затем вернуть 202 Accepted. Приложение также должно возвращать информацию, которая позволит клиенту опрашивать статус завершения. Обычно это какой-то URI «статуса». Стандарт требует либо ссылку на монитор статуса, либо примерное время, когда запрос будет выполнен. Это очень нечёткое требование, и именно здесь реализации начинают расходиться. Один из вариантов - вернуть URI статуса в теле 202 Accepted (например, в JSON). Другой вариант - вернуть URI статуса в заголовке ответа Location. Как только клиент получит URI статуса, он может начать периодически вызывать этот URI для получения статуса асинхронной операции. Опять же, реализации расходятся в деталях. Пока операция не завершена, некоторые реализации возвращают 200 OK с каким-то индикатором «незавершённого статуса» в теле. Другие снова возвращают 202 Accepted. Сервер также может дополнительно включать «процент завершения» в тело ответа и/или заголовок Retry-After, если он имеет предполагаемое время завершения (для предотвращения чрезмерно частых вызовов). Если операция завершается с ошибкой, то URI статуса может либо вернуть 200 OK с «ошибкой» в теле ответа, либо вернуть соответствующий код ошибки (4xx/5xx) с дополнительными сведениями об ошибке в теле ответа. В случае успеха URI статуса может возвращать 303 See Other с заголовком Location для ресурса, который был обновлён/изменён через асинхронное сообщение. Либо вернуть 200 OK, 201 Created или 204 No Content, чтобы указать на успешное завершение. Как видите, реализуется асинхронный обмен сообщениями с точки зрения REST API по-разному. Нет никаких стандартов или общепринятого образца. Не тратьте время на поиск «правильного» варианта, просто чётко задокументируйте, какой подход вы используете. И последнее замечание: промежуточные результаты, вроде статуса операции, прогресса или URI обновлённого ресурса не обязательно должны быть долговечными. Их можно хранить в памяти, например в общем кэше. Уведомление В некоторых случаях клиентам необходимо немедленно знать, когда сообщение было обработано. В этом случае можно использовать систему уведомлений, инициированную сервером. В наши дни это почти всегда WebSockets (или SignalR), хотя старые решения, такие как длинный опрос или даже серверные события (SSE), тоже всё ещё существуют. Все эти решения позволяют приложению отправлять сообщение уже подключённому клиенту. Смысл в том, что бэкенд-сервис подключается к HTTP-приложению через какую-то шину (например, через хаб SignalR), а затем отправляет сообщение по этой шине непосредственно в HTTP-приложение, которое уведомляет клиентов. Есть только одно предостережение: шина должна быть готова к масштабированию. Некоторые системы (например, SignalR) по умолчанию не настроены на масштабирование. Окончание следует… Источник: blog.stephencleary.com/2021/01…lts.html
  • Реклама

  • .NET Разработчик

    День 1037. Асинхронный Обмен Сообщениями. Продолжение 1. Основы распределённой архитектуры 2. Перманентные очереди Примеры перманентных очередей 3. Бэкенд-сервисы Назначение фонового сервиса - обработка сообщений очереди. Это другая сторона «трубы». HTTP-приложение - это производитель, помещающий сообщения в очередь, а фоновый сервис - потребитель, извлекающий сообщения из очереди и обрабатывающий их. Обычно рекомендуется, чтобы фоновый сервис был независимым от HTTP-приложения, но это не обязательно. Причины для отдельного сервиса: - Независимое масштабирование: приложение масштабируется на основе HTTP-запросов, а фоновый сервис на основе количества сообщений в очереди. - Отдельный сервис требует использования внешней перманентной очереди. Если сервис и приложением находятся в одном процессе, не очевидно, что очередь в памяти является неподходящим решением. И в будущем команда, сопровождающая систему, может «оптимизировать» реализацию очереди, поместив её в память. - Отдельный сервис полностью состоит из кода внешнего запроса, поэтому необходимо соблюдать особую осторожность, чтобы обеспечить надлежащее завершение работы HTTP-приложения, если сервис является его частью. Идемпотентность Операция является идемпотентной, если её можно применять несколько раз и каждый раз получать один и тот же результат. Другими словами, как только была применена идемпотентная операция, будущие применения этой же операции игнорируются. Это необходимо, т.к. перманентные очереди доставляют сообщения как минимум один раз. Т.е. фоновый сервис может получить одно и то же сообщение более одного раза. В идеале код обработки дубликата должен приводить к пустой операции (noop). Иногда это означает захват дополнительной информации о «состоянии» системы во время постановки сообщения в очередь и включение этого состояния в сообщения. Когда идемпотентность невозможна, можно использовать дедупликацию: явно проверять наличие повторяющихся сообщений в течение разумного периода времени. Примеры бэкенд-сервисов Основные решения - облачные, в частности, Functions as a Service (FaaS). Основные облачные провайдеры имеют встроенную поддержку для объединения своих перманентных очередей с FaaS: Azure Functions, AWS Lambdas или Google Cloud Functions. Поддержка включает в себя логику масштабирования: каждый облачный провайдер будет автоматически масштабировать FaaS-потребителей на основе объёма сообщений в очереди. Для локальных решений естественным подходом является служба Win32 (в Windows) или демон в Linux. Эти фоновые службы работают всё время, пока серверная машина включена, независимо от того, вошел ли пользователь в систему, и не допускают прямого взаимодействия с пользователем. Другое решение - консольное приложение в контейнере Docker, который можно развернуть локально или в облаке. Этот подход имеет лучшую поддержку горизонтального масштабирования. Наименее рекомендуемое решение - бэкенд-сервис как часть HTTP-приложения. Некоторые выбирают этот вариант, несмотря на недостатки. Заметьте, что хост должен быть уведомлён о фоновой работе, иначе она может быть потеряна. Кроме того, любые «вышестоящие» системы, такие как HTTP-прокси, балансировщики нагрузки и сценарии развертывания, могут потребовать изменений, чтобы они также знали о нестандартных правилах завершения работы. Замечание: в ASP.NET Core используйте IHostedService или IHostApplicationLifetime для обнаружения и блокировки завершения работы приложения HTTP. BackgroundService также можно использовать, но имейте в виду, что работа может быть потеряна, если время завершения работы хоста истечёт. В ASP.NET Framework используйте HostingEnvironment.QueueBackgroundWorkItem или IRegisteredObject. Продолжение следует… Источник: blog.stephencleary.com/2021/01…sor.html
  • .NET Разработчик

    В канале t.me/DotnetBackendStudy выкладываются конспекты по различным темам направления .NET backend. Они помогут тебе как изучить что-то новое, так и подтянуть то, что уже забылось. Переходи и подписывайся. Конспекты выкладываются два раза в неделю. #реклама
    .NET backend study

    Образовательный канал для backend .NET разработчиков. Вместе изучаем .NET, SQL, DevOps и немного Web. Сотрудничество: @sterlyukin [email protected]

    Telegram
  • .NET Разработчик

    День 1036. Асинхронный Обмен Сообщениями. Продолжение 1. Основы распределённой архитектуры 2. Перманентные очереди Примеры перманентных очередей Наиболее предпочтительным вариантом будут облачные очереди, когда это возможно, потому что облачный провайдер управляет ими, они очень хорошо масштабируются и дают вам настройки, позволяющие контролировать, насколько параноидально надёжной должна быть ваша очередь. Примерами могут быть Azure Storage Queue, Amazon Simple Queue Service (SQS) и Google Cloud Tasks. Все облачные системы очередей предоставляют перманентные очереди, которые можно масштабировать автоматически. Какими бы прекрасными ни были облачные решения, локальные системы очередей вполне жизнеспособны. Невозможно получить те же возможности масштабирования, что и в облачном решении, но вы можете снизить задержки. Самыми распространёнными локальными перманентными очередями в наши дни являются RabbitMQ и Kafka. Для старых систем Windows обычно использовалась очередь сообщений Microsoft (MSMQ), хотя в наши дни это больше не рекомендуется. Обратите внимание, что некоторые локальные решения для очередей (например, RabbitMQ) не используют постоянные хранилища по умолчанию, поэтому необходима некоторая настройка, чтобы сделать очередь действительно перманентной. Есть и другие решения как для облачных, так и для локальных сетей. База данных как перманентная очередь Ещё одно решение, которое иногда используется, - база данных. Обычно это должна быть база данных, которая гарантирует ACID. Некоторые базы данных NoSql также можно использовать в качестве перманентных очередей, если они действительно имеют перманентную запись. Но имейте в виду, что некоторые базы данных NoSql могут терять записи, и в этом случае они не считаются перманентными очередями. Большинство баз данных, используемых в качестве перманентных очередей, полностью ACID (т.е. транзакционные). Использование базы данных ACID в качестве перманентной очереди позволяет использовать паттерн исходящих сообщений (Outbox). Когда сервис хочет опубликовать сообщение оно считается успешно записанным тогда и только тогда, когда определённая транзакция базы данных завершается успешно и сообщение записывается в базу данных как часть этой транзакции. Сервис не может сообщить об успехе до обновления базы данных, потому что обновление базы данных может завершиться ошибкой. Также сервер не может сообщить об успехе, пока не завершится обновление базы данных, потому что, если возникнет проблема с попаданием сообщения в постоянную очередь, сообщение будет потеряно. Таким образом, используя непосредственно базу данных в качестве постоянной очереди, сервис гарантирует, что сообщение об успехе будет возвращено тогда и только тогда, когда произойдёт обновление базы данных. Паттерн исходящих сообщений получил свое название, потому что обычно существует специальная таблица "outbox", в которой хранятся только опубликованные сообщения. Можно сделать так, чтобы потребитель очереди читал таблицу исходящих сообщений напрямую, но более распространённым решением является использование таблицы исходящих сообщений в качестве временного хранилища для сообщений на пути к другой перманентной очереди - обычно той, которая используется остальной частью приложения (например, облачная очередь или локальная постоянная очередь). В этом случае сервис публикации (или другой отдельный сервис) считывает сообщения из таблицы исходящих сообщений, отправляет их в постоянную очередь, а затем удаляет эти сообщения из таблицы исходящих сообщений. Продолжение следует… Источник: blog.stephencleary.com/2021/01…ues.html
  • .NET Разработчик

    День 1035. Асинхронный Обмен Сообщениями. Продолжение 1. Основы распределённой архитектуры 2. Перманентные очереди Перманентная очередь должна как минимум записывать новый элемент на диск, когда он помещается в очередь. Это минимально приемлемое поведение: сообщения должны сохраняться после отключения сервиса. Этого достаточно для многих (большинства?) приложений. Более перманентной (или более надёжной) будет очередь, которая записывает на несколько дисков. Это позволяет сообщениям также пережить сбой одного диска. Ещё более надёжная очередь использует диски на нескольких серверах, а самые параноидальные очереди записывают на несколько серверов в разных географических точках. Для большинства приложений такой уровень отказоустойчивости не требуется. Но важно отметить, что минимально приемлемая надежность - это запись на диск. Очереди в памяти недостаточно надежны. Проблема с очередями в памяти Что же значит фраза «асинхронные сообщения должны выдерживать отключения сервиса»? Легче всего понять это, обдумывая вопрос: «Когда безопасно отключить HTTP-сервис?» Протокол HTTP используется всеми видами API и веб-сервисов. Однако иногда забывается одна важная деталь: это протокол запроса/ответа. Т.е. на каждый запрос есть ровно один ответ. С точки зрения HTTP-сервиса, поступает запрос, а затем через некоторое время отправляется ответ и этот запрос завершается. Так когда безопасно отключить HTTP-сервис? Самый простой ответ - когда был отправлен ответ на каждый запрос, т.е. когда больше нет невыполненных запросов. Это настолько естественный ответ, что каждый HTTP-сервер подразумевает его по умолчанию. ASP.NET, Node.js, Ruby on Rails… - каждая среда отслеживает, сколько невыполненных запросов у неё есть, и считает себя «безопасной для завершения», когда это число достигает нуля. Это также верно для балансировщиков нагрузки и прокси-серверов. Вот почему внешний запрос опасен: всё это поведение по умолчанию внезапно оказывается неправильным. Служба HTTP сообщает, что завершение работы безопасно, когда это не так (в очереди ещё есть сообщения). Отключения - это нормально Часто разработчики неверно трактуют эту проблему, пытаясь изменить поведение HTTP-серверов на "Безопасно завершать работу только тогда, когда я говорю, что это безопасно". Но тогда придётся также поменять логику всех прокси-серверов и балансировщиков. Даже если это сработает, существует бесконечная проблема обслуживания: теперь ваша ферма серверов обрабатывает отключения совершенно иначе, чем все другие фермы. Таким образом разработчик хочет, чтобы их HTTP-приложение работало «бесконечно». И это серьёзное непонимание принципов работы: на самом деле системы более устойчивы, если серверы не работают бесконечно. Отключения - это нормально, и мы должны принимать отключения как нормальную часть жизни. Один из примеров - обновления. Когда разрабатывается новая версия приложения, она должна заменить старые версии. Обычный способ сделать это - скользящие обновления: для каждого сервера вышестоящий прокси прекращает отправку новых запросов, дожидается, пока у сервиса не останется невыполненных запросов, выключает его, устанавливает обновление, снова запускает и начинает отправку новых запросов. Завершение работы необходимо для выполнения скользящих обновлений. Другими примерами могут быть обновления ОС или плановые перезапуски веб-сервера. Резонный вывод - отключения - это нормально. Все HTTP-приложения должны работать правильно при завершении работы. Следствие: всё ПО, которое предполагает, что оно никогда не завершится, по своей сути содержит ошибки. Наконец, вернёмся к тому, что означает «перманентный». Очереди в памяти не выдерживают отключений. Следовательно, «минимально приемлемая надежность» означает, что очередь сообщений выдерживает отключения, которые являются нормальным и обычным явлением. Продолжение следует… Источник: blog.stephencleary.com/2021/01…ues.html
  • .NET Разработчик

    День 1034. Асинхронный Обмен Сообщениями. Начало 1. Основы распределённой архитектуры Это совсем не новая проблема, но в последние несколько лет она становится все более и более распространённой. Кроме того, эту проблему трудно решить быстро - или даже быстро описать решение. Задача Проблема обычно проявляется в желании досрочно вернуться из HTTP-запроса. Как только запрос был получен, разработчик хочет, чтобы серверный API не дожидался завершения обработки, а немедленно отправил ответ клиенту. Обычно это называют «запустил и забыл». Цель состоит в том, чтобы HTTP-вызов просто запускал рабочий процесс. Затем этот рабочий процесс выполнялся бы на стороне сервера без дополнительных действий со стороны клиентского приложения. Назовём это «внешний запрос», т.к. это код, который выполняется вне основного запроса. Это может быть опасно, поэтому решение более сложное, чем кажется на первый взгляд необходимым. Решение Подходящим решением для внешнего запроса является асинхронный обмен сообщениями. Он состоит из двух частей (с необязательной третьей частью): 1) Перманентная очередь Под «перманентной» подразумевается очередь, которая, по крайней мере, сохраняется на диск при добавлении элемента. Другими словами, сообщения, отправленные в очередь, долговечны. Таким образом, очереди в памяти Queue<T>, BlockingCollection<T> или ChannelWriter<T> не являются «перманентными очередями». 2) Бэкенд-сервис Это независимый сервис, который читает из этой перманентной очереди и обрабатывает элементы в ней (т.е. выполняет длительную операцию). 3) (необязательно) Какой-либо метод получения результатов. Если клиенту необходимо знать результат длительной операции, то это та часть, которая предоставляет этот результат клиенту. Один из распространённых примеров - отправка e-mail. Если API хочет отправить письмо, но не хочет ждать окончания отправки перед возвратом клиенту, то API должен добавить сообщение в перманентную очередь с описанием отправляемого письма, а затем вернуться. Поскольку это перманентная очередь, сообщение очереди сохраняется на диск перед отправкой HTTP-ответа клиенту. Затем отдельный сервис, читающий из этой очереди, извлекает сообщение и фактически отправляет e-mail. Другой распространённый пример - запись в базу данных. Иногда возникают ситуации, когда API знает, что писать в БД, но не хочет заставлять клиента ждать. В этом случае API должен записать информацию в перманентную очередь, а затем выдать ответ клиенту. Затем отдельный бэкенд-сервис, читающий из этой очереди, извлекает информацию и выполняет фактическое обновление БД. Получения результатов часто не требуется. Например, собственно письмо обычно является результатом отправки e-mail, а записи в базе данных в итоге будут отображаться, когда пользователь обновит страницу. Но иногда вам нужно, чтобы клиент был уведомлён о результатах. Это возможно либо с помощью опроса, либо с помощью упреждающего уведомления с использованием технологии обмена сообщениями, такой как WebSockets. Далее мы подробно остановимся на каждой части решения и рассмотрим конкретные подходы. Продолжение следует… Источник: blog.stephencleary.com/2021/01…ure.html

© Telegram-site 2018-2026

Сайт про Telegram каналы(неофициальный) | Почта для связи: [email protected]