后台打印程序概念/ API和渠道:将服务传递到服务HTTP中的队列

时间:2019-06-01 09:03:30

标签: go

在这里已经获得了一些帮助,这使我在尝试这一概念时向前迈进了一步,但是它仍然无法正常工作,我遇到了我似乎无法解决的冲突。

我在这里尝试在流程图中说明我想要的内容-请注意,客户端可以有许多将通过printjobs发送的客户端,因此我们当时无法答复工人正在处理我们的工作,但是对于大多数情况下(高峰时间不会,因为打印的处理工作会花费时间)。

enter image description here

type QueueElement struct {
    jobid string
    rw   http.ResponseWriter
  doneChan chan struct{}
}

type GlobalVars struct {
    db   *sql.DB
    wg   sync.WaitGroup
    jobs chan QueueElement
}

func (gv *GlobalVars) ServeHTTP(w http.ResponseWriter, r *http.Request) {

    switch r.URL.Path {
    case "/StartJob":
        fmt.Printf ("incoming\r\n")

            doneC := make(chan struct{}, 1) //Buffered channel in order not to block the worker routine
            newPrintJob := QueueElement{
                    doneChan: doneC,    
                    jobid:    "jobid",
            }

            gv.jobs <- newPrintJob
            func(doneChan chan struct{},w http.ResponseWriter) {

                  ctx, cancel := context.WithTimeout(context.Background(), 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")
                            fmt.Printf ("took longer than 5 secs\r\n")
                    case <-doneChan:
                            fmt.Fprintf(w, "instant reply from serveHTTP\r\n")
                            fmt.Printf ("instant\r\n")
                    }
            }(doneC,w)

    default:
            fmt.Fprintf(w, "No such Api")
    }
}

func worker(jobs <-chan QueueElement) {
    for {
            job := <-jobs
            processExec ("START /i /b try.cmd")
            fmt.Printf ("job done")
        //  processExec("START /i /b processAndPrint.exe -" + job.jobid)
            job.doneChan <- struct{}{}

    }
}

上面的代码是serveHTTP的代码,工作人员在这里提供了帮助,最初,ServeHTTP内的func是一个go例程,在这里,整个冲突对我来说都是一个问题-概念是在serveHTTP中它产生了一个进程如果该员工能够在5秒钟内及时处理该工作,它将得到该员工的答复。

如果工作能够在1秒钟内完成,我想在那1秒钟后立即回覆给客户,如果需要3秒钟,我想在3秒钟后回复,如果花费超过5秒钟,我会在之后回复5秒(如果这项工作需要13秒,我仍然想在5秒后回复)-客户必须从现在开始轮询该工作-但冲突是:

a)当ServeHTTP退出时-然后ResponseWriter关闭-为了能够回复客户端,我们必须将答案写到ResponseWriter。

b)如果我阻止了serveHTTP(例如下面的代码示例,其中我未将func作为go例程调用),那么它不仅会影响单个API调用,而且似乎还会影响此后的所有其他API,因此第一次呼叫将得到正确的及时服务,但在第一次呼叫之后的同一时间将被阻塞操作延迟。

c)如果我不阻止它-并将其更改为执行例程:

    gv.jobs <- newPrintJob
    go func(doneChan chan struct{},w http.ResponseWriter) {

然后没有延迟-可以调用许多api-但问题是在这里serveHTTP立即存在,从而杀死了ResponseWriter,然后我无法回复给客户端!

这真的让我头痛不已,我该如何解决这个冲突,在这种情况下我不会造成任何服务HTTP阻塞,因此我可以并行处理所有请求,但仍能够回复有问题的ResponseWriter。

即使函数退出,是否仍可以阻止serveHTTP关闭responsewriter?

3 个答案:

答案 0 :(得分:1)

我已经为您的代码添加了一些更新。现在,它可以按照您所描述的那样工作。

package main

import (
    "database/sql"
    "fmt"
    "log"
    "math/rand"
    "net/http"
    "sync"
    "time"
)

type QueueElement struct {
    jobid    string
    rw       http.ResponseWriter
    doneChan chan struct{}
}

type GlobalVars struct {
    db   *sql.DB
    wg   sync.WaitGroup
    jobs chan QueueElement
}

func (gv *GlobalVars) ServeHTTP(w http.ResponseWriter, r *http.Request) {

    switch r.URL.Path {
    case "/StartJob":
        fmt.Printf("incoming\r\n")

        doneC := make(chan struct{}, 1) //Buffered channel in order not to block the worker routine

        go func(doneChan chan struct{}, w http.ResponseWriter) {
            gv.jobs <- QueueElement{
                doneChan: doneC,
                jobid:    "jobid",
            }
        }(doneC, w)

        select {
        case <-time.Tick(time.Second * 5):
            fmt.Fprintf(w, "job is taking more than 5 seconds to complete\r\n")
            fmt.Printf("took longer than 5 secs\r\n")
        case <-doneC:
            fmt.Fprintf(w, "instant reply from serveHTTP\r\n")
            fmt.Printf("instant\r\n")
        }
    default:
        fmt.Fprintf(w, "No such Api")
    }
}

func worker(jobs <-chan QueueElement) {
    for {
        job := <-jobs
        fmt.Println("START /i /b try.cmd")
        fmt.Printf("job done")

        randTimeDuration := time.Second * time.Duration(rand.Intn(7))

        time.Sleep(randTimeDuration)

        //  processExec("START /i /b processAndPrint.exe -" + job.jobid)
        job.doneChan <- struct{}{}
    }
}

func main() {

    // create a GlobalVars instance
    gv := GlobalVars{
        //db:   db,
        jobs: make(chan QueueElement),
    }
    go worker(gv.jobs)
    // create an http.Server instance and specify our job manager as
    // the handler for requests.
    server := http.Server{
        Handler: &gv,
        Addr:    ":8888",
    }
    // start server and accept connections.
    log.Fatal(server.ListenAndServe())
}

答案 1 :(得分:0)

select语句应位于goroutine函数之外,并阻塞请求,直到作业执行结束或达到超时为止。

答案 2 :(得分:0)

是的,您的观点是正确的“ c)如果我不阻止它的话”

为了保存响应编写器,您不应在其中调用go例程。相反,您应该将 ServeHTTP 作为常规例程调用,大多数http服务器实现都可以这样做。
这样,您就不会阻止任何api调用,每个api调用都将在不同的go例程中运行,并受到其功能的阻止。

由于您的“ jobs chan QueueElement” 是单个通道(而不是缓冲通道),因此所有进程都在“ gv.jobs <-newPrintJob” 处被阻止。
您应该使用缓冲通道,以便所有api调用都可以将其添加到队列中,并根据工作完成或超时来获取响应。

具有缓冲通道也可以模拟打印机的实际内存限制。 (队列长度1是一种特殊情况)