一、API 模式简介:同步与异步的对比
API 是客户端和服务器之间通信的桥梁。大多数 API 采用同步模式,执行的流程如下:
- 客户端发送请求。
- 服务器处理请求。
- 服务器返回响应。
同步模式对快速操作非常有效,比如数据查询或简单更新。但是,当遇到耗时较长的操作时,问题就显现出来了。例如:
- 处理大文件(例如视频编码、音频分析)。
- 大规模的数据分析与报告生成。
- 图像处理(例如生成缩略图、优化图片)。
在这些场景中,客户端必须长时间等待,可能导致超时错误;而服务器也会因为单个长时间运行的请求而降低整体吞吐量。
二、同步模式的局限性
同步模式有几个核心问题:
- 客户端等待时间过长: 请求处理时间过长,用户体验不佳。
- 超时风险: 网络不稳定或数据量较大时,请求容易超时,导致失败。
- 服务器资源占用: 单个长时间请求会占用服务器资源,影响其他用户请求的响应时间。
这些问题促使我们寻找更高效的解决方案,这就是异步 API。
三、异步 API 的概念与实现
3.1 什么是异步 API?
异步 API 的核心理念是将请求的接收与处理分离。异步模式包括以下两个阶段:
- 接收请求并快速响应: 服务器立即返回一个状态或追踪 ID,表示请求已被接收。
- 后台异步处理: 请求的实际工作在后台完成,客户端可以通过状态查询接口获取处理进度。
3.2 异步 API 的关键优势
- 即时响应: 客户端能快速获得反馈,而不需要长时间等待。
- 非阻塞操作: 服务器可以并行处理多个请求,不会因为单个任务耗时过长而阻塞其他请求。
- 灵活的扩展性: 后台处理任务可以单独扩展,提高系统的吞吐量。
- 更好的错误处理: 如果任务失败,可以保存进度并重试,不会影响其他请求。
3.3 同步模式的具体问题示例
以下是一个常见的同步 API 示例,处理图片上传及优化:
[HttpPost]
public async Task<IActionResult> UploadImage(IFormFile file)
{
if (file is null)
{
return BadRequest();
}
// 保存原始图片
var originalPath = await SaveOriginalAsync(file);
// 生成缩略图
var thumbnails = await GenerateThumbnailsAsync(originalPath);
// 优化所有图片
await OptimizeImagesAsync(originalPath, thumbnails);
return Ok(new { originalPath, thumbnails });
}
此模式的问题在于:
- 客户端必须等待整个流程完成。
- 网络连接或图片较大时,请求容易超时。
- 如果图片处理失败,客户端需重新上传,浪费资源。
3.4 异步 API 的解决方案
新的图片上传接口设计
异步模式将操作拆分为两部分:快速接收请求与后台处理。以下是改进后的接口:[HttpPost] public async Task<IActionResult> UploadImage(IFormFile? file) { if (file is null) { return BadRequest("未上传文件。"); } if (!imageService.IsValidImage(file)) { return BadRequest("无效的图片文件。"); } // 阶段 1:接收请求 var id = Guid.NewGuid().ToString(); var folderPath = Path.Combine(_uploadDirectory, "images", id); var fileName = $"{id}{Path.GetExtension(file.FileName)}"; var originalPath = await imageService.SaveOriginalImageAsync( file, folderPath, fileName ); // 将阶段 2 的任务加入后台队列 var job = new ImageProcessingJob(id, originalPath, folderPath); await jobQueue.EnqueueAsync(job); // 返回状态 URL var statusUrl = GetStatusUrl(id); return Accepted(statusUrl, new { id, status = "queued" }); }
后台任务处理
实际的图片处理任务移交到后台执行,利用独立的线程或服务完成繁重的操作:public class ImageProcessor : BackgroundService { protected override async Task ExecuteAsync(CancellationToken ct) { await foreach (var job in jobQueue.DequeueAsync(ct)) { try { await statusTracker.SetStatusAsync(job.Id, "processing"); // 生成缩略图 await GenerateThumbnailsAsync(job.OriginalPath, job.OutputPath); // 优化图片 await OptimizeImagesAsync(job.OriginalPath, job.OutputPath); await statusTracker.SetStatusAsync(job.Id, "completed"); } catch (Exception ex) { await statusTracker.SetStatusAsync(job.Id, "failed"); logger.LogError(ex, "图片处理失败 {Id}", job.Id); } } } }
四、实时状态更新的实现
4.1 状态查询接口
客户端可以通过以下接口查询任务的状态:
[HttpGet("{id}/status")]
public IActionResult GetStatus(string id)
{
if (!statusTracker.TryGetStatus(id, out var status))
{
return NotFound();
}
var response = new
{
id,
status,
links = new Dictionary<string, string>()
};
if (status == "completed")
{
response.links = new Dictionary<string, string>
{
["original"] = GetImageUrl(id),
["thumbnail"] = GetThumbnailUrl(id, width: 200),
["preview"] = GetThumbnailUrl(id, width: 800)
};
}
return Ok(response);
}
4.2 实时通知:减少轮询
虽然状态查询接口是一个解决方案,但它增加了客户端和服务器的负担。特别是客户端需要频繁轮询状态,导致大量无效的请求。
使用 SignalR 和 WebSocket 可以实现实时通知。状态变化时,服务器主动向客户端推送更新,减少网络流量并提高响应速度。例如:
- 当图片处理完成时,服务器通过 WebSocket 通知客户端。
- 用户可以立即获取完成的结果,而无需多次刷新页面。
4.3 其他通知方式
- 电子邮件通知: 适用于长时间运行的任务,用户可在任务完成时收到通知,而无需保持浏览器页面开启。
- Webhook 回调: 适用于系统间通信,任务完成时服务器主动通知其他系统,实现自动化工作流。
五、性能优化与扩展性
5.1 任务队列设计
对于单服务器应用,可以使用 .NET 的 Channel
管理内存中的任务队列:
public class JobQueue
{
private readonly Channel<ImageProcessingJob> _channel;
public JobQueue()
{
var options = new BoundedChannelOptions(1000)
{
FullMode = BoundedChannelFullMode.Wait
};
_channel = Channel.CreateBounded<ImageProcessingJob>(options);
}
public async ValueTask EnqueueAsync(ImageProcessingJob job,
CancellationToken ct = default)
{
await _channel.Writer.WriteAsync(job, ct);
}
public IAsyncEnumerable<ImageProcessingJob> DequeueAsync(
CancellationToken ct = default)
{
return _channel.Reader.ReadAllAsync(ct);
}
}
对于分布式系统,可以使用 RabbitMQ 或 Redis 实现分布式任务队列,提高扩展性。
5.2 错误处理与重试机制
利用 Polly 等库实现重试策略。例如:
- 图片优化失败时,系统可以自动重试多次,避免用户操作中断。
- 如果某个任务失败,可以记录进度并重新排队,确保系统稳定性。
5.3 动态扩展
异步 API 的设计使得后台任务处理可以独立扩展。例如,增加更多的服务器专门处理任务,而主服务器则专注于接收请求。这种分离提高了整个系统的吞吐量和可靠性。
六、总结
同步 API 简单高效,适用于快速操作,但在处理耗时任务时暴露出等待时间长、超时风险高、资源占用严重等局限性。为解决这些问题,异步 API 应运而生,其核心是将请求接收与处理分离。通过即时响应和后台异步处理,异步 API 提供了更好的用户体验和系统性能。结合任务队列、实时状态更新(如 SignalR)以及动态扩展等技术,异步 API 实现了高并发、低延迟和灵活扩展,适合处理复杂任务场景。同时,配套的错误重试和通知机制提升了系统的可靠性和用户满意度。