.NET Разработчик
6.54K subscribers
442 photos
3 videos
14 files
2.12K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1547. #Microservices
12 Способов Улучшить Монолит Перед Переходом на Микросервисы. Окончание
Начало
Продолжение

10. Сделайте монолит модульным
Если ваш монолит представляет собой запутанный клубок кода, вы вполне можете получить запутанный клубок распределённого кода после завершения миграции. Подобно уборке дома перед капитальным ремонтом, модульность монолита является необходимым подготовительным этапом.
Модульный монолит — это паттерн разработки ПО, представляющий собой вертикально расположенные модули, независимые и взаимозаменяемые. Противоположностью является классический N-уровневый монолит, в коде которого слишком много зависимостей (иногда циклических), что затрудняет внесение изменений.
Модульный монолит — ступенька к микросервисам. Модули могут обмениваться данными только через общедоступные API и по умолчанию всё приватно. В результате код менее переплетён, отношения легко идентифицировать, а зависимости чётко очерчены.
Два паттерна могут помочь вам провести рефакторинг монолита:
1) Душитель
Проводится рефакторинг монолита от края к центру, постепенно переписывая отдельные функции, пока монолит не будет полностью переделан.
Подробнее о паттерне «Душитель».
2) Слой защиты от повреждений
Вы обнаружите, что в некоторых случаях изменения в одном модуле распространяются на другие по мере рефакторинга монолита. Чтобы бороться с этим, вы можете создать слой перевода между быстро меняющимися модулями. Этот слой защиты предотвращает влияние изменений в одном модуле на остальные.
Подробнее о паттерне «Слой защиты от повреждений».

11. Разделите данные
Сила микросервисов в возможности развёртывать любой микросервис в любое время практически без координации с другими микросервисами. Вот почему следует любой ценой избегать связывания данных, поскольку оно создаёт зависимости между сервисами. Каждый микросервис должен иметь отдельную и независимую базу данных.
Может быть шокирующим осознание того, что вам нужно денормализировать общую базу данных монолита на (часто избыточные) меньшие базы данных. Но локальность данных — это то, что в итоге позволит микросервисам работать автономно.
После разделения нужно будет установить механизмы для синхронизации старых и новых данных во время перехода. Вы можете, например, настроить сервис зеркалирования данных или изменить код, чтобы транзакции записывались в оба набора баз данных.

12. Улучшите наблюдение
Новая система должна быть быстрее, производительнее и масштабируемее, чем старая. Иначе зачем возиться с микросервисами? Нужен базовый уровень для сравнения старого с новым. Перед началом миграции убедитесь, что у вас есть хорошие метрики и журналы. Может быть хорошей идеей установить какую-нибудь централизованную службу ведения журналов и мониторинга, так как это ключевой компонент для наблюдения за любым микросервисным приложением.

Итого
Путь к микросервисам никогда не бывает лёгким. Но я надеюсь, что с помощью этих советов вы сможете сэкономить время и нервы.
Не забывайте выполнять итерации небольшими шагами, использовать CI/CD, чтобы гарантировать, что монолит тестируется на регрессии, и хранить всё в одном репозитории, чтобы вы всегда могли отмотать назад, если что-то пойдет не так.

Источник: https://semaphoreci.com/blog/monolith-microservices
👍9👎1
День 1700. #ЗаметкиНаПолях #Microservices
Оркестрация Или Хореография. Начало
Работа с распределёнными системами одновременно интересна и сложна. Одной из задач является разработка эффективной коммуникации между службами. Больше централизации или меньше централизации? Больше связи или меньше связи? Больше контроля или меньше контроля?

Внутри монолитной системы общение происходит посредством прямых вызовов методов. Это простой подход, который хорошо работает, когда все компоненты находятся в одном процессе. Однако это не работает с микросервисами.

Оркестрация – коммуникация через команды
Это централизованный подход к взаимодействию микросервисов. Один из сервисов берёт на себя роль оркестратора и координирует взаимодействие между сервисами. Оркестрация использует взаимодействие, управляемое командами. Команда сообщает цель действия. Отправитель хочет, чтобы что-то произошло, и получателю не обязательно знать, кто отправил команду.

Примером оркестрации может быть паттерн Saga, реализованный с помощью RabbitMQ.

Преимущества:
- Простота
- Централизованность
- Простота мониторинга и устранения неполадок

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

Недостатки:
- Тесная связанность
- Единая точка отказа
- Сложности добавления, удаления или замены микросервисов.

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

Окончание следует…

Источник:
https://www.milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking
👍17
День 1701. #ЗаметкиНаПолях #Microservices
Оркестрация Или Хореография. Окончание
Начало

Хореография — коммуникация через события
Это децентрализованный подход к коммуникации. Хореография использует взаимодействие, управляемое событиями. Событие – это то, что произошло в прошлом и является фактом. Отправитель не знает, кто будет обрабатывать событие и что произойдёт после его обработки.

Преимущества:
- Слабая связанность
- Простота обслуживания
- Децентрализованное управление
- Асинхронная связь

Хореография позволяет микросервисам быть слабо связанными, что означает, что они могут работать независимо и асинхронно. Это делает систему более масштабируемой и отказоустойчивой. Сбой одного микросервиса не обязательно повлияет на остальные.

Недостатки:
- Сложность
- Усложнён мониторинг и устранение неполадок
- Сложнее реализовать и поддерживать, чем оркестрацию.

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

Итого
Оркестрация определяет последовательность шагов, которым должен следовать каждый микросервис. Это отлично подходит для выявления и устранения сложных взаимозависимостей сервисов. Ещё одним преимуществом является то, что бизнес-логикой можно управлять и отслеживать её в одном месте.

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

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

Источник: https://www.milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking
👍9
День 2196. #ЗаметкиНаПолях #Microservices
Отказоустойчивые HTTP-запросы в .NET. Начало
Не всегда всё идет по плану: запросы по сети рандомно завершаются неудачей, серверы приложений перегружаются, и возникают неожиданные ошибки. Отказоустойчивые приложения могут восстанавливаться после временных сбоев и продолжать функционировать. Устойчивость достигается путём проектирования приложений, которые могут корректно обрабатывать сбои и быстро восстанавливаться. Рассмотрим инструменты и методы, которые есть в .NET для создания отказоустойчивых систем.

Зачем?
Отправка HTTP-запросов — распространённый подход к связи между удалёнными сервисами. Но они подвержены сбоям из-за проблем с сетью или сервером. Эти сбои могут нарушить доступность сервисов, особенно по мере увеличения зависимостей и риска каскадных сбоев. Вот несколько стратегий повышения устойчивости:
- Повторные попытки - повторить запрос, который завершился неудачей из-за временной ошибки.
- Тайм-ауты - отмена запросов, которые превышают указанный лимит времени.
- Хэджирование и откаты - альтернативные действия или результаты для неудавшихся операций.
- Аварийное отключение - временное прекращение связи с недоступными сервисами.

Эти стратегии можно использовать по отдельности или в сочетании для оптимальной устойчивости HTTP-запросов.

Конвейеры устойчивости
Начиная с .NET 8 интеграция отказоустойчивости в приложения стала намного проще. Можно использовать Microsoft.Extensions.Resilience и Microsoft.Extensions.Http.Resilience, которые построены на основе Polly. Polly — библиотека отказоустойчивости и обработки временных сбоев в .NET. Она позволяет определять стратегии обеспечения устойчивости, описанные выше. Polly получила новый API в последней версии (V8), который был реализован в сотрудничестве с Microsoft. Если вы ранее использовали Microsoft.Extensions.Http.Polly, теперь рекомендуется использовать следующие пакеты:
Install-Package Microsoft.Extensions.Resilience
Install-Package Microsoft.Extensions.Http.Resilience


Cначала создадим конвейер, состоящий из стратегий отказоустойчивости. Каждая стратегия, которую мы настраиваем как часть конвейера, будет выполняться в порядке конфигурации. Порядок важен! Создадим конвейер с помощью построителя:
ResiliencePipeline pipeline = 
new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder()
.Handle<ConflictException>(),
Delay = TimeSpan.FromSeconds(1),
MaxRetryAttempts = 2,
BackoffType = DelayBackoffType.Exponential,
UseJitter = true
})
.AddTimeout(new TimeoutStrategyOptions
{
Timeout = TimeSpan.FromSeconds(10)
})
.Build();

await pipeline.ExecuteAsync(
async ct => await httpClient
.GetAsync("https://google.com", ct),
cancellationToken);

Вот что мы добавляем в конвейер отказоустойчивости:
- AddRetry — настраивает стратегию повторных попыток, которую мы можем дополнительно настроить, передав экземпляр RetryStrategyOptions. Здесь вы предоставляем предикат для свойства ShouldHandle, чтобы определить, какие исключения (ConflictException) должна обрабатывать стратегия. Также мы указываем максимальное количество попыток повтора и настраиваем время ожидания до следующего повтора. В данном случае оно будет экспоненциально расти (DelayBackoffType.Exponential) и в разных случаях будет немного варьироваться (UseJitter). См. подробнее про настройки стратегии повтора.
- AddTimeout — настраивает стратегию тайм-аута, которая выдаст TimeoutRejectedException, если делегат не завершится до истечения времени ожидания. Мы можем передать нужное ожидания в экземпляр TimeoutStrategyOptions. Время ожидания по умолчанию - 30 секунд.

В конце мы строим конвейер, и используем настроенный экземпляр для HTTP-запроса в методе ExecuteAsync.

Продолжение следует…

Источник:
https://www.milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet
👍31
День 2197. #ЗаметкиНаПолях #Microservices
Отказоустойчивые HTTP-запросы в .NET. Продолжение

Начало

Внедрение зависимостей
.NET 8 представляет новый метод расширения AddResiliencePipeline для интерфейса IServiceCollection, который позволяет регистрировать конвейеры отказоустойчивости. Каждый конвейер должен иметь уникальный ключ, который мы можем использовать для разрешения соответствующего экземпляра конвейера:
services.AddResiliencePipeline("retry", builder =>
{
builder.AddRetry(new RetryStrategyOptions
{
Delay = TimeSpan.FromSeconds(1),
MaxRetryAttempts = 2,
BackoffType = DelayBackoffType.Exponential,
UseJitter = true
});
});


Также можно указать обобщённые аргументы, чтобы настроить типизированный конвейер, используя ResiliencePipelineBuilder<TResult>. Так мы можем получить доступ к стратегиям хеджирования и отката.

В следующем примере мы настраиваем стратегию отката, вызывая AddFallback. Это позволяет предоставить «запасное» значение, которое мы можем вернуть в случае сбоя. Оно может быть статическим значением или поступать из другого HTTP-запроса или БД.
services.AddResiliencePipeline<string, GitHubUser?>(
"gh-fallback", builder =>
{
builder.AddFallback(
new FallbackStrategyOptions<GitHubUser?>
{
FallbackAction = _ =>
Outcome.FromResultAsValueTask<GitHubUser?>(
GitHubUser.Empty
)
});
});


Чтобы использовать конвейеры из DI-контейнера, можно использовать метод GetPipeline класса ResiliencePipelineProvider, передав ему ключ:
app.MapGet("users", async (
HttpClient httpClient,
ResiliencePipelineProvider<string> plProv) =>
{
var pipeline =
plProv.GetPipeline<GitHubUser?>("gh-fallback");

var user = await pipeline.ExecuteAsync(async token =>
await httpClient.GetAsync("api/users", token),
cancellationToken);
});


Стратегии отказоустойчивости и Polly
Стратегии устойчивости являются основным компонентом Polly. Они предназначены для запуска пользовательских обратных вызовов, одновременно добавляя отказоустойчивость. Мы не можем запускать стратегии напрямую, только через конвейер. Polly делит стратегии на:
- Реактивные - обрабатывают определённые исключения или результаты.
- Проактивные - решают прервать или отменить выполнение обратных вызовов с помощью стратегий ограничения скорости или тайм-аута.

Polly имеет следующие встроенные стратегии отказоустойчивости:
- Повтор - классический подход «попробуйте ещё раз». Отлично подходит для временных сетевых сбоев.
- Аварийное отключение - предотвращает нагрузку на отказавшую систему. Если ошибки накапливаются, временное отключение даёт системе время на восстановление.
- Откат - обеспечивает безопасный ответ по умолчанию, если основной вызов не удаётся.
- Хеджирование - выполняет несколько запросов одновременно, принимая первый успешный ответ. Полезно, если в системе есть несколько способов обработки чего-либо.
- Тайм-аут - предотвращает вечное зависание запросов, завершая их, если превышено время ожидания.
- Ограничитель скорости - ограничивает исходящие запросы, чтобы предотвратить перегрузку внешних сервисов.

Окончание следует…

Источник:
https://www.milanjovanovic.tech/blog/building-resilient-cloud-applications-with-dotnet
👍15
День 2198. #ЗаметкиНаПолях #Microservices
Отказоустойчивые HTTP-запросы в .NET. Окончание

Начало
Продолжение

Отказоустойчивость HTTP-клиентов
Библиотека Microsoft.Extensions.Http.Resilience поставляется с готовыми к использованию конвейерами отказоустойчивости для отправки HTTP-запросов. Мы можем добавить отказоустойчивость к исходящим запросам HttpClient с помощью метода AddStandardResilienceHandler, в том числе настроив его по умолчанию для всех HTTP-клиентов приложения:
builder.Services
.AddHttpClient()
.ConfigureHttpClientDefaults(
http => http.AddStandardResilienceHandler(o =>
{
o.Retry.Delay = TimeSpan.FromSeconds(1);

}));

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

Проблема в том, что, если вам потребуется другая стратегия для какого-то специфического сценария, она не будет применена, а будет использована стратегия по умолчанию. Это большое упущение команды .NET, и они о нём в курсе. Над предложением по исправлению этой проблемы уже работают (проследить можно здесь). А пока есть решение вручную.

Создадим метод расширения, который очищает все обработчики из конвейера отказоустойчивости. Это позволит удалить обработчики по умолчанию и добавить свои:
public static class ResilienceExtensions
{
public static IHttpClientBuilder
RemoveAllResilienceHandlers(
this IHttpClientBuilder builder)
{
builder.ConfigureAdditionalHttpMessageHandlers(
static (handlers, _) =>
{
for (int i = handlers.Count - 1; i >= 0; i--)
{
if (handlers[i] is ResilienceHandler)
{
handlers.RemoveAt(i);
}
}
});

return builder;
}
}

Теперь его можно использовать, чтобы удалить стандартную стратегию отказоустойчивости и добавить другую в случаях, когда стандартная не подходит:
builder.Services
.AddHttpClient("github")
.ConfigureHttpClient(cl =>
{
cl.BaseAddress = new Uri("https://api.github.com");
})
.RemoveAllResilienceHandlers()
.AddResilienceHandler("custom", p =>
{
// Настраиваем другую политику
// для Http-клиента GitHub…
});


Итого
Отказоустойчивость — основной принцип создания надёжных программных систем. Имея в нашем распоряжении такие мощные инструменты, как Microsoft.Extensions.Resilience и Polly, мы можем использовать их для проектирования систем, которые грамотно обрабатывают любые временные сбои.

Источник: https://www.milanjovanovic.tech/blog/overriding-default-http-resilience-handlers-in-dotnet
👍18
День 2433. #SystemDesign101 #Microservices
Как Выглядит Типичная Микросервисная Архитектура?


Балансировщик нагрузки: устройство или приложение, которое распределяет сетевой или прикладной трафик между несколькими серверами.

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

API-шлюз: обрабатывает входящие запросы и направляет их соответствующим сервисам. Он взаимодействует с провайдером идентификации и выполняет обнаружение сервисов. См. подробнее.

Провайдер идентификации: отвечает за аутентификацию и авторизацию пользователей.

Регистрация и обнаружение сервисов (Service Registry и Service Discovery): Service Registry — это база данных, которая хранит информацию о сервисах и их экземплярах, а Service Discovery — это механизм, использующий этот реестр для автоматического обнаружения, регистрации и отслеживания доступности сервисов в распределенной системе, что особенно важно для микросервисных архитектур, где сервисы могут динамически масштабироваться. API-шлюз ищет соответствующие сервисы в этом компоненте для взаимодействия с ними.

Менеджмент: этот компонент отвечает за мониторинг сервисов.

Микросервисы: микросервисы разрабатываются и развёртываются в разных доменах. Каждый домен имеет свою собственную базу данных. API-шлюз взаимодействует с микросервисами через REST API или другие протоколы, а микросервисы в пределах одного домена взаимодействуют друг с другом с помощью RPC (удалённого вызова процедур).

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

Источник: https://blog.bytebytego.com
👍8