Go实现RabbitMQ消息模式

【目标】

  1. go实现RabbitMQ简单模式和work工作模式

  2. go实现RabbitMQ 消息持久化和手动应答

  3. go实现RabbitMQ 发布订阅模式

  4. go使用MQ实现评论后排行榜更新

1. go实现简单模式

编写路由实现生产消息

实现生产消息

MQ消息执行为命令行执行,所以创建命令行执行函数main,用来消费消息

创建mq/demo/main.go

浏览器中访问路由,执行生产者生产消息

打开http://localhost:15672/#/queues, 查看RabbitMQ客户端查看是否消息

执行消费者,实现消息消费

进入 mq/demo/中,执行bee run

2. go实现work工作模式

在启动另一个窗口,实现第二个消费者

生产消息

打开RabbitMQ客户端,查看消费者

查看work消费

两个work时,轮询执行消费

2.1 go实现RabbitMQ消息持久化和手动应答

消息持久化

消息的可靠性是RabbitMQ的一大特色,那么RabbitMQ是如何保证消息可靠性的呢——消息持久化。
为了保证RabbitMQ在退出或者crash等异常情况下数据没有丢失,需要将queue,exchange和Message都持久化

生产者实现消息持久化

第二个参数设置为true,即durable=true.

消费者实现消息持久化

在RabbitMQ服务重启或者服务宕机的情况下,也不会丢失消息。

可以将Queue与Message都设置为可持久化(durable),这样可以保证绝大部分情况下RabbitMQ消息不会丢失。

手动应答

RabbitMQ 消息应答机制

消费者处理一个任务是需要一段时间的,如果有一个消费者正在处理一个比较耗时的任务并且只处理了一部分,突然这个时候消费者宕机了,那么会出现什么情况呢?

如果是自动应答模式,消费者在处理任务的过程中宕机了,那么消息将会丢失,而手动应答则能够保证消息不会被丢失,所以在实际的应用当中绝大多数都采用手动应答

为了保证消息从队列可靠地达到消费者并且被消费者消费处理,RabbitMQ 提供了消息应答机制,RabbitMQ 有两种应答机制,自动应答和手动应答

1、自动应答

RabbitMQ 只要将消息分发给消费者就被认为消息传递成功,就会将内存中的消息删除,而不管消费者有没有处理完消息

2、手动应答

RabbitMQ 将消息分发给了消费者,并且只有当消费者处理完成了整个消息之后才会被认为消息传递成功了,然后才会将内存中的消息删除

消息应答:

消费者在接收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了

手动应答优点:

可以批量应答并且减少网络拥堵

消费方法中设置手动应答

效果:

关闭自动应答

RabbitMQ中查看

开启手动应答后,才返回消息执行成功,保证了消息不会被丢失

3. go实现RabbitMQ 发布订阅模式

RabbitMq消息模式的核心思想是:

一个生产者并不会直接往一个队列中发送消息,事实上,生产者根本不知道它发送的消息将被转发到哪些队列。

  实际上,生产者只能把消息发送给一个exchange(交换机),exchange只做一件简单的事情:一方面它们接收从生产者发送过来的消息,另一方面,它们把接收到的消息推送给队列。一个exchage必须清楚地知道如何处理一条消息。

有四种类型的交换器,分别是:direct、topic、headers、fanous(广播模式)

广播模式交换器很简单,从字面意思也能理解,它其实就是把接收到的消息推送给所有它知道的队列。在我们的日志系统中正好需要这种模式。

go实现RabbitMQ 发布订阅模式 RabbitMQ tutorial - Publish/Subscribe | RabbitMQ

实现广播模式(发布订阅模式)demo

生产者向交换机中发送消息

和简单模式、work模式相比,多了创建交换机

消费者拉取交换机中消息实现消费

和简单模式、work模式相比,多了创建交换机、创建了临时队列、绑定临时队列

临时队列

  我们使用的队列都是有具体的队列名,创建命名队列是很必要的,因为我们需要将消费者指向同一名字的队列。因此,要想在生产者和消费者中间共享队列就必须要使用命名队列。

 demo中的日志系统也可以使用非命名队列(可以不手动命名),我们希望收到所有日志消息,而不是部分。并且我们希望总是接收到新的日志消息而不是旧的日志消息。为了解决这个问题,需要分两步走。

  首先,无论何时我们的消费者连接到RabbitMq,我们都需要一个新的、空的队列来接收日志消息,因此,消费者在连接上RabbitMq之后需要创建一个任意名字的队列,或者让RabbitMq生成任意的队列名字。

  其次,一旦该消费者断开了与RabbitMq的连接,队列也被自动删除。

  通过queueDeclare()来创建一个非持久化、专有的、自动删除的、名字随机生成的队列。

实现发布订阅模式:

创建消息路由

控制器中实现生产者消息推送到交换机

创建mq/fanout/main.go,实现消费者从交换机中获取消息实现消费

效果:

执行生产者,实现消息生产

打开RabbitMQ客户端,查看消息状态

执行消费者,实现消费

注:因为是发布订阅模式。所以我们启动两个消费者实现多个用户消费同一消息

消费者1

消费者2

当生产者生产消息时,所订阅的消费者会执行消费

消费者1

消费者2

4. go实现RabbitMQ 路由模式


一个通过路由把One的消息取出来,另一个通过路由把two的消息取出来,一个队列打印奇数,一个队列打印偶数

生产者代码

消费者代码奇数代码


消费者代码偶数代码

运行效果

5. go实现RabbitMQ 主题模式


生产者代码

// topic主题push
// @router /mq/topic/push [*]
func (this *MqDemoController) GetTopic() {
   //创建线程执行(发送自增的数字到队列中)
   go func() {
      count := 0
      for {
         if count%2 == 0 {
            //strconv.Itoa 把count转化为字符串
            mq.PublishEx("wsyb.demo.topic", "topic", "wsyb.video", "wsyb.video"+strconv.Itoa(count))
         } else {
            mq.PublishEx("wsyb.demo.topic", "topic", "user.wsyb", "user.wsyb"+strconv.Itoa(count))
         }
         count++
         time.Sleep(1 * time.Second)

      }
   }()

   this.Ctx.WriteString("topic")
}

// topic主题push
// @router /mq/topictwo/push [*]
func (this *MqDemoController) GetTopicTwo() {
   //创建线程执行(发送自增的数字到队列中)
   go func() {
      count := 0
      for {
         if count%2 == 0 {
            //strconv.Itoa 把count转化为字符串
            mq.PublishEx("wsyb.demo.topic", "topic", "a.frog.name", "a.frog.name"+strconv.Itoa(count))
         } else {
            mq.PublishEx("wsyb.demo.topic", "topic", "b.frog.uid", "b.frog.uid"+strconv.Itoa(count))
         }
         count++
         time.Sleep(1 * time.Second)

      }
   }()

   this.Ctx.WriteString("topic")
}

消费所有主题代码(#)

// 包名必须是main否则消费不成功
package main

import (
   "fmt"
   "wsybapi/services/mq"
)

func main() {
   //执行消费  # 代表获取所有的数据
   mq.ConsumerEx("wsyb.demo.topic", "topic", "#", callback)
}

// 回调函数
func callback(s string) {
   //打印消费结果
   fmt.Printf("topic all msg is :%s\n", s)
}

匹配多个规则进行消费

// 包名必须是main否则消费不成功
package main

import (
   "fmt"
   "wsybapi/services/mq"
)

func main() {
   //执行消费 * 匹配一个或者多个符合规则的数据
   mq.ConsumerEx("wsyb.demo.topic", "topic", "*.frog.*", callback)
}

// 回调函数
func callback(s string) {
   //打印消费结果
   fmt.Printf("topic frog msg is :%s\n", s)
}

匹配一个规则进行消费

// 包名必须是main否则消费不成功
package main

import (
   "fmt"
   "wsybapi/services/mq"
)

func main() {
   //执行消费
   mq.ConsumerEx("wsyb.demo.topic", "topic", "wsyb.*", callback)
}

// 回调函数
func callback(s string) {
   //打印消费结果
   fmt.Printf("tpic wsyb msg is :%s\n", s)
}

运行结果

6. rabbitmq死信队列

6.1应用场景:
  1. 发送消息规定10分钟以后发送给用户
  2. 规定消息每天固定的时间发送
    3.下了订单没有支付,30分钟以后就会取消订单
    4.订单相关的,下单以后会定时收到会系统的提示消息
6.2什么是死信队列呢:

死信队列产生的条件,不仅是ttl时间过期了,还有消息被拒绝,队列达到最大长度,都会产生死信,相信大家已经明白了

7. go使用MQ实现评论后排行榜更新

修改逻辑,新增评论时更新redis排行榜的数据

发布评论

打开MQ客户端,查看队列状态

创建mq/top/main.go,连接数据库

在消费回调函数中,编写消费者逻辑实现排行榜更新

执行消费者

效果:

先评论内容

打开redis可视化界面,查看排行榜评论数

再次评论

打开redis可视化界面,查看排行榜评论数是否实现更新

golang中,使用RabbitMQ实现多个生产者的方式有多种方法。 一种常见的方法是使用分布式锁。在这种方法中,每个生产者在发送消息之前尝试获取一个全局锁。只有一个生产者能够成功获取锁,然后发送消息RabbitMQ。其他没有获取到锁的生产者会等待一定的时间后再尝试获取锁。这样可以确保每次只有一个生产者能够发送消息,避免多个生产者同时发送导致消息的乱序或重复。 另一种方法是使用消息队列的事务机制。每个生产者在发送消息之前,开始一个事务并将消息发送到RabbitMQ。然后,生产者提交事务。RabbitMQ会确保只有一个生产者能够成功提交事务,其他生产者在提交事务时会失败。这样可以确保每次只有一个生产者能够成功发送消息,避免重复消息的问题。 还有一种方法是使用RabbitMQ的发布-订阅模式。每个生产者将消息发送到一个特定的交换机中,然后交换机将消息广播给所有的消费者。这种方式下,多个生产者可以同时向交换机发送消息,而不需要进行同步或者互斥操作。这种方式适用于需要将消息广播给多个消费者的场景。 无论使用哪种方式,多个生产者可以并发地发送消息RabbitMQ,提高整体系统的吞吐量和并发性能。但是需要注意的是,当多个生产者发送到同一个队列时,可能会引发消息的重复或乱序的问题。因此,在设计多个生产者的架构时,需要根据具体场景来选择合适的方式,并进行适当的消息幂等性设计,以保证消息的一致性和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值