扛不住的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. OFFICE 文档转换为html在线预览

    OFFICE 文档在线预览方案很多: 服务器先转换为PDF,再转换为SWF,最后通过网页加载Flash预览,比如flexpaper Office文档直接转换为SWF,通过网页加载Flash预览 微软的 ...

  2. qt中setStyleSheet导致的内存泄漏

    原始日期: 2017-01-05 19:31 现象:程序运行至某一个界面下,内存出现缓慢持续的占用内存增长   原因:经过排查,确定是在事件派发的槽函数中频繁重复调用setStyleSheet导致的 ...

  3. 类型转换之 PropertyEditorSupport类

    这个类可以用于自定义的类型转换, 子类继承这个类之后可以重写子类的方法 ,其中比较重要的是setAsText和setValue方法,setAsText 子自己的方式处理转换,setValue将转换的结 ...

  4. git视频教程

    git 精简版视频教程-2小时快速入门精华版,小教程很快就可以看完. 旺旺 QQ:Git是目前世界上最先进的分布式版本控制系统(没有之一). Git有非常高的逼格,简单来说就是:高端大气上档次. 这么 ...

  5. php 函数形参前面加上&

    <?php function test(&$a){ $a=$a+100; } $b=1; echo $b;//输出1 test($b);//这里$b传递给函数的其实是$b的变量内容所处的 ...

  6. 【LeetCode】122. Best Time to Buy and Sell Stock II

    题目: Say you have an array for which the ith element is the price of a given stock on day i. Design a ...

  7. 【Android Developers Training】 51. 序言:打印内容

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  8. 10.application对象

    1.application对象实现了用户数据的共享,可存放全局变量 2.application开始于服务器的启动,终止于服务器的关闭. 3.在用户的前后连接或不同用户之间的连接中,可以对applica ...

  9. Linux系统网卡设置

    由于做了虚拟机的克隆,发现克隆机和被克隆机的MAC地址相同了,下面我将要介绍一下linux中网卡的配置步骤,我使用的linux是CentOS release 6.9 (Final) 1.root用户编 ...

  10. JavaScript 语言基础

    js语言基础 一 基本知识 UniCode编码 区分大小写(HTML不区分/XHTML区分) Unicode转义序列 \uxxxx (\u加4位16进制表示) 注释 单行注释:// 多行注释:/* * ...