如何在serverHTTP和通道之间正确实现延迟的答复/超时?

时间:2019-05-31 06:10:15

标签: go

我这里有一个概念,我不知道如何正确解决问题,而对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秒钟的时间来答复他的答复,如果他的工作在这段时间内得到了处理,或者没有,告诉他他需要稍后再打来检查他的工作状态。

我想到了几种方法:

  1. 在我的QueueElemnt中,我传递了http.ResponseWriter,因此我能够从Worker写入Responsewriter(回复给客户端)。如果我让serveHTTP处于睡眠状态,因为Go例程存在时ResponseWriter将关闭,我可以这样做。因此,在这里,我需要在serveHTTP中等待,然后在等待时允许工作进程写入ResponseWriter。

    这种方法的问题是,如果工作距离很近,Worker不会向该ResponseWriter写任何东西,并且serveHTTP不会知道是否已从Worker发送了回复。

    < / li>
  2. 我可以为每个QueueElement创建一个通道,以便serveHTTP不仅可以使用工作程序,而且可以在工作程序处理后的实际作业之间相互通信。

    这种方法我还没有测试过,但是我在这里还担心它的过大杀伤力以及对系统的沉重负担,因为我们可能会遇到这样的情况:我们有很多很多api请求传入,因此正在处理一个大队列,因此即使我需要在5秒钟后超时/取消操作,我仍然认为这个概念过于严格了?

  3. 我可能会在queueElement中传递一个互斥的值,这两个servHTTP最多可以检查5秒钟,并且队列可以进行检查/操作,但是如果作业完成,则queueElement消失了,所以可能导致冲突。

  4. 我可以做no 1)的变体,其中我写了自己的responsewriter,并且如果已经向其中写入了内容,则使用该标志,因此serveHTTP将对此进行最多5秒钟的检查,以检查是否Worker已经向客户端写了一个回复,在这种情况下,退出服务HTTP而没有答案,或者如果没有写,则serveHTTP会将消息写回到客户端,有点像this

但是我感觉这些都不是很平稳,并且我不想永远启动大量的例程或通道,也不想将自己锁定在各处的互斥体中,因为我不知道它对系统的影响。

有人能以一种正确的工作方式协助我实施这种事情吗?我一直在阅读下一页的内容,却没有找到一种实现此目标的好方法。

2 个答案:

答案 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"
}