一.Windows下安装RabbitMq
1.安装erlang语言开发包(rabbitmq为erlang语言开发)
a.Erlang 下载安装地址 http://www.erlang.org/downloads 下载完成后运行安装
b.配置环境变量
1)添加系统环境变量ERLANG_HOME,值为安装目录.
2) 修改系统环境变量Path,在PATH变量中添加“%ERL_HOME%\bin”
3) 重启电脑后,在控制台输入 erl,如果出现类似“Eshell V6.1 (abort with ^G)”字样,说明安装成功。
2.安装RabbitMq
a.RabbitMq下载安装地址 http://www.rabbitmq.com/install-windows.html 下载完成后运行安装
b.运行服务
rabbitMq默认自启动
可以修改rabbitmq的配置文件,也可以用默认配置运行。在开始菜单栏里可以看到运行指令reinstall/remove/start/stop
或者直接打开RabbitMQ Command Prompt命令框。
c.Rabbit MQ插件
激活Rabbit MQ’s Management Plugin 使用Rabbit MQ 管理插件,可以更好的可视化方式查看Rabbit MQ 服务器实例的状态,你可以在命令行中使用下面的命令激活。
输入:rabbitmq-plugins.bat enable rabbitmq_management
创建管理用户
输入:rabbitmqctl.bat add_user gujie gujie1991 (用户名 密码) (这一步及接下来的两步可以不设置,有默认的账号 guest,密码guest)
设置管理员
输入:rabbitmqctl.bat set_user_tags gujie administrator
设置权限
输入:rabbitmqctl.bat set_permissions -p / zhangweizhong “." ".” “.*”
其他命令查询用户: rabbitmqctl.bat list_users查询vhosts(虚拟机): rabbitmqctl.bat list_vhosts权限、作用域等基本就设置完了。
启动:rabbitmq-server start 访问localhost:15672出现下列页面表示成功。(此时可对连接、通道、交换机、队列、用户进行管理)
二.RabbitMq原理
RabbitMQ是消息代理。从本质上说,它接受来自生产者的信息,并将它们传递给消费者。在两者之间,它可以根据你给它的路由,缓冲规则进行传递消息。
示例图
1.Exchange
1)交换机属性:
name:交换机名称
type:交换机类型 direct,topic,fanout,headers
durability:是否需要持久化,true为持久化
auto delete:当最后一个绑定到exchange上的队列被删除后,exchange没有绑定的队列了,自动删除该exchange
internal:当前exchange是否用于rabbitMQ内部使用,默认为false
arguments:扩展参数,用于扩展AMQP协议自制定化使用
2)direct exchange类型:
direct exchange:所有发送到direct exchange的消息被转发到routing key中指定的queue
注意:direct模式可以使用rabbitMQ自带的exchange:default exchange,所以不需要将exchange进行任何绑定(binding)操作,消息传递时,routingkey必须完全匹配才会被队列接收,否则该消息会被抛弃。
流转示意图如下
3)topic exchange类型:
topic exchange:所有发送到topic exchange的消息被转发到所有关心routingkey中topic的queue上
exchange将routingkey和某topic进行模糊匹配,此时队列需要绑定一个topic。
注意:topic可以使用通配符进行模糊匹配#匹配一个或多个词,注意是词只能匹配一个词例如“log.#”能匹配到“log.info.oa”“log.”只能匹配到“log.erro”这种格式
具体示例图如下图,usa.news能被usa.#,#.news所消费,usa.weather能被usa.#,#.weather所消费…
4)fanout exchange类型:
fanout exchange:不处理路由键,只需要简单的将队列绑定到交换机上,发送到该交换机的消息都会被转发到于该交换机绑定的所有队列上,fanout交换机由于不需要进行routingkey的对比 直接发送所以绑定的queue,所以转发消息是最快的
示意图如下图所示
headers类型的不常用,就不介绍了
2.Binding
binding:绑定exchange和exchange/queue之间的连接关心。binding中可以包含routingkey或者参数
3.Queue
queue:消息队列,实际存储消息数据,durability表示是否持久化,durable表示是,transient表示否。auto delete:如选择yes,表示当最后一个监听被移除后,该queue会被自动删除。
4.Message
message:服务器和应用程序之间传送的数据 本质上就是一段数据,由properties和payload(body)组成
常用属性:delivery mode,headersheaders(自定义属性),content_type,content_encoding,priority,correlation_id,reply_to,expiration,message_id,timestamp,type,user_id,app_id,cluster_id
5.Virtual host
virtual host 虚拟主机
虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个virtual host里面可以有若干个exchange和queue,但是里面不能有相同名称的exchange或queue
6.Channel
消息通道,包含了大量的API可用于编程。在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。
消费模式pull与push
pull模式:
客户端(指一个connection,一般情况指一个tcp的连接建立)连接到broker之后,启动一个线程,这个线程的任务就是循环调用方法从broker中拉取相应的消息至本地。如果是同步方法调用获取则将相应的消息存放在本地内存中,当同步方法消费消息时,则从该内存区中直接获取相应的消息进行消费;
push模式:
客户端连接到broker之后,启动一个线程,这个线程的任务就是循环调用方法从broker中拉取相应的消息至本地。如果是异步方法调用,则直接调用监听器方法,间接调用业务消费消息的方法,而不使用本地内存进行消息的缓存;所以这里的异步只是客户端的异步,而非broker的主动推送。通过这种方式既能解决多客户端的连接,也能解决类似服务端的push型的消息推送。在互联网中这种实现才具有普便性,因为这种方式即解决了性能问题又解决了异步消息的需求。
消息队列的使用过程大概如下:
1)客户端连接到消息队列服务器,打开一个channel。
2)客户端声明一个exchange,并设置相关属性。
3)客户端声明一个queue,并设置相关属性。
4)客户端使用routing key,在exchange和queue之间建立好绑定关系。
5)客户端投递消息到exchange。exchange接收到消息后,就根据消息的
key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。
RabbitMQ支持消息的持久化,也就是数据写在磁盘上,为了数据安全考虑,我想大多数用户都会选择持久化。消息队列持久化包括3个部分:
1)exchange持久化,在声明时指定durable 为 1
2)queue持久化,在声明时指定durable 为 1
3)消息持久化,在投递时指定delivery_mode 为 2(1是非持久化)
三.SpringBoot整合RabbitMQ
1.添加依赖
在pom.xml中添加如下代码:
<!-- 添加springboot对amqp的支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.编写配置信息
根据自己的配置在application.properties中加入如下信息:
(这里端口是5672,不是15672…15672是管理端的端口!)
spring.application.name=spirng-boot-rabbitmq-sender
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
3.Direct模式
1).生产者
配置Queue
@Configuration
public class SenderConf {
@Bean
public Queue queue() {
return new Queue("queue");
}
}
使用AmqpTemplate去发送消息
@Component
public class HelloSender {
@Autowired
private AmqpTemplate template;
public void send() {
template.convertAndSend("queue","hello,rabbit~");
}
}
编写测试类
@SpringBootTest(classes=App.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class TestRabbitMQ {
@Autowired
private HelloSender helloSender;
@Test
public void testRabbit() {
helloSender.send();
}
}
2).消费者
@Component
public class HelloReceive {
@RabbitListener(queues="queue") //监听器监听指定的Queue
public void processC(String str) {
System.out.println("Receive:"+str);
}
}
注意:Direct模式相当于一对一模式,一个消息被发送者发送后,会被转发到一个绑定的消息队列中,然后被一个接收者接收
4.Topic转发模式
1).生产者
配置队列Queue及交换机Exchange,按照相应的规则绑定
@Configuration
public class SenderConf {
@Bean(name="message")
public Queue queueMessage() {
return new Queue("topic.message");
}
@Bean(name="messages")
public Queue queueMessages() {
return new Queue("topic.messages");
}
@Bean
public TopicExchange exchange() {
return new TopicExchange("exchange");
}
@Bean
Binding bindingExchangeMessage(@Qualifier("message") Queue queueMessage, TopicExchange exchange) {
return BindingBuilder.bind(queueMessage).to(exchange).with("topic.message");
}
@Bean
Binding bindingExchangeMessages(@Qualifier("messages") Queue queueMessages, TopicExchange exchange) {
return BindingBuilder.bind(queueMessages).to(exchange).with("topic.#");//*表示一个词,#表示零个或多个词
}
}
使用AmqpTemplate去发送消息
@Component
public class HelloSender {
@Autowired
private AmqpTemplate template;
public void send() {
template.convertAndSend("exchange","topic.message","hello,rabbit!");
}
}
注意:方法的第一个参数是交换机名称,第二个参数是发送的key,第三个参数是内容,RabbitMQ将会根据第二个参数去寻找有没有匹配此规则的队列,如果有,则把消息给它,如果有不止一个,则把消息分发给匹配的队列(每个队列都有消息!)
2).消费者
@RabbitListener(queues="topic.message") //监听器监听指定的Queue
public void process1(String str) {
System.out.println("message:"+str);
}
@RabbitListener(queues="topic.messages") //监听器监听指定的Queue
public void process2(String str) {
System.out.println("messages:"+str);
}
5.Fanout形式
1).生产者
配置队列Queue及交换机Exchange,按照相应的规则绑定
@Configuration
public class SenderConf {
@Bean(name="Amessage")
public Queue AMessage() {
return new Queue("fanout.A");
}
@Bean(name="Bmessage")
public Queue BMessage() {
return new Queue("fanout.B");
}
@Bean(name="Cmessage")
public Queue CMessage() {
return new Queue("fanout.C");
}
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");//配置广播路由器
}
@Bean
Binding bindingExchangeA(@Qualifier("Amessage") Queue AMessage,FanoutExchange fanoutExchange) {
return BindingBuilder.bind(AMessage).to(fanoutExchange);
}
@Bean
Binding bindingExchangeB(@Qualifier("Bmessage") Queue BMessage, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(BMessage).to(fanoutExchange);
}
@Bean
Binding bindingExchangeC(@Qualifier("Cmessage") Queue CMessage, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(CMessage).to(fanoutExchange);
}
}
使用AmqpTemplate去发送消息
@Component
public class HelloSender {
@Autowired
private AmqpTemplate template;
public void send() {
template.convertAndSend("fanoutExchange","","hello,rabbit!");
}
}
注意:Fanout Exchange形式又叫广播形式,因此我们发送到路由器的消息会使得绑定到该路由器的每一个Queue接收到消息,这个时候就算指定了Key,或者规则(即convertAndSend方法的参数2),也会被忽略!
2).消费者
@Component
public class HelloReceive {
@RabbitListener(queues="fanout.A")
public void processA(String str1) {
System.out.println("ReceiveA:"+str1);
}
@RabbitListener(queues="fanout.B")
public void processB(String str) {
System.out.println("ReceiveB:"+str);
}
@RabbitListener(queues="fanout.C")
public void processC(String str) {
System.out.println("ReceiveC:"+str);
}
}
6.Pull模式与Push模式拉取消息:
1).生产者
package com.example.tutorials;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* 使用 channel.basicGet() 方法,拉取指定队列中的内容
* @create 2017-09-05
* amqp-client 4.2.0
**/
public class D7_PullSend {
private final static String QUEUE_NAME = "pull_queue";
/**
* 生产者,
* @param argv
* @throws Exception
*/
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
//设置登录账号
factory.setHost(ServerInfo.host);
factory.setPort(ServerInfo.port);
factory.setUsername(ServerInfo.uName);
factory.setPassword(ServerInfo.uPwd);
//链接服务器
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
Map<String,Object> args = new HashMap<>();
// args.put("x-message-ttl",15*1000);//消息过期时间为 15 秒
//args.put("x-expires",1*60*1000); //队列过期时间为 1 分钟
//定义一个队列
boolean duiable=false;//持久化
boolean exclusive = false;//排他队列
boolean autoDelete=false;//没有consumer时,队列是否自动删除
channel.queueDeclare(QUEUE_NAME, duiable, exclusive, autoDelete, args);
//发送消息
System.out.println("输入要发送的消息,退出输入 x ");
String message ;
//
AMQP.BasicProperties prop = new AMQP.BasicProperties
.Builder()
.expiration("15000") //消息过期时间为 15 秒
.build();
do{
Scanner scanner = new Scanner(System.in);
message = scanner.next();
channel.basicPublish(""
, QUEUE_NAME
, prop
, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "'");
}while(!"x".equals(message));
//关闭链接
channel.close();
connection.close();
}
}
2).消费者(pull)
package com.example.tutorials;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.GetResponse;
import java.util.Scanner;
/**
* 使用 channel.basicGet() 方法,拉取指定队列中的内容
* @create 2017-09-05
* amqp-client 4.2.0
**/
public class D7_PullRecv {
private final static String QUEUE_NAME = "pull_queue";
/**
* 消费者, "Hello World!"
* @param argv
* @throws Exception
*/
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
//设置登录账号
factory.setHost(ServerInfo.host);
factory.setPort(ServerInfo.port);
factory.setUsername(ServerInfo.uName);
factory.setPassword(ServerInfo.uPwd);
//链接服务器
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//定义一个队列
boolean duiable=false;//持久化
boolean exclusive = false;//排他队列
boolean autoDelete=false;//没有consumer时,队列是否自动删除
channel.queueDeclare(QUEUE_NAME, duiable, exclusive, autoDelete, null);
//接收消息
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
boolean autoAck = true; //自动应答
System.out.println("输入回车获取消息,退出输入 x ");
String message ="";
GetResponse resp ;
do{
Scanner scanner = new Scanner(System.in);
scanner.nextLine();
resp = channel.basicGet(QUEUE_NAME,autoAck);
if(resp==null){
System.out.println(QUEUE_NAME+" 队列无消息");
continue;
}
message = new String(resp.getBody(), "UTF-8");
System.out.println(String.format(" [x] Recv Count %s , msg = %s;"
,resp.getMessageCount()
,message));
}while(!"x".equals(message));
}
}
3).消费者(push)
ConnectionFactory factory = new ConnectionFactory();
// 设置登录账号
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
// 链接服务器
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 定义一个队列
boolean duiable = true;// 持久化
boolean exclusive = false;// 排他队列
boolean autoDelete = false;// 没有consumer时,队列是否自动删除
channel.exchangeDeclare(EXCHANGE, "direct", true, false, false, null);
channel.queueDeclare(QUEUE_NAME, duiable, exclusive, autoDelete, null);
channel.queueBind(QUEUE_NAME, EXCHANGE, ROUTINGKEY2);
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(QUEUE_NAME, true, consumer);
String string = "";
while (true) {
Delivery delivery = consumer.nextDelivery();
string = new String(delivery.getBody());
}