Я борюсь с одним из наших приложений do tnet core 3.0, надежно заполняющим базу данных при запуске приложения. В двух словах, проблема заключается в том, что мы запускаем метод заполнения данных при запуске приложения для запуска миграций и заполнения данных. В DEV он работает нормально, но периодически зависает на UAT / PRD. Вот некоторые факты о приложении и вещах, которые я пробовал:
- API - это приложение ASP. NET Core 3.0, а интерфейс - Angular 7
- Приложение, а также связанные службы все докеризируются в контейнеры.
- В DEV + UAT мы не сохраняем базу данных в постоянном томе, когда мы уменьшаем / увеличиваем контейнеры, база данных воссоздан / пересеян. В PRD, очевидно, у нас есть постоянный том.
- Мы запускаем контейнеры для Web API, SqlServer 2017, Elasticsearch, Nginx и Kibana (все linux). Мы используем одни и те же файлы Docker в средах DEV / UAT / PRD с динамически применяемыми настройками конфигурации.
- Мы обновили приложение с do tnet core 2.1 до 3.0 примерно за 2-3 месяца go и все работает нормально, за исключением заполнения.
- Методы заполнения данных используют методы расширения массовой вставки для EF Core 3.1.1, которые я недавно добавил, полагая, что это поможет с зависанием при заполнении в UAT.
- Первоначально у нас были вызовы для нашего
DbInitializer
класса в Configure
в Startup.cs .. Впоследствии я переместил его из Configure
(см. Следующий пункт) - Я переместил начальные вызовы в
DbInitializer
в HostedService
в попытке исправить ситуацию. Я подозревал, что, поскольку все наши начальные методы имеют асин c, вызов их в Configure
может быть проблемой, так как это не асин c, и я прочитал, что это может вызвать взаимоблокировки задач. Созданная мною размещенная служба полностью asyn c, и снова все мои методы в DbInitializer
также являются asyn c. - Мне известен метод заполнения
HasData()
в EF Core 2.1+. , но я понимаю, что это хорошо для «простого» заполнения, поэтому мы выбрали альтернативный подход. У меня было подозрение, что, возможно, проблема была SQL Сервер внутри docker Контейнер, так что в качестве теста я указал на локальный экземпляр SQL Server 2017, работающий в нашей среде, без разницы.
Я четыре раза проверил DbInitializer
для любых вызовов, которые вызываются синхронно, их нет, все должным образом ожидает и вызывает правильный метод asyn c.
- Когда происходит зависание, ошибок нет, вывод журнала приложения просто останавливается у записи «начало» и никогда не выходит в сеть. Я проверил журналы БД, опять же, ничего.
- После зависания заполнения, если я перезапущу контейнер Asp. Net Core docker через
docker restart {containerID}
, заполнение обычно (~ 95% времени) будет успешным, и приложение станет доступным немедленно.
Вот как выглядят журналы приложения при зависании заполнения:
[19:10:14 INF] MyApp.App is starting...
[19:10:18 INF] Program :: Checking if DB is ready...
[19:10:20 INF] Start installing Hangfire SQL objects...
[19:10:20 INF] Hangfire SQL objects installed.
[19:10:21 WRN] No XML encryptor configured. Key {29fcce48-c9a9-4635-979f-870377ed8604} may be persisted to storage in unencrypted form.
[19:10:23 INF] DbInitializer :: Initializing DB...
[19:10:23 INF] DbInitializer :: Applying Migrations...
[19:10:26 INF] DbInitializer :: Finished DB Initialization!!!
[19:10:26 INF] DbInitializer :: Ensuring Seed Data...
[19:10:26 INF] Seed Data: Add Roles Begin
[19:10:27 INF] Seed Data: Add Roles Success
[19:10:27 INF] Seed Data: Add Countries Begin
[19:10:28 INF] Seed Data: Add Countries Success
[19:10:28 INF] Seed Data: Add Provinces Begin
[19:10:28 INF] Seed Data: Add Provinces Success
[19:10:28 INF] Seed Data: Add Users Begin
[19:10:29 INF] Seed Data: Add Users Success
[19:10:29 INF] Seed Data: Add Orgs Begin
[19:10:29 INF] Seed Data: Add Orgs Success
[19:10:29 INF] Seed Data: Add UserProfile Begin
[19:10:29 INF] Seed Data: Add UserProfile Success
[19:10:29 INF] Seed Data: Add Studies Begin
[19:10:29 INF] Seed Data: Add Studies Success
[19:10:29 INF] Seed Data: Add StudyStatements Begin
Вот вывод журнала успешного заполнения, который снова всегда успешно выполняется на моем компьютере разработчика в Visual Studio, и примерно 50% времени в UAT:
[19:12:35 INF] MyApp.App is starting...
[19:12:36 INF] Program :: Checking if DB is ready...
[19:12:38 INF] Start installing Hangfire SQL objects...
[19:12:38 INF] Hangfire SQL objects installed.
[19:12:40 INF] DbInitializer :: Initializing DB...
[19:12:40 INF] DbInitializer :: Finished DB Initialization!!!
[19:12:40 INF] DbInitializer :: Ensuring Seed Data...
[19:12:40 INF] Seed Data: Add Roles Begin
[19:12:41 INF] Seed Data: Add Roles Success
[19:12:41 INF] Seed Data: Add Countries Begin
[19:12:41 INF] Seed Data: Add Countries Success
[19:12:41 INF] Seed Data: Add Provinces Begin
[19:12:41 INF] Seed Data: Add Provinces Success
[19:12:41 INF] Seed Data: Add Users Begin
[19:12:41 INF] Seed Data: Add Users Success
[19:12:41 INF] Seed Data: Add Orgs Begin
[19:12:41 INF] Seed Data: Add Orgs Success
[19:12:41 INF] Seed Data: Add UserProfile Begin
[19:12:41 INF] Seed Data: Add UserProfile Success
[19:12:41 INF] Seed Data: Add Studies Begin
[19:12:42 INF] Seed Data: Add Studies Success
[19:12:42 INF] Seed Data: Add StudyStatements Begin
[19:12:42 INF] Seed Data: Add StudyStatements Success
[19:12:42 INF] Seed Data: Add StudyCodes Begin
[19:12:42 INF] Seed Data: Add StudyCodes Success
[19:12:42 INF] Seed Data: Add StudyStructure Begin
[19:12:42 INF] Seed Data: Add StudyStructure Success
[19:12:42 INF] Seed Data: Add Instances Begin
[19:12:42 INF] Seed Data: Add Instaces Success
[19:12:42 INF] Seed Data: Add PaymentMethod Begin
[19:12:42 INF] Seed Data: Add PaymentMethod Success
[19:12:42 INF] Seed Data: Add Packages Begin
[19:12:43 INF] Seed Data: Add Packages Success
[19:12:43 INF] DbInitializer :: Ensuring Test Data...
[19:12:43 INF] Test Data: Add IdentityUsers Begin
[19:12:43 INF] Test Data: Add IdentityUsers Success
[19:12:43 INF] Test Data: Add UserProfile Begin
[19:12:43 INF] Test Data: Add UserProfile Success
[19:12:43 INF] Test Data: Add Studies Begin
[19:12:43 INF] Test Data: Add Studies Success
[19:12:43 INF] Test Data: Add StudyStatements Begin
[19:12:43 INF] Test Data: Add StudyStatements Success
[19:12:43 INF] Test Data: Add StudyCodes Begin
[19:12:43 INF] Test Data: Add StudyCodes Success
[19:12:43 INF] Test Data: Add StudyStructure Begin
[19:12:43 INF] Test Data: Add StudyStructure Success
[19:12:43 INF] Test Data: Add Instances Begin
[19:12:44 INF] Test Data: Add Instances Success
[19:12:44 INF] Starting Hangfire Server using job storage: 'SQL Server: {redacted}@MyApp'
[19:12:44 INF] Using the following options for SQL Server job storage: Queue poll interval: 00:00:15.
[19:12:44 INF] Using the following options for Hangfire Server:
Worker count: 20
Listening queues: 'default'
Shutdown timeout: 00:00:15
Schedule polling interval: 00:00:15
[19:12:44 INF] AppConfigure :: Trying to ensure valid DB connection for MyAppDbContext
[19:12:44 INF] AppConfigure :: MyAppDbContext DB connection was successful in 0.0569003s!
[19:12:44 INF] Starting IdentityServer4 version 3.0.0.0
[19:12:44 INF] You are using the in-memory version of the persisted grant store. This will store consent decisions, authorization codes, refresh and reference tokens in memory only. If you are using any of those features in production, you want to switch to a different store implementation.
[19:12:44 INF] Using the default authentication scheme Identity.Application for IdentityServer
[19:12:44 INF] Starting Hangfire Server using job storage: 'SQL Server: {redacted}@MyApp'
[19:12:44 INF] Using the following options for SQL Server job storage: Queue poll interval: 00:00:15.
[19:12:44 INF] Using the following options for Hangfire Server:
Worker count: 20
Listening queues: 'default'
Shutdown timeout: 00:00:15
Schedule polling interval: 00:00:15
[19:12:44 INF] Server 49d71a12c49d:1:d6fbae79 successfully announced in 165.7588 ms
[19:12:44 INF] Server 49d71a12c49d:1:3bcc860c successfully announced in 21.242800000000003 ms
[19:12:44 INF] Server 49d71a12c49d:1:3bcc860c is starting the registered dispatchers: ServerWatchdog, ServerJobCancellationWatcher, ExpirationManager, CountersAggregator, Worker, DelayedJobScheduler, RecurringJobScheduler...
[19:12:44 INF] Server 49d71a12c49d:1:d6fbae79 is starting the registered dispatchers: ServerWatchdog, ServerJobCancellationWatcher, ExpirationManager, CountersAggregator, Worker, DelayedJobScheduler, RecurringJobScheduler...
В ConfigureServices
У меня есть эта запись для моего HostedService:
services.AddHostedService<DataInitHostedService>();
Код для моей размещенной службы выглядит следующим образом:
public class DataInitHostedService : IHostedService
{
private readonly IServiceProvider _serviceProvider;
public DataInitHostedService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
using (var scope = _serviceProvider.CreateScope())
{
var dbInitalizaer = scope.ServiceProvider.GetRequiredService<DbInitializer>();
await dbInitalizaer.DoSeeding();
}
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
Класс DbInitializer
большой, поэтому я опубликую только основной метод init, который переносит базу данных, а также метод семенного примера:
public async Task Initialize()
{
_logger.LogInformation("DbInitializer :: Initializing DB...");
var pendingMigrations = await _dbContext.Database.GetPendingMigrationsAsync();
if (pendingMigrations.Any())
{
_logger.LogInformation("DbInitializer :: Applying Migrations...");
await _dbContext.Database.MigrateAsync();
}
_logger.LogInformation("DbInitializer :: Finished DB Initialization!!!");
}
//there's a series of methods that look very similar to this which performs the seeding
// The List<T> parameters are manually created in code and passed to the relevant methods
// The Seed() method asynchronously awaits each one of the methods that looks like this, for
// each entity that we're seeding
private async Task AddUserProfilesAsync(List<UserProfile> userProfiles)
{
List<UserProfile> filteredList = new List<UserProfile>();
foreach (UserProfile profile in userProfiles)
{
if (await _dbContext.UserProfiles.FirstOrDefaultAsync(u => u.Id == profile.Id) == null)
{
filteredList.Add(profile);
}
}
await _dbContext.BulkInsertAsync(filteredList, new BulkConfig { SetOutputIdentity = true,
PreserveInsertOrder = true });
}
Вот вопросы, которые у меня есть, в дополнение к очевидному вопросу ' Почему, черт возьми, это периодически зависает? ':
- Я озадачен тем, что нет никаких исключений времени выполнения или записей журнала, которые указывали бы на проблему. Возможно ли, что мне чего-то не хватает, например, конфигурации или настройки, которые подавляют запись в журнале? Я имею в виду, по моему опыту, если возникает ошибка во время выполнения, я легко вижу ее в журналах .. это просто зависает, как в моем примере вставки журнала выше ..
- Кто-нибудь сталкивался с этим в do tnet core 3, где при запуске приложения оно просто зависает без какой-либо значимой обратной связи? Любые советы / предложения?
- Подход, который мы принимаем к решению проблемы? Должны ли мы посеять по-другому? Как уже упоминалось выше, я знаю о методе
HasData
для EfCore builder, только все, что я прочитал, указывало, что он хорош для небольшого заполнения. Подход, который мы берем проблему? Имейте в виду, что заполнение происходит в asyn c HostedService, который, как я думал, был лучше, чем синхронно в Startup => Configure
.. Мысли?
Любая помощь или предложения будут БОЛЬШОЙ оценил ... Я лысый, и я собираюсь отрастить мои волосы назад, чтобы я мог их выдернуть, вот как я разочарован :) Lol, серьезно, любые советы / помощь были бы великолепны!
Заранее спасибо!