1、什么是RabbitMQ工作队列

  我们在应用程序使用消息系统时,一般情况下生产者往队列里插入数据时速度是比较快的,但是消费者消费数据往往涉及到一些业务逻辑处理导致速度跟不上生产者生产数据。因此如果一个生产者对应一个消费者的话,很容易导致很多消息堆积在队列里。这时,就得使用工作队列了。一个队列有多个消费者同时消费数据。

  下图取自于官方网站(RabbitMQ)的工作队列的图例

P:消息的生产者

C1:消息的消费者1

C2:消息的消费者2

红色:队列

生产者将消息发送到队列,多个消费者同时从队列中获取消息。

  工作队列有两种分发数据的方式:轮询分发(Round-robin)和  公平分发(Fair dispatch)。轮询分发:队列给每一个消费者发送数量一样的数据。公平分发:消费者设置每次从队列中取一条数据,并且消费完后手动应答,继续从队列取下一个数据。下面分别是两种分发方式不同的写法。

2、轮询分发(Round-robin)

  生产者(Send)生产10条数据,消费者1(Receive1)接收数据并假设处理业务逻辑1s,消费者2(Receive2)接收数据并假设处理业务逻辑2s(生产者先运行,两个消费者同时运行)。

2.1、生产者(Send)代码

public class Send
{
//队列名称
private static final String QUEUE_NAME = "test_work_round_robin_queue"; public static void main(String[] args)
{
try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 10; i++)
{
String message = "this is work_round_robin queue message" + i;
System.out.println("[send]:" + message);
//发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("utf-8"));
Thread.sleep(20 * i);
}
channel.close();
connection.close();
}
catch (IOException | TimeoutException | InterruptedException e)
{
e.printStackTrace();
}
}
} 运行结果:

[send]:this is work_round_robin queue message0
[send]:this is work_round_robin queue message1
[send]:this is work_round_robin queue message2
[send]:this is work_round_robin queue message3
[send]:this is work_round_robin queue message4
[send]:this is work_round_robin queue message5
[send]:this is work_round_robin queue message6
[send]:this is work_round_robin queue message7
[send]:this is work_round_robin queue message8
[send]:this is work_round_robin queue message9

2.2、消费者1(Receive1)代码

public class Receive1
{
//队列名称
private static final String QUEUE_NAME = "test_work_round_robin_queue"; public static void main(String[] args)
{
try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel)
{
//当消息到达时执行回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException
{
String message = new String(body, "utf-8");
System.out.println("[1] Receive message:" + message);
try
{
//消费者休息1s处理业务
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
System.out.println("[1] done");
}
}
};
//监听队列
channel.basicConsume(QUEUE_NAME, true, consumer);
}
catch (IOException e)
{
e.printStackTrace();
} }
} 运行结果:

[1] Receive message:this is work_round_robin queue message0
[1] done
[1] Receive message:this is work_round_robin queue message2
[1] done
[1] Receive message:this is work_round_robin queue message4
[1] done
[1] Receive message:this is work_round_robin queue message6
[1] done
[1] Receive message:this is work_round_robin queue message8
[1] done

2.3、消费者2(Receive2)代码

public class Receive2
{
//队列名称
private static final String QUEUE_NAME = "test_work_round_robin_queue"; public static void main(String[] args)
{ try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel)
{
//当消息到达时执行回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException
{
String message = new String(body, "utf-8");
System.out.println("[2] Receive message:" + message);
try
{
//消费者休息2s处理业务
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
System.out.println("[2] done");
}
}
};
//监听队列
channel.basicConsume(QUEUE_NAME, true, consumer);
}
catch (IOException e)
{
e.printStackTrace();
} }
} 运行结果:

[2] Receive message:this is work_round_robin queue message1
[2] done
[2] Receive message:this is work_round_robin queue message3
[2] done
[2] Receive message:this is work_round_robin queue message5
[2] done
[2] Receive message:this is work_round_robin queue message7
[2] done
[2] Receive message:this is work_round_robin queue message9
[2] done

总结:两个消费者得到的数据量一样的。从运行时可以看到消费者1会先执行完,消费者2会后执行完。并不会因为两个消费者处理数据速度不一样使得两个消费者取得不一样数量的数据。并且当队列数量大的时候通过观察RabbitMQ的管理后台,可以看到管理界面队列中的数据很快就没了,但是这个时候两个消费者其实并没有消费完数据。这种分发方式存在着很大的隐患。

3、公平分发(Fair dispatch)

  生产者(Send)生产10条数据,消费者1(Receive1)接收数据并假设处理业务逻辑1s,消费者2(Receive2)接收数据并假设处理业务逻辑2s(生产者先运行,两个消费者同时运行)。

消费者设置每次从队列里取一条数据,并且关闭自动回复机制,每次取完一条数据后,手动回复并继续取下一条数据。

3.1、生产者(Send)代码

public class Send
{
//队列名称
private static final String QUEUE_NAME = "test_work_fair_dispatch_queue"; public static void main(String[] args)
{
try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 10; i++)
{
String message = "this is work_fair_dispatch queue message" + i;
System.out.println("[send]:" + message);
//发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("utf-8"));
Thread.sleep(20 * i);
}
channel.close();
connection.close();
}
catch (IOException | TimeoutException | InterruptedException e)
{
e.printStackTrace();
}
}
} 运行结果:

[send]:this is work_fair_dispatch queue message0
[send]:this is work_fair_dispatch queue message1
[send]:this is work_fair_dispatch queue message2
[send]:this is work_fair_dispatch queue message3
[send]:this is work_fair_dispatch queue message4
[send]:this is work_fair_dispatch queue message5
[send]:this is work_fair_dispatch queue message6
[send]:this is work_fair_dispatch queue message7
[send]:this is work_fair_dispatch queue message8
[send]:this is work_fair_dispatch queue message9

3.2、消费者1(Receive1)代码

public class Receive1
{
//队列名称
private static final String QUEUE_NAME = "test_work_fair_dispatch_queue"; public static void main(String[] args)
{ try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//设置每次从队列里取一条数据
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel)
{
//当消息到达时执行回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException
{
String message = new String(body, "utf-8");
System.out.println("[1] Receive message:" + message);
try
{
//消费者休息1s处理业务
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
System.out.println("[1] done");
//手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//设置手动应答
boolean autoAck = false;
//监听队列
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
catch (IOException e)
{
e.printStackTrace();
} }
} 运行结果:

[1] Receive message:this is work_fair_dispatch queue message1
[1] done
[1] Receive message:this is work_fair_dispatch queue message2
[1] done
[1] Receive message:this is work_fair_dispatch queue message4
[1] done
[1] Receive message:this is work_fair_dispatch queue message5
[1] done
[1] Receive message:this is work_fair_dispatch queue message7
[1] done
[1] Receive message:this is work_fair_dispatch queue message8
[1] done

3.3、消费者2(Receive2)代码

public class Receive2
{
//队列名称
private static final String QUEUE_NAME = "test_work_fair_dispatch_queue"; public static void main(String[] args)
{ try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//保证一次只分发一个
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel)
{
//当消息到达时执行回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException
{
String message = new String(body, "utf-8");
System.out.println("[2] Receive message:" + message);
try
{
//消费者休息2s处理业务
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
System.out.println("[2] done");
//手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//设置手动应答
boolean autoAck = false;
//监听队列
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
catch (IOException e)
{
e.printStackTrace();
} }
} 运行结果:

[2] Receive message:this is work_fair_dispatch queue message0
[2] done
[2] Receive message:this is work_fair_dispatch queue message3
[2] done
[2] Receive message:this is work_fair_dispatch queue message6
[2] done
[2] Receive message:this is work_fair_dispatch queue message9
[2] done

总结:消费者1处理了6条数据,消费者2处理了4条数据

  与轮询分发不同的是,当每个消费都设置了每次只会从队列取一条数据时,并且关闭自动应答,在每次处理完数据后手动给队列发送确认收到数据。这样队列就会公平给每个消息费者发送数据,消费一条再发第二条,而且可以在管理界面中看到数据是一条条随着消费者消费完从而减少的,并不是一下子全部分发完了。显然公平分发更符合系统设计。

  

注意:本文仅代表个人理解和看法哟!和本人所在公司和团体无任何关系!

RabbitMQ学习第二记:工作队列的两种分发方式,轮询分发(Round-robin)和 公平分发(Fair dispatch)的更多相关文章

  1. 记Bootstrap Table两种渲染方式

    这里主要区别两种Bootstrap Table的数据渲染方式,一.属性渲染方式,二.JS渲染方式 工作直接接手前人的,之前都直接在table标签上渲染属性,后面因为项目需要,同一页面的表格,需要申请不 ...

  2. Android学习笔记(36):Android的两种事件处理方式

    Android提供了两种事件处理的方式:基于回调的事件处理 和 基于监听的事件处理. 我们来说的easy理解一点: (1)基于回调的事件处理就是继承GUI组件,并重写该组件的事件处理方法.除了一些特定 ...

  3. POJ 3225 线段树区间更新(两种更新方式)

    http://blog.csdn.net/niuox/article/details/9664487 这道题明显是线段树,根据题意可以知道: (用0和1表示是否包含区间,-1表示该区间内既有包含又有不 ...

  4. Java学习-014-文本文件写入实例源代码(两种写入方式)

    此文源码主要为应用 Java 读取文本文件内容实例的源代码.若有不足之处,敬请大神指正,不胜感激! 第一种:文本文件写入,若文件存在则删除原文件,并重新创建文件.源代码如下所示: /** * @fun ...

  5. Spring学习之Spring与Mybatis的两种整合方式

    本机使用IDEA 2020.1.MySql 8.0.19,通过Maven进行构建 环境准备 导入maven依赖包 <dependencies> <dependency> < ...

  6. ElasticSearch 学习记录之Text keyword 两种基本类型区别

    ElasticSearch 系列文章 1 ES 入门之一 安装ElasticSearcha 2 ES 记录之如何创建一个索引映射 3 ElasticSearch 学习记录之Text keyword 两 ...

  7. 【原】iOS学习之XML与JSON两种数据结构比较和各自底层实现

    1.XML与JSON两种数据结构的优缺点 1> XML 优点:
 格式统一, 符合标准
 容易与其他系统进行远程交互, 数据共享比较方便 
 缺点: XML文件格式文件庞大, 格式复杂, 传输占 ...

  8. struts2+spring的两种整合方式

    也许有些人会因为学习了struts1,会以为struts2.struts1与spring的整合也是一样的,其实这两者相差甚远.下面就来讲解一下struts2与spring的整合两种方案.(部分转载,里 ...

  9. 串的两种模式匹配方式(BF/KMP算法)

    前言 串,又称作字符串,它是由0个或者多个字符所组成的有限序列,串同样可以采用顺序存储和链式存储两种方式进行存储,在主串中查找定位子串问题(模式匹配)是串中最重要的操作之一,而不同的算法实现有着不同的 ...

随机推荐

  1. 1022 Digital Library (30 分)

    1022 Digital Library (30 分)   A Digital Library contains millions of books, stored according to thei ...

  2. 20140814 explicit

    1.explicit explicit   只对构造函数起作用,用来抑制隐式转换. 如:     class   A   {        A(int   a);   };  int   Functi ...

  3. 20140719 找到单链表的倒数第K个节点 判断一个链表是否成为一个环形 反转

    1.找到单链表的倒数第K个节点 2.判断一个单链表对否形成环形 3.单链表翻转

  4. 【转】elasticsearch中字段类型默认显示{ "foo": { "type": "text", "fields": { "keyword": {"type": "keyword", "ignore_above": 256} }

    官方原文链接:https://www.elastic.co/cn/blog/strings-are-dead-long-live-strings 转载原文连接:https://segmentfault ...

  5. Hive之explode和lateral view

    Hive之explode 一. explode, 行转列. 1.1. 用于array类型的数据 table_name 表名 array_col 为数组类型的字段 new_col array_col被e ...

  6. Kettle 中的循环

    说道循环,小伙伴们肯定不陌生,就是重复干一件事:而kettle中的循环一般是发生在job中,因为job是串行执行的: kettle的循环有两种,一种是通过js脚本,另外一种是通过遍历前面步骤返回的每一 ...

  7. python 2 学习历程(一)

    在用户输入字符串的时候,有时会带有一些其他的字符,例如常见的空格 除非在网页或者某个位置声明了空格也算字符,或者一些账号等安全程度较高的环节,多了一个空格很少有人会注意到,并且愿意即时改正它们,那么这 ...

  8. Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci...

    最近刚接触mysql,今天用mysql插入中文字符的时候,程序报错“Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_g ...

  9. could not load the tomcat server configuration ...

    参考文档:  https://blog.csdn.net/u013381651/article/details/51567758 问题解决. 原因是你把项目里的tomcat的server项目也关闭了, ...

  10. (PASS)字符数组\字符串数组 和 字符串 的相互转换

    1,字符数组 转换为 字符串 java可以使用两种方法直接将字符数组转为字符串. 方法1:直接在构造String时转换. char[] data = {'a', 'b', 'c'}; String s ...