深入探讨异步 API 的设计与实现

一、API 模式简介:同步与异步的对比

API 是客户端和服务器之间通信的桥梁。大多数 API 采用同步模式,执行的流程如下:

  1. 客户端发送请求。
  2. 服务器处理请求。
  3. 服务器返回响应。

同步模式对快速操作非常有效,比如数据查询或简单更新。但是,当遇到耗时较长的操作时,问题就显现出来了。例如:

  • 处理大文件(例如视频编码、音频分析)。
  • 大规模的数据分析与报告生成。
  • 图像处理(例如生成缩略图、优化图片)。

在这些场景中,客户端必须长时间等待,可能导致超时错误;而服务器也会因为单个长时间运行的请求而降低整体吞吐量。

二、同步模式的局限性

同步模式有几个核心问题:

  1. 客户端等待时间过长: 请求处理时间过长,用户体验不佳。
  2. 超时风险: 网络不稳定或数据量较大时,请求容易超时,导致失败。
  3. 服务器资源占用: 单个长时间请求会占用服务器资源,影响其他用户请求的响应时间。

这些问题促使我们寻找更高效的解决方案,这就是异步 API。

三、异步 API 的概念与实现

3.1 什么是异步 API?

异步 API 的核心理念是将请求的接收与处理分离。异步模式包括以下两个阶段:

  1. 接收请求并快速响应: 服务器立即返回一个状态或追踪 ID,表示请求已被接收。
  2. 后台异步处理: 请求的实际工作在后台完成,客户端可以通过状态查询接口获取处理进度。
3.2 异步 API 的关键优势
  1. 即时响应: 客户端能快速获得反馈,而不需要长时间等待。
  2. 非阻塞操作: 服务器可以并行处理多个请求,不会因为单个任务耗时过长而阻塞其他请求。
  3. 灵活的扩展性: 后台处理任务可以单独扩展,提高系统的吞吐量。
  4. 更好的错误处理: 如果任务失败,可以保存进度并重试,不会影响其他请求。
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 });
}

此模式的问题在于:

  1. 客户端必须等待整个流程完成。
  2. 网络连接或图片较大时,请求容易超时。
  3. 如果图片处理失败,客户端需重新上传,浪费资源。
3.4 异步 API 的解决方案
  1. 新的图片上传接口设计
    异步模式将操作拆分为两部分:快速接收请求与后台处理。以下是改进后的接口:

    [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" });
    }
    
  2. 后台任务处理
    实际的图片处理任务移交到后台执行,利用独立的线程或服务完成繁重的操作:

    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 实时通知:减少轮询

虽然状态查询接口是一个解决方案,但它增加了客户端和服务器的负担。特别是客户端需要频繁轮询状态,导致大量无效的请求。
使用 SignalRWebSocket 可以实现实时通知。状态变化时,服务器主动向客户端推送更新,减少网络流量并提高响应速度。例如:

  • 当图片处理完成时,服务器通过 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);
    }
}

对于分布式系统,可以使用 RabbitMQRedis 实现分布式任务队列,提高扩展性。

5.2 错误处理与重试机制

利用 Polly 等库实现重试策略。例如:

  • 图片优化失败时,系统可以自动重试多次,避免用户操作中断。
  • 如果某个任务失败,可以记录进度并重新排队,确保系统稳定性。
5.3 动态扩展

异步 API 的设计使得后台任务处理可以独立扩展。例如,增加更多的服务器专门处理任务,而主服务器则专注于接收请求。这种分离提高了整个系统的吞吐量和可靠性。

六、总结

同步 API 简单高效,适用于快速操作,但在处理耗时任务时暴露出等待时间长、超时风险高、资源占用严重等局限性。为解决这些问题,异步 API 应运而生,其核心是将请求接收与处理分离。通过即时响应和后台异步处理,异步 API 提供了更好的用户体验和系统性能。结合任务队列、实时状态更新(如 SignalR)以及动态扩展等技术,异步 API 实现了高并发、低延迟和灵活扩展,适合处理复杂任务场景。同时,配套的错误重试和通知机制提升了系统的可靠性和用户满意度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/925294.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【JavaEE初阶 — 网络编程】TCP流套接字编程

TCP流套接字编程 1. TCP &#xff06; UDP 的区别 TCP 的核心特点是面向字节流&#xff0c;读写数据的基本单位是字节 byte 2 API介绍 2.1 ServerSocket 定义 ServerSocket 是创建 TCP 服务端 Socket 的API。 构造方法 方法签名 方法说明 ServerS…

Scala入门基础(20)数据集复习拓展

一.Stack栈二.Queue 队列 一.Stack栈 Stack:栈&#xff0c;特殊的结构。它对元素的操作是在头部&#xff1a;栈顶 先进后出的队列。pop表示取出&#xff0c;push表示在栈中添加元素 二.Queue 队列 Queue 队列;先进先出.enqueue入队&#xff0c;dequeue出队。

ThinkPHP Nginx 重写配置

目录 NGINX 重写 Admin项目隐藏入口文件&#xff0c;且禁用Admin模块&Admin.php 1️⃣配置仅用模块 2️⃣新增admin_xyz.php文件&#xff08;自定义入口文件名&#xff09;&#xff0c;并绑定admin模块 3️⃣配置nginx 重写规则 NGINX 重写 在Nginx低版本中&#xff0…

深度学习基础3

目录 1.过拟合与欠拟合 1.1 过拟合 1.2 欠拟合 1.2 解决欠拟合 1.2.1 L2正则化 1.2.2 L1正则化 1.2.3 Dropout 1.2.4 简化模型 1.2.5 数据增强 1.2.6 早停 1.2.7 模型集成 1.2.8 交叉验证 2.批量标准化 2.1 实现过程 2.1.1 计算均值和方差 2.1.2 标准化 2.1.3…

Scala习题

姓名&#xff0c;语文&#xff0c;数学&#xff0c;英语 张伟&#xff0c;87&#xff0c;92&#xff0c;88 李娜&#xff0c;90&#xff0c;85&#xff0c;95 王强&#xff0c;78&#xff0c;90&#xff0c;82 赵敏&#xff0c;92&#xff0c;88&#xff0c;91 孙涛&#xff0c…

【赵渝强老师】PostgreSQL的数据库

PostgreSQL的逻辑存储结构主要是指数据库中的各种数据库对象&#xff0c;包括&#xff1a;数据库集群、数据库、表、索引、视图等等。所有数据库对象都有各自的对象标识符oid&#xff08;object identifiers&#xff09;,它是一个无符号的四字节整数&#xff0c;相关对象的oid都…

(C语言) 8大翻译阶段

(C语言) 8大翻译阶段 文章目录 (C语言) 8大翻译阶段⭐前言&#x1f5c3;️8大阶段&#x1f5c2;️1. 字符映射&#x1f5c2;️2. 行分割&#x1f5c2;️3. 标记化&#x1f5c2;️4. 预处理&#x1f5c2;️5. 字符集映射&#x1f5c2;️6. 字符串拼接&#x1f5c2;️7. 翻译&…

安全基线检查

一、安全基线检测基础知识 安全基线的定义 安全基线检查的内容 安全基线检查的操作 二、MySQL的安全基线检查 版本加固 弱口令 不存在匿名账户 合理设置权限 合理设置文件权限 日志审核 运行账号 可信ip地址控制 连接数限制 更严格的基线要求 1、禁止远程连接数据库 2、修改…

玩转 uni-app 静态资源 static 目录的条件编译

一. 前言 老生常谈&#xff0c;了解 uni-app 的开发都知道&#xff0c;uni-app 可以同时支持编译到多个平台&#xff0c;如小程序、H5、移动端 App 等。它的多端编译能力是 uni-app 的一大特点&#xff0c;让开发者可以使用同一套代码基于 Vue.js 的语法编写程序&#xff0c;然…

[2024年3月10日]第15届蓝桥杯青少组stema选拔赛C++中高级(第二子卷、编程题(2))

方法一&#xff08;string&#xff09;&#xff1a; #include <iostream> #include <string> using namespace std;// 检查是否为回文数 bool isPalindrome(int n) {string str to_string(n);int left 0, right str.size() - 1;while (left < right) {if (s…

快速排序hoare版本和挖坑法(代码注释版)

hoare版本 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h>// 交换函数 void Swap(int* p1, int* p2) {int tmp *p1;*p1 *p2;*p2 tmp; }// 打印数组 void _printf(int* a, int n) {for (int i 0; i < n; i) {printf("%d ", a[i]);}printf("…

C5.【C++ Cont】getchar,putchar和scanf

目录 1.回顾C语言文章24.【C语言】getcha和putchar的使用 2.C中和C语言不同的地方 3.关键点 4.scanf 5.练习1 题目描述 输入描述: 输出描述: 输入 输出 6.练习2 题目描述 输入格式 输出格式 输入输出样例 说明/提示 1.回顾C语言文章24.【C语言】getcha和putchar…

深入理解 AI 产品的核心价值——《AI产品经理手册》

现在&#xff0c;人们对AI 充满了兴趣和看法。这些年&#xff0c;我亲身经历了对AI 的感受和认识的此起彼伏。我还是学生时&#xff0c;就对AI 以及伴随而来的第四次工业革命感到无比激动和期待。然而&#xff0c;当我开始组织读书会&#xff0c;每月阅读有关AI 的书籍&#xf…

Spring Boot拦截器(Interceptor)详解

拦截器Interceptor 拦截器我们主要分为三个方面进行讲解&#xff1a; 介绍下什么是拦截器&#xff0c;并通过快速入门程序上手拦截器拦截器的使用细节通过拦截器Interceptor完成登录校验功能 1. 快速入门 什么是拦截器&#xff1f; 是一种动态拦截方法调用的机制&#xff…

python代码示例(读取excel文件,自动播放音频)

目录 python 操作excel 表结构 安装第三方库 代码 自动播放音频 介绍 安装第三方库 代码 python 操作excel 表结构 求出100班同学的平均分 安装第三方库 因为这里的表结构是.xlsx文件,需要使用openpyxl库 如果是.xls格式文件,需要使用xlrd库 pip install openpyxl /…

构建 LLM (大型语言模型)应用程序——从入门到精通(第七部分:开源 RAG)

通过检索增强生成 (RAG) 应用程序的视角学习大型语言模型 (LLM)。 本系列博文 简介数据准备句子转换器矢量数据库搜索与检索大语言模型开源 RAG&#xff08;本帖&#xff09;评估服务LLM高级 RAG 1. 简介 我们之前的博客文章广泛探讨了大型语言模型 (LLM)&#xff0c;涵盖了其…

2024健康大数据与智能医疗(ICHIH 2024)

大会官网&#xff1a;www.ic-ichih.net 大会时间&#xff1a;2024年12月13-15日 大会地点&#xff1a;中国珠海 收录检索&#xff1a;IEEE Xplore&#xff0c;EI Compendex&#xff0c;Scopus

从0开始学PHP面向对象内容之常用设计模式(适配器,桥接,装饰器)

二&#xff0c;结构型设计模式 上两期咱们讲了创建型设计模式&#xff0c;都有 单例模式&#xff0c;工厂模式&#xff0c;抽象工厂模式&#xff0c;建造者模式&#xff0c;原型模式五个设计模式。 这期咱们讲结构型设计模式 1、适配器模式&#xff08;Adapter&#xff09; …

原生微信小程序画表格

wxml部分&#xff1a; <view class"table__scroll__view"><view class"table__header"><view class"table__header__item" wx:for"{{TableHeadtitle}}" wx:key"index">{{item.title}}</view></…

TDengine 签约深圳综合粒子,赋能粒子研究新突破

在高能物理和粒子研究领域&#xff0c;实验装置的不断升级伴随着海量数据的产生与处理。尤其是随着大湾区综合性国家科学中心的建设步伐加快&#xff0c;深圳综合粒子设施研究院&#xff08;以下简称“研究院”&#xff09;作为承载“双区驱动”战略的重要科研机构&#xff0c;…