RabbitMQ是一个处理在离线的消息中间件也可以说是一个消息引擎,它的标准用法是:生产者(productor)生产消息发送到队列,消费者(consumer)从队列中取出并处理消息,生产者无需关心谁来消费,消费者也不用关心消息的来源,从而达到解耦的目的。RabbitMQ完成分布式系统异步通信在大中型分布式系统中,RabbitMQ可以帮助各个子系统的数据及时同步到后台模块,并提供数据通道帮助触发其他的业务流程,如函数处理、消息通知等。下面是RabbitMQ的安装,在安装RabbitMQ时要注意,因为RabbitMQ是Erlang语言编写,所以必须得安装Erlang语言。
第一步:下载安装Erlang语言。官网:https://www.erlang.org/downloads
下载安装好以后开始配置环境变量:
配置Erlang的环境变量:
配置完环境变量后将环境变量添加到PATH中
在DOS命令行中输入erl,出现如下的界面说明就可以了。
第二步:下载安装RabbitMQ。地址:https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.16/rabbitmq-server-3.7.16.exe
安装的时候注意红框里面的不要勾选,否则汇报错。
下载安装好以后配置环境变量:
将环境变量添加到PATH中:
在dos命令行中输入rabbitmqctl status命令出现下图所示表示RabbitMQ的有些插件没有安装,RabbitMQ没有启动:
解决办法:
到安装目录下:D:\KaiFaUtils\RabbitMQ\install\rabbitmq_server-3.7.16\sbin 继续下面操作。
安装插件,命令:rabbitmq-plugins.bat enable rabbitmq_management 出现:
出现上面图中的提示说明安装成功。然后输入命令:rabbitmq-server.bat 启动RabbitMQ
输入RabbitMQ默认的用户名、密码(都是一样的):guest
进入后是这样的:
此时再在dos命令行中输入:rabbitmqctl status 出现如下图所示:
至此RabbitMQ就安装完毕了。接下来我们模拟消息的生产者和消费者,来看看RabbitMQ是如何来工作的。
1:消息生产者和消息消费者单一通道
1.1:创建一个公共的链接工具:
package com.alibaba;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 创建用于连接RabbitMQ的工具类
*/
public class ConnectUtil {
/**
* 获取RabbitMQ的连接
*/
public static Connection getRabbitMqConnect(){
//创建RabbitMQ的连接工厂
ConnectionFactory connectionFactory = null;
try{
//实例化对象工厂
connectionFactory = new ConnectionFactory();
//进行初始化的设置
connectionFactory.setHost("localhost");//设置主机名
connectionFactory.setPort(5672);//设置AMQP的端口
connectionFactory.setUsername("guest");//设置用户名
connectionFactory.setPassword("guest");//设置密码
return connectionFactory.newConnection();//创建连接
}
catch (Exception e){
System.out.println("ConnectUtil/getRabbitMqConnect Exception:" + e.getMessage());
return null;
}
}
}
1.2:创建一个消息的生产者:
package com.alibaba;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.util.HashMap;
import java.util.Map;
/**
* 消息的生成者
*/
public class Provider {
//创建一个消息队列的名称
private static final String QUEUE_NAME = "USER_MASSAGE_QUEUE";
public void providerMassage(){
//获取一个连接
Connection connection = null;
Channel channel = null;
try{
connection = ConnectUtil.getRabbitMqConnect();
channel = connection.createChannel();//从连接中获取一个通道
/**
* 声明一个队列。里面有几个参数:
* 1:queue --> 队列的名称。
*
* 2:durable --> 是否持久化。true持久化,队列会保存磁
* 盘。服务器重启时可以保证不丢失相关信息。
*
* 3:exclusive --> 设置是否排他。true排他的。如果一个
* 队列声明为排他队列,该队列仅对首次声明它的连接可见,
* 并在连接断开时自动删除。
*
* 4:autoDelete --> 设置是否自动删除。true是自动删除。
* 自动删除的前提是:致少有一个消费者连接到这个队列,之
* 后所有与这个队列连接的消费者都断开 时,才会自动删除。
* 生产者创建这个队列,或者没有消费者客户端与这个队列连
* 接时,都不会自动删除这个队列。
*
* 5:arguments --> 设置队列的一些其它参数。如 x-message-ttl,x-expires等。
*
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//创建10000个消息
for (int i = 0 ;i < 10000 ;i++){
String massage = "chendawei" + i;//创建一个消息
/**
* 消息生产者发送消息给消息队列
*
* 1:exchange -- 交换机名称
* 2:routingKey -- 路由关键字
* 3:props -- 消息的基本属性,例如路由头等
* 4:byte[] body -- 消息体
*/
channel.basicPublish("",QUEUE_NAME,null,massage.getBytes());
}
}catch (Exception e){
System.out.println("Provider/providerMassage Exception:" + e.getMessage());
}finally{
try{
if(connection != null){
connection.close();
}
if(channel != null){
channel.close();
}
}catch (Exception e){
System.out.println("Provider/providerMassage/finally Exception:" + e.getMessage());
}
}
}
public static void main(String[] args) {
new Provider().providerMassage();
}
}
1.3:创建一个消息的消费者:
package com.alibaba;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
//创建一个消息队列的名称
private static final String QUEUE_NAME = "USER_MASSAGE_QUEUE";
public void consumerMassage(){
//获取一个连接
Connection connection = null;
Channel channel = null;
try{
connection = ConnectUtil.getRabbitMqConnect();
channel = connection.createChannel();//从连接中获取一个通道
//接受一万个消息
for(int i = 0; i < 10000 ;i++){
//定义一个消费者
com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String str = new String(body,"UTF-8");
System.out.println("收到消息为:" + str);
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}catch (Exception e){
System.out.println("Consumer/consumerMassage Exception:" + e.getMessage());
}finally{
try{
if(connection != null){
connection.close();
}
if(channel != null){
channel.close();
}
}catch (Exception e){
System.out.println("Consumer/consumerMassage/finally Exception:" + e.getMessage());
}
}
}
public static void main(String[] args) {
new Consumer().consumerMassage();
}
}
1.4:消息的生产者发送消息,消息的消费者接收消息。
以上是单一通道的消息生产者和消费者,相当于一对一的关系,而且这个是单一类型。接下来我们深入聊聊:每次只需要50个消息,也就是限定消息的条数。
2:消息生产者和消息消费者单一通道(队列限制消息条数)
一个队列限制消息的条数时,后来的消息条数会将原先的消息覆盖。比如:消费生产者往MQ中的队列最大发送10条消息,但在通道关闭之前消费生产者发送了10000条,那么这个队列只会保留消息生产者发送的最后10条,前面的都将被覆盖。
2.1:消息的生产者:
package com.alibaba;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.util.HashMap;
import java.util.Map;
/**
* 消息的生成者
*/
public class Provider {
//创建一个消息队列的名称,在消息服务一次启动的时候,名称不能相同也就是两次发送消息不能共用一个消息队列的名称
private static final String QUEUE_NAME = "USER_MASSAGE_QUEUE_TWO";
public void providerMassage(){
//获取一个连接
Connection connection = null;
Channel channel = null;
try{
connection = ConnectUtil.getRabbitMqConnect();
channel = connection.createChannel();//从连接中获取一个通道
/**
* 声明一个队列。里面有几个参数:
* 1:queue --> 队列的名称。
*
* 2:durable --> 是否持久化。true持久化,队列会保存磁
* 盘。服务器重启时可以保证不丢失相关信息。
*
* 3:exclusive --> 设置是否排他。true排他的。如果一个
* 队列声明为排他队列,该队列仅对首次声明它的连接可见,
* 并在连接断开时自动删除。
*
* 4:autoDelete --> 设置是否自动删除。true是自动删除。
* 自动删除的前提是:致少有一个消费者连接到这个队列,之
* 后所有与这个队列连接的消费者都断开 时,才会自动删除。
* 生产者创建这个队列,或者没有消费者客户端与这个队列连
* 接时,都不会自动删除这个队列。
*
* 5:arguments --> 设置队列的一些其它参数。如 x-message-ttl,x-expires等。
*
*/
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-max-length", 10);
channel.queueDeclare(QUEUE_NAME,false,false,false,args);
for (int i = 0 ;i < 10000 ;i++){
String massage = "chendawei" + i;//创建一个消息
/**
* 消息生产者发送消息给消息队列
*
* 1:exchange -- 交换机名称
* 2:routingKey -- 路由关键字
* 3:props -- 消息的基本属性,例如路由头等
* 4:byte[] body -- 消息体
*/
channel.basicPublish("",QUEUE_NAME,null,massage.getBytes());
}
}catch (Exception e){
System.out.println("Provider/providerMassage Exception:" + e.getMessage());
}finally{
try{
if(connection != null){
connection.close();
}
if(channel != null){
channel.close();
}
}catch (Exception e){
System.out.println("Provider/providerMassage/finally Exception:" + e.getMessage());
}
}
}
public static void main(String[] args) {
new Provider().providerMassage();
}
}
2.2:消息的消费者:
package com.alibaba;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
//创建一个消息队列的名称,在消息服务一次启动的时候,名称不能相同也就是两次发送消息不能共用一个消息队列的名称
private static final String QUEUE_NAME = "USER_MASSAGE_QUEUE_TWO";
public void consumerMassage(){
//获取一个连接
Connection connection = null;
Channel channel = null;
try{
connection = ConnectUtil.getRabbitMqConnect();
channel = connection.createChannel();//从连接中获取一个通道
//接受一万个消息
for(int i = 0; i < 10000 ;i++){
//定义一个消费者
com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String str = new String(body,"UTF-8");
System.out.println("收到消息为:" + str);
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}catch (Exception e){
System.out.println("Consumer/consumerMassage Exception:" + e.getMessage());
}finally{
try{
if(connection != null){
connection.close();
}
if(channel != null){
channel.close();
}
}catch (Exception e){
System.out.println("Consumer/consumerMassage/finally Exception:" + e.getMessage());
}
}
}
public static void main(String[] args) {
new Consumer().consumerMassage();
}
}
1.3:消息消费者接收到的消息:
可以看到的是我发送了10000条消息,并且限定了发送的条数,结果和预期的一致的确在消息的消费者中接收的到了10条信息。但是可以看到的是,消息的消费者获取到的信息是最后的10条,我想接收0-9条记录该怎么办呢?如果是单机版的这里可以借助程序方面的直接在程序里判断就可以,如果是分布式集群的话可以借助zookeeper或者redis。
3:消息生产者和消息消费者单一通道(生成对象和消费对象)
User的JavaBean对象:
package com.alibaba; import java.io.Serializable; public class User implements Serializable { private static final long serialVersionUID = 5543721434662429382L; public String username; public String password; public User(String username,String password){ this.username = username; this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "用户名为:" + this.username + "-->密码为:" + this.password; } }
provider:
package com.alibaba; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.ConfirmListener; import com.rabbitmq.client.Connection; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 消息的生成者 */ public class Provider { //创建一个消息队列的名称,在消息服务一次启动的时候,名称不能相同也就是两次发送消息不能共用一个消息队列的名称 private static final String QUEUE_NAME = "USER_MASSAGE_QUEUE_Five"; private int num = 0; public void providerMassage(){ //获取一个连接 Connection connection = null; Channel channel = null; try{ connection = ConnectUtil.getRabbitMqConnect(); channel = connection.createChannel();//从连接中获取一个通道 /** * 声明一个队列。里面有几个参数: * 1:queue --> 队列的名称。 * * 2:durable --> 是否持久化。true持久化,队列会保存磁 * 盘。服务器重启时可以保证不丢失相关信息。 * * 3:exclusive --> 设置是否排他。true排他的。如果一个 * 队列声明为排他队列,该队列仅对首次声明它的连接可见, * 并在连接断开时自动删除。 * * 4:autoDelete --> 设置是否自动删除。true是自动删除。 * 自动删除的前提是:致少有一个消费者连接到这个队列,之 * 后所有与这个队列连接的消费者都断开 时,才会自动删除。 * 生产者创建这个队列,或者没有消费者客户端与这个队列连 * 接时,都不会自动删除这个队列。 * * 5:arguments --> 设置队列的一些其它参数。如 x-message-ttl,x-expires等。 * */ Map<String, Object> args = new HashMap<String, Object>(); args.put("x-max-length", 10); channel.queueDeclare(QUEUE_NAME,false,false,false,args); /** * 消息生产者发送消息给消息队列 * * 1:exchange -- 交换机名称 * 2:routingKey -- 路由关键字 * 3:props -- 消息的基本属性,例如路由头等 * 4:byte[] body -- 消息体 */ User user = new User("小明","123456"); User user1 = new User("小强","654321"); List<User> list = new ArrayList<User>(); list.add(user); list.add(user1); ObjectMapper objectMapper = new ObjectMapper(); byte[] userByte = objectMapper.writeValueAsBytes(user); byte[] user1Byte = objectMapper.writeValueAsBytes(user1); channel.basicPublish("",QUEUE_NAME,null,userByte); channel.basicPublish("",QUEUE_NAME,null,user1Byte); }catch (Exception e){ System.out.println("Provider/providerMassage Exception:" + e.getMessage()); }finally{ try{ if(connection != null){ connection.close(); } if(channel != null){ channel.close(); } }catch (Exception e){ System.out.println("Provider/providerMassage/finally Exception:" + e.getMessage()); } } } public static void main(String[] args) { new Provider().providerMassage(); } }
consumer:
package com.alibaba; import com.alibaba.fastjson.JSONObject; import com.rabbitmq.client.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; import static javafx.scene.input.KeyCode.J; public class Consumer { //创建一个消息队列的名称,在消息服务一次启动的时候,名称不能相同也就是两次发送消息不能共用一个消息队列的名称 private static final String QUEUE_NAME = "USER_MASSAGE_QUEUE_Five"; public void consumerMassage(){ //获取一个连接 Connection connection = null; Channel channel = null; try{ connection = ConnectUtil.getRabbitMqConnect(); channel = connection.createChannel();//从连接中获取一个通道 //接受一万个消息 for (int i = 0; i< 2 ;i++){ com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String str = new String(body,"UTF-8"); System.out.println("收到消息为:" + str); User user = JSONObject.parseObject(str,User.class); System.out.println(user.toString()); } }; channel.basicConsume(QUEUE_NAME,true,consumer); } }catch (Exception e){ System.out.println("Consumer/consumerMassage Exception:" + e.getMessage()); }finally{ try{ if(connection != null){ connection.close(); } if(channel != null){ channel.close(); } }catch (Exception e){ System.out.println("Consumer/consumerMassage/finally Exception:" + e.getMessage()); } } } public static void main(String[] args) { new Consumer().consumerMassage(); } }
POM.xml:
<dependencies> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.9</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> </dependencies>
最终的结果:
以上就是单通道下一对一方式的对象加入消息队列的方法。上面的代码是有瑕疵的,在消费者端我们应该是以消息队列中有几条消息就获取几条,上面的代码中是写si在哪了。所以需要在消费者端加一个判断消息队列中消息数量方法。改进以后的策略:
consumer:
package com.alibaba; import com.alibaba.fastjson.JSONObject; import com.rabbitmq.client.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; import static javafx.scene.input.KeyCode.J; public class Consumer { //创建一个消息队列的名称,在消息服务一次启动的时候,名称不能相同也就是两次发送消息不能共用一个消息队列的名称 private static final String QUEUE_NAME = "USER_MASSAGE_QUEUE_Five"; public void consumerMassage(){ //获取一个连接 Connection connection = null; Channel channel = null; try{ connection = ConnectUtil.getRabbitMqConnect(); channel = connection.createChannel();//从连接中获取一个通道 //从通道中获取名称为USER_MASSAGE_QUEUE_Five的消息队列。并获得一个消息成功标志的对象 AMQP.Queue.DeclareOk aqd = channel.queueDeclarePassive(QUEUE_NAME); //获取对应的队列中未消费的消息的数量 int messageCount = aqd.getMessageCount(); //根据队列中未消费的消息的数量来循环接收未消费的消息 for (int i = 0; i< messageCount ;i++){ com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String str = new String(body,"UTF-8"); System.out.println("收到消息为:" + str); User user = JSONObject.parseObject(str,User.class); System.out.println(user.toString()); } }; channel.basicConsume(QUEUE_NAME,true,consumer); } }catch (Exception e){ System.out.println("Consumer/consumerMassage Exception:" + e.getMessage()); }finally{ try{ if(connection != null){ connection.close(); } if(channel != null){ channel.close(); } }catch (Exception e){ System.out.println("Consumer/consumerMassage/finally Exception:" + e.getMessage()); } } } public static void main(String[] args) { new Consumer().consumerMassage(); } }
至此RabbitMQ中单通道的消息队列的消息生成和消费就说到这,下次一聊聊多通道的消息队列的消息生产和消费。