处理来自RabbitMQ的消息时限制并发性

时间:2017-08-24 10:02:01

标签: go

我正在尝试从队列中读取URL(RabbitMQ)并进行有限数量的并发HTTP请求,即拥有10个工作池,对从队列中接收的URL进行并发请求(永远)。

到目前为止,我已根据RabbitMQ教程实现了一个消费者: https://www.rabbitmq.com/tutorials/tutorial-one-go.html

并尝试了在网络上发现的示例中的一些方法,结束于此处的示例: http://jmoiron.net/blog/limiting-concurrency-in-go/

不幸的是,我当前的代码运行了大约一分钟,然后无限期冻结。我已经尝试过添加/移动常规程序,但我似乎无法按预期工作(我对Go来说很新)。

当前代码:

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/Xide/bloom"
    "github.com/streadway/amqp"
)

func failOnError(err error, msg string) {
    if err != nil {
        log.Fatalf("%s: %s", msg, err)
        panic(fmt.Sprintf("%s: %s", msg, err))
    }
}

var netClient = &http.Client{
    Timeout: time.Second * 10,
}

func getRequest(url string) {
    //resp, err := http.Get(string(url))
    resp, err := netClient.Get(string(url))
    if err != nil {
        log.Printf("HTTP request error: %s", err)
        return
    }
    fmt.Println("StatusCode:", resp.StatusCode)
    fmt.Println(resp.Request.URL)
}

func main() {
    bf := bloom.NewDefaultScalable(0.1)

    conn, err := amqp.Dial("amqp://127.0.0.1:5672/")
    failOnError(err, "Failed to connect to RabbitMQ")
    defer conn.Close()

    ch, err := conn.Channel()
    failOnError(err, "Failed to open a channel")
    defer ch.Close()

    q, err := ch.QueueDeclare(
        "urls",            // name
        true,              // durable
        false,             // delete when unused
        false,             // exclusive
        false,             // no-wait
        nil,               // arguments
    )
    failOnError(err, "Failed to declare a queue")

    err = ch.Qos(
        1,     // prefetch count
        0,     // prefetch size
        false, //global
    )
    failOnError(err, "Failed to set Qos")

    msgs, err := ch.Consume(
        q.Name, // queue
        "",     // consumer
        false,  // auto-ack
        false,  // exclusive
        false,  // no-local
        false,  // no-wait
        nil,    // args
    )
    failOnError(err, "Failed to register a consumer")

    forever := make(chan bool)

    concurrency := 10
    sem := make(chan bool, concurrency)
    go func() {
        for d := range msgs {
            sem <- true
            url := string(d.Body)
            if bf.Match(url) == false {
                bf.Feed(url)
                log.Printf("Not seen: %s", d.Body)
                go func(url string) {
                    defer func() { <-sem }()
                    getRequest(url)
                }(url)
            } else {
                log.Printf("Already seen: %s", d.Body)
            }
            d.Ack(false)
        }
        for i := 0; i < cap(sem); i++ {
            sem <- true
        }
    }()

    log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
    <-forever
}

2 个答案:

答案 0 :(得分:3)

您没有正确处理HTTP响应,从而导致一组不断增加的打开连接。试试这个:

func getRequest(url string) {
    resp, err := netClient.Get(string(url))
    if err != nil {
        log.Printf("HTTP request error: %s", err)
        return
    }
    // Add this bit:
    defer func() {
        io.Copy(ioutil.Discard, resp.Body)
        resp.Body.Close()
    }()
    fmt.Println("StatusCode:", resp.StatusCode)
    fmt.Println(resp.Request.URL)
}

在您完成从频道阅读邮件后,这似乎没有必要,也可能存在问题:

    for i := 0; i < cap(sem); i++ {
        sem <- true
    }

为什么在读完队列中的所有邮件后填写sem频道?您已经向通道添加了与您希望从中读取的消息一样多的消息,因此这对于来说是毫无意义的,如果对其余代码进行了错误的更改,则可能会导致问题。

与您的问题无关,但这是多余的:

if err != nil {
    log.Fatalf("%s: %s", msg, err)
    panic(fmt.Sprintf("%s: %s", msg, err))
}

每{{}}},Fatalf已退出,因此永远不会调用panic。如果您要记录并panic,请尝试the documentation,这是为此目的而设计的。

答案 1 :(得分:0)

当您收到消息时,您正在添加sem,但只有在您看到网址时才会从sem移除。

所以,一旦你已经看过&#34;已经看过&#34; 10个网址,您的应用将锁定。 因此,您需要将<-sem添加到记录了#34;已经看过&#34;的其他语句中。

无论哪种方式,这是一种相当奇怪的方式来实现这种并发性。 这是一个以更惯用的方式on Play执行此操作的版本。

请注意,在这个版本中,我们只会产生10个侦听兔子频道的goroutine。

package main

导入(     &#34; FMT&#34;     &#34;登录&#34;     &#34;净/ HTTP&#34;     &#34;时间&#34;

"github.com/Xide/bloom"
"github.com/streadway/amqp"

func failOnError(错误错误,msg字符串){     if err!= nil {         log.Fatalf(&#34;%s:%s&#34;,msg,err)     } }

var netClient =&amp; http.Client {     超时:time.Second * 10, }

func getRequest(url string){     // resp,err:= http.Get(string(url))     resp,err:= netClient.Get(string(url))     if err!= nil {         log.Printf(&#34; HTTP请求错误:%s&#34;,错误)         返回     }     resp.Body.Close()     fmt.Println(&#34; StatusCode:&#34;,resp.StatusCode)     fmt.Println(resp.Request.URL) }

func main() {
    bf := bloom.NewDefaultScalable(0.1)

    conn, err := amqp.Dial("amqp://127.0.0.1:5672/")
    failOnError(err, "Failed to connect to RabbitMQ")
    defer conn.Close()

    ch, err := conn.Channel()
    failOnError(err, "Failed to open a channel")
    defer ch.Close()

    q, err := ch.QueueDeclare(
        "urls", // name
        true,   // durable
        false,  // delete when unused
        false,  // exclusive
        false,  // no-wait
        nil,    // arguments
    )
    failOnError(err, "Failed to declare a queue")

    err = ch.Qos(
        1,     // prefetch count
        0,     // prefetch size
        false, //global
    )
    failOnError(err, "Failed to set Qos")

    msgs, err := ch.Consume(
        q.Name, // queue
        "",     // consumer
        false,  // auto-ack
        false,  // exclusive
        false,  // no-local
        false,  // no-wait
        nil,    // args
    )
    failOnError(err, "Failed to register a consumer")

    concurrency := 10
    var wg sync.Waitgroup              // used to coordinate when they are done, ie: if rabbit conn was closed
    for x := 0; x < concurrency; x++ { // spawn 10 goroutines, all reading from the rabbit channel
        wg.Add(1)
        go func() {
            defer wg.Done() // signal that this goroutine is done
            for d := range msgs {
                url := string(d.Body)
                if bf.Match(url) == false {
                    bf.Feed(url)
                    log.Printf("Not seen: %s", d.Body)
                    getRequest(url)
                } else {
                    log.Printf("Already seen: %s", d.Body)
                }
                d.Ack(false)
            }
            log.Println("msgs channel closed")
        }()
    }

    log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
    wg.Wait() // when all goroutine's exit, the app exits
}