扛不住的Hello World模式

上篇《RabbitMQ入门-从HelloWorld开始》介绍了RabbitMQ中最基本的Hello World模型。正如其名,Hello World模型组成简单,也很好理解,我们也看到了一条消息时如何从一个生产者最终流向队列并最终被消费者消费的过程。

但是,过于简单、单调的模型设计也存在一些缺陷。假使现在队列Queue中挤压了很多的消息没有被消费,Hello World模型中只有一个消费者,在消费消息时会显得力不从心。如果遇上网络状况异常等情况,则消费速率就更加不同乐观,从而影响了消息的处理效率,影响网站应用的性能。

很直观的思路,我们能想到的是,一个人不行,那就多来几个人,这时候就有了我们的Work模型。

多管齐下的Work模式

该模型具有以下特征

  • 一个消息生产者P,一个消息存储队列Q,多个消息消费者C

  • Work模型能够较好的解决资源密集型场景的问题,不需要像Hello World那样孤注一掷的等唯一的消费者消费完

  • 多个消费者,多管齐下,更加高效的并行处理消息

实例

如何构造一个资源密集型的场景

相较于Hello World,Work模式主要是在资源密集型的场景更能发挥威力,那么没有工作环境或者很难遇到这样的情况,我们怎么办?

其实,这个场景的本质是为了体现一个消费者处理要很长时间的时候,这个模式是如何发挥作用的。那么,我们可以让每个消费者处理的时间长点不就行了,要让Consumer处理的时间长很简单,只要调用Thread.sleep()即可。

发送端

对于发送端相对Hello World类型来说,没有什么不同。这是我们队这里发送的消息采用指定的格式比如“hello......”,在后面的发送端接收消息后,当遇到"."则停顿1秒或者2秒,所以程序如下

package com.ximalaya.openapi.rabbitmq.work;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
/**
* Created by jackie on 17/8/4.
*/
public class NewTask { private static final String TASK_QUEUE_NAME = "task_queue"; public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.3.161");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); String message = getMessage(argv); channel.basicPublish("", TASK_QUEUE_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes("UTF-8"));
channel.basicPublish("", TASK_QUEUE_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes("UTF-8"));
channel.basicPublish("", TASK_QUEUE_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes("UTF-8"));
channel.basicPublish("", TASK_QUEUE_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "'"); channel.close();
connection.close();
} private static String getMessage(String[] strings) {
if (strings.length < 1)
return "Hello World!";
return joinStrings(strings, " ");
} private static String joinStrings(String[] strings, String delimiter) {
int length = strings.length;
if (length == 0) return "";
StringBuilder words = new StringBuilder(strings[0]);
for (int i = 1; i < length; i++) {
words.append(delimiter).append(strings[i]);
}
return words.toString();
}
}

注意:这里getMessage方法,如果在运行的配置参数中添加了输入参数,则使用输入参数,如果没有填写,则使用默认值"Hello World"。

填写输入参数的方法是在如下图位置写上输入参数

我们执行发送端代码,向队列"task_queue"中塞入4条消息

从这个动态图片可以发现,通过发送端一次性发送了4条消息。

接收端

package com.ximalaya.openapi.rabbitmq.work;

import com.rabbitmq.client.*;

import java.io.IOException;
/**
* Created by jackie on 17/8/4.
*/
public class Worker { private static final String TASK_QUEUE_NAME = "task_queue"; public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.3.161");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel(); channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); channel.basicQos(1); final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8"); System.out.println(" [x] Received '" + message + "'");
try {
doWork(message);
} finally {
System.out.println(" [x] Done");
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(TASK_QUEUE_NAME, false, consumer);
} private static void doWork(String task) {
for (char ch : task.toCharArray()) {
if (ch == '.') {
try {
Thread.sleep(2000);
} catch (InterruptedException _ignored) {
Thread.currentThread().interrupt();
}
}
}
}
}

注意:这里的doWork方法,该方法当遇到"."是就会睡眠2秒钟,所以像"hello..."这样的消息就会睡眠6秒。

下面分两种情况来看接收端的处理信息的情况

一个消费者

如果此时只运行一个接收端的代码,说明只启动了一个Consumer,我们看看消息的消费过程

  • 图中Ready的消息依次从4->3->2->1->0,表示消息依次被派出消费

  • Uncknowledged表示没有确认的,这里始终是1,因为消息时一个个发送的,等一个个发完了,最终变为0

  • Total表示总共剩余的消息个数,最终消费完变为0

两个消费者

如果这时候启动两个客户端,我们看下消息是如何被消费的

  • 图中的Ready从4->2->0,这是因为有两个消费者,消息分别分发到两个消费者上,一次派发两个,分两次派发完

  • Unacknowledged从0->2->0,过程为在一次发送两条消息时,说明有两条消息等待确认是否被消费掉

  • Total则与Ready变化趋势一致

对比“一个消费者”和“两个消费者”的消费情况,我们确实发现Work的消费处理效率要比Hello World高。

细心的你可能发现了,为什么在“两个消费者”的情况下能够做到如此公平的每个消费者分配两个,有关这块,限于篇幅,将在下篇详细介绍。

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

RabbitMQ入门-高效的Work模式的更多相关文章

  1. RabbitMQ入门_03_推拉模式

    我们知道,消费者有两种方式从消息中间件获取消息: 推模式:消息中间件主动将消息推送给消费者 拉模式:消费者主动从消息中间件拉取消息 推模式将消息提前推送给消费者,消费者必须设置一个缓冲区缓存这些消息. ...

  2. RabbitMQ入门-Routing直连模式

    Hello World模式,告诉我们如何一对一发送和接收消息: Work模式,告诉我们如何多管齐下高效的消费消息: Publish/Subscribe模式,告诉我们如何广播消息 那么有没有灵活强一点的 ...

  3. RabbitMQ入门-Topic模式

    上篇<RabbitMQ入门-Routing直连模式>我们介绍了可以定向发送消息,并可以根据自定义规则派发消息.看起来,这个Routing模式已经算灵活的了,但是,这还不够,我们还有更加多样 ...

  4. RabbitMQ入门到进阶(Spring整合RabbitMQ&SpringBoot整合RabbitMQ)

    1.MQ简介 MQ 全称为 Message Queue,是在消息的传输过程中保存消息的容器.多用于分布式系统 之间进行通信. 2.为什么要用 MQ 1.流量消峰 没使用MQ 使用了MQ 2.应用解耦 ...

  5. RabbitMQ入门-消息订阅模式

    消息派发 上篇<RabbitMQ入门-消息派发那些事儿>发布之后,收了不少反馈,其中问的最多的还是有关消息确认以及超时等场景的处理. 楼主,有遇到消费者后台进程不在,但consumer连接 ...

  6. RabbitMQ入门(三)订阅模式

      在之前的文章RabbitMQ入门(二)工作队列中,我们创建了一个工作队列.工作队列背后的假设是每一项任务都被准确地传送至一个worker.在本文中,我们将会做一些不同的事情--我们将会把一个消息发 ...

  7. RabbitMQ入门教程(十七):消息队列的应用场景和常见的消息队列之间的比较

    原文:RabbitMQ入门教程(十七):消息队列的应用场景和常见的消息队列之间的比较 分享一个朋友的人工智能教程.比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看. 这是网上的一篇教程写的很好,不知原作 ...

  8. RabbitMQ入门案例

    RabbitMQ入门案例 Rabbit 模式 https://www.rabbitmq.com/getstarted.html 实现步骤 构建一个 maven工程 导入 rabbitmq的依赖 启动 ...

  9. RabbitMQ入门与使用篇

    介绍 RabbitMQ是一个由erlang开发的基于AMQP(Advanced Message Queue)协议的开源实现.用于在分布式系统中存储转发消息,在易用性.扩展性.高可用性等方面都非常的优秀 ...

随机推荐

  1. 流畅的python学习笔记:第一章

    这一章中作者简要的介绍了python数据模型,主要是python的一些特殊方法.比如__len__, __getitem__. 并用一个纸牌的程序来讲解了这些方法 首先介绍下Tuple和nametup ...

  2. Vue实战Vue-cli项目构建(Vue+webpack系列之一)

    用Vue比较长一段时间了,大大小小做了一些项目,最近想总结一下知识点,出一个Vue+webpack系列,先从项目构建说起--vue-cli. 由于是Vue+webpack这里就不赘述git那些东西,默 ...

  3. crontab问题处理

    用pyhton写了一些爬虫,由于数据量比较大,需要跑的时间也比较长,所以将代码部署到服务器上.选择用crontab完成爬虫的定时爬取数据,这样避免了人工的干预,减少一些人为错误.但在部署crontab ...

  4. js中的事件,内置对象,正则表达式

    [JS中的事件分类] 1.鼠标事件: click/dbclick/mouseover/mouseout/mousemove/mousedown/mouseup 2.键盘事件: keydown: 键盘按 ...

  5. maven 不同环境加载不同的properties 文件

    http://haohaoxuexi.iteye.com/blog/1900568 //参考文章 实际项目中pom配置如下 <profiles> <profile> <i ...

  6. 我的学习之路_第二十一章_JDBC连接池

    JDBC连接池和DButils [DBCP连接池工具类] 使用读取配置文件的方式 DBCP中有一个工厂类 BasicDataSourceFactory 工厂类中有一个静态方法 返回值为: DataSo ...

  7. JAVAEE——spring02:使用注解配置spring、sts插件、junit整合测试和aop演示

    一.使用注解配置spring 1.步骤 1.1 导包4+2+spring-aop 1.2 为主配置文件引入新的命名空间(约束) 1.3 开启使用注解代替配置文件 1.4 在类中使用注解完成配置 2.将 ...

  8. Linux常见命令(三)

    今天我们来介绍第三个命令:pwd. Linux中用 pwd 命令来查看”当前工作目录“的完整路径. 简单得说,每当你在终端进行操作时,你都会有一个当前工作目录. 在不太确定当前位置时,就会使用pwd来 ...

  9. 如何将md文件转换成带目录的html文件

    配置环境node 去官网下一个node安装包,下一步下一步: 由于现在的node都自带npm,直接 npm install i5ting_toc 这样安装好了i5ting_toc这个包, 进入你实现准 ...

  10. Python重定向到文件

    1.方式1 __file__ = open(r'log.txt', 'a') print >>__file__, "hello, world!" __file__.cl ...