一、为什么我们需要自动重试和错误日志记录
在开发过程中,文件上传功能是很多系统的基础需求,尤其是在使用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); // 异步上传
});
关键点说明:
Polly可以灵活定义重试条件,比如只对网络异常或服务端5xx错误重试- 每次重试之间有延迟,避免立即重试加重服务器负担
- 重试次数不宜过多,通常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();
}
日志记录最佳实践:
- 记录关键操作的成功/失败状态
- 包含操作耗时等性能指标
- 错误日志要包含完整的异常堆栈
- 使用结构化日志方便后续查询分析
五、完整方案的技术细节与优化
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("开始处理文件上传");
}
六、应用场景与技术选型分析
适用场景
- 需要高可靠性的文件上传服务(如医疗影像系统)
- 网络环境不稳定的移动端应用
- 需要详细操作日志的审计场景
技术优缺点
| 方案 | 优点 | 缺点 |
|---|---|---|
| 基础重试 | 实现简单 | 缺乏灵活的重试策略 |
| Polly重试 | 策略丰富,支持熔断等高级特性 | 需要学习Polly的API |
| 控制台日志 | 快速调试 | 不利于长期存储分析 |
| Serilog日志 | 结构化,支持多种输出 | 需要额外配置 |
注意事项
- 重试可能导致重复操作(如创建重复文件),需要业务层处理幂等性
- 日志文件要定期归档清理,避免磁盘占满
- 敏感文件路径不要记录在日志中
七、总结
通过Polly和Serilog这两个强大的.NET库,我们实现了:
- 智能的自动重试机制,提升系统健壮性
- 完善的日志记录,便于运维排查问题
- 可扩展的架构,方便后续添加更多功能(如熔断机制)
完整方案的代码虽然比基础版本复杂一些,但换来的可靠性提升是值得的,特别适合对稳定性要求高的生产环境。
评论