我正在使用推荐的streadyway/amqp库与Go中的RabbitMQ进行交互。
当服务无法连接到RabbitMQ时,我希望服务正常地失败。这意味着注意到它无法连接,等待n
秒,然后尝试重新连接(永远循环)。
我很确定我需要使用Channel.NotifyClose方法,但无法解决:
func (ch *Channel) NotifyClose(c chan *Error) chan *Error
NotifyClose注册一个侦听器,以了解服务器何时以Connection.Close或Channel.Close方法的形式发送通道或连接异常。连接异常将广播到所有打开的通道,所有通道将关闭,其中通道异常将仅广播给该通道的侦听器。
关闭频道时,提供的频道将关闭,并且在正常关闭时,不会发送错误。
这是我尝试过的:
graceful := make(chan *amqp.Error)
errs := channel.NotifyClose(graceful)
for {
case <-graceful:
fmt.Println("Graceful close!")
reconnect()
case <-errs:
fmt.Println("Not graceful close")
reconnect()
}
有时候,这可行!有时,重新连接后,它将反复打印出:
2018/11/04 15:29:26 Other close
2018/11/04 15:29:26 Connected!
2018/11/04 15:29:26 Graceful close!
2018/11/04 15:29:26 Connected!
2018/11/04 15:29:26 Other close
2018/11/04 15:29:26 Connected!
2018/11/04 15:29:26 Graceful close!
...
很快。
我希望能够在一个终端上运行该服务,而在另一终端上运行Rabbit。只要我愿意,我就应该能够停止并重新启动Rabbit,并且服务会一直重新连接。
我对NotifyClose
方法有点困惑-c
通道在关闭连接时是否刚刚关闭?为什么返回另一个频道?
干杯。
我的整个代码。它没有push
或pop
函数,因为这是演示连接失败时重新连接的最小示例。推送和弹出式实施将取决于重新连接的实现方式。
也欢迎任何代码审查评论。
package main
import (
"github.com/streadway/amqp"
"io"
"log"
"sync"
"time"
)
// RabbitMQ ...
type RabbitMQ struct {
Logger *log.Logger
IsConnected bool
addr string
name string
connection *amqp.Connection
channel *amqp.Channel
queue *amqp.Queue
wg *sync.WaitGroup
done chan bool
}
const retryDelay = 5 * time.Second
// NewQueue creates a new queue instance.
func NewQueue(logOut io.Writer, name string, addr string) *RabbitMQ {
rabbit := RabbitMQ{
IsConnected: false,
addr: addr,
name: name,
wg: new(sync.WaitGroup),
done: make(chan bool),
Logger: log.New(logOut, "", log.LstdFlags),
}
rabbit.wg.Add(1)
rabbit.Connect()
go rabbit.reconnect()
return &rabbit
}
// reconnect waits to be notified about a connection
// error, and then attempts to reconnect to RabbitMQ.
func (rabbit *RabbitMQ) reconnect() {
defer rabbit.wg.Done()
graceful := make(chan *amqp.Error)
errs := rabbit.channel.NotifyClose(graceful)
for {
select {
case <-rabbit.done:
return
case <-graceful:
graceful = make(chan *amqp.Error)
rabbit.Logger.Println("Graceful close!")
rabbit.IsConnected = false
rabbit.Connect()
rabbit.IsConnected = true
errs = rabbit.channel.NotifyClose(graceful)
case <-errs:
graceful = make(chan *amqp.Error)
rabbit.Logger.Println("Normal close")
rabbit.IsConnected = false
rabbit.Connect()
errs = rabbit.channel.NotifyClose(graceful)
}
}
}
// Connect will block until a new connection to
// RabbitMQ is formed.
func (rabbit *RabbitMQ) Connect() {
for {
conn, err := amqp.Dial(rabbit.addr)
if err != nil {
rabbit.Logger.Println("Failed to establish connection")
time.Sleep(retryDelay)
continue
}
ch, err := conn.Channel()
if err != nil {
rabbit.Logger.Println("Failed to create a channel")
time.Sleep(retryDelay)
continue
}
queue, err := ch.QueueDeclare(
name,
false, // Durable
false, // Delete when unused
false, // Exclusive
false, // No-wait
nil, // Arguments
)
if err != nil {
rabbit.Logger.Println("Failed to publish a queue")
time.Sleep(retryDelay)
continue
}
rabbit.Logger.Println("Connected!")
rabbit.IsConnected = true
rabbit.connection = conn
rabbit.channel = ch
rabbit.queue = &queue
return
}
}
// Close the connection to RabbitMQ and stop
// checking for reconnections.
func (rabbit *RabbitMQ) Close() error {
close(rabbit.done)
rabbit.wg.Wait()
return rabbit.connection.Close()
}
以及如何使用它:
package main
import (
"fmt"
"os"
)
const (
name = "job_queue"
addr = "amqp://guest:guest@localhost:5672/"
)
func main() {
fmt.Println("Starting...")
NewQueue(os.Stdout, name, addr)
for {}
}