rabbitmq原理和应用
0.1、索引
https://blog.waterflow.link/articles/1663772504649
RabbitMQ 是一个轻量级且易于部署的消息队列。它支持开箱即用的多种消息传递协议。我们将使用 AMQP(高级消息队列协议)
1、概念
既然是消息队列,顾名思义,肯定会有生产者生产消息,消费者消费消息,还会有队列用来保存消息,等等。
我们先来看下这些概念:
- Producer: 将消息推送到rabbitmq交换机的应用
- Consumer: 从队列读取消息并处理他们的应用
- Exchange: 交换机负责在Binding和Routing key的帮助下,将消息路由到不同的队列。从上图可以看出rabbitmq有多种类型的交换机
- Binding: Binding是队列和交换机之间的链接
- Routing key: 交换机用来决定如何将消息路由到队列的键。可以看做是消息的地址
- Queue: 存储消息的缓冲区
- Connection:生产者到Broker(rabbitmq服务),消费者到Broker的连接
- Channel:为了复用一个连接,一个connection下可以有多个channel,可以把connection理解成电线,channel就是电线里面的铜丝。
消息传递的完整流程是这样的:
- 生产者初始化一个到rabbitmq服务的连接
- 获取连接的管道,通过管道声明一个交换机
- 通过管道声明一个队列,通过绑定的路由键将队列和交换机绑定(发送消息的时候声明一个队列并绑定交换机,消息会进到队列里。如果不声明也可以放到消费者去声明队列和绑定交换机。需要注意的是生产者没有声明队列的话,此时已经生产多条消息,然后去开启消费者消费,是不会消费到之前的消息的)
- 通过管道发送消息到指定的交换机
- 消费者初始化一个到rabbitmq服务的连接
- 获取连接的管道,通过管道声明一个队列
- 通过绑定的路由键将队列和交换机绑定
- 从队列中消费消息
交换机类型:
- direct:直接指定到某个队列
- topic:发布订阅模式,一个交换机可以对应多个队列,通过路由规则匹配
- fanout:顾名思义,无脑广播模式
2、示例
生产者:
package main
import (
"fmt"
"time"
"github.com/streadway/amqp"
)
var (
conn *amqp.Connection
channel *amqp.Channel
queue amqp.Queue
mymsg = "Hello HaiCoder"
err error
confirms chan amqp.Confirmation
)
func main() {
// 建立连接
conn, err = amqp.Dial("amqp://guest:guest@127.0.0.1:5672/")
if err != nil {
fmt.Println(err)
return
}
defer conn.Close()
// 创建channel
if channel, err = conn.Channel(); err != nil {
fmt.Println(err)
return
}
// 声明交换机
err = channel.ExchangeDeclare("liutest", amqp.ExchangeDirect, false, false, false, false, nil)
if err != nil {
fmt.Println("ExchangeDeclare Err =", err)
return
}
// 创建队列
if queue, err = channel.QueueDeclare("liutest", false, false, false, false, nil); err != nil {
fmt.Println("QueueDeclare Err =", err)
return
}
// 队列和交换机绑定
err = channel.QueueBind(queue.Name, "queueroutekey", "liutest", false, nil)
if err != nil {
fmt.Println("QueueBind Err =", err)
return
}
channel.Confirm(false)
confirms = channel.NotifyPublish(make(chan amqp.Confirmation, 1))
//发送数据
go func() {
for {
if err = channel.Publish("liutest", "queueroutekey", false, false, amqp.Publishing{
ContentType: "text/plain",
Body: []byte(mymsg),
}); err != nil {
fmt.Println("Publish Err =", err)
return
}
fmt.Println("Send msg ok, msg =", mymsg)
time.Sleep(time.Second * 5)
}
}()
go func() {
for confirm := range confirms {
if confirm.Ack {
fmt.Printf("confirmed delivery with delivery tag: %d \n", confirm.DeliveryTag)
} else {
fmt.Printf("confirmed delivery of delivery tag: %d \n", confirm.DeliveryTag)
}
}
}()
select {}
}
消费者:
package main
import (
"fmt"
"github.com/streadway/amqp"
)
var (
conn *amqp.Connection
channel *amqp.Channel
queue amqp.Queue
err error
msgs <-chan amqp.Delivery
)
func main() {
// 建立连接
conn, err = amqp.Dial("amqp://guest:guest@127.0.0.1:5672/")
if err != nil {
fmt.Println(err)
return
}
defer conn.Close()
// 创建channel
if channel, err = conn.Channel(); err != nil {
fmt.Println(err)
return
}
// 创建队列
if queue, err = channel.QueueDeclare("liutest", false, false, false, false, nil); err != nil {
fmt.Println("QueueDeclare Err =", err)
return
}
err = channel.QueueBind("liutest", "queueroutekey", "liutest", false, nil)
if err != nil {
fmt.Println("QueueBind Err =", err)
return
}
//读取数据
if msgs, err = channel.Consume(queue.Name, "", false, false, false, false, nil); err != nil {
fmt.Println("Consume Err =", err)
return
}
go func() {
for msg := range msgs {
fmt.Println("Receive Msg =", string(msg.Body))
msg.Ack(false)
}
}()
select {}
}
3、消息可靠性
生产者可靠性
// 将通道设置为确认模式
func (ch *Channel) Confirm(noWait bool) error {
if err := ch.call(
&confirmSelect{Nowait: noWait},
&confirmSelectOk{},
); err != nil {
return err
}
ch.confirmM.Lock()
ch.confirming = true
ch.confirmM.Unlock()
return nil
}
// 用于接受服务端的确认响应
func (ch *Channel) NotifyPublish(confirm chan Confirmation) chan Confirmation {
ch.notifyM.Lock()
defer ch.notifyM.Unlock()
if ch.noNotify {
close(confirm)
} else {
ch.confirms.Listen(confirm)
}
return confirm
}
Confirm 将此通道置为确认模式,以便生产者可以确保服务端已成功接收所有消息。进入该模式后,服务端将发送一个basic.ack或basic.nack消息,其中deliver tag设置为一个基于1的增量索引(用来标识消息的唯一性),对应于该方法返回后收到的每次ack。
在 Channel.NotifyPublish上监听以响应ack。如果未调用 Channel.NotifyPublish,则ack将被忽略。
ack的顺序不受投递消息顺序的约束。
Ack 和 Nack 确认将在未来的某个时间到达。
在通知任何 Channel.NotifyReturn 侦听器后,立即确认不可路由的mandatory或immediate消息。当所有应该将消息路由到它们的队列都已收到传递确认或已将消息加入队列时,其他消息将被确认,必要时将消息持久化。
注:当mandatory标志位设置为true时,如果exchange根据自身类型和消息routingKey无法找到一个合适的queue存储消息,那么broker会调用basic.return方法将消息返还给生产者;当mandatory设置为false时,出现上述情况broker会直接将消息丢弃;通俗的讲,mandatory标志告诉broker代理服务器至少将消息route到一个队列中,否则就将消息return给发送者;
当 noWait 为真时,客户端不会等待响应。如果服务端不支持此方法,则可能会发生通道异常。
具体代码实现如下:
...
// 设置消息确认
channel.Confirm(false)
confirms = channel.NotifyPublish(make(chan amqp.Confirmation, 1))
...
go func() {
for confirm := range confirms {
if confirm.Ack { // 消息已确认
fmt.Printf("confirmed delivery with delivery tag: %d \n", confirm.DeliveryTag)
} else { // 未确认的消息可以重新发送
fmt.Printf("failed confirmed delivery of delivery tag: %d \n", confirm.DeliveryTag)
}
}
}()
...
消费者可靠性
// 将autoAck设置为false
func (ch *Channel) Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args Table) (<-chan Delivery, error) {
// When we return from ch.call, there may be a delivery already for the
// consumer that hasn't been added to the consumer hash yet. Because of
// this, we never rely on the server picking a consumer tag for us.
if err := args.Validate(); err != nil {
return nil, err
}
if consumer == "" {
consumer = uniqueConsumerTag()
}
req := &basicConsume{
Queue: queue,
ConsumerTag: consumer,
NoLocal: noLocal,
NoAck: autoAck,
Exclusive: exclusive,
NoWait: noWait,
Arguments: args,
}
res := &basicConsumeOk{}
deliveries := make(chan Delivery)
ch.consumers.add(consumer, deliveries)
if err := ch.call(req, res); err != nil {
ch.consumers.cancel(consumer)
return nil, err
}
return (<-chan Delivery)(deliveries), nil
}
立即开始消费排队的消息。
在 Connection 或 Channel 上的任何其他操作之前开始接收返回的 chan Delivery。
消息会继续往返回的 chan Delivery 传递,直到发生 Channel.Cancel、Connection.Close、Channel.Close 或 AMQP 异常。消费者必须在 chan 范围内确保收到所有消息。未收到的消息将阻塞同一连接上的所有方法。
AMQP 中的所有消息都必须得到确认。消费者在成功处理消息后最好手动调用 Delivery.Ack。如果消费者被取消或通道或连接被关闭,任何未确认的消息将在同一队列的末尾重新入队。
消费者由一个字符串标识,该字符串是唯一的,适用于该channal上的所有消费者。如果希望最终取消消费者,请在 Channel.Cancel 中使用相同的非空标识符。空字符串将导致重新成唯一标识。消费者身份将包含在 ConsumerTag 字段中的每个消息中
当 autoAck(也称为 noAck)为真时,服务器将在将消息写入网络之前向该消费者确认确认。当 autoAck 为真时,消费者不应调用 Delivery.Ack。自动确认消息意味着如果服务器投递消息后消费者无法处理某些消息,则可能会丢失某些消息。
当exclusive 为true 时,服务器将确保这是该队列中的唯一消费者。当exclusive 为false 时,服务器将在多个消费者之间公平地分发消息。 RabbitMQ 不支持 noLocal 标志。建议对 Channel.Publish 和 Channel.Consume 使用单独的连接,以免在发布时 TCP 回推影响消费消息的能力,因此这里主要是为了完整性。当 noWait 为 true 时,不要等待服务器确认请求并立即开始消费。如果无法消费,则会引发通道异常并关闭通道。
消费消息时,将autoAck设置为false
func (d Delivery) Ack(multiple bool) error {
if d.Acknowledger == nil {
return errDeliveryNotInitialized
}
return d.Acknowledger.Ack(d.DeliveryTag, multiple)
}
客户端消费到消息后,需要调用ack确认接收到消息
AMQP 中的所有消息的投递都必须得到确认。如果使用 autoAck true 调用 Channel.Consume,那么服务端将自动确认每条消息,但是不应该调用此方法,因为这个不能保证消费端业务处理成功。所以,必须在成功处理消息后调用 Delivery.Ack。当multiple 为真时,此消息和同一通道上所有先前未确认的消息将被确认,这对于消息的批处理很有用(但是有个弊端就是,如果有一个出错了,所有批处理的数据都需要重发)。对于每个未自动确认的消息,都必须调用 Delivery.Ack、Delivery.Reject 或 Delivery.Nack。
消费端的确认机制的实现:
...
//读取数据
if msgs, err = channel.Consume(queue.Name, "", false, false, false, false, nil); err != nil {
fmt.Println("Consume Err =", err)
return
}
go func() {
for msg := range msgs {
fmt.Println("Receive Msg =", string(msg.Body))
// 确认消息
msg.Ack(false)
}
}()
...
rabbitmq原理和应用的更多相关文章
- Spring Boot2.X整合消息中间件RabbitMQ原理简浅探析
目录 1.简单概述RabbitMQ重要作用 2.简单概述RabbitMQ重要概念 3.Spring Boot整合RabbitMQ 前言 RabbitMQ是一个消息队列,主要是用来实现应用程序的异步和解 ...
- RabbitMQ原理和架构图解(附6大工作模式)
为什么要使用RabbitMQ? 1.解耦 系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦. 2.异步 将消息写入消息队列,非必要的业务逻辑以异步的方式运 ...
- RabbitMQ原理与相关操作(一)
小编是菜鸟一枚,最近想试试MQ相关的技术,所以自己看了下RabbitMQ官网,试着写下自己的理解与操作的过程. 刚开始的第一篇,原理只介绍 生产者.消费者.队列,至于其他的内容,会在后续中陆续补齐. ...
- RabbitMQ原理与相关操作(二)
接着 上篇随笔 增加几个概念: RabbitMQ是一个在AMQP(高级消息队列协议)标准基础上完整的,可服用的企业消息系统. AMQP模型的功能组件图(上图摘自 Sophia_tj 的 第2章 AMQ ...
- RabbitMQ原理与相关操作(三)消息持久化
现在聊一下RabbitMQ消息持久化: 问题及方案描述 1.当有多个消费者同时收取消息,且每个消费者在接收消息的同时,还要处理其它的事情,且会消耗很长的时间.在此过程中可能会出现一些意外,比如消息接收 ...
- RabbitMQ原理
vhosts(broker) connection 与 channel(连接与信道) exchange 与 routingkey(交换机与路由键) queue(队列) Binding(绑定) cli ...
- RabbitMQ原理——exchange、route、queue的关系
从AMQP协议可以看出,MessageQueue.Exchange和Binding构成了AMQP协议的核心,下面我们就围绕这三个主要组件 从应用使用的角度全面的介绍如何利用Rabbit MQ构建 ...
- RabbitMQ原理介绍
RabbitMQ历史 RabbitMQ消息系统是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.在同步消息通讯的世界里有很多公开标准(如COBAR的IIO ...
- SpringCloud之RabbitMQ消息队列原理及配置
本篇章讲解RabbitMQ的用途.原理以及配置,RabbitMQ的安装请查看SpringCloud之RabbitMQ安装 一.MQ用途 1.同步变异步消息 场景:用户下单完成后,发送邮件和短信通知. ...
随机推荐
- 技术分享 | ARM下中标麒麟系统ky10使用Xtrabackup-8.0.25
欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 一.需求背景 查询Percona官方手册,Xtrabackup 8.0可以备份M ...
- 万答#15,都有哪些情况可能导致MGR服务无法启动
欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 本文转载自微信公众号 "老叶茶馆" 欢迎大家关注! 1.都有 ...
- 用 Scanner 扫描CSV文件时报错:“java.util.nosuchelementexception:no line found”的解决方法
最近用 java 对一个很大的 CSV 文件进行处理.打算用 Scanner 逐行扫描进来,结果报错 "java.util.nosuchelementexception:no line fo ...
- 054_末晨曦Vue技术_处理边界情况之组件之间的循环引用
组件之间的循环引用 点击打开视频讲解更详细 假设你需要构建一个文件目录树,像访达或资源管理器那样的.你可能有一个 <tree-folder> 组件,模板是这样的: <p> &l ...
- z—libirary最新地址获取,zlibirary地址获取方式,zliabary最新地址,zliabary官网登录方式,zliabary最新登陆
Z-Library(缩写为z-lib,以前称为BookFinder)是Library Genesis的镜像,一个影子图书馆项目,用于对学术期刊文章.学术文本和大众感兴趣的书籍(其中一些是盗版的)进行文 ...
- 编写X86的ShellCode
ShellCode 定义 ShellCode是不依赖环境,放到任何地方都能够执行的机器码 编写ShellCode的方式有两种,分别是用编程语言编写或者用ShellCode生成器自动生成 ShellCo ...
- 刷题记录:Codeforces Round #731 (Div. 3)
Codeforces Round #731 (Div. 3) 20210803.网址:https://codeforces.com/contest/1547. 感觉这次犯的低级错误有亿点多-- A 一 ...
- C#基础_利用Stopwatch计时器可暂停计时,继续计时
最近程序上用到了计时功能,对某个模块进行计时,暂停的时候模块也需要暂停,启动的时候计时继续 用到了Stopwatch Stopwatch的命名空间是using System.Diagnostics; ...
- ELK技术-Logstash
1.背景 1.1 简介 Logstash 是一个功能强大的工具,可与各种部署集成. 它提供了大量插件,可帮助业务做解析,丰富,转换和缓冲来自各种来源的数据. Logstash 是一个数据流引擎 它是用 ...
- 利用ldt_struct 与 modify_ldt 系统调用实现任意地址读写
利用ldt_struct 与 modify_ldt 系统调用实现任意地址读写 ldt_struct与modify_ldt系统调用的介绍 ldt_struct ldt是局部段描述符表,里面存放的是进程的 ...