扛不住的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. PHPUnit-函数依赖-数据提供-异常-忽略-自动生成

    1. 本文目的 本文目的是收录一些PHPUnit的有用技巧,这些技巧能够为给PHPUnit单元测试带来很多便利.本文将要介绍的技巧如下: 函数依赖测试 数据提供函数 异常测试 跳过忽略测试 自动生成测 ...

  2. ntopng-一款流量审计框架的安装以及应用

    核心交换机镜像流量审计对于企业应急响应和防患于未然至关重要,本文想通过介绍ntopng抛砖引玉讲一讲流量审计的功能和应用. 安装 安装依赖环境: sudo yum install subversion ...

  3. 原生js二级联动

    今天说的这个是原生js的二级联动,在空白页面里动态添加并作出相对应的效果. 1 //创建两个下拉列表 select标签 是下拉列表 var sel = document.createElement(& ...

  4. java 得到uuid并处理

    java 得到uuid String s = UUID.randomUUID().toString(); //去掉“-”符号 return s.substring(0,8)+s.substring(9 ...

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

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

  6. CSS学习笔记10 相对定位,绝对定位与固定定位

    文档流中的元素的位置由元素在 (X)HTML 中的位置决定,这就是最原始的普通流,前面讲到的浮动CSS学习笔记08 浮动可以改变元素在文档流中的位置,除了这个我们还可以通过使用CSS的position ...

  7. JAVA基础——内部类详解

    JAVA内部类详解 在我的另一篇java三大特性的封装中讲到java内部类的简单概要,这里将详细深入了解java内部类的使用和应用. 我们知道内部类可分为以下几种: 成员内部类 静态内部类 方法内部类 ...

  8. 2.Smarty的引入和实例化

    1.把demo和lib复制出来,并且创建一个test文件夹作为工作的目录 如图所示: 2.这是libs里面的内容,其中smarty.class.php包含了smarty各种方法和功能,需要实例化它还工 ...

  9. hadoop伪分布式环境搭建

    环境:Centos6.9+jdk+hadoop1.下载hadoop的tar包,这里以hadoop2.6.5版本为例,下载地址https://archive.apache.org/dist/hadoop ...

  10. 使用JDK自带的MessageDigest计算消息摘要

    使用JDK自带的MessageDigest计算消息摘要 上代码 /** * 使用JDK自带MessageDigest */ public class MessageDigestUtils { /** ...