ASP.NET Core:不使用BuildServiceProvider延迟SQL配置初始化方案
解决方案:避免使用BuildServiceProvider,延迟缓存配置初始化
核心思路
不在ConfigureServices阶段同步拉取SQL配置,而是借助ASP.NET Core的服务延迟解析和托管服务启动钩子,将配置获取逻辑移至应用启动后的预初始化阶段,同时保证缓存服务能正确加载SQL中的配置值。
具体实现步骤
1. 先注册基础依赖服务
首先完成ISqlRepository和FusionCache的基础注册,暂不绑定分布式缓存(Garnet)的具体配置:
public void ConfigureServices(IServiceCollection services) { // 注册SQL仓储(保持原有的Scoped生命周期) services.AddScoped<ISqlRepository, SqlRepository>(); // 注册FusionCache核心服务,先设置通用默认配置 services.AddFusionCache() .WithDefaultEntryOptions(options => { options.Duration = TimeSpan.FromMinutes(5); // 临时默认值,后续会被SQL配置覆盖 options.IsFailSafeEnabled = true; }); // 注册Web API基础服务 services.AddControllers(); }
2. 编写缓存配置初始化托管服务
创建单例托管服务,负责在应用启动时异步拉取SQL配置,并完成FusionCache与Garnet的绑定:
public class CacheConfigInitializer : IHostedService { private readonly IServiceProvider _serviceProvider; private readonly IFusionCache _fusionCache; private readonly SemaphoreSlim _initLock = new SemaphoreSlim(0, 1); public CacheConfigInitializer(IServiceProvider serviceProvider, IFusionCache fusionCache) { _serviceProvider = serviceProvider; _fusionCache = fusionCache; } public async Task StartAsync(CancellationToken cancellationToken) { try { // 创建Scope解析Scoped类型的ISqlRepository using var scope = _serviceProvider.CreateScope(); var sqlRepo = scope.ServiceProvider.GetRequiredService<ISqlRepository>(); // 异步从SQL拉取缓存配置 var configParamStore = await sqlRepo.GetConfigParamStoreAsync(cancellationToken); // 初始化Garnet分布式缓存配置 var garnetOptions = new GarnetCacheOptions { Configuration = configParamStore.GarnetConnectionString, InstanceName = configParamStore.GarnetInstanceName }; // 将Garnet挂载为FusionCache的L2分布式缓存 _fusionCache.WithDistributedCache(new GarnetCache(garnetOptions)); // 用SQL配置覆盖FusionCache全局默认值 _fusionCache.SetDefaultEntryOptions(options => { options.Duration = TimeSpan.FromSeconds(configParamStore.CacheDurationSeconds); options.IsFailSafeEnabled = configParamStore.EnableFailSafe; }); } finally { _initLock.Release(); // 标记初始化完成 } } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; // 对外提供等待初始化完成的方法,供业务代码调用 public async Task WaitForInitCompleteAsync(CancellationToken cancellationToken = default) { await _initLock.WaitAsync(cancellationToken); _initLock.Release(); // 释放锁,让后续请求无需等待 } }
3. 注册初始化服务
在ConfigureServices中添加托管服务注册,确保应用启动时自动执行配置初始化:
public void ConfigureServices(IServiceCollection services) { // ... 之前的服务注册代码 ... // 注册缓存配置初始化托管服务 services.AddHostedService<CacheConfigInitializer>(); }
4. 处理缓存未初始化的边界场景
在业务代码中,先等待缓存初始化完成再使用FusionCache,避免空配置导致的错误:
public class BusinessService { private readonly IFusionCache _fusionCache; private readonly CacheConfigInitializer _configInitializer; public BusinessService(IFusionCache fusionCache, CacheConfigInitializer configInitializer) { _fusionCache = fusionCache; _configInitializer = configInitializer; } public async Task<DataModel> GetDataAsync(int id) { // 等待缓存配置初始化完成 await _configInitializer.WaitForInitCompleteAsync(); return await _fusionCache.GetOrSetAsync( cacheKey: $"data_{id}", factory: async () => await FetchDataFromDbAsync(id), options: opts => opts.Duration = TimeSpan.FromMinutes(10) ); } }
方案优势
- 彻底规避
BuildServiceProvider带来的服务生命周期不一致、内存泄漏等风险 - 利用
IHostedService异步执行初始化,不阻塞Web API的启动流程 - 通过
Scope正确解析Scoped类型的仓储服务,符合DI设计规范 - 加锁逻辑确保业务代码不会在缓存未就绪时执行,避免异常
内容的提问来源于stack exchange,提问作者Creative Learner
相关产品推荐
相关产品推荐

