You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 7 REST API自动缩放时单例类异常问题求助

多实例场景下适配单例类操作共享目录的解决方案

问题根源

你当前的代码在启动时直接实例化Engine并注册为单例,多实例部署时每个API实例都会拥有独立的Engine实例。当它们同时操作共享的wwwroot目录(比如挂载了同一个网络存储)时,会引发文件访问冲突、操作互斥问题;如果是本地存储,还会出现各实例数据不一致的情况。

具体解决方案

1. 引入分布式锁控制共享资源访问

针对共享目录的操作,必须通过分布式锁保证同一时间只有一个实例执行文件操作。可以使用基于Redis的分布式锁(.NET中可通过StackExchange.Redis或自定义实现分布式锁)。

修改Engine类,在关键操作(删除、加载文件夹)前后加分布式锁:

public class Engine
{
    private readonly ILogger _logger;
    private readonly IDistributedLock _distributedLock;
    private const string LockKey = "wwwroot-folder-operation-lock";

    public Engine(ILoggerFactory loggerFactory, IDistributedLock distributedLock)
    {
        _logger = loggerFactory.CreateLogger<Engine>();
        _distributedLock = distributedLock;
    }

    public void DeleteFolder(string folderPath)
    {
        using var lockHandle = _distributedLock.TryAcquireLock(LockKey, TimeSpan.FromSeconds(30));
        if (lockHandle == null)
        {
            _logger.LogWarning("获取分布式锁失败,无法执行文件夹删除操作");
            throw new InvalidOperationException("当前有其他实例正在操作目录,请稍后重试");
        }

        // 执行实际删除逻辑
        Directory.Delete(Path.Combine("wwwroot", folderPath), recursive: true);
    }

    public void LoadFolder(string folderPath, Stream content)
    {
        using var lockHandle = _distributedLock.TryAcquireLock(LockKey, TimeSpan.FromSeconds(30));
        if (lockHandle == null)
        {
            _logger.LogWarning("获取分布式锁失败,无法执行文件夹加载操作");
            throw new InvalidOperationException("当前有其他实例正在操作目录,请稍后重试");
        }

        // 执行实际加载/写入逻辑
        var targetPath = Path.Combine("wwwroot", folderPath);
        Directory.CreateDirectory(targetPath);
        using var fileStream = new FileStream(Path.Combine(targetPath, "content.dat"), FileMode.Create);
        content.CopyTo(fileStream);
    }
}

注册分布式锁服务(以Redis为例):

// 注册StackExchange.Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
});

// 注册分布式锁实现
builder.Services.AddSingleton<IDistributedLock, RedisDistributedLock>();

// 注册Engine为单例(让DI容器负责实例化,避免进程间独立实例)
builder.Services.AddSingleton<Engine>();

2. 统一使用共享存储

确保所有API实例都指向同一个共享存储(比如NAS、云对象存储如Azure Blob、AWS S3等),替换本地wwwroot目录。云存储服务本身会处理多客户端访问的一致性问题,无需额外实现锁逻辑。

比如改用Azure Blob Storage的实现:

public class Engine
{
    private readonly ILogger _logger;
    private readonly BlobServiceClient _blobServiceClient;

    public Engine(ILoggerFactory loggerFactory, BlobServiceClient blobServiceClient)
    {
        _logger = loggerFactory.CreateLogger<Engine>();
        _blobServiceClient = blobServiceClient;
    }

    public async Task DeleteFolderAsync(string folderPrefix)
    {
        var containerClient = _blobServiceClient.GetBlobContainerClient("wwwroot-container");
        await foreach (var blob in containerClient.GetBlobsAsync(prefix: folderPrefix))
        {
            await containerClient.DeleteBlobAsync(blob.Name);
        }
    }

    public async Task LoadFolderAsync(string folderPrefix, Stream content)
    {
        var containerClient = _blobServiceClient.GetBlobContainerClient("wwwroot-container");
        var blobClient = containerClient.GetBlobClient($"{folderPrefix}/content.dat");
        await blobClient.UploadAsync(content, overwrite: true);
    }
}

3. 修正单例注册方式

你当前提前实例化Engine的方式,会导致每个进程创建独立的实例。正确的做法是让DI容器负责实例化,保证进程内单例的同时,配合分布式锁或共享存储解决跨进程问题:

// 错误方式:提前实例化,每个进程会生成独立的Engine实例
// Engine _engine = new Engine(loggerFactory);
// builder.Services.AddSingleton<Engine>(_engine);

// 正确方式:由DI容器管理实例化进程内单例
builder.Services.AddSingleton<Engine>();

额外注意事项

  • 操作文件/存储时要捕获IOException、网络异常等,避免因锁超时、网络波动导致服务崩溃。
  • 如果业务允许各实例数据独立,可配置各实例使用本地wwwroot,但这种场景不适用于需要数据一致的业务。

内容的提问来源于stack exchange,提问作者user2030504

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 05:07:43