RabbitMQ放弃

发布确认

用于消息发布到broker之后,broker确认有没有收到,这样做可以防止rabbitMQ突然崩溃,消息还没有持久化,从而导致的消息丢失问题


package main

import (
	"context"
	"fmt"
	"log"
	"strconv"
	"sync"
	"time"

	amqp "github.com/rabbitmq/amqp091-go"
)

func main() {
	// This example acts as a bridge, shoveling all messages sent from the source
	// exchange "log" to destination exchange "log".

	// Confirming publishes can help from overproduction and ensure every message
	// is delivered.

	// Setup the source of the store and forward
	source, err := amqp.Dial("amqp://guest:guest@192.168.31.110:5672/")
	if err != nil {
		log.Fatalf("connection.open source: %s", err)
	}
	defer source.Close()

	ch, err := source.Channel()
	if err != nil {
		log.Fatalf("channel.open source: %s", err)
	}
	//信道开启发布确认模式
	if err := ch.Confirm(false); err != nil {
		log.Fatalf("confirm.select destination: %s", err)
	}

	q, err := ch.QueueDeclare("", true, true, false, false, nil)
	if err != nil {
		log.Fatalf("queue.declare source: %s", err)
	}

	//确认的消息将返回这个chan中以
	confirms := ch.NotifyPublish(make(chan amqp.Confirmation, 1))

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	dict := sync.Map{}

	go func() {
		for confirm := range confirms {
			//确认成功的话就删除map中保存的数据
			if confirm.Ack{
				dict.Delete(confirm.DeliveryTag)
			}
		}
	}()

	for i := 0; i < 1000; i++ {
		dict.Store(ch.GetNextPublishSeqNo(), "message "+strconv.Itoa(i))
		err = ch.PublishWithContext(
			ctx,
			"",
			q.Name,
			false,
			false,
			amqp.Publishing{
				// Copy all the properties
				ContentType: "text/plain",
				Body:        []byte("message " + strconv.Itoa(i)),
			})
	}
	time.Sleep(1 * time.Second)
	dict.Range(walk)
	var forever chan struct{}
	<-forever

}

func walk(key, value interface{}) bool {
	fmt.Println("Key =", key, "Value =", value)
	return true
}

死信队列

当消费者无法正常消费消息、消息发生异常时,为了保证数据不丢失,将异常的消息置为死信,放入死信队列。在死信队列中的消息,将启动单独的消费程序特殊处理。
成为死信消息的三种原因:

  • 消息被拒(Reject /Nack) 且 requeue = false。
  • 消息 TTL 过期。
  • 队列满了,无法再添加。
    在这里插入图片描述

producer.go

package main

import (
	"context"
	amqp "github.com/rabbitmq/amqp091-go"
	"log"
	"os"
	"strings"
	"time"
)

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

func main() {
	// # ========== 1.创建连接 ==========
	conn, err := amqp.Dial("amqp://guest:guest@192.168.31.110: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()

	// # ========== 2.设置队列(队列、交换机、绑定) ==========
	// 声明队列
	_, err = ch.QueueDeclare("normal-queue", true, false, false, false, amqp.Table{
		"x-message-ttl":             5000,            // 消息过期时间,毫秒
		"x-dead-letter-exchange":    "dead-exchange", // 指定死信交换机
		"x-dead-letter-routing-key": "dead-key",      // 指定死信routing-key
		"x-max-length":              6,               //指定队列最大长度,如果超过,消息也会变成死信
	})
	failOnError(err, "创建normal队列失败")

	// 声明交换机
	err = ch.ExchangeDeclare("normal-exchange", amqp.ExchangeDirect, true, false, false, false, nil)
	failOnError(err, "创建normal交换机失败")

	// 队列绑定(将队列、routing-key、交换机三者绑定到一起)
	err = ch.QueueBind("normal-queue", "normal-key", "normal-exchange", false, nil)
	failOnError(err, "normal:队列、交换机、routing-key 绑定失败")

	// # ========== 3.设置死信队列(队列、交换机、绑定) ==========
	// 声明死信队列
	// args 为 nil。切记不要给死信队列设置消息过期时间,否则失效的消息进入死信队列后会再次过期。
	_, err = ch.QueueDeclare("dead-queue", true, false, false, false, nil)
	failOnError(err, "创建dead队列失败")

	// 声明交换机
	err = ch.ExchangeDeclare("dead-exchange", amqp.ExchangeDirect, true, false, false, false, nil)
	failOnError(err, "创建dead队列失败")

	// 队列绑定(将队列、routing-key、交换机三者绑定到一起)
	err = ch.QueueBind("dead-queue", "dead-key", "dead-exchange", false, nil)
	failOnError(err, "dead:队列、交换机、routing-key 绑定失败")

	// # ========== 4.发布消息 ==========
	body := bodyFrom(os.Args)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	// 发布消息
	err = ch.PublishWithContext(ctx, "normal-exchange", "normal-key", false, false, amqp.Publishing{
		ContentType: "text/plain",
		Body:        []byte(body),
		//Expiration:  "5000", //也可以在消息里设置TTL,更加灵活
	})
	failOnError(err, "消息发布失败")
}

func bodyFrom(args []string) string {
	var s string
	if (len(args) < 2) || os.Args[1] == "" {
		s = "hello"
	} else {
		s = strings.Join(args[1:], " ")
	}
	return s
}

normalConsumer.go

此程序不允许,可以导致消息因为TTL和长度限制而成为死信,运行可以通过拒绝消息让消息成为死信

package main

import (
	amqp "github.com/rabbitmq/amqp091-go"
	"log"
)

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

func main() {
	// # ========== 1.创建连接 ==========
	conn, err := amqp.Dial("amqp://guest:guest@192.168.31.110: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()

	// # ========== 2.消费消息 ==========
	msgsCh, err := ch.Consume("normal-queue", "", false, false, false, false, nil)
	failOnError(err, "消费normal队列失败")

	forever := make(chan struct{})
	go func() {
		for d := range msgsCh {
			// 要实现的逻辑
			log.Printf("接收的消息: %s", d.Body)

			// 手动应答
			//d.Ack(false)
			d.Reject(false)
		}
	}()
	log.Printf("[*] Waiting for message, To exit press CTRL+C")
	<-forever
}

deadConsumer.go

package main

import (
	amqp "github.com/rabbitmq/amqp091-go"
	"log"
)

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

func main() {
	// # ========== 1.创建连接 ==========
	conn, err := amqp.Dial("amqp://guest:guest@192.168.31.110: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()

	// # ========== 2.消费消息 ==========
	msgsCh, err := ch.Consume("dead-queue", "", false, false, false, false, nil)
	failOnError(err, "消费normal队列失败")

	forever := make(chan struct{})
	go func() {
		for d := range msgsCh {
			// 要实现的逻辑
			log.Printf("接收的消息: %s", d.Body)

			// 手动应答
			d.Ack(false)
			//d.Reject(false)
		}
	}()
	log.Printf("[*] Waiting for message, To exit press CTRL+C")
	<-forever
}

延迟队列

通过死信队列的介绍,我们可以很容易的实现一个延迟队列。

  • 将正常队列的消费者取消;
  • 发消息时设置TTL;

通过上面两点,正常队列的消息始终不会被消费,而是等待消息TTL到期,进入死信队列,让死信消费者进行消费,从而达到延迟队列的效果。

上面看上去似乎没什么问题,实测一下就会发现消息不会“如期死亡”。

当先生产一个TTL为60s的消息,再生产一个TTL为5s的消息,第二个消息并不会再5s后过期进入死信队列,而是需要等到第一个消息TTL到期后,与第一个消息一同进入死信队列。这是因为RabbitMQ 只会判断队列中的第一个消息是否过期。

对于上文的问题,自然有解决方法,那就是通过 RabbitMQ 的 rabbitmq_delayed_message_exchange 插件来解决
docker版安装教程:
https://blog.csdn.net/m0_67393827/article/details/126643162

生产者实现的关键点:

1.在声明交换机时不在是direct类型,而是x-delayed-message类型,这是由插件提供的类型;

2.交换机要增加"x-delayed-type": "direct"参数设置;

3.发布消息时,要在 Headers 中设置x-delay参数,来控制消息从交换机过期时间;

4.只需要一个队列即可

producer.go

package main

import (
	"context"
	amqp "github.com/rabbitmq/amqp091-go"
	"log"
	"os"
	"strings"
	"time"
)

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

func main() {
	// # ========== 1.创建连接 ==========
	conn, err := amqp.Dial("amqp://guest:guest@192.168.31.110: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()

	// # ========== 2.设置队列(队列、交换机、绑定) ==========
	// 声明队列
	_, err = ch.QueueDeclare("delay-queue", true, false, false, false, amqp.Table{})
	failOnError(err, "创建normal队列失败")

	// 声明交换机
	err = ch.ExchangeDeclare("delay-exchange", "x-delayed-message", true, false, false, false, amqp.Table{
		"x-delayed-type": "direct",
	})
	failOnError(err, "创建normal交换机失败")

	// 队列绑定(将队列、routing-key、交换机三者绑定到一起)
	err = ch.QueueBind("delay-queue", "delay-key", "delay-exchange", false, nil)
	failOnError(err, "normal:队列、交换机、routing-key 绑定失败")

	// # ========== 3.发布消息 ==========
	body := bodyFrom(os.Args)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	// 发布消息
	err = ch.PublishWithContext(ctx, "delay-exchange", "delay-key", false, false, amqp.Publishing{
		ContentType: "text/plain",
		Body:        []byte(body),
		//Expiration:  "5000",
		Headers: map[string]interface{}{
			"x-delay": "5000", // 消息从交换机过期时间,毫秒(x-dead-message插件提供)
		},
	})
	failOnError(err, "消息发布失败")
}

func bodyFrom(args []string) string {
	var s string
	if (len(args) < 2) || os.Args[1] == "" {
		s = "hello"
	} else {
		s = strings.Join(args[1:], " ")
	}
	return s
}

consumer.go


package main

import (
	amqp "github.com/rabbitmq/amqp091-go"
	"log"
)

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

func main() {
	// # ========== 1.创建连接 ==========
	conn, err := amqp.Dial("amqp://guest:guest@192.168.31.110: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()

	// # ========== 2.消费消息 ==========
	msgsCh, err := ch.Consume("delay-queue", "", false, false, false, false, nil)
	failOnError(err, "消费normal队列失败")

	forever := make(chan struct{})
	go func() {
		for d := range msgsCh {
			// 要实现的逻辑
			log.Printf("接收的消息: %s", d.Body)
			// 手动应答
			d.Ack(false)
			//d.Reject(false)
		}
	}()
	log.Printf("[*] Waiting for message, To exit press CTRL+C")
	<-forever
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值