我这里有一个概念,我不知道如何正确解决问题,而对Go中的系统的影响却最小。
我正在创建一个“打印后台处理程序”,客户端可以在该后台处理程序上调用API(/ StartJob)来处理打印作业。
由于只有一台打印机,所以瓶颈是一个工人同时处理每个作业,但是客户可以在任何给定时间通过一个作业,因此它将排队,并且该工人将同时处理每个作业它需要逐步进行。
我这样做的方式是ServeHTTP将作业推送到通道上(请注意,这里我只是传递ID,工作人员将从该ID中查找打印数据):
func (gv *GlobalVariables) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/StartJob":
newPrintJob := QueueElement {jobid: "jobid"}
gv.printQueue <- newPrintJob
fmt.Fprintf(w, "instant reply from serveHTTP\r\n")
default:
fmt.Fprintf(w, "No such Api")
}
}
然后,Worker一直运行,并处理所有传入的作业。实际代码还有很多,但最后它执行一个外部过程:
func worker(jobs <-chan QueueElement) {
for {
job := <-jobs
processExec ("START /i /b processAndPrint.exe -"+job.jobid)
}
这里的事情是外部过程可能要花一些时间才能执行,有时它需要立即执行,但在某些情况下,返回任务之前可能需要1分钟才能执行。
我的问题是,现在在serverHTTP中,我不知不觉地立即写回客户端,如果该作业是第一行并且可以立即处理,或者是否已经排队等待,可能需要几秒钟的时间,或者处理前几分钟:
fmt.Fprintf(w, "instant reply from serveHTTP\r\n")
我想给客户最多5秒钟的时间来答复他的答复,如果他的工作在这段时间内得到了处理,或者没有,告诉他他需要稍后再打来检查他的工作状态。
我想到了几种方法:
在我的QueueElemnt中,我传递了http.ResponseWriter,因此我能够从Worker写入Responsewriter(回复给客户端)。如果我让serveHTTP处于睡眠状态,因为Go例程存在时ResponseWriter将关闭,我可以这样做。因此,在这里,我需要在serveHTTP中等待,然后在等待时允许工作进程写入ResponseWriter。
这种方法的问题是,如果工作距离很近,Worker不会向该ResponseWriter写任何东西,并且serveHTTP不会知道是否已从Worker发送了回复。
< / li>我可以为每个QueueElement创建一个通道,以便serveHTTP不仅可以使用工作程序,而且可以在工作程序处理后的实际作业之间相互通信。
这种方法我还没有测试过,但是我在这里还担心它的过大杀伤力以及对系统的沉重负担,因为我们可能会遇到这样的情况:我们有很多很多api请求传入,因此正在处理一个大队列,因此即使我需要在5秒钟后超时/取消操作,我仍然认为这个概念过于严格了?
我可能会在queueElement中传递一个互斥的值,这两个servHTTP最多可以检查5秒钟,并且队列可以进行检查/操作,但是如果作业完成,则queueElement消失了,所以可能导致冲突。
我可以做no 1)的变体,其中我写了自己的responsewriter,并且如果已经向其中写入了内容,则使用该标志,因此serveHTTP将对此进行最多5秒钟的检查,以检查是否Worker已经向客户端写了一个回复,在这种情况下,退出服务HTTP而没有答案,或者如果没有写,则serveHTTP会将消息写回到客户端,有点像this。
但是我感觉这些都不是很平稳,并且我不想永远启动大量的例程或通道,也不想将自己锁定在各处的互斥体中,因为我不知道它对系统的影响。
有人能以一种正确的工作方式协助我实施这种事情吗?我一直在阅读下一页的内容,却没有找到一种实现此目标的好方法。
答案 0 :(得分:2)
我认为最简单的方法是对第一个方法稍加修改。您可以将http.ResponseWriter
传递给工作程序,该工作程序将覆盖实际执行该工作的另一个工作程序,而“父”工作程序则等待其完成或超时。一旦两个事件之一首先发生,它将立即回复HTTP客户端。
func (gv *GlobalVariables) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/StartJob":
newPrintJob := QueueElement {writer: w, jobid: "jobid"}
gv.printQueue <- newPrintJob
fmt.Fprintf(w, "instant reply from serveHTTP\r\n")
default:
fmt.Fprintf(w, "No such Api")
}
}
func worker(jobs <-chan QueueElement) {
for {
done := make(chan struct{})
job := <-jobs
go func(d chan struct{}, q QueueElement) {
processExec ("START /i /b processAndPrint.exe -"+q.jobid)
d <- struct{}{}
}(done, job)
select {
//Timeout
case <-time.After(time.Second * 5):
fmt.Fprintf(w, "job is taking more than 5 seconds to complete\r\n")
//Job processing finished in time
case <-done:
fmt.Fprintf(w, "instant reply from serveHTTP\r\n")
}
}
您可以在收到HTTP请求后立即产生“ waiting” goroutine。这样,超时计时器将考虑到请求/作业的整个处理过程。
示例:
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func (gv *GlobalVariables) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/StartJob":
doneC := make(chan struct{}, 1) //Buffered channel in order not to block the worker routine
newPrintJob := QueueElement{
doneChan: doneC,
jobid: "jobid",
}
go func(doneChan chan struct{}) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
select {
//If this triggers first, then this waiting goroutine would exit
//and nobody would be listeding the 'doneChan'. This is why it has to be buffered.
case <-ctx.Done():
fmt.Fprintf(w, "job is taking more than 5 seconds to complete\r\n")
case <-doneChan:
fmt.Fprintf(w, "instant reply from serveHTTP\r\n")
}
}(doneC)
gv.printQueue <- newPrintJob
default:
fmt.Fprintf(w, "No such Api")
}
}
func worker(jobs <-chan QueueElement) {
for {
job := <-jobs
processExec("START /i /b processAndPrint.exe -" + job.jobid)
job.doneChan <- struct{}{}
}
}
答案 1 :(得分:0)
我将避免长时间保留请求,因为我们不确定何时处理该作业。
我能想到的一种方法是:
最初从服务器回复为已接受/排队,然后返回job_id。
{
"job_id": "1",
"status": "queued"
}
客户端可以轮询(例如每5秒一次)或使用长时间轮询来检查作业的状态。
何时运行:
{
"job_id": "1",
"status": "processing"
}
完成后:
{
"job_id": "1",
"status": "success/failed"
}