一、为什么我们需要自动重试和错误日志记录

在开发过程中,文件上传功能是很多系统的基础需求,尤其是在使用OBS(对象存储服务)时,网络波动、服务端限流、临时故障等问题可能导致上传失败。如果每次失败都让用户手动重试,体验会很糟糕。而完善的错误日志记录则能帮助开发者快速定位问题。

举个例子,假设我们有一个电商系统,用户上传商品图片到OBS,如果因为网络抖动导致上传失败,系统自动重试3次,同时记录详细的错误信息到日志文件,这样既能提升用户体验,又能方便后续排查问题。

二、C#/.NET 实现OBS文件上传的基本流程

在C#中,我们可以使用华为云OBS官方提供的.NET SDK进行操作。首先,我们需要安装HuaweiCloud.SDK.OBS NuGet包。

// 示例:基本OBS文件上传代码(技术栈:C#/.NET 6+)
using HuaweiCloud.SDK.Core;
using HuaweiCloud.SDK.OBS.V1;
using HuaweiCloud.SDK.OBS.V1.Model;

var clientConfig = new Config
{
    AccessKeyId = "your-access-key",
    SecretAccessKey = "your-secret-key",
    Endpoint = "https://your-obs-endpoint"
};

var client = new ObsClient(clientConfig);

try
{
    var request = new PutObjectRequest
    {
        BucketName = "your-bucket",
        ObjectKey = "example.jpg",
        FilePath = "local-path/example.jpg"
    };
    
    var response = client.PutObject(request);
    Console.WriteLine("文件上传成功!");
}
catch (Exception ex)
{
    Console.WriteLine($"上传失败:{ex.Message}");
}

这段代码完成了最基本的OBS文件上传,但没有任何重试机制和日志记录,接下来我们逐步完善它。

三、实现自动重试机制

为了让上传操作更健壮,我们可以引入Polly这个强大的.NET弹性库来处理重试逻辑。

// 示例:带自动重试的文件上传(技术栈:C#/.NET 6+ + Polly)
using Polly;
using System.Net;

// 配置重试策略:最多重试3次,每次间隔2秒
var retryPolicy = Policy
    .Handle<WebException>() // 捕获网络异常
    .Or<ObsException>(ex => ex.StatusCode >= 500) // 捕获OBS服务端错误
    .WaitAndRetryAsync(
        retryCount: 3,
        sleepDurationProvider: attempt => TimeSpan.FromSeconds(2),
        onRetry: (exception, delay, attempt, context) =>
        {
            Console.WriteLine($"第{attempt}次重试,原因:{exception.Message}");
        });

await retryPolicy.ExecuteAsync(async () =>
{
    var request = new PutObjectRequest
    {
        BucketName = "your-bucket",
        ObjectKey = "example.jpg",
        FilePath = "local-path/example.jpg"
    };
    await client.PutObjectAsync(request); // 异步上传
});

关键点说明:

  1. Polly可以灵活定义重试条件,比如只对网络异常或服务端5xx错误重试
  2. 每次重试之间有延迟,避免立即重试加重服务器负担
  3. 重试次数不宜过多,通常3-5次比较合理

四、集成错误日志记录

日志记录我们选择使用Serilog,它可以方便地将日志输出到文件、控制台甚至ELK等系统。

// 示例:完整的上传+重试+日志记录(技术栈:C#/.NET 6+ + Polly + Serilog)
using Serilog;

// 配置Serilog
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.File("logs/obs-upload-.log", rollingInterval: RollingInterval.Day)
    .CreateLogger();

try
{
    await retryPolicy.ExecuteAsync(async () =>
    {
        var request = new PutObjectRequest
        {
            BucketName = "your-bucket",
            ObjectKey = "example.jpg",
            FilePath = "local-path/example.jpg"
        };
        
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();
        var response = await client.PutObjectAsync(request);
        stopwatch.Stop();
        
        Log.Information("上传成功!文件:{ObjectKey},耗时:{ElapsedMs}ms", 
            request.ObjectKey, stopwatch.ElapsedMilliseconds);
    });
}
catch (Exception ex)
{
    Log.Error(ex, "文件上传最终失败:{FilePath}", "local-path/example.jpg");
}
finally
{
    Log.CloseAndFlush();
}

日志记录最佳实践:

  1. 记录关键操作的成功/失败状态
  2. 包含操作耗时等性能指标
  3. 错误日志要包含完整的异常堆栈
  4. 使用结构化日志方便后续查询分析

五、完整方案的技术细节与优化

5.1 重试策略的进阶配置

更复杂的场景可能需要:

  • 根据不同的错误类型采用不同的重试间隔(比如网络错误立即重试,服务限流则等待更久)
  • 重试次数动态调整(首次失败重试3次,后续相同文件可减少重试)
// 进阶重试策略示例
var advancedRetryPolicy = Policy
    .Handle<ObsException>(ex => ex.StatusCode == 429) // 限流错误
    .WaitAndRetryAsync(
        sleepDurationProvider: attempt => 
            TimeSpan.FromSeconds(Math.Pow(2, attempt)), // 指数退避
        onRetry: (ex, delay, attempt, ctx) => 
            Log.Warning("遇到限流,第{Attempt}次重试将在{Delay}ms后执行...", 
                attempt, delay.TotalMilliseconds)
    );

5.2 日志上下文增强

通过Serilog的上下文特性,可以为同一请求的所有日志添加统一标识:

using (LogContext.PushProperty("RequestId", Guid.NewGuid()))
{
    // 该作用域内所有日志都会自动包含RequestId字段
    Log.Information("开始处理文件上传");
}

六、应用场景与技术选型分析

适用场景

  1. 需要高可靠性的文件上传服务(如医疗影像系统)
  2. 网络环境不稳定的移动端应用
  3. 需要详细操作日志的审计场景

技术优缺点

方案 优点 缺点
基础重试 实现简单 缺乏灵活的重试策略
Polly重试 策略丰富,支持熔断等高级特性 需要学习Polly的API
控制台日志 快速调试 不利于长期存储分析
Serilog日志 结构化,支持多种输出 需要额外配置

注意事项

  1. 重试可能导致重复操作(如创建重复文件),需要业务层处理幂等性
  2. 日志文件要定期归档清理,避免磁盘占满
  3. 敏感文件路径不要记录在日志中

七、总结

通过Polly和Serilog这两个强大的.NET库,我们实现了:

  • 智能的自动重试机制,提升系统健壮性
  • 完善的日志记录,便于运维排查问题
  • 可扩展的架构,方便后续添加更多功能(如熔断机制)

完整方案的代码虽然比基础版本复杂一些,但换来的可靠性提升是值得的,特别适合对稳定性要求高的生产环境。