什么是 任务与消息队列?

异步任务

任务(Task)是Web后端开发中一个常见的场景。

举个例子,有一个用户注册扇贝单词的账号时,

发送了一个请求,带有用户名/密码/邮箱/手机号等信息。

在这个请求到达服务端时,我们要做的事情可能有:

在User表里创建一条新记录

给该用户发送验证邮件

给该用户发送注册短信

给该用户初始化各种学习数据

返回注册成功的响应

步骤很多,如果都在请求的生命周期内去做的话

会导致响应时间很高,进而减弱服务端处理请求的并发量

其实我们分析一下会发现:这个请求的响应内容只是事件1的结果:创建成功或者创建失败。

而标号2/3/4的事件非常缓慢.而且和响应结果并不相关。这几件事可以不用阻塞在请求的生命周期里,而是异步的去做。只要我们知道,最终这些事情一定会被执行就可以了。

这样一来,步骤就变成了:

创建User记录;

启动一个Task异步地去做2/3/4;

不用管Task的处理,直接返回注册成功响应。

大大的缩短了响应时间!

总结一下就是说在一个请求到达服务端时 Web程序需要处理一些事情

然后把响应返回给客户端。但有时因为这些事情太重,而且我们并不在意这些事情的结果(只在意它们在有限时间内一定会被执行)那我们可以异步地执行他们,这些事情被叫做一种任务(Task)。

那这种Task应该怎么样实现呢?

有同学会想到:我们可以把这些很重的事情包装成一个函数,

在请求来了之后另起一个线程或进程去执行这个函数。

可是,这样做只是看起来解决了响应时间的问题。

想象一下,如果某天扇贝做了一些推广活动,注册人数突然暴增,瞬间机器上就要新建上千个进程(线程)做这些耗时的重操作。而且可能在大量Task还未结束时,注册的请求还在源源不断的过来,这时我们的Web服务器可能已经挂掉了。

在实际生产环境中,我们会使用消息队列中间件来控制Task的执行。

消息队列

分析一下上面的场景和实现方式导致我们会挂掉的原因本质上还是:在大量请求(几乎)同时过来的时候

我们去瞬时地启动Task,想要把他们(几乎)同时的完成。

这就不得不依赖机器本身的性能

而分析上面Task的需求:

给用户发注册邮件和短信,然后给他初始化学习数据。

这些事情根本没有瞬时完成的必要,即使要花费几十秒甚至两三分钟也是完全可以接受的。那么,我们设置一个Task的最大并行数,然后让这些Task排队去执行,问题就得到解决了。

消息队列就提供了这样的机制:

这个模型可以被简化为上图。这时生产者是接收HTTP请求的web server程序,它接收到一个注册的请求时,发送一个消息到消息队列

消费者是专门用来执行Task的程序(往往是单独用来执行Task的服务器)

他从消息队列取得消息,然后根据消息内容启动进程执行Task。

以此为例,讲一下消息队列的几种常见职能

错峰控流

上面让Task可以排队的方案,就是消息队列的一个重要职能:错峰控流。可能我们的web server能接收瞬时上万的请求,但是处理Task的速度是很难提升的,比如数据库和网络I/O等耗时的处理,又比如假设上例中短信系统限制了每秒只能发几百条,那处理完几百条请求我们就必须等待了。所以,利用消息队列这种中间的通信系统,等到下游有能力处理消息的时候再去处理,是一种合理的解决方案。

解耦

消息队列解决的本质问题,其实是逻辑的解耦:web server只用做完自己最核心的业务,然后发出去一条消息通知别人。以上面的场景为例,处理请求的服务器只需要往User表里写入一条新记录,然后带着这个记录的信息(user_id/邮箱/手机号/注册时间等)发出一条消息到消息队列,直接响应就结束了。不用关心后面不影响主流程的动作,也不会出现因短信通知失败而导致整个注册接口失败这种可怕的后果。

广播

假设扇贝的系统架构是这样的(实际上并不是这样):

处理注册请求的web server是一个web项目

而邮件系统和短信系统都是单独的rpc项目

另外存放学习数据的又是一个单独的web项目。

如果没有广播的功能,写注册接口的时候,我们需要另外再写一个函数,这个函数里去分别调用邮件系统/短信系统/用户学习数据系统提供的接口。

最后我们运行一个带着这个函数的消费者程序,它在收消息后运行这个函数。

有了广播的功能后,这四个系统才真正做到解耦

注册系统只管发送消息到消息队列,这条消息是被广播的,

另外三个系统都可以接收到消息,

而各自去决定收到这种消息时应该做什么。此时,另三个系统都是消费者

此时,注册系统不在意其他系统需要注册成功的消息做什么。

这个时候扇贝阅读这个业务突然决定:

在新用户注册成功后就给他送几本书(实际上并不是这样)

那它只需要从消息队列里接收被广播的注册成功消息,然后自己去做。而注册系统不会有任何改动

消息队列有很多个,他们的异同可以参考:https://www.zhihu.com/question/43557507

Python Web应用中常用的消息队列是RabbitMQ

通常搭配celery来做异步任务。

除了异步任务之外,celery还能实现:

定时任务(某一时刻或者时间周期 )和倒计时任务。

使用方式直接参考celery的文档即可。

本文是我同事小哥花费很多精力

总结出来的良心干货

最近也在知乎开了专栏

做后端开发,除了写代码,你还需要知道这些

  • 发表于:
  • 原文链接:https://kuaibao.qq.com/s/20180608B0ACLF00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券