我正在尝试从队列中读取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
}
答案 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
}