任务队列以及我们为什么需要它们。
背景介绍:
我们该如何实现这个目标?
结论
封面照片:© Unsplash/Camille Chen
背景介绍:
什么是任务队列?为什么需要它?
类比
好,为了回答这个问题,我们来设想一个场景。
假设有一家餐厅,餐厅里有几名员工(比如10名),包括服务员、厨师、收银员、接待员、经理等等。现在,请回想一下你在餐厅点餐时会发生什么。
- 请告知您的需求🗣️。(请求)
- 服务员记下您的订单📄,并向您保证您的餐点很快就会准备好🛎️。(确认)
- 服务员把你的订单交给厨师🧑🍳,厨师会把它添加到订单列表中。(排队)
- 然后服务员去接受另一位顾客的点餐👥。(下一个要求)
- 多位厨师可能正在根据订单准备食物🥪,他们可能逐一准备,也可能同时准备多道菜⌛。(流程)
- 过一会儿,当你的菜做好后,厨师会叫服务员把菜递给你🥪。(出餐)
- 服务员过来为你上菜😋。(回复)
- 然后服务员去招呼另一位顾客。(下一个要求)
服务员和厨师是分开的,服务员负责点餐,厨师负责准备食物。
现在想象一下,如果所有员工都能胜任各种工作(点餐、烹饪等等),
那么工作流程就会变成这样。
- 服务员过来,记下你的订单📄,并告诉你你的食物马上就好。
- 同一个服务员会带着你的订单去厨房🏃,开始准备它们🧑🍳。
- 当他/她准备好你的食物后,会回来🏃并为你上菜🥪。
你可能觉得这没什么大不了的,对吧?再想想,这家餐厅只有10个员工,如果有20或25位顾客在排队点餐怎么办?
前一种处理订单的方式可以轻松应对压力,但后一种方式肯定会崩溃🚧,因为如果所有员工都在忙着为前10位顾客准备食物,谁👻来接待剩下的顾客?如果新来的顾客几分钟内得不到服务,他们肯定会离开😠。
我们需要它们去哪里?
当我们构建需要服务器端执行耗时较长(超过几毫秒)或长时间运行的 Web 应用/服务时,与简单的 CRUD 操作(例如复杂的计算、文件处理或数据分析)不同,我们应该始终使用任务队列。您可以将其理解为更高级的异步机制(类似于 JavaScript 中的 Promise 或 Async/await)。任务队列可以帮助我们将任务加入队列进行处理,并在实际处理之前立即向客户端发送确认信息,然后继续处理下一个请求(就像服务员一样)。另一个服务器(或者可能是同一个服务器,但它会启动另一个工作实例/进程)会检查任务队列中是否有待处理的任务,并进行处理(就像厨师一样)。任务完成后,它会向 API 服务器发送确认信息,API 服务器再通过 WebSocket、推送通知、电子邮件或您能想到的任何其他方式通知客户端任务已完成。
如果你的 API 服务器像第二个例子中的餐厅那样一次性处理所有请求,速度就会变得非常慢⏱️,因为服务器会接收你的请求,处理它,完成繁重的计算🏋️(这很耗时),然后一次性返回响应。这意味着客户端必须等待整个操作完成,浏览器会一直加载🐌,直到服务器发送响应。如果有人在这期间发送了新的请求,服务器必须先处理完第一个请求才能处理第二个请求并返回响应。想象一下,如果每秒有成千上万个请求,那速度会变得非常慢,体验也会非常糟糕🙅。
我们该如何实现这个目标?
在详细介绍如何使用任务队列之前,让我先介绍一下本系列文章中广泛使用的一些术语。
- 队列 -队列就像实际的队列,其中类似的作业/任务被分组在一起,等待由工作人员以 FIFO(先进先出)的方式进行处理。
- 作业/任务 -它们是包含有关待处理作业的实际详细信息的对象。
- 发布者——负责将任务添加到队列中的人员。
- 消费者 -它监视作业队列中是否有待处理的任务,并将其发送进行处理。
- Worker——实际执行任务并通知任务是否成功的底层机制。如果需要,可以将 Worker 的逻辑放在 Consumer 内部。
现在你已经有了基本的了解,让我们来深入了解细节。
- 首先,我们搭建了一个 API 服务器,并设置了一些端点来响应客户端的 HTTP 请求。
- API 服务器将任务发布到其相应的队列,并向客户端发送某种确认信息,例如 ```json
{
"job": "conversion",
"id": "dcj32q3",
"status": "ok"
}
or in case it fails
```json
{
"job": "conversion",
"id": "dcj32q5",
"status": "failed",
"reason": "auth_failed"
}
并关闭连接。
- 消费者监视并消费队列,并将任务发送给工作进程进行处理。
- 工作进程处理任务(一次可以处理一个或多个),
progress并根据需要报告处理过程中的状态,任务完成后会触发一个事件。需要注意的是,任务在这个阶段也可能失败,因此它会触发一个success事件failure,以便进行相应的处理。 - API 服务器查询
progress并将其报告给客户端(通过 websockets 或轮询 XHR/Fetch 请求),以便应用程序可以在 UI 中显示漂亮的进度条。 - 它还会监听
success或failure事件,并向客户端发送通知。 - 客户端现在可以通过另一个 API 调用来请求资源,服务器会将请求的资源响应给客户端并关闭连接。
这样一来,客户就能立即得到保证。
嘿,我正在处理你的任务。完成后我会通知你,在此期间你可以做点其他事情。
这样一来,用户无需长时间等待,服务器也能高效地处理更多传入请求。
任务队列本质上是将所有这些组件(API 服务器和工作进程)连接起来,使它们协同工作,将负载从 API 服务器转移到工作进程,从而确保更低的响应时间和更少的停机时间。
结论
太棒了!🎉 现在你应该已经了解了任务队列的基础知识,明白了为什么需要它以及它的优势✨。仔细想想,这种架构具有很高的横向可扩展性,可以通过增加工作进程来应对不断增长的需求。
希望这篇文章对初学者有所帮助。如果你喜欢这篇文章,请点个赞💗,并继续关注📻更多精彩内容。
如有任何疑问或建议,请在下方留言,也欢迎随时联系我😄。
下一篇文章将提供一份分步指南,介绍如何在 Node.js 中设置一个简单的任务队列。
