Go 在处理来自RabbitMQ的消息时限制并发性
我正在尝试从队列(RabbitMQ)读取URL,并进行有限数量的并发HTTP请求,即,有10个工作线程池对从队列接收的URL进行并发请求(永远) 到目前为止,我已经按照RabbitMQ教程实现了一个消费者: 并从网络上发现的示例中尝试了许多方法,以下面的示例结束: 不幸的是,我当前的代码运行大约一分钟,然后无限期冻结。我尝试过添加/移动围棋套路,但似乎无法让它按预期工作(我是新手) 当前代码:Go 在处理来自RabbitMQ的消息时限制并发性,go,Go,我正在尝试从队列(RabbitMQ)读取URL,并进行有限数量的并发HTTP请求,即,有10个工作线程池对从队列接收的URL进行并发请求(永远) 到目前为止,我已经按照RabbitMQ教程实现了一个消费者: 并从网络上发现的示例中尝试了许多方法,以下面的示例结束: 不幸的是,我当前的代码运行大约一分钟,然后无限期冻结。我尝试过添加/移动围棋套路,但似乎无法让它按预期工作(我是新手) 当前代码: package main import ( "fmt" "log" "n
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
}
主程序包
进口(
“fmt”
“日志”
“net/http”
“时间”
“github.com/Xide/bloom”
“github.com/streadway/amqp”
)
func failOnError(错误,消息字符串){
如果错误!=零{
log.Fatalf(“%s:%s”,消息,错误)
死机(fmt.Sprintf(“%s:%s”,消息,错误))
}
}
var netClient=&http.Client{
超时:时间。秒*10,
}
func getRequest(url字符串){
//resp,err:=http.Get(字符串(url))
resp,err:=netClient.Get(字符串(url))
如果错误!=零{
Printf(“HTTP请求错误:%s”,错误)
返回
}
fmt.Println(“状态代码:”,对应状态代码)
fmt.Println(resp.Request.URL)
}
func main(){
bf:=bloom.NewDefaultScalable(0.1)
连接,错误:=amqp.拨号(“amqp://127.0.0.1:5672/")
FailOneError(错误,“未能连接到RabbitMQ”)
延迟连接关闭()
通道,错误:=连接通道()
FailOneError(错误,“无法打开通道”)
延迟关闭
q、 错误:=ch.QueueDeclare(
“URL”,//名称
正确,//持久
false,//未使用时删除
false,//独占
false,//没有等待
nil,//参数
)
FailOneError(错误,“未能声明队列”)
err=ch.Qos(
1,//预取计数
0,//预取大小
false,//全局
)
FailOneError(错误,“设置Qos失败”)
msgs,err:=ch.Consume(
q、 名称,//队列
“”,//消费者
false,//自动确认
false,//独占
false,//没有本地
false,//没有等待
nil,//args
)
FailOneError(错误,“未能注册消费者”)
永远:=制造(陈波)
并发性:=10
sem:=make(chan bool,并发)
go func(){
对于d:=范围msgs{
sem您没有正确处理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
}
根据,Fatalf
已经存在,因此将永远不会调用panic
。如果您想登录并panic
,请尝试,这是专门为此设计的。当您收到消息时,您将添加到sem
,但只有在您没有看到url时才从sem
中删除
因此,一旦你“已经看到”10个URL,你的应用程序就会锁定。
因此,您需要添加您是否可以将日志输出添加到问题中,这将帮助人们了解使用-race
标志运行程序的情况,这可能有助于您进行调试:当并发设置为10时,它会发出大约60个HTTP请求(逐渐变慢)然后冻结。Building with-race不提供任何信息。说“客户端在完成响应体后必须关闭响应体:”我在代码中找不到关闭响应体的位置。所以我猜想,所有这些连接都会不确定地保持打开状态(但只有60个调用,这应该已经不是问题了。)如果我没记错的话,如果您没有完全阅读响应的主体,那么也会有问题,但是我找不到指向它的文档。但是我记得我做过类似于io.Copy(resp.body,ioutil.Discard)的事情
或其他什么。可能这是迷信。上面的示例存在:panic:sync:negative WaitGroup counter
@david budworth更新示例时,我忽略了使用工作进程数(并发)初始化WaitGroup。我无法实际运行该应用程序,因为我没有任何提交项目,因此您可能需要进行一些调整。重点更在于展示另一种方式,并解释解决方案挂起的原因。var wg sync.Waitgroup
=>var wg sync.Waitgroup
和添加wg.add(并发性)
@Cui是的,这更直接,只是wg.Add(并发)
,但我通常更喜欢在wg.Add(1)
和wg.Done()
之间有一个明显的联系。另外,如果我后来(错误地)将for循环更改为
if err != nil {
log.Fatalf("%s: %s", msg, err)
panic(fmt.Sprintf("%s: %s", msg, err))
}
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)
}
}
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
}
resp.Body.Close()
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")
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
}