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 节点组成,节点之间共享 用户名.虚拟目录.队列消息.运行参数 等, 这个 节点的集合被 ...
随机推荐
- ADB server didn't ACK问题,连上手机问题(转)
出现如下情况 ADB server didn't ACK* failed to start daemon * 解决办法: 方法一: (1)查看任务管理器,关闭所有adb.exe,或者运行->cm ...
- Git Note - git tag
git tag is used to create labels, usually for version numbers. Format: git tag <TagName> <r ...
- win7 virtio 驱动下载
下载地址: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/
- django系列6--Ajax05 请求头ContentType, 使用Ajax上传文件
一.请求头ContentType ContentType指的是请求体的编码类型,常见的类型共有三种: 1.application/x-www-form-urlencoded 这应该是最常见的 POST ...
- mysql--视图,触发器,事务,存储过程
一.视图 视图是一个虚拟表(非真实存在),是跑到内存中的表,真实表是硬盘上的表,怎么就得到了虚拟表,就是你查询的结果,只不过之前我们查询出来的虚拟表,从内存中取出来显示在屏幕上,内存中就没有了这些表的 ...
- mysql遇到的问题:can't creat/write to file "/var/mysql/xxxx.MYI"
这个问题困扰了我,可能有两个原因. 1.文件夹权限不够,至少也要给出 USERS 组的可读可写权限: 2.文件夹的磁盘满了,文件写不进去了: 如果是这个不能创建和写的问题,很大的概率就是文件的权限.没 ...
- [Objective-C语言教程]继承(25)
面向对象编程中最重要的概念之一是继承.继承允许根据一个类定义另一个类,这样可以更容易地创建和维护一个应用程序. 这也提供了重用代码功能和快速实现时间的机会. 在创建类时,程序员可以指定新类应该继承现有 ...
- iOS 一张图片引发的崩溃SEGV_ACCERR
出错日志一直报SEGV_ACCERR,原因原来是第三方库SDWebImage下载图片,远程图片的json文件不对导致的闪退 解决方法: 1.command + B 编译工程(最好在编译工程时,清除下缓 ...
- 如何使用Node爬虫利器Puppteer进行自动化测试
文:华为云DevCloud 乐少 1.背景 1.1 前端自动化测试较少 前端浏览器众多导致页面兼容性问题比较多,另外界面变化比较快,一个月内可能页面改版两三次,这样导致对前端自动化测试较少,大家也不是 ...
- Description &&debugDescription && runtime(debug模式下调试model)
description 在开发过程中, 往往会有很多的model来装载属性. 而在开发期间经常会进行调试查看model里的属性值是否正确. 那么问题来了, 在objective-c里使用NSLog(& ...