前言:
现时看官们对“异步请求怎么处理”大概比较关切,大家都想要知道一些“异步请求怎么处理”的相关文章。那么小编在网络上汇集了一些对于“异步请求怎么处理””的相关内容,希望兄弟们能喜欢,小伙伴们一起来了解一下吧!一些场景下后端的逻辑处理需要以异步(而非同步)的方式进行,通过异步请求-响应模式,把逻辑处理与前端进行解耦,同时返回给前端一个逻辑清晰的响应。
产生背景
在当前最常见的应用开发模式下,客户端应用程序(包括网络浏览器里的代码)通常靠远端的API服务提供业务逻辑上的支持,从而提供完整的用户体验。API服务可以是和客户端应用程序同时开发的,也可以是第三方的服务。大多数API调用基于HTTP(S)实现,并且遵循REST规范;目前移动端也会采用基于HTTP/2的gRPC协议。
在绝大多数应用场景中,API设计时以快速响应为目标,指标上体现为100ms以内。可能导致响应延迟的因素很多,包括:
应用的托管模式:物理机/虚拟机/容器及对应的编排方案安全组件:流量清洗服务等client 与 server 的物理距离:如果一个中国的浏览器访问美国的网站,不考虑CDN、中间没有任何路由,数据也要绕一个地球,额外增加130ms的延迟;网络基础设施:中转路由器的数量、性能(是否有专线)、网络接入速度、网卡速度等当前的网络负载:如果负载比较高,甚至有网络拥塞,响应会变慢请求报文的大小要处理事件的队列大小后端处理请求的耗时
上面提到的任何因素都会增加网络延迟。一些可以通过对后端服务扩容来缓解,另外一些可能是开发者不可控的部分,比如网络基础设施。大多数API都能够在同一个连接上快速响应请求。client端代码可以以非阻塞的方式进行API同步调用,比较适合IO密集型服务。注意:非阻塞看起来有点像异步处理,但阻塞/非阻塞、同步/异步是完全不同的概念,阻塞/非阻塞是线程从网卡读取/发送数据角度的概念,同步/异步是服务架构角度client/server进行交互相关的概念,网上有很多相关的面试八股,这里不细说了。
尽管常规的API对短请求做了很多优化,在一些业务场景中,后端处理的时间比较长,以秒、分甚至小时作为计时单位。这类场景下,如果等着任务完成后再响应客户端的请求,必然会有问题。
为了解决这类问题,一些架构引入了消息队列中间件,消息队列把请求阶段和响应阶段进行了分离。这类架构的额外收益是可以对请求进行削峰,以避免高峰期服务器被打垮,比如美国和印度每年一次的报税日。请求-响应分离还允许前端和后端API能够独立进行扩缩容,同时增加了架构的复杂度,因为client接收成功通知也需要异步处理。
在异步请求-响应模式下要考量很多个方面,分布式系统中Server间的REST API调用存在同样的问题,一般出现在微服务架构中。
解决方案
最简单的解决方案是HTTP轮询。在client端(浏览器/安卓/iOS/小程序等)代码中我们只能轮询,因为client端基本上无法提供回调接口,也很难使用长连接。即便理论上回调是可支持的,额外的库依赖或服务依赖使得技术方案极其复杂,让人望而却步。
使用HTTP轮询的规范如下:
client端发起对API服务的同步调用,触发一个长任务的创建和执行;API服务在最短的时间内对client端同步做出响应。返回的HTTP 202 (Accepted),表示请求已经被接收;
注意:API服务在创建长任务之前,应当校验请求的合法性和即将执行的操作的合理性。如果请求不合理,应当立即返回400(Bad Request)
响应中应当携带任务状态的轮询地址,以便client端后续检查长任务的执行状态;API服务把请求存储到数据库,并发送给消息队列中间件;对于每一个状态轮询请求,如果成功则返回200。如果任务还处于执行中,响应中应该包含对应的状态码。一旦任务执行完成,响应中应当包含一个状态码,标识任务完成,或重定向到一个新的URL(查看任务产出的数据)。
一个典型的工作流如下图所示:
client发送一个请求,接收到状态码为 HTTP 202 (Accepted) 的响应;client持续对状态查询接口发起HTTP GET请求,任务状态为执行中,所以返回HTTP 200;在某个时间点,任务执行完成,查询接口返回HTTP 302 (Found),并重定向到对应的资源;client 从资源URL获取任务的执行结果;问题和注意事项仅仅基于HTTP,异步请求-响应模式就有很多实现方式,并不是所有上游服务都遵循同样的语义。举个例子,当远端的任务还在执行中时,大多数服务的状态查询接口并不会返回HTTP 202;如果遵循纯粹的REST语义,应该返回HTTP 404(Not Found)。由于请求的结果还没有产出,这两种状态码都是合理的;HTTP 202 的响应应该包含轮询的地址和频率,通常定义在header里:
Header
描述
补充说明
Location
client轮询任务状态的地址URL
如果涉及权限校验,URL里可以包含一个token
Retry-After
预估任务完成的时间
这个header时为了避免client不停地retry
我们可以使用代理去操作响应的header或payload(数据)如果状态查询接口重定向到任务完成的资源链接,可以使用 HTTP 302/303;任务创建成功后,client再去调用 header["Location"] 中的URL,接收到的状态码应当是 200(OK), 201(Created), 或204(No Content) ;如果在任务执行中出现错误,应该把错误在 header["Location"] 中的URL中体现出来,如果client访问这个URL应当接收到4xx错误;旧的client可能不支持该模式,就需要我们在异步服务至上封装一层同步API,以保证兼容性;在一些场景中,API需要提供取消机制,通常以提供取消接口的方式实现;什么时候(不)使用该模式
该模式适合的场景有:
client端代码很难提供回调接口,或者使用长链接代价过高。比如网络浏览器、移动端app;服务端调用只支持HTTP协议,且由于防火墙Server端无法发起回调;服务端需要兼容比较旧的系统,无法使用现代的回调技术,比如WebSocket 或 Webhook。
该模式不适合的场景有:
有现成通知机制的服务;响应必须以实时流的方式返回给client,比如SSE;client必须从多个服务获取结果,对每个结果的延迟比较敏感。这时可以考虑Service Bus模式;可使用持久化连接技术,比如WebSockets或SignalR,client-server可借助这些技术实现双向通信;网络防火墙允许开放一些端口,接收异步回调或webhooks;例子调度框架:比如大数据调度框架YARN、容器编排工具Kubernetes;支付场景:需要先对transaction进行持久化,再扔到队列里,由worker捡起任务执行;大量数据的下载
有一些比较典型的数据量大但不适用异步的场景,比如推广搜中的广告召回等;
还有一些场景处于模糊地带,比如大数据计算,从早期基于Hive/Spark的异步计算,逐渐演变到Clickhouse的同步计算,大大提升了用户体验。