Go RabbitMQ 工作队列 (二)
rabbitMQ工作队列
在之前内容中我们通过一个队列实现了消息的发送跟接收。接下来我们创建工作队列(Work Queue),用于在多个工作者之间分配耗时的任务
工作队列(任务队列)背后的核心主要是避免立即执行资源密集型的任务,必须等待其工作完成。我们将任务封装为消息后将其发送到队列,后台的工作进程将弹出任务并最终执行,当我们运行很多Worker时候,任务将在它们之间共享
round-robin 调度
- 使用任务队列的优点之一就是能够轻松的并行化工作
- 默认情况下,RabbitMQ会将每一条信息按照消费者顺序发送给一个消费者,这样平均每个消费者会接收到相同数量的消息,这种消息分发的模式叫做round-robin(启动多个接收端,然后发送多个消息试试)
message acknowledgment(消息确认)
为了确保消息不会丢失,RabbitMQ支持消息确认,消费者消费了一个消息之后会发送一个ack给RabbitMQ,这样RabbitMQ就可以删除掉这个消息
如果一个消费者异常(通道关闭或链接关闭或TCP链接丢失)没有发送ACK给rabbitMQ,rabbitMQ会将该消息重新放入队列当中。此时如果有其他消费者在线,rabbitMQ会重新将该消息再次投递到另一个消费者
手动确认ACK
- 手动确认ACK我们可以在创建消费者的时候将auto-ack设置为false,一旦我们消费消息任务完毕的时候使用d.Ack(false)来确认ack,告诉RabbitMQ该消息可以删除
msgs,err := ch.Consume(
q.Name,
"",
false,//将autoAck设置为false,则需要在消费者每次消费完成
// 消息的时候调用d.Ack(false)来告诉RabbitMQ该消息已经消费
false,
false,
false,
nil,
)
FailError(err,"Failed to register a consumer")
forever := make(chan bool)
go func() {
for d := range msgs{
log.Printf("Received a message: %s", d.Body)
dot_count := bytes.Count(d.Body, []byte("."))
t := time.Duration(dot_count)
time.Sleep(t * time.Second)
log.Printf("Done")
//multiple为true的时候:此次交付和之前没有确认的交付都会在通过同一个通道交付,这在批量处理的时候很有用
//为false的时候只交付本次。只有该方法执行了,RabbitMQ收到该确认才会将消息删除
d.Ack(false)
}
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
<-forever
使用以上设置后,我们可以保证即使worker在执行任务的时候意外退出也不会丢失消息。在worker意外退出的不久之后消息将会被重新投递。确认ack必须使用接收到消息的通道,如果使用不同的通道将会导致一个通道协议异常
忘记确认ack
- 在开发的时候经常会忘记对消费过的消息进行ack确认,这是一个很严重的错误,可以使用以下命令查看RabbitMQ中有多少消息在准备中或是未确认的: sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged
Listing queues for vhost / ...
name messages_ready messages_unacknowledged
hello 0 1
```
* messages_ready:未投递的消息
* messages_unacknowledged:投递未收到回复的消息
消息持久
我们已经知道如何确保即使消费者意外退出的情况下保证任务不会丢失。但是如果RabbitMQ服务停止的话任务还是会丢失。当RabbitMQ退出或异常的时候,它将会丢失队列和消息,除非你设置RabbitMQ的两个地方:将队列和消息进行标记为持久的
首先设置队列durable为true
q, err := ch.QueueDeclare(
"hello", // name
true, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
```
RabbitMQ不允许使用不同参数重新定义一个已经存在的队列,所以队列已经存在的话修改了上面的配置后运行程序是不会改变已经存在的队列的
然后设置消息为持久化存储:
err = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false,
amqp.Publishing {
DeliveryMode: amqp.Persistent,
ContentType: "text/plain",
Body: []byte(body),
})
```
注意:设置消息持久化并不能保证消息不会丢失,因为仍然有一小段时间片处于RabbitMQ收到消息但是还没保存,它可能只是保存在内存当中。但是已经满足我们的基本使用,如果你需要强保证的话可以使用**publisher confirms**
公平调度(Fair dispatch)
- RabbitMQ的默认消息分配不能够满足我们的需要,比如有两个消费者,其中一个消费者经常忙碌的状态,另外一个消费者几乎不做任何工作,但是RabbitMQ仍然均匀的在两者之间调度消息。这是因为RabbitMQ只做队列当中的消息调度而没有查看某个消费者中未确认的消息,它只是盲目的将第n条消息发送给第n个消费者
- 解决以上问题我们可以设置prefetch count数值为1,这样只有当消费者消费完消息并返回ack确认后RabbitMQ才会给其分发消息,否则只会将消息分发给其他空闲状态的消费者
err = ch.Qos(
1, // prefetch count
0, // prefetch size
false, // global
)
注意:消费者必须要设置,生产者不用设置
完整代码
- new_task.go
func main() {
conn,err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failError(err,"send:Failed to connect to RabbitMQ")
defer conn.Close()
ch,err := conn.Channel()
failError(err,"Failed to open a channel")
defer ch.Close()
q,err := ch.QueueDeclare(
"task_queue",
true,// 设置为true之后RabbitMQ将永远不会丢失队列,否则重启或异常退出的时候会丢失
false,
false,
false,
nil,
)
failError(err,"Failed to declare a queue")
fmt.Println(q.Name)
body := bodyFrom(os.Args)
//生产者将消息发送到默认交换器中,不是发送到队列中
ch.Publish(
"",//默认交换器
q.Name,//使用队列的名字来当作route-key是因为声明的每一个队列都有一个隐式路由到默认交换器
false,
false,
amqp.Publishing{
DeliveryMode:amqp.Persistent,
ContentType:"text/plain",
Body:[]byte(body),
})
failError(err,"Failed to publish a message")
log.Printf(" [x] Sent %s",body)
}
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
}
func failError(err error,msg string) {
if err != nil {
log.Fatal("%s : %s",msg,err)
}
}
- Worker.go
func main() {
conn,err := amqp.Dial("amqp://guest:guest@localhost:5672/")
FailError1(err,"receive:Failed to connect to RabbitMQ")
defer conn.Close()
ch,err := conn.Channel()
FailError1(err,"receive:Failed to open a channel")
defer ch.Close()
q,err := ch.QueueDeclare(
"task_queue",
true,
false,
false,
false,
nil,
)
err = ch.Qos(
1, //// 在没有返回ack之前,最多只接收1个消息
0,
false,
)
FailError1(err,"Failed to set Qos")
msgs,err := ch.Consume(
q.Name,
"",
false,//将autoAck设置为false,则需要在消费者每次消费完成
// 消息的时候调用d.Ack(false)来告诉RabbitMQ该消息已经消费
false,
false,
false,
nil,
)
FailError1(err,"Failed to register a consumer")
forever := make(chan bool)
go func() {
for d := range msgs{
log.Printf("Received a message: %s", d.Body)
dot_count := bytes.Count(d.Body, []byte("."))
t := time.Duration(dot_count)
fmt.Println()
time.Sleep(t * time.Second)
log.Printf("Done")
//multiple为true的时候:此次交付和之前没有确认的交付都会在通过同一个通道交付,这在批量处理的时候很有用
//为false的时候只交付本次。只有该方法执行了,RabbitMQ收到该确认才会将消息删除
d.Ack(false)
}
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
<-forever
}
func FailError1(err error,msg string) {
if err != nil {
log.Fatal("%s : %s",msg,err)
}
}
Go RabbitMQ 工作队列 (二)的更多相关文章
- .NET 云原生架构师训练营(模块二 基础巩固 RabbitMQ 工作队列和交换机)--学习笔记
2.6.4 RabbitMQ -- 工作队列和交换机 WorkQueue Publish/Subscribe Routing EmitLog WorkQueue WorkQueue:https://w ...
- RabbitMQ (二)工作队列 -摘自网络
这篇中我们将会创建一个工作队列用来在工作者(consumer)间分发耗时任务.工作队列的主要任务是:避免立刻执行资源密集型任务,然后必须等待其完成.相反地,我们进行任务调度:我们把任务封装为消息发送给 ...
- RabbitMQ (二)工作队列
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37620057 本系列教程主要来自于官网入门教程的翻译,然后自己进行了部分的修改与 ...
- python使用rabbitMQ介绍二(工作队列模式)
一模式介绍 第一章节的生产-消费者模式,是非常简单的模式,一发一收.在实际的应用中,消费者有的时候需要工作较长的时间,则需要增加消费者. 队列模型: 这时mq实现了一下几个功能: rabbitmq循环 ...
- rabbitmq系列二 之工作队列
---恢复内容开始--- 1.工作队列的简介 在上一篇中,我们已经写了一个从已知队列中发送和获取消息的程序,在这里,我们创建一个工作队列(work queue), 会发送一些耗时的任务给多个工作者.模 ...
- 轻松搞定RabbitMQ(二)——工作队列之消息分发机制
转自 http://blog.csdn.net/xiaoxian8023/article/details/48681987 上一篇博文中简单介绍了一下RabbitMQ的基础知识,并写了一个经典语言入门 ...
- RabbitMQ 工作队列
创建一个工作队列用来在工作者(consumer)间分发耗时任务. 工作队列的主要任务是:避免立刻执行资源密集型任务,然后必须等待其完成.相反地,我们进行任务调度:我们把任务封装为消息发送给队列.工作进 ...
- RabbitMQ(二)
一.启用 rabbitmq_management 插件(官网提供的 web 版管理工具) cd /usr/sbin rabbitmq-plugins enable rabbitmq_managemen ...
- 消息队列的使用 RabbitMQ (二): Windows 环境下集群的实现
一.RabbitMQ 集群的基本概念 一个 RabbitMQ 中间件(broker) 由一个或多个 erlang 节点组成,节点之间共享 用户名.虚拟目录.队列消息.运行参数 等, 这个 节点的集合被 ...
随机推荐
- 论文研读之Spinnaker
论文:Using Paxos to Build a Scalable, Consistent, and Highly Available Datastore Motivation 可扩展性: 随着数据 ...
- 通过Jenkins进行提权的一个思路
作者:欧根亲王号 所属团队:Arctic Shell Jenkins是一款由Java编写的开源的持续集成工具,其本身具有执行脚本的功能 在Jenkins的说明信息中列出我们可以使用任意Groovy ...
- Usboot V1.68版本
V1.68版本,我的收藏之一 官方的介绍: 市面上现在大多数U盘都支持启动机器的功能,但是要制作启动型U盘,需要进入WIN98,现在很多人机器 上都没有98了吧,呵呵.为了做个启动盘,装一个98,多冤 ...
- ElasticSearch安装拼音插件 elasticsearch-analysis-pinyin
elasticsearch-analysis-pinyin 是 ElasticSearch的拼音插件.强大的功能支持拼音等的搜索 1.下载源代码 源码地址https://github.com/medc ...
- 基于Dubbo的Hessian协议实现远程调用
Dubbo基于Hessian实现了自己Hessian协议,可以直接通过配置的Dubbo内置的其他协议,在服务消费方进行远程调用,也就是说,服务调用方需要使用Java语言来基于Dubbo调用提供方服务, ...
- ubuntu下apache2使用的简单总结
一. 修改apache2原80端口为90端口 1. 修改/etc/apache2/ports.conf, 将端口80改为90,443,改为444 2. 修改/etc/apache2/sites ...
- P2278 操作系统
P2278 操作系统 题目描述 写一个程序来模拟操作系统的进程调度.假设该系统只有一个CPU,每一个进程的到达时间,执行时间和运行优先级都是已知的.其中运行优先级用自然数表示,数字越大,则优先级越高. ...
- 图的基础---关键路径理解和实现(Java)
引言 之前所说的拓扑排序是为了解决一个工程能否顺利进行的问题.但在生活中,我们还会经常遇到如何解决工程完成需要的最短时间问题. 举个例子,我们需要制作一台汽车,我们需要先造各种各样的零件,然后 ...
- C#-MVC基础-模型(Model)、视图(View)和控制器(Controller)
搜狗百科:http://baike.sogou.com/v25227.htm?fromTitle=MVC MVC全名是Model View Controller,是软件工程中的一种软件架构模式,把软件 ...
- opencv 将视频分解成图片和使用本地图片合成视频
代码如下: // cvTest.cpp : Defines the entry point for the console application. #include "stdafx.h&q ...