在工作中发现,有些时候消息因为某些原因在消费一次后,如果消息失败,这时候不ack,消息就回一直重回队列首部,造成消息拥堵。

如是有了如下思路:

消息进入队列前,header默认有参数 retry_num=0 表示尝试次数;

消费者在消费时候的,如果消息失败,就把消息插入另外一个队列(队列abc);该队列abc 绑定一个死信队列(原始消费的队列),这样形成一个回路;

当消息失败后,消息就进入队列abc,队列abc拥有ttl过期时间,ttl过期时间到了后,该消息进入死信队列(死信队列刚好是刚开始我们消费的队列);

这样消息就又回到原始消费队列尾部了;

最后可以通过队列消息头部的header参数retry_num 可以控制消息消费多少次后,直接插入db日志;

db日志可以记录交换机 路由,queuename,这样,可以做一个后台管理,可以手动一次把消息重新放入队列,进行消息(因为有时间消费队列里面可能在请求其它服务,其它服务也可能会挂掉)

这时候消息无论你消费多少次都没有用,但是入库db后,可以一键重回队列消息(当我们知道服务已经正常后)

图解:

附上代码

git clone https://github.com/sunlongv520/go-msgserver.git

package rabbitmq

import (
"errors"
"fmt"
"github.com/streadway/amqp"
"sync"
"time"
) // 定义全局变量,指针类型
var mqConn *amqp.Connection
var mqChan *amqp.Channel // 定义生产者接口
type Producer interface {
MsgContent() string
} // 定义生产者接口
type RetryProducer interface {
MsgContent() string
} // 定义接收者接口
type Receiver interface {
Consumer([]byte) error
} // 定义RabbitMQ对象
type RabbitMQ struct {
connection *amqp.Connection
channel *amqp.Channel
dns string
queueName string // 队列名称
routingKey string // key名称
exchangeName string // 交换机名称
exchangeType string // 交换机类型
producerList []Producer
retryProducerList []RetryProducer
receiverList []Receiver
mu sync.RWMutex
wg sync.WaitGroup
} // 定义队列交换机对象
type QueueExchange struct {
QuName string // 队列名称
RtKey string // key值
ExName string // 交换机名称
ExType string // 交换机类型
Dns string //链接地址
} // 链接rabbitMQ
func (r *RabbitMQ)mqConnect() (err error){
//var err error
//RabbitUrl := fmt.Sprintf("amqp://%s:%s@%s:%d/", "guest", "guest", "192.168.2.232", 5672)
//mqConn, err = amqp.Dial(RabbitUrl)
mqConn, err = amqp.Dial(r.dns)
r.connection = mqConn // 赋值给RabbitMQ对象
if err != nil {
return err
//fmt.Printf("MQ打开链接失败:%s \n", err)
}
mqChan, err = mqConn.Channel()
r.channel = mqChan // 赋值给RabbitMQ对象
if err != nil {
return err
//fmt.Printf("MQ打开管道失败:%s \n", err)
}
return err
} // 关闭RabbitMQ连接
func (r *RabbitMQ)mqClose() {
// 先关闭管道,再关闭链接
err := r.channel.Close()
if err != nil {
fmt.Printf("MQ管道关闭失败:%s \n", err)
}
err = r.connection.Close()
if err != nil {
fmt.Printf("MQ链接关闭失败:%s \n", err)
}
} // 创建一个新的操作对象
func New(q *QueueExchange) *RabbitMQ {
return &RabbitMQ{
queueName:q.QuName,
routingKey:q.RtKey,
exchangeName: q.ExName,
exchangeType: q.ExType,
dns:q.Dns,
}
} // 启动RabbitMQ客户端,并初始化
func (r *RabbitMQ) Start() (err error){
// 开启监听生产者发送任务
for _, producer := range r.producerList {
err = r.listenProducer(producer)
} // 开启监听接收者接收任务
for _, receiver := range r.receiverList {
//r.listenReceiver(receiver)
r.wg.Add()
go func() {
err = r.listenReceiver(receiver)
}() }
r.wg.Wait()
time.Sleep(time.Microsecond*)
return err
} type SendRbmqPro struct {
msgContent string
} // 实现生产者
func (t *SendRbmqPro) MsgContent() string {
return t.msgContent
} // 注册发送指定队列指定路由的生产者
func (r *RabbitMQ) RegisterProducer(msg string) {
a := &SendRbmqPro{msgContent:msg}
a.MsgContent()
r.producerList = append(r.producerList, a)
} // 发送任务
func (r *RabbitMQ) listenProducer(producer Producer) (err error){
// 验证链接是否正常,否则重新链接
if r.channel == nil {
err = r.mqConnect()
if err !=nil {
return err
}
}
err = r.channel.ExchangeDeclare(r.exchangeName, r.exchangeType, true, false, false, false, nil)
if err != nil {
fmt.Printf("MQ注册交换机失败:%s \n", err)
} _, err = r.channel.QueueDeclare(r.queueName, true, false, false, false, nil)
if err != nil {
fmt.Printf("MQ注册队列失败:%s \n", err)
} // 队列绑定
err = r.channel.QueueBind(r.queueName, r.routingKey, r.exchangeName, true,nil)
if err != nil {
fmt.Printf("MQ绑定队列失败:%s \n", err)
} header := make(map[string]interface{},) header["retry_nums"] = int32() // 发送任务消息
err = r.channel.Publish(r.exchangeName, r.routingKey, false, false, amqp.Publishing{
ContentType: "text/plain",
Body: []byte(producer.MsgContent()),
Headers:header,
}) if err != nil {
fmt.Printf("MQ任务发送失败:%s \n", err)
}
return err
} func (r *RabbitMQ) listenRetryProducer(producer RetryProducer,retry_nums int32 ,args ...string) {
fmt.Println("消息处理失败,进入延时队列.....")
//defer r.mqClose()
// 验证链接是否正常,否则重新链接
if r.channel == nil {
r.mqConnect()
} err := r.channel.ExchangeDeclare(r.exchangeName, r.exchangeType, true, false, false, false, nil)
if err != nil {
fmt.Printf("MQ注册交换机失败:%s \n", err)
return
} //原始路由key
oldRoutingKey := args[]
//原始交换机名
oldExchangeName := args[] table := make(map[string]interface{},)
table["x-dead-letter-routing-key"] = oldRoutingKey
table["x-dead-letter-exchange"] = oldExchangeName table["x-message-ttl"] = int64() _, err = r.channel.QueueDeclare(r.queueName, true, false, false, false, table)
if err != nil {
fmt.Printf("MQ注册队列失败:%s \n", err)
return
} // 队列绑定
err = r.channel.QueueBind(r.queueName, r.routingKey, r.exchangeName, true,nil)
if err != nil {
fmt.Printf("MQ绑定队列失败:%s \n", err)
return
} header := make(map[string]interface{},) header["retry_nums"] = retry_nums + int32() // 发送任务消息
err = r.channel.Publish(r.exchangeName, r.routingKey, false, false, amqp.Publishing{
ContentType: "text/plain",
Body: []byte(producer.MsgContent()),
Headers:header,
}) if err != nil {
fmt.Printf("MQ任务发送失败:%s \n", err)
return
}
} // 注册接收指定队列指定路由的数据接收者
func (r *RabbitMQ) RegisterReceiver(receiver Receiver) {
r.mu.Lock()
r.receiverList = append(r.receiverList, receiver)
r.mu.Unlock()
} // 监听接收者接收任务 消费者
func (r *RabbitMQ) listenReceiver(receiver Receiver) (err error) {
// 处理结束关闭链接
defer r.mqClose()
defer r.wg.Done()
//defer
// 验证链接是否正常
if r.channel == nil {
err = r.mqConnect()
if err != nil{
return errors.New(fmt.Sprintf("MQ注册队列失败:%s \n", err))
}
}
// 用于检查队列是否存在,已经存在不需要重复声明
_, err = r.channel.QueueDeclare(r.queueName, true, false, false, false, nil)
if err != nil {
return errors.New(fmt.Sprintf("MQ注册队列失败:%s \n", err))
}
// 绑定任务
err = r.channel.QueueBind(r.queueName, r.routingKey, r.exchangeName, false, nil)
if err != nil {
return errors.New(fmt.Sprintf("绑定队列失败:%s \n", err))
}
// 获取消费通道,确保rabbitMQ一个一个发送消息
err = r.channel.Qos(, , false)
msgList, err := r.channel.Consume(r.queueName, "", false, false, false, false, nil)
if err != nil {
return errors.New(fmt.Sprintf("获取消费通道异常:%s \n", err))
}
for msg := range msgList { retry_nums,ok := msg.Headers["retry_nums"].(int32)
if(!ok){
retry_nums = int32()
}
// 处理数据
err := receiver.Consumer(msg.Body)
if err!=nil {
//消息处理失败 进入延时尝试机制
if retry_nums < {
r.retry_msg(msg.Body,retry_nums)
}else{
//消息失败 入库db
fmt.Printf("消息处理失败 入库db")
}
err = msg.Ack(true)
if err != nil {
return errors.New(fmt.Sprintf("确认消息未完成异常:%s \n", err))
}
}else {
// 确认消息,必须为false
err = msg.Ack(true) if err != nil {
return errors.New(fmt.Sprintf("确认消息完成异常:%s \n", err))
}
return err
}
}
return
} type retryPro struct {
msgContent string
} // 实现生产者
func (t *retryPro) MsgContent() string {
return t.msgContent
} //消息处理失败之后 延时尝试
func(r *RabbitMQ) retry_msg(Body []byte,retry_nums int32){
queueName := r.queueName+"_retry_3"
routingKey := r.queueName+"_retry_3"
exchangeName := r.exchangeName
queueExchange := &QueueExchange{
queueName,
routingKey,
exchangeName,
"direct",
r.dns,
}
mq := New(queueExchange)
msg := fmt.Sprintf("%s",Body)
t := &retryPro{
msg,
}
mq.listenRetryProducer(t,retry_nums,r.routingKey,exchangeName)
}

go-msgserver/utils/rabbitmq/receiver.go

package main

import (
"fmt"
"github.com/ichunt2019/go-msgserver/utils/rabbitmq"
"time"
"errors"
) type RecvPro struct { } //// 实现消费者 消费消息失败 自动进入延时尝试 尝试3次之后入库db
func (t *RecvPro) Consumer(dataByte []byte) error {
fmt.Println(string(dataByte))
return errors.New("顶顶顶顶")
//return nil
} func main() { //消费者实现 下面接口即可
//type Receiver interface {
// Consumer([]byte) error
//}
t := &RecvPro{} queueExchange := &rabbitmq.QueueExchange{
"fengkong_static_count",
"fengkong_static_count",
"fengkong_exchange",
"direct",
"amqp://guest:guest@192.168.2.232:5672/",
}
for{ mq := rabbitmq.New(queueExchange)
mq.RegisterReceiver(t)
err :=mq.Start()
if err != nil{ fmt.Println(err)
}
time.Sleep(time.Second)
} }

demo-recv.go

package main

import (
"fmt"
_ "fmt"
"github.com/ichunt2019/go-msgserver/utils/rabbitmq"
"strconv"
) func main() { queueExchange := &rabbitmq.QueueExchange{
"b_test_rabbit",
"b_test_rabbit",
"b_test_rabbit_mq",
"direct",
"amqp://guest:guest@192.168.2.232:5672/",
}
mq := rabbitmq.New(queueExchange)
for i := ;i<;i++{
mq.RegisterProducer("这是测试任务"+strconv.Itoa(i))
}
err := mq.Start()
if(err != nil){
fmt.Println("发送消息失败")
} }

demo-send.go

golang实现rabbitmq消息队列失败尝试的更多相关文章

  1. golang监听rabbitmq消息队列任务断线自动重连接

    需求背景: goalng常驻内存任务脚本监听rbmq执行任务 任务脚本由supervisor来管理 当rabbitmq长时间断开连接会出现如下图 进程处于fatal状态 假如因为不可抗拒因素,rabb ...

  2. (转)RabbitMQ消息队列(七):适用于云计算集群的远程调用(RPC)

    在云计算环境中,很多时候需要用它其他机器的计算资源,我们有可能会在接收到Message进行处理时,会把一部分计算任务分配到其他节点来完成.那么,RabbitMQ如何使用RPC呢?在本篇文章中,我们将会 ...

  3. RabbitMQ消息队列(七):适用于云计算集群的远程调用(RPC)

            在云计算环境中,很多时候需要用它其他机器的计算资源,我们有可能会在接收到Message进行处理时,会把一部分计算任务分配到其他节点来完成.那么,RabbitMQ如何使用RPC呢?在本篇 ...

  4. RabbitMQ学习系列二:.net 环境下 C#代码使用 RabbitMQ 消息队列

    一.理论: .net环境下,C#代码调用RabbitMQ消息队列,本文用easynetq开源的.net Rabbitmq api来实现. EasyNetQ 是一个易于使用的RabbitMQ的.Net客 ...

  5. RabbitMQ 消息队列 安装及使用

    RabbitMQ 消息队列安装: linux版本:CentOS 7 安装第一步:先关闭防火墙 1.Centos7.x关闭防火墙 [root@rabbitmq /]# systemctl stop fi ...

  6. (五)RabbitMQ消息队列-安装amqp扩展并订阅/发布Demo(PHP版)

    原文:(五)RabbitMQ消息队列-安装amqp扩展并订阅/发布Demo(PHP版) 本文将介绍在PHP中如何使用RabbitMQ来实现消息的订阅和发布.我使用的系统依然是Centos7,为了方便, ...

  7. C# .net 环境下使用rabbitmq消息队列

    消息队列的地位越来越重要,几乎是面试的必问问题了,不会使用几种消息队列都显得尴尬,正好本文使用C#来带你认识rabbitmq消息队列 首先,我们要安装rabbitmq,当然,如果有现成的,也可以使用, ...

  8. 基于ASP.NET Core 5.0使用RabbitMQ消息队列实现事件总线(EventBus)

    文章阅读请前先参考看一下 https://www.cnblogs.com/hudean/p/13858285.html 安装RabbitMQ消息队列软件与了解C#中如何使用RabbitMQ 和 htt ...

  9. RabbitMQ消息队列(一): Detailed Introduction 详细介绍

     http://blog.csdn.net/anzhsoft/article/details/19563091 RabbitMQ消息队列(一): Detailed Introduction 详细介绍 ...

随机推荐

  1. Mycat分布式数据库架构解决方案--Linux安装运行Mycat

    echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!!! Myc ...

  2. 入职第一天,装环境 .Ubuntu装jdk1.8,装idea 及tomcat

    入职第一天,和之前公司的开发环境感觉天壤之别了,不过万变不离其宗,之前公司eclipse+widows.所以很少玩linux了.今天来就干了一件事.装环境 jdk安装. 下载地址:https://ww ...

  3. NLP预训练模型-百度ERNIE2.0的效果到底有多好【附用户点评】

    ERNIE是百度自研的持续学习语义理解框架,该框架支持增量引入词汇(lexical).语法 (syntactic) .语义(semantic)等3个层次的自定义预训练任务,能够全面捕捉训练语料中的词法 ...

  4. python super原理,不是指父类

    class a(object): def __init__(self): print('in a') class b(a): def __init__(self): print('in b') sup ...

  5. P2579 [ZJOI2005]沼泽鳄鱼(邻接矩阵,快速幂)

    题目简洁明了(一点都不好伐) 照例,化简题目 给一张图,每一个时间点有一些点不能走,(有周期性),求从起点第k秒恰好在终点的方案数,可重复,不可停留. 额dp实锤 于是就被打脸了.... 有一种东西叫 ...

  6. wordpress插件:multiple post thumbnails(可为文章添加多个特色图片)

    我们经常会给wordpress的文章加上特色图片来实现日志缩略图的需求,但是很多时候一张图片并不能够完美而又全面的表达我们wordpress文章的内容,这时候您可能就会需要这样一个能让wordpres ...

  7. PCA降维的原理、方法、以及python实现。

    PCA(主成分分析法) 1. PCA(最大化方差定义或者最小化投影误差定义)是一种无监督算法,也就是我们不需要标签也能对数据做降维,这就使得其应用范围更加广泛了.那么PCA的核心思想是什么呢? 例如D ...

  8. SysTick系统定时器

    1.SysTick定时器介绍 SysTick定时器也叫SysTick滴答定时器,它是Cortex-M3内核的一个 外设,被嵌入在 NVIC 中.它是一个24 位向下递减的定时器,每计数一 次所需时间为 ...

  9. linux系统LNMP环境部署

    源码安装 nginx# yum -y install gcc openssl-devel# useradd -s /sbin/nologin nginx# tar xf nginx-1.14.0.ta ...

  10. Python基础之JSON

    作用 对Python对象进行序列化,便于存储和传输 Python对象与JSON字符串相互转换 Python对象转JSON字符串 import json data = [ { 'a' : 1, 'b' ...