Golang Rabbit MQ Fanout Exchange Multiple Consumers - go

I am publishing messages in fanout exchange from Java application. I am able to receive message in multiple consumer in Java. I have 2 consumers in golang app but only one of the consumer (alternatively ) is receiving the message (Not both of them for a published message).
func HandleMessageFanout1(){
conn := system.EltropyAppContext.RabbitMQConn
channel, err := conn.Channel()
if(err!=nil){
log.Println(err)
}
//forever := make(chan bool)
deliveries,err := channel.Consume(
"example.queue", //queue
"qw",
true,
false,
false,
false,
nil)
if(err!=nil){
log.Println(err)
}
go func() {
for d := range deliveries {
log.Printf("Message recived in fanout 1")
log.Printf("Received a message: %s", d.Body)
}
}()
//<-forever
}
//2nd Consumer
package consumer
import (
"github.com/eltropy/shehnai/backend/golang/common-packages/system"
log "github.com/Sirupsen/logrus"
)
func HandleMessageFanout2() {
conn := system.EltropyAppContext.RabbitMQConn
channel, err := conn.Channel()
if (err!=nil) {
log.Println(err)
}
//forever := make(chan bool)
deliveries, err := channel.Consume(
"example.queue", //queue
"q2",
true,
false,
false,
false,
nil)
if (err!=nil) {
log.Println(err)
}
go func() {
for d := range deliveries {
log.Printf("Message recived in fanout 2")
log.Printf("Received a message: %s", d.Body)
}
}()
//<-forever
}
I am using https://github.com/streadway/amqp library for rabbit mq.

On the channel type, before you publish, declare an exchange like this:
err = channel.ExchangeDeclare(
"example.queue", // name
"fanout", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
See the official RabbitMQ tutorial.

Related

Consuming from multiple queue

I have a rabbit mq instance that I use to listen to changes. However, I have different messages that I need to consume. What I am not sure about now is how to go about it. Would I need to just keep duplicating my Consume method and what is the danger of doing that if any.
type Consumer struct {
conn *amqp.Connection
db *mongo.Database
queueName string
}
func (consumer *Consumer) setup() error {
_, err := consumer.conn.Channel()
if err != nil {
return err
}
return nil
}
// NewConsumer returns a new Consumer
func NewConsumer(conn *amqp.Connection, db *mongo.Database) (Consumer, error) {
consumer := Consumer{
conn: conn,
db: db,
}
return consumer, nil
}
// Listen will listen for all new Queue publications
// and print them to the console.
func (consumer *Consumer) Listen(topics []string) error {
ch, err := consumer.conn.Channel()
if err != nil {
return err
}
defer ch.Close()
msgs, err := ch.Consume("charge.get", "", true, false, false, false, nil)
if err != nil {
return err
}
forever := make(chan bool)
go func() {
for msg := range msgs {
switch msg.RoutingKey {
case "charge.get":
worker.Refund(paymentRepo.NewPaymentRepository(consumer.db), msg.Body)
}
// acknowledege received event
log.Printf("Received a message: %s ROUTIG KEY %s", msg.Body, msg.RoutingKey)
}
}()
log.Printf("[*] Waiting for message [Exchange, Queue][%s, %s]. To exit press CTRL+C", " getExchangeName()", "q.Name")
<-forever
return nil
}
Do I just add another msgs, err := ch.Consume("charge.update", "", true, false, false, false, nil) below the first one to consume from this queue? Or how can I go about this.

how to instrument / measure delays within a loop of multiple Goroutines

For reproducing an error (404) Reason: "NOT_FOUND - no queue when subscribing to rabbitmq queues, I am using the following code to concurrently declare and consume queues:
package main
import (
"fmt"
"log"
"os"
"sync"
"time"
uuid "github.com/satori/go.uuid"
"github.com/streadway/amqp"
)
func exit1(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
func main() {
rUSER := "bunny"
rPASS := "test"
rHOST := "my-rabbit"
rPORT := "5672"
rVHOST := "hole"
// read from ENV
if e := os.Getenv("RABBITMQ_USER"); e != "" {
rUSER = e
}
if e := os.Getenv("RABBITMQ_PASS"); e != "" {
rPASS = e
}
if e := os.Getenv("RABBITMQ_HOST"); e != "" {
rHOST = e
}
if e := os.Getenv("RABBITMQ_PORT"); e != "" {
rPORT = e
}
if e := os.Getenv("RABBITMQ_VHOST"); e != "" {
rVHOST = e
}
conn, err := amqp.Dial(fmt.Sprintf("amqp://%s:%s#%s:%s/%s",
rUSER, rPASS, rHOST, rPORT, rVHOST))
exit1(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
exit1(err, "Failed to open a channel")
defer ch.Close()
// buggy part
args := map[string]interface{}{
"x-message-ttl": int32(3000),
"x-expires": int32(8000), // <-- culprit
}
concurrent := 500
wg := sync.WaitGroup{}
semaphore := make(chan struct{}, concurrent)
for i := 0; i < 1000; i++ {
semaphore <- struct{}{}
wg.Add(1)
go func() {
queueName := fmt.Sprintf("carrot-%s-%s", time.Now().Format("2006-01-02"), uuid.Must(uuid.NewV4()))
fmt.Printf("Creating queue: %s\n", queueName)
defer func() {
<-semaphore
wg.Done()
}()
q, err := ch.QueueDeclare(
queueName,
false, // durable
false, // delete when usused
false, // exclusive
false, // no-wait
args, // arguments
)
exit1(err, "Failed to declare a queue")
// how to measure here time elapsed between ch.Consume is called
_, err = ch.Consume(
q.Name, // queue
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
exit1(err, "Failed to register a consumer")
}()
}
wg.Wait()
}
Giving more context, the code will work fine and subscribe to all
the 1000 queues when having few concurrent clients < 100, but when adding more concurrent clients a kind of "race condition" occurs and clients start to receive an error 404, this happens because of declaring a TTL for a queue, in this case, 8 seconds:
"x-expires": int32(8000),
A better solution would be to use exclusive queues otherwise, the queue is being deleted before the client can consume it, but within this "buggy" code, I would like to measure the delay is taking between the ch.QueueDeclare and the ch.Consume.
A client basically is doing:
q, err := ch.QueueDeclare(
queueName,
false, // durable
false, // delete when usused
false, // exclusive
false, // no-wait
args, // arguments
)
And then:
_, err = ch.Consume(
q.Name, // queue
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
When having multiple concurrent clients, after doing the QueueDeclare there seems to be a delay that reaches up to 8s seconds before the Consume is called and because of this the error 404 but what and how could I instrument, adapt to code to measure this delays?

Reconnecting to RabbitMQ after disconnection [duplicate]

I have a RabbitMQ consumer script in Go. This is a simple script from RabbitMQ tutorial that uses streadway/amqp library.
The problem is that if the RabbitMQ server is stopped, the consumer script does not exit; and when RabbitMQ server is restarted, the consumer does not receive messages anymore.
Is there a way to detect that the consumer connection is dead and reconnect, or at least terminate the consumer script?
I know that the library sets a default 10 sec. heartbeat interval for the connection; is it possible to use that someway?
func main() {
conn, err := amqp.Dial("amqp://guest:guest#localhost: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(
"test_task_queue", // 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)
go func() {
for d := range msgs {
log.Printf("Received a message: %s", d.Body)
d.Ack(false)
dot_count := bytes.Count(d.Body, []byte("."))
t := time.Duration(dot_count)
time.Sleep(t * time.Second)
log.Printf("Done")
}
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
<-forever
}
amqp.Connection has method NotifyClose() which return channel signalling a transport or protocol error.
So something like
for { //reconnection loop
conn, err := amqp.Dial("amqp://guest:guest#localhost:5672/") //setup
notify := conn.NotifyClose(make(chan *amqp.Error)) //error channel
...
ch, err := conn.Channel()
msgs, err := ch.Consume(
...
for{ //receive loop
select { //check connection
case err = <-notify:
//work with error
break //reconnect
case d = <- msgs:
//work with message
...
}
}
}
There are a couple ways of doing this: checking whether the delivery channel is closed or using Channel.NotifyClose.
Checking the delivery channel
After starting the consumer, you will receive from the delivery channel. As you know, the receive operation may take the special form x, ok := <-ch, where ok is false when x has a zero value due the channel being closed (and empty):
conn, _ := amqp.Dial(url)
ch, _ := conn.Channel()
delivery, _ := ch.Consume(
queueName,
consumerName,
true, // auto ack
false, // exclusive
false, // no local
true, // no wait,
nil, // table
)
for {
payload, ok := <- delivery
if !ok {
// ... channel closed
return
}
}
This works because the Go channel <-chan amqp.Delivery will be closed when the AMQP channel is closed or an error occurs:
[It] continues deliveries to the returned chan Delivery until Channel.Cancel, Connection.Close, Channel.Close, or an AMQP exception occurs.
Using Channel.NotifyClose
This is straightforward. And the principle is the same:
NotifyClose registers a listener for when the server sends a channel or connection exception in the form of a Connection.Close or Channel.Close method.
The channel returned by NotifyClose is the same you pass as argument; the method only registers it internally, so you can do just:
errC := ch.NotifyClose(make(chan *amqp.Error, n))
where n is a non-zero buffer size. Make sure to pass a buffered channel to NotifyClose otherwise, depending on how your code is structured, the library may block on send.
Then you can receive on the errC channel and take action depending on the type of error you get. In short, the error can be:
a connection error, usually unrecoverable
a channel error, also called soft exception, usually recoverable by resetting the connection
nil if the program calls conn.Close() on purpose
To know whether the error is recoverable or not, you can inspect the amqp.Error's Code field and/or the Recover field, which is set to true in case of soft exceptions.
The following func shows how error codes can be distinguished — this is provided as additional insight. For the general case, just check Error.Recover:
const (
ConnectionError = 1
ChannelError = 2
)
func isConnectionError(err *amqp.Error) bool {
return errorType(err.Code) == ConnectionError
}
func isChannelError(err *amqp.Error) bool {
return errorType(err.Code) == ChannelError
}
func errorType(code int) int {
switch code {
case
amqp.ContentTooLarge, // 311
amqp.NoConsumers, // 313
amqp.AccessRefused, // 403
amqp.NotFound, // 404
amqp.ResourceLocked, // 405
amqp.PreconditionFailed: // 406
return ChannelError
case
amqp.ConnectionForced, // 320
amqp.InvalidPath, // 402
amqp.FrameError, // 501
amqp.SyntaxError, // 502
amqp.CommandInvalid, // 503
amqp.ChannelError, // 504
amqp.UnexpectedFrame, // 505
amqp.ResourceError, // 506
amqp.NotAllowed, // 530
amqp.NotImplemented, // 540
amqp.InternalError: // 541
fallthrough
default:
return ConnectionError
}
}
This may help someone
// MAIN PACKAGE - "cmd/my-project-name/main.go"
package main
import (
"my-proyect-name/rmq"
)
func main() {
// RMQ
rmq.ConnectToRMQ()
}
// RMQ PACKAGE - "rmq"
import (
"errors"
"log"
"ms-gcp-cloud-storage/constants"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
const (
rmqCredentials string = "amqp://user:pswd#localhost:5672"
rmqQueue string = "golang-queue:new"
rmqExchange string = constants.RMQ_DIRECT_EXCHANGE // "" empty string
rmqContentType string = "application/json"
)
var conn *amqp.Connection
var chann *amqp.Channel
func hasError(err error, msg string) {
if err != nil {
log.Printf("%s: %s", msg, err)
}
}
func ConnectToRMQ() (err error) {
conn, err = amqp.Dial(rmqCredentials)
if err != nil {
return errors.New("Error de conexion: " + err.Error())
}
chann, err = conn.Channel()
if err != nil {
return errors.New("Error al abrir canal: " + err.Error())
}
q, err := chann.QueueDeclare(
rmqQueue, // name
true, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
if err != nil {
log.Fatalf("Error al declarar queue %v\n", q.Name)
}
log.Printf("Conectado al Queue: %v\n", q.Name)
observeConnection()
return nil
}
func observeConnection() {
go func() {
log.Printf("Conexion perdida: %s\n", <-conn.NotifyClose(make(chan *amqp.Error)))
log.Printf("Intentando reconectar con RMQ\n")
closeActiveConnections()
for err := ConnectToRMQ(); err != nil; err = ConnectToRMQ() {
log.Println(err)
time.Sleep(5 * time.Second)
}
}()
}
// Can be also implemented in graceful shutdowns
func closeActiveConnections() {
if !chann.IsClosed() {
if err := chann.Close(); err != nil {
log.Println(err.Error())
}
}
if conn != nil && !conn.IsClosed() {
if err := conn.Close(); err != nil {
log.Println(err.Error())
}
}
}
// SendMessage - message without response
func SendMessage(body string) {
err := chann.Publish(
rmqExchange, // exchange
rmqQueue, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: rmqContentType,
DeliveryMode: constants.RMQ_PERSISTENT_MSG,
Body: []byte(body),
})
if err != nil {
log.Printf("%s\n %s\n", "Error al publicar mensaje", err)
log.Println(body)
}
}
It has not been found that the go-amqp library implements the disconnection and reconnection function of the connection pool.
There is an open source code based on Amqp secondary packaging on github.
Reconnect after disconnection and abnormal reconnect have been supported. The code is also relatively simple to use, and each service has a connection and channel.
Source Code here
Example Code:
package main
import (
"go-rabbit/rabbit"
)
/*
support isconnection and reconnection function
And Failure re-send function
#author : Bill
*/
func main() {
var(
addr = "amqp://guest:guest#localhost:5672/"
queue = "testQueue"
exchange = "test_exchange"
routerKey = "/test"
msg = "test1!"
//delay
delayQueue = "delay_queue"
delayExchange = "delay_exchange"
delayRouterKey = "delay_exchange"
prefix = "v1_prefix"
sep = "_"
eType = "F"
_ttl = 60 * 1000
)
var rabbitProduct1 = rabbit.NewRabbitProduct(addr,_ttl,prefix,sep,delayExchange,delayQueue,delayRouterKey)
// register recycle
go rabbitProduct1.InitDefdelay(false)
go rabbitProduct1.InitDefdelay(true)
go rabbitProduct1.RegisterDelayWithPreFix("delay_queue","delay_exchange","delay_exchange")
// ttl is dead recycle time if ttl > 0 then recycle
rabbitProduct1.PubMessage(true,eType,queue,exchange,routerKey,msg,rabbitProduct1.GetBool(1),rabbitProduct1.GetBool(0),_ttl)
}
Wish it will help you or give you some idea

Limiting concurrency when processing messages from RabbitMQ

I'm attempting to read URLs from a queue (RabbitMQ) and make a limited number of concurrent HTTP requests i.e. have a pool of 10 workers making concurrent requests to URLs received from the queue (forever).
So far I've implemented a consumer as per the RabbitMQ tutorials:
https://www.rabbitmq.com/tutorials/tutorial-one-go.html
And have tried a number of methods from examples discovered on the web, ending at the example here:
http://jmoiron.net/blog/limiting-concurrency-in-go/
Unfortunately, my current code runs for approximately one minute and then freezes indefinitely. I've tried adding/moving go routines around but I can't seem to get it to work as intended (I'm very new to Go).
Current code:
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
}
You're not properly handling your HTTP responses, leading to a growing set of open connections. Try this:
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)
}
This, after you finish reading messages from the channel, seems unnecessary and potentially problematic:
for i := 0; i < cap(sem); i++ {
sem <- true
}
Why fill the sem channel after you've read all the messages from the queue? You've added exactly as many messages to the channel as you expect to read from it, so this is pointless at best, and could cause problems if you make the wrong change to the rest of the code.
Unrelated to your issue, but this is redundant:
if err != nil {
log.Fatalf("%s: %s", msg, err)
panic(fmt.Sprintf("%s: %s", msg, err))
}
Per the documentation, Fatalf already exits, so the panic will never be called. If you want to log and panic, try log.Panicf, which is designed for that purpose.
You are adding to sem when you get a message, but only removing from sem when you haven't seen a url.
so, once you've "already seen" 10 urls, your app will lock up.
So you need to add <-sem to your else statement that logs "Already seen".
Either way, that's a fairly odd way to do this kind of concurrency.
Here's a version that does this in a more idiomatic way on Play.
Note, in this version, we just spawn 10 goroutines that listen to the rabbit channel.
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
}

How to detect dead RabbitMQ connection?

I have a RabbitMQ consumer script in Go. This is a simple script from RabbitMQ tutorial that uses streadway/amqp library.
The problem is that if the RabbitMQ server is stopped, the consumer script does not exit; and when RabbitMQ server is restarted, the consumer does not receive messages anymore.
Is there a way to detect that the consumer connection is dead and reconnect, or at least terminate the consumer script?
I know that the library sets a default 10 sec. heartbeat interval for the connection; is it possible to use that someway?
func main() {
conn, err := amqp.Dial("amqp://guest:guest#localhost: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(
"test_task_queue", // 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)
go func() {
for d := range msgs {
log.Printf("Received a message: %s", d.Body)
d.Ack(false)
dot_count := bytes.Count(d.Body, []byte("."))
t := time.Duration(dot_count)
time.Sleep(t * time.Second)
log.Printf("Done")
}
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
<-forever
}
amqp.Connection has method NotifyClose() which return channel signalling a transport or protocol error.
So something like
for { //reconnection loop
conn, err := amqp.Dial("amqp://guest:guest#localhost:5672/") //setup
notify := conn.NotifyClose(make(chan *amqp.Error)) //error channel
...
ch, err := conn.Channel()
msgs, err := ch.Consume(
...
for{ //receive loop
select { //check connection
case err = <-notify:
//work with error
break //reconnect
case d = <- msgs:
//work with message
...
}
}
}
There are a couple ways of doing this: checking whether the delivery channel is closed or using Channel.NotifyClose.
Checking the delivery channel
After starting the consumer, you will receive from the delivery channel. As you know, the receive operation may take the special form x, ok := <-ch, where ok is false when x has a zero value due the channel being closed (and empty):
conn, _ := amqp.Dial(url)
ch, _ := conn.Channel()
delivery, _ := ch.Consume(
queueName,
consumerName,
true, // auto ack
false, // exclusive
false, // no local
true, // no wait,
nil, // table
)
for {
payload, ok := <- delivery
if !ok {
// ... channel closed
return
}
}
This works because the Go channel <-chan amqp.Delivery will be closed when the AMQP channel is closed or an error occurs:
[It] continues deliveries to the returned chan Delivery until Channel.Cancel, Connection.Close, Channel.Close, or an AMQP exception occurs.
Using Channel.NotifyClose
This is straightforward. And the principle is the same:
NotifyClose registers a listener for when the server sends a channel or connection exception in the form of a Connection.Close or Channel.Close method.
The channel returned by NotifyClose is the same you pass as argument; the method only registers it internally, so you can do just:
errC := ch.NotifyClose(make(chan *amqp.Error, n))
where n is a non-zero buffer size. Make sure to pass a buffered channel to NotifyClose otherwise, depending on how your code is structured, the library may block on send.
Then you can receive on the errC channel and take action depending on the type of error you get. In short, the error can be:
a connection error, usually unrecoverable
a channel error, also called soft exception, usually recoverable by resetting the connection
nil if the program calls conn.Close() on purpose
To know whether the error is recoverable or not, you can inspect the amqp.Error's Code field and/or the Recover field, which is set to true in case of soft exceptions.
The following func shows how error codes can be distinguished — this is provided as additional insight. For the general case, just check Error.Recover:
const (
ConnectionError = 1
ChannelError = 2
)
func isConnectionError(err *amqp.Error) bool {
return errorType(err.Code) == ConnectionError
}
func isChannelError(err *amqp.Error) bool {
return errorType(err.Code) == ChannelError
}
func errorType(code int) int {
switch code {
case
amqp.ContentTooLarge, // 311
amqp.NoConsumers, // 313
amqp.AccessRefused, // 403
amqp.NotFound, // 404
amqp.ResourceLocked, // 405
amqp.PreconditionFailed: // 406
return ChannelError
case
amqp.ConnectionForced, // 320
amqp.InvalidPath, // 402
amqp.FrameError, // 501
amqp.SyntaxError, // 502
amqp.CommandInvalid, // 503
amqp.ChannelError, // 504
amqp.UnexpectedFrame, // 505
amqp.ResourceError, // 506
amqp.NotAllowed, // 530
amqp.NotImplemented, // 540
amqp.InternalError: // 541
fallthrough
default:
return ConnectionError
}
}
This may help someone
// MAIN PACKAGE - "cmd/my-project-name/main.go"
package main
import (
"my-proyect-name/rmq"
)
func main() {
// RMQ
rmq.ConnectToRMQ()
}
// RMQ PACKAGE - "rmq"
import (
"errors"
"log"
"ms-gcp-cloud-storage/constants"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
const (
rmqCredentials string = "amqp://user:pswd#localhost:5672"
rmqQueue string = "golang-queue:new"
rmqExchange string = constants.RMQ_DIRECT_EXCHANGE // "" empty string
rmqContentType string = "application/json"
)
var conn *amqp.Connection
var chann *amqp.Channel
func hasError(err error, msg string) {
if err != nil {
log.Printf("%s: %s", msg, err)
}
}
func ConnectToRMQ() (err error) {
conn, err = amqp.Dial(rmqCredentials)
if err != nil {
return errors.New("Error de conexion: " + err.Error())
}
chann, err = conn.Channel()
if err != nil {
return errors.New("Error al abrir canal: " + err.Error())
}
q, err := chann.QueueDeclare(
rmqQueue, // name
true, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
if err != nil {
log.Fatalf("Error al declarar queue %v\n", q.Name)
}
log.Printf("Conectado al Queue: %v\n", q.Name)
observeConnection()
return nil
}
func observeConnection() {
go func() {
log.Printf("Conexion perdida: %s\n", <-conn.NotifyClose(make(chan *amqp.Error)))
log.Printf("Intentando reconectar con RMQ\n")
closeActiveConnections()
for err := ConnectToRMQ(); err != nil; err = ConnectToRMQ() {
log.Println(err)
time.Sleep(5 * time.Second)
}
}()
}
// Can be also implemented in graceful shutdowns
func closeActiveConnections() {
if !chann.IsClosed() {
if err := chann.Close(); err != nil {
log.Println(err.Error())
}
}
if conn != nil && !conn.IsClosed() {
if err := conn.Close(); err != nil {
log.Println(err.Error())
}
}
}
// SendMessage - message without response
func SendMessage(body string) {
err := chann.Publish(
rmqExchange, // exchange
rmqQueue, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: rmqContentType,
DeliveryMode: constants.RMQ_PERSISTENT_MSG,
Body: []byte(body),
})
if err != nil {
log.Printf("%s\n %s\n", "Error al publicar mensaje", err)
log.Println(body)
}
}
It has not been found that the go-amqp library implements the disconnection and reconnection function of the connection pool.
There is an open source code based on Amqp secondary packaging on github.
Reconnect after disconnection and abnormal reconnect have been supported. The code is also relatively simple to use, and each service has a connection and channel.
Source Code here
Example Code:
package main
import (
"go-rabbit/rabbit"
)
/*
support isconnection and reconnection function
And Failure re-send function
#author : Bill
*/
func main() {
var(
addr = "amqp://guest:guest#localhost:5672/"
queue = "testQueue"
exchange = "test_exchange"
routerKey = "/test"
msg = "test1!"
//delay
delayQueue = "delay_queue"
delayExchange = "delay_exchange"
delayRouterKey = "delay_exchange"
prefix = "v1_prefix"
sep = "_"
eType = "F"
_ttl = 60 * 1000
)
var rabbitProduct1 = rabbit.NewRabbitProduct(addr,_ttl,prefix,sep,delayExchange,delayQueue,delayRouterKey)
// register recycle
go rabbitProduct1.InitDefdelay(false)
go rabbitProduct1.InitDefdelay(true)
go rabbitProduct1.RegisterDelayWithPreFix("delay_queue","delay_exchange","delay_exchange")
// ttl is dead recycle time if ttl > 0 then recycle
rabbitProduct1.PubMessage(true,eType,queue,exchange,routerKey,msg,rabbitProduct1.GetBool(1),rabbitProduct1.GetBool(0),_ttl)
}
Wish it will help you or give you some idea

Resources