1.linux下安装
(1)资源:rabbitmq-server-3.6.1-1.noarch.rpm
(2)上传rabbitmq-server-3.4.1-1.noarch.rpm文件到/usr/local/src/rabbitmq/
安装:rpm –ivh rabbitmq-server.3.4.1-1.noarch.rpm
(3)设置配置文件
cd /etc/rabbitmq
cp /usr/share/doc/rabbitmq-server-3.4.1/rabbitmq.config.example /etc/rabbitmq/
(安装目录:/usr/share/doc/rabbitmq-server-3.4.1)
mv rabbitmq.config.example rabbitmq.config
(4)开启用户远程访问控制
vi /etc/rabbitmq/rabbitmq.configP64行
注意:有两处,修改如图处
去掉前面的两个%%,和最后面的逗号,保存。
(5)启动停止服务
servicerabbitmq-server start
servicerabbitmq-server stop
servicerabbitmq-server restart
设置开机启动
chkconfig rabbitmq-server on
(6)开启后台管理插件
执行下面命令,开启web界面管理工具
rabbitmq-pluginsenable rabbitmq_management
servicerabbitmq-server restart
(7)访问管理平台
firefox #打开虚拟机上的火狐
默认用户名密码都为guest
注意:出于安全的考虑,guest这个默认的用户只能通过http://localhost:15672本地来登录,不能外部服务器登录,也就是不能远程访问,这对于服务器上没有安装桌面的情况是无法管理维护的。必须新创建管理员账号。
(8)打开端口
15672是Web控制台的访问端口;5672是程序连接的端口。
/sbin/iptables–I INPUT –p tcp --dport 15672 –j ACCEPT
/sbin/iptables–I INPUT –p tcp --dport 5672 –j ACCEPT
/etc/rc.d/init.d/iptablessave
/etc/init.d/iptablesstatus
2.环境配置
(1)添加用户
用户名sysdebug,密码123456
添加后
用户角色:
序号 | 角色名称 | 说明 |
1. | 超级管理员(administrator) | 可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。 |
2. | 监控者(monitoring) | 可登陆管理控制台,同时可以查看rabbingmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等) |
3. | 策略制定者(policymaker) | 可登陆管理控制台,同时可以对policy进行管理。但无法查看节点的相关信息 |
4. | 普通管理者(management) | 仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。 |
5. | 其他 | 无法登录管理控制台,通常就是普通的生产者和消费者。 |
(2)添加virtualhosts
相当于mysql中的数据库
添加/jt,注意前面的/,不然数据可能造成丢失。
(3)权限控制
选择可以访问的用户
设置好权限
切换到User界面
3.rabbitmq使用,direct简单模式
(1)提供6中消息队列
http://www.rabbitmq.com/getstarted.html
(2)引入依赖包
<!-- 消息队列 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
(3)helloword
P代表生产者,C代表消费者,红色代码消息队列。P将消息发送到消息队列,C对消息进行处理。
工具类,获取链接
package com.rabbitmq.util;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
public class ConnectionUtil {
public static Connection getConnection() throws Exception {
//定义连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置服务地址
factory.setHost("localhost");
//端口,HTTP管理端口默认15672,访问端口默认5672
factory.setPort(5672);
//设置账号信息,用户名、密码、vhost
factory.setVirtualHost("jtmq"); //注意这个值,vh配置如果加了斜杠,这里也必须加
factory.setUsername("jtmqadmin");
factory.setPassword("123456");
// 通过工程获取连接
Connection connection = factory.newConnection();
return connection;
}
}
(4)发消息
package com.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.util.ConnectionUtil;
public class Send {
private final static String QUEUE_NAME = "test_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
// 从连接中创建通道
Channel channel = connection.createChannel();
// 声明(创建)队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 消息内容
String message = "Hello World!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
//关闭通道和连接
channel.close();
connection.close();
}
}
(5)控制台查看消息
注意,需要用sysdebug登录才能看到。
(6)接收消息
package com.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.util.ConnectionUtil;
public class Recv {
private final static String QUEUE_NAME = "test_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列
channel.basicConsume(QUEUE_NAME, true, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
}
}
}
接收消息后,将消息删除。
3.工作模式(work)竞争的
使用场景:抢红包
一个消息产生者,多个消息的消费者。竞争抢消息。
(1)产生多个消息
package com.rabbitmq.work;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.util.ConnectionUtil;
public class Send {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 100; i++) {
// 消息内容
String message = "" + i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
Thread.sleep(i * 10); //越往后,停歇的时间越长
}
channel.close();
connection.close();
}
}
控制台查看消息
产生100条消息。
(2)消费者一
package com.rabbitmq.work;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.util.ConnectionUtil;
public class Recv {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 同一时刻服务器只会发一条消息给消费者,每一次服务器只会向客户端发送一条
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
//休眠
Thread.sleep(10);
// 返回确认状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
(3)消费者2
package com.rabbitmq.work;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.util.ConnectionUtil;
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 同一时刻服务器只会发一条消息给消费者,每一次服务器只会向客户端发送一条
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成状态
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
// 休眠1秒
Thread.sleep(1000);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
先注释掉两个消费者中的channel.basicQos(1);运行产生消息,同时运行消费者1和消费者2。消费者1中延时Thread.sleep(10)~10毫秒,消费者2中延时Thread.sleep(1000)~1秒。模拟两个消费者处理速度是不同的。
发送消息:
消费者一
消费者二
观察控制台,会发现在产生消息的同时,两个消费者开始同时消费消息。但产生一个现象。消费者1消费偶数消息,消费者2消费奇数消息。平均分配合理吗?
这明显不合理,干的快的应该处理的多,干的慢的应该处理的少。应该是能者多劳,多劳多得。
(4)消息负载均衡
解决办法:放开之前的注释,也就是加上channel.basicQos(1)。同一时刻服务器只会发一条消息给消费者,每一次服务器只会向客户端发送一条。测试可以看到实现能者多劳,多劳多得。
4.发布订阅(群发)
上面是单消息队列,下面有两个队列。
生产者将消息不是直接发送到队列,而是发送到X交换机,然后由交换机发送给两个队列,两个消费者各自监听一个队列,来消费消息。
这个X交换机就类似QQ群一样。老师发送一个作业题,大家都可以从这里获取。
这种方式实现同一个消息被多个消费者消费。工作模式是同一个消息只能有一个消费者。
(1)fanout exchange
一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。
(2)发消息
package com.rabbitmq.ps;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.util.ConnectionUtil;
public class Send {
private final static String EXCHANGE_NAME = "jt_test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明exchange
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 消息内容
String message = "Hello World!";
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
}
注意:如果没有队列绑定到交换机,那么发送到交换机的消息将丢失。
(3)消费者1
package com.rabbitmq.ps;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.util.ConnectionUtil;
public class Recv {
private final static String QUEUE_NAME = "test_queue_exchange";
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
Thread.sleep(10);
// 返回消息消费状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
(4)消费者2
package com.rabbitmq.ps;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.util.ConnectionUtil;
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_exchange2";
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
注意:
1)消费者1、消费者2都必须绑定到交换机上,channel.queueBind。否则无法获取消息。
2)消息消费标示
在工作模式中消费完消息会返回消息消费状态为true。
channel.basicConsume(QUEUE_NAME, true, consumer);
在发布订阅时,不是立即返回消息消费状态,而是手工设置返回消息消费状态
channel.basicConsume(QUEUE_NAME, false, consumer);
……
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
例如:当获取到消息时,开始处理业务,这时有台计算机突然宕机了,此时这个消息并没有消费完。实际应当等业务逻辑处理完,在个服务端发出一个消息消费完表示。
这里是在开发时要根据具体的应用场景选择不同的学习方法。
问题:能否选择性的接收消息。
例如新增消息,对于前台系统就不需要,但对于搜寻系统就需要。那这样的情况下,是否能需要的信息才接收,不需要的不管。答案当然可以有。这就是路由模式。
5.路由模式
应用场景:将log4j的日志按级别分别存放到不同的队列中
(1)direct exchange
需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配,这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键“cow”,则只有被标记为“cow”的消息才能被转发,不会转发cat,也不会转发mouse,只会转发cow。
对于路由的这种结构,我们就可以将它应用在我们的项目中,前台系统只接收更新的消息,而检索可以接收新增和更新的消息。
(2)发消息
package com.rabbitmq.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.util.ConnectionUtil;
public class Send {
private final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明exchange
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 消息内容
String message = "Hello World!";
channel.basicPublish(EXCHANGE_NAME, "key2", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
}
(3)消费者一
package com.rabbitmq.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.util.ConnectionUtil;
public class Recv {
private final static String QUEUE_NAME = "test_queue_direct_work";
private final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
(4)消费者二
package com.rabbitmq.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.util.ConnectionUtil;
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_direct_work2";
private final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key2");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
注意:在绑定时,设置路由KEY。
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME,"key2");
(5)绑定结果
可以看到定义了一个路由KEY。
![](https://i-blog.csdnimg.cn/blog_migrate/adbae1c3708c4181a203066ec1e813aa.png)
6.topic模式
(1)topic exchange
将路由键和某模式进行匹配。此时队列需要绑定在一个模式上。符合#匹配一个或多个词,符号*匹配不多不少一个词。
(2)发消息
package com.rabbitmq.topic;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.util.ConnectionUtil;
public class Send {
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明exchange
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 消息内容
String message = "Hello World! topic";
channel.basicPublish(EXCHANGE_NAME, "item.update", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
}
(3)接收消息
package com.rabbitmq.topic;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.util.ConnectionUtil;
public class Recv {
private final static String QUEUE_NAME = "test_queue_topic_work";
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "item.#");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
(4)绑定结果