1、什么是消息中间件
1.1 什么是消息
消息(Message)是指在应用间传送的数据。
消息可以非常简单,比如只包含文本字符串、JSON 等,也可以很复杂,比如内嵌对象。
1.2 什么是消息中间件
消息队列中间件(Message Queue Middleware,简称为MQ),也可以称为消息队列或者消息中间件,是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,可以在分布式环境下扩展进程间的通信。
1.3 消息中间件的消息传递模式
消息中间件一般有两种传递模式:点对点(P2P,Point-to-Point)模式和发布/订阅(Pub/Sub)模式。
点对点模式是基于队列的,消息生产者发送消息到队列,消息消费者从队列中接收消息,队列的存在使得消息的异步传输成为可能。
发布订阅模式定义了如何向一个内容节点发布和订阅消息,这个内容节点称为主题(topic) ,主题可以认为是消息传递的中介,消息发布者将消息发布到某个主题,而消息订阅者则从主题中订阅消息。主题使得消息的订阅者与消息的发布者互相保持独立,不需要进行接触即可保证消息的传递,发布/订阅模式在消息的一对多广播时采用。
1.4 消息中间件的特点
面向消息的中间件(简称为 MOMMessage Oriented Middleware) 提供了以松散藕合的灵活方式集成应用程序的一种机制。它们提供了基于存储和转发的应用程序之间的异步数据发送,即应用程序彼此不直接通信,而是与作为中介的消息中间件通信。消息中间件提供了有保证的消息发送,应用程序开发人员无须了解远程过程调用(RPC) 和网络通信协议的细节。
1.5 消息中间件的适用范围
消息中间件适用于需要可靠的数据传送的分布式环境,采用消息中间件的系统中,不同的对象之间通过传递消息来激活对方的事件以完成相应的操作。发送者将消息发送给消息服务器,服务器将消息存放在若干队列中,在合适的时候再将消息转发给接收者,消息中间件能在不同平台之间通信,它常被用来屏蔽各种平台及协议之间的特性,实现应用程序之间的协同,其优点在于能够在客户和服务器之间提供同步和异步的连接,并且在任何时刻都可以将消息进行传送或者存储转发,这也是它比远程过程调用更进步的原因。
举例说明,应用程序A与应用程序B通过使用消息中间件的应用程序编程接口(API Application Program Interface)发送消息来进行通信。
消息中间件将消息路由给应用程序,这样消息就可存在于完全不同的计算机上。消息中间件负责处理网络通信,如果网络连接不可用,消息中间件会存储消息直到连接变得可用再将消息转发给应用程序。灵活性的另一方面体现在当应用程序发送其消息时,应用程序B甚至可以处于不运行状态,消息中间件将保留这份消息,直到应用程序开始执行并消费消息,这样还防止了应用程序因为等待应用程序消费消息而出现阻塞。这种异步通信方式要求应用程序的设计与现在大多数应用不同。不过对于时间无关或并行处理的场景,它可能是一个极其有用的方法。
2、 消息中间件的作用
消息中间件凭借其独到的特性在不同的应用场景下可以展现不同的作用。总的来说,消息中间件的作用可以概括如下。
解耦:在项目启动之初来预测将来会碰到什么需求是极其困难的。消息中间件在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口,这允许你独立地扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束即可。
冗余(存储):有些情况下,处理数据的过程会失败。消息中间件可以把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。在把一个消息从消息中间件中删除之前,需要你的处理系统明确地指出该消息己经被处理完成,从而确保你的数据被安全地保存直到你使用完毕。
扩展性:因为消息中间件解耦了应用的处理过程,所以提高消息入队和处理的效率是很容易的,只要另外增加处理过程即可,不需要改变代码,也不需要调节参数。
削峰:在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果以能处理这类峰值为标准而投入资源无疑是巨大的浪费,使用消息中间件能够使关键组件支撑突发访问压力,不会因为突发的超负荷请求而完全崩惯。
可恢复性:当系统一部分组件失效时,不会影响到整个系统。消息中间件降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入消息中间件中的消息仍然可以在系统恢复后进行处理。
顺序保证:在大多数使用场景下,数据处理的顺序很重要,大部分消息中间件支持一定程度上的顺序性。
缓冲:在任何重要的系统中,都会存在需要不同处理时间的元素。消息中间件通过一个缓冲层来帮助任务最高效率地执行,写入消息中间件的处理会尽可能快速。该缓冲层有助于控制和优化数据流经过系统的速度。
异步通信:在很多时候应用不想也不需要立即处理消息。消息中间件提供了异步处理机制,允许应用把一些消息放入消息中间件中但并不立即处理它,在之后需要的时候再慢慢处理。
3、 RabbitMQ 的简介
RabbitMQ 是由 RabbitMQ Technologies Ltd开发并且提供商业支持的采用Erlang语言实现AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,最初起源于金融系统,用于在分布式系统中存储转发消息。
4、 RabbitMQ 的特点
RabbitMQ 发展到今天,被越来越多的人认可,这和它在易用性、扩展性、可靠性和高可用性等方面的卓著表现是分不开的。RabbitM 的具体特点可以概括为以下几点。
可靠性:RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认及发布确认等。
灵活的路由:在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,可以通过插件机制来实现自己的交换器。
扩展性:多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。
高可用性:队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。
多种协议:RabbitMQ 除了原生支持AMQP,还支持STOMP、MQTT等多种消息中间件协议。
多语言客户端:RabbitMQ几乎支持所有常用语言,比如Java、Python、Ruby、PHP、JavaScript等。
管理界面:RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。
插件机制:RabbitMQ 提供了许多插件以实现从多方面进行扩展,当然也可以编写自己的插件。
5、RabbitMQ 的安装
RabbitMQ 是由Erlang语言编写的,因此在安装 RabbitMQ 之前需要安装 Erlang,这里Erlang使用19.X的版本,建议采用较新版的Erlang(官网http://www.erlang.orgldownloads),这样可以获得较多更新和改进,RabbitMQ(官网http://www.rabbitmq.comlreleases/rabbitmq_server/)使用3.6.x的版本。
5.1 安装Erlang
第一步:解压安装包并配置安装目录,这里预备安装到/opt/erlang 目录下
[root@hidden ~]# tar zxvf otp_src_19.3.tar.gz
[root@hidden ~]# cd otp_src_19 . 3
[root@hidden otp_src_19.3]# ./configure --prefix=/opt/er1ang
第二步:如果出现类似关键报错信息 Nocurses library functions found。那么此时需要安装ncurses,安装步骤(遇到提示输入y后直接回车即可)如下
[root@hidden otp_src_19.3]# yum install ncurses-devel
第三步:安装Erlang
[root@hidden otp_src_19.3]# make
[root@hidden otp_src_19.3]# make install
如果在安装的过程中出现类似"No***** found"的提示,可根据提示信息安装相应的包,之后再执行第二或者第三步直到提示安装完毕为止。
第四步:修改/etc/profile 配置文件,添加下面的环境变量
ERLANG HOME=/opt/erlang
export PATH=$PATH:$ERLANG HOME/bin
export ERLANG_HOME
第五步:执行如下命令让配置文件生效
[root@hidden otp_src_19 .3]# source /etc/profile
最后输入erl 命令验证 Erlang 是否安装成功,如果出现类似以下的提示即表示安装成功:
[root@hidden ~]# erl
Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V8.1 (abort with ^G)
1>
5.2 安装RabbitMQ
RabbitMQ 安装比Erlang 安装要简单,直接将下载的安装包解压到相应的目录下即可,这里选择将RabbitMQ 安装到与 Erlang个目录(/opt)下面
[root@hidden ~]# tar zvxf rabbitmq-server-generic-unix-3.6.10.tar.gz -C /opt
[root@hidden ~]# cd /opt
[root@hidden ~]# mv rabbitmq_server-3.6.10 rabbitmq
同样修改/etc/profile 文件 添加下面的环境变量
export PATH=$PATH:/opt/rabbitmq/sbin
export RABBITMQ_HOME=/opt/rabbitmq
之后执行source/etc/profile 命令让配置文件生效。
5.3 RabbitMQ 的运行
在修改了/etc/profile配置文件之后,可以任意打开一个shell窗口,输入如下命令以运行 RabbitMQ服务:
rabbitmq-server [-detached]
在rabbitmq-server 命令后面添加"-detached"参数能够让 RabbitMQ 服务以守护进程的方式在后台运行,这样就不会因为当前 Shell 窗口的关闭而影响服务。
运行rabbitmqctl status 命令查看 RabbitMQ 是否正常启动,示例如下:
[root@hidden ~]# rabbitmqctl status
Status of node rabbit@hidden
[{pid,6458},
{running_applications,
[{rabbitmq_management,"RabbitMQ Management Console","3.6.10"},
{rabbitmq_management agent,"RabbitMQ Management Agent","3.6.10"},
{rabbitmq_web_dispatch,"RabbitMQ Web Dispatcher","3.6.10"},
{rabbit,"RabbitMQ ","3.6.10"},
{mnesia,"MNESIA CXC 138 12","4.14.1"},
{amqp c1ient,"RabbitMQ AMQP Client","3.6.10"},
{os_mon,"CPO CXC 138 46","2.4.1"} ,
{rabbit_common,"Modules shared by rabb tmq server and rabbitmq-erlang-client","3.6.10"},
{compiler,"ERTS CXC 138 10","7.0.2"},
{inets,"INETS CXC 138 49","6.3.3"},
{cowboy,"Small, fast, modular HTTP server.","1.0.4"},
{ranch,"Socket acceptor pool for TCP protocols.","1.3.0"},
{ssl,"Erlang/OTP SSL appl cation","8.0.2"},
{public_key,"Public key infrastructure","1.2"},
{cowlib,"Support library for manipulating Web protocols.","1.0.2"},
{crypto,"CRYPTO","3.7.1"},
{sytax_tools,"Syntax tools","2.1"},
{asn1,"The Erlang ASN1 compi1er version 4.0.4","4.0.4"},
{xmerl,"XML parser","1.3.12"},
{sasl,"SASL CXC 138 11","3.0.1"},
{stdlib,"ERTS CXC 138 10","3.1"},
{kernel,"ERTS CXC 138 10","5.1"}]},
{os,{unix,linux}},
{er1ang_version,"Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:4:4]
[asyc-threads:64] [hipe] [kernel-po11:true]\n"},
{memory,
[{total, 61061688},
{connection_readers,O},
{connection_writers,O},
{connection_channels,O},
{connection_other,2832},
{queue_procs,2832},
{queue_slave_procs,O},
{plugins,487104},
{other_proc,21896528},
{mnesia,60800},
{metrics,193616},
{mgmt_db,137720},
{msg_index,43392},
{other_ets,2485240},
{binary,132984},
{code,24661210},
{atom,1033401},
{other_system,10114813}]},
{alarms,[]},
{listeners,[{clustering,25672,"::"},{amqp,5672,"::"},{http,15672,": :"}]},
{vm_memory_high_watermark,0.4},
{vm_memory_limit,3301929779},
{disk_free_limit,50000000},
{disk_free,30244855808},
{file_descriptors [{total_limit,924},{total_used,2},{sockets_limit,829},{sockets_used,O}]},
{processes,[{limit,1048576},{used,323}]},
{run_queue,O},
{uptime,ll},
{kernel,{net_ticktime,60}}]
如果RabbitMQ 正常启动会输出以上所示的信息。也可以通过rabbitmqctl cluster_status 命令来查看集群信息,目前只有一个RabbitMQ 服务节点,可以看作单节点的集群
[root@hidden ~]# rabbitmqctl cluster_status
Cluster status of node rabbit@hidden
[{nodes,[{disc,[rabbit@hidden]}]),
{running_nodes,[rabbit@hidden]),
{cluster name,<<"rabbit@hidden">>),
{partitions,[]},
{alarms,[{rabbit@hidden,[]}])]
5.4 RabbitMQ 添加用户、角色、权限
RabbitMQ 安装运行后有一个默认账户(用户名和密码都是guest),但是这个账户有限制,只能通过本地网络(如localhost)访问而远程网络访问受限,所以在实现生产和消费消息前需要另外添加用户并设置相应的访问权限。
添加新用户,用户名为"root",密码为"root123":
[root@hidden ~]# rabbitmqct1 add user root root
Creating user "root" ...
为root用户设置所有权限:
[root@hidden ~]# rabbitmqct1 set_permissions -p / root ".*" " .*" " .*"
Setting permissions for user "root" in vhost "/" ...
设置root用户为管理员角色:
[root@hidden ~]# rabbitmqct1 set user_tags root administrator
Setting tags for user "root" to [administrator]
如果在使用RabbitMQ 的过程中遇到类似如下的报错:
Exception in thread "main" com.rabbitmq.c1ient.AuthenticationFai1ureException: ACCESS REFUSED - Login was refused using authentication mechanism PLAIN.For details see the broker 1ogfi1e.
那么很可能就是账户管理的问题,需要根据上面的步骤进行设置之后再运行程序。
6、 生产和消费消息
目前最新的RabbitMQ Java客户端版本为4.2.l,相应的 maven构建文件如下:
计算机的世界是从Hello World!"开始的,这里也沿用惯例,首先生产者发送一条消息"Hello World !"至RabbitMQ 中,之后由消费者消费。
生产者客户端代码:
package com.test.rabbitmq.demo;
import com.rabbitmq.c1ient.Channe1;
import com.rabbitmq.c1ient.Connection;
import com.rabbitmq.c1ient.ConnectonFactory;
import com.rabbitmq.c1ient.MessageProperties;
import java.io.IOException;
import java.uti1.concurrent.TimeoutException;
pub1ic c1ass RabbitProducer{
private static fina1 String EXCHANGE_NAME = "exchange_demo";
private static fina1 String ROUTING_KEY = "routingkey_demo";
private static fina1 String QUEUE_NAME = "queue_demo";
private static fina1 String IP_ADDRESS = "192.168.0.2";
// RabbitMQ 服务端默认端口号为 5672
private static fina1 int PORT = 5672;
public static void main(String[] args) throws IOException,TimeoutException , InterruptedException{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(IP_ADDRESS);
factory.setPort(PORT);
factory.setUsername("root");
factory.setPassword("root123");
// 创建连接
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 创建一个type="direct"、持久化的、非自动删除的交换器
channel.exchangeDeclare(EXCHANGE_NAME,"direct",true,false,null);
// 创建一个持久化、非排他的、非自动删除的队列
channel. queueDeclare(QUEUE_NAME,true,false,false,null);
// 将交换器与队列通过路由键绑定
channel.queueBind(QUEUE NAME,EXCHANGE_NAME,ROUTING_KEY);
// 发送一条持久化的消息hello world !
String message = "Hello World !";
channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY,MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
// 关闭资源
channel.close();
connection.close();
}
}
生产者客户端代码首先和RabbitMQ 服务器建立一个连接Connection,然后在这个连接之上创建一个信道Channel,之后创建一个交换器Exchange和一个队列 Queue并通过路由键进行绑定,然后发送一条消息,最后关闭资源。
消费者客户端代码:
package com.test.rabbitmq.demo;
import com.rabbitmq.client.*;
import java.IOException;
import java.util.concurrent.TimeUnit;
import java.uti1.concurrent.TimeoutException;
public class RabbitConsumer{
private static final String QUEUE_NAME = "queue_demo";
private static final String IP_ADDRESS = "192.168.0.2";
private static final int PORT = 5672;
Public static void main(String[] args) throws IOException,TimeoutException,InterruptedException {
Address[] addresses = new Address[] { new Address(IP ADDRESS , PORT) };
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("root");
factory.setPassword("root123");
// 这里的连接方式与生产者的demo略有不同,注意辨别区别
// 创建连接
Connection connection = factory.newConnection(addresses);
// 创建信道
final Channel channel = connection.createChannel();
// 设置客户端最多接收未被ack的消息的个数
Channel.basicQos(64);
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] body) throws IOException{
System.out.println("recv message: " + new String(body)) ;
try{
TimeUnit.SECONDS.sleep(l);
}catch(InterruptedException e){
e.printStackTrace();
}
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(QUEUE_NAME,consumer);
//等待回调函数执行完毕之后 关闭资源
TimeUnit.SECONDS.sleep(5);
channel.c1ose();
connection.close();
}
}
这里采用继承DefaultConsurner的方式来实消费,有过RabbitMQ 使用经验的读者也许会采用QueueingConsurner的方式来实消费,但并不推荐使用, 因为QueueingConsurner会有一些隐患,同时RabbitMQ Java客户端4.0.0 版本开始将QueueingConsurner标记为@Deprecated,在后面的大版本中会删除这个类。