在Web应用的开发中,不论是网站还是服务接口,我们可能会遇到来自客户端的某个请求,而这请求的背后,隐藏着要执行的大量的繁重任务,如果我们在后台程序中,同步的进行处理,那么程序执行时间比较久,用户体验是糟糕的,甚至会导致502执行超时。针对这种情况,有很多成熟的解决方案【据我粗浅的认知,使用队列是一个较好的方案】,但实现起来稍显复杂和繁琐。如果我们对要异步执行的任务没有特别的要求【比如失败重试或异步任务执行完毕后的事件回调】,那么,可以用一种非常轻松的方式来简单实现:nohup 要执行的命令 > /dev/null 2>&1 &。
如果,你看到这里,觉得这没什么新奇的,那说明你水平挺好,至少比我要好【发自真心】。也希望你在离开之前,说一说有没有啥更好的方法,分享一下。
应用场景目前,我在两种具体的场景下,实际使用了这种异步执行任务的方法。
1、早前,做一个网站,数据是另一个同事从第三方采集的,采集下来的数据,需要导入到我的数据库中,于是,我在网站后台提供了一个功能,一个文件上传导入的按钮,当同事将他采集的数据,通过文件上传的方式,保存在服务器上的时候,后台程序会读取这个文件的内容,并基于里面的数据,进行一下必要的分析,最后将分析后的数据,通过SQL写入到数据库中。此过程执行的快与慢,取决于数据文件的大小,一个几千行数据的文件,最后可能要执行一分多钟。如果采用传统的同步执行,那么从文件上传 -> 数据分析 -> 写入数据库 这整个过程中,浏览器都在转圈圈,若是时间再长点,执行就超时了,前功尽弃。所以,这里我采用了异步执行任务的方式,在数据文件成功上传后,服务端直接响应回浏览器,显示一个“数据导入成功,正在进行处理”的提示,整个前端的交互就到此为止,后面数据分析和写入数据库,就交给另一个单独的进程去处理了。
2、就在前两天,我刚用这种方式,写了这样的功能。我们做了一个APP【用APICloud做的一个不入流的APP】,当用户在使用APP发布了一个内容后,我们需要调用百度AI的内容审核接口,对用户发布的文字和图片,进行自动审核,如果发现其中包含不良信息,则自动审核不过。而这个调用百度接口的过程,是略微有点耗时的,这主要取决于用户发布的内容中所包含图片的多少,图多自然百度接口处理响应的慢。同样的,如果用同步的方式,用户发布内容 -> 调用百度接口 -> 等待接口返回数据 -> 判断是否审核通过,太耗时了,这样在用户看来,就是内容发出去之后,等待了好几秒,甚至十几秒,最后才有反应,这用户体验就太差了。所以,可以做到当用户发布了内容后,立刻提示“发布成功,正在审核”的字样,而在几秒钟之后,用户就看到他刚刚发布的内容审核通过,并出现在内容列表中的时候,是多么自然的一个过程。
实现思路所以,有的时候,异步的处理一下任务,还是很有必要的。既然咱们开头说到,在Web应用的开发中,那就跳不出Linux服务器,现如今除了.NET系的可能还会部署在Windows上【博客园貌似就不是】,其他的后端应用,基本都会部署在Linux上,而我们开头提到的实现方式,就是在Linux下的命令实现。
首先,要实现程序的异步执行,大概有两种方式:线程 和 进程【说大概,是因为听说有的语言还支持协程。嗯?什么鬼? -_-!!!】。像Java这种,支持线程的编程语言,异步执行可以用线程实现,也可以用进程实现【Runtime.exec()】;而像PHP这种,在默认情况下,是没有线程的,并且大家普遍也都不在PHP下使用线程,那么,这就只能通过其内部函数,调用外部进程,去实现异步任务的执行。
在PHP下,执行一个外部程序,并且要求这个外部程序是在后台运行,且不会让你的宿主程序等待挂起【宿主也就是执行调用外部命令的PHP程序】,有一点要特别注意,这是在官方手册的exec函数中特意提到的:
If a program is started with this function, in order for it to continue running in the background, the output of the program must be redirected to a file or another output stream. Failing to do so will cause PHP to hang until the execution of the program ends.
意思就是,为了让外部程序在后台运行,这个外部程序的输出【指标准输出【像 Python中的 print,PHP中的 echo 和 Java中的 System.out.print】和标准错误】必须重定向到一个文件或另一个输出流中。否则,宿主程序可能会挂起,等待外部程序执行完毕后,才会终止结束他的生命周期。