RabbitMQ介绍
MQ全称为MessageQueue,即消息队列,RabbitMQ是由erlang语言开发,基于AMQP(AdvancedMessageQueue高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。RabbitMQ官方地址:http://www.rabbitmq.com/
开发中消息队列通常有如下应用场景:
1,任务异步处理
2,应用程序解耦合
RabbitMQ的工作原理:
组成说明:
Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue。
Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。
Producer:消息生产者,即生产方客户端,生产方客户端将消息发送到MQ。
Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。
快速入门(测试类,了解原理 没啥用)
创建maven工程
1 <dependency> 2 <groupId>com.rabbitmq</groupId> 3 <artifactId>amqp-client</artifactId> 4 <version>4.0.3</version><!--此版本与spring boot 1.5.9版本匹配--> 5 </dependency> 6 <dependency> 7 <groupId>org.springframework.boot</groupId> 8 <artifactId>spring-boot-starter-logging</artifactId> 9 </dependency>
生产者
发送端操作流程:1,创建连接 2,创建通道 3,声明队列 4,发送消息
1 public class Producer01 { 2 3 //队列 4 private static final String QUEUE = "helloworld"; 5 6 public static void main(String[] args) { 7 //通过连接工厂创建新的连接和mq建立连接 8 ConnectionFactory connectionFactory = new ConnectionFactory(); 9 connectionFactory.setHost("127.0.0.1"); 10 connectionFactory.setPort(5672);//端口 11 connectionFactory.setUsername("guest"); 12 connectionFactory.setPassword("guest"); 13 //设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的mq 14 connectionFactory.setVirtualHost("/"); 15 Connection connection = null; 16 Channel channel = null; 17 try { 18 //建立新连接 19 connection = connectionFactory.newConnection(); 20 //创建会话通道,生产者和mq服务所有通信都在channel通道中完成 21 channel = connection.createChannel(); 22 //声明队列,如果队列在mq 中没有则要创建 23 //参数:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments 24 /** 25 * 参数明细 26 * 1、queue 队列名称 27 * 2、durable 是否持久化,如果持久化,mq重启后队列还在 28 * 3、exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建 29 * 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除) 30 * 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间 31 */ 32 channel.queueDeclare(QUEUE,true,false,false,null); 33 //发送消息 34 //参数:String exchange, String routingKey, BasicProperties props, byte[] body 35 /** 36 * 参数明细: 37 * 1、exchange,交换机,如果不指定将使用mq的默认交换机(设置为"") 38 * 2、routingKey,路由key,交换机根据路由key来将消息转发到指定的队列,如果使用默认交换机,routingKey设置为队列的名称 39 * 3、props,消息的属性 40 * 4、body,消息内容 41 */ 42 //消息内容 43 String message = "hello world 黑马程序员"; 44 channel.basicPublish("",QUEUE,null,message.getBytes()); 45 System.out.println("send to mq "+message); 46 } catch (Exception e) { 47 e.printStackTrace(); 48 } finally { 49 //关闭连接 50 //先关闭通道 51 try { 52 channel.close(); 53 } catch (IOException e) { 54 e.printStackTrace(); 55 } catch (TimeoutException e) { 56 e.printStackTrace(); 57 } 58 try { 59 connection.close(); 60 } catch (IOException e) { 61 e.printStackTrace(); 62 } 63 } 64 } 65 66 }
消费者
接收端操作流程:1,创建连接 2,创建通道 3,声明队列 4,监听队列 5,接收消息
1 public class Consumer01 { 2 3 //队列 4 private static final String QUEUE = "helloworld"; 5 6 public static void main(String[] args) throws IOException, TimeoutException { 7 //通过连接工厂创建新的连接和mq建立连接 8 ConnectionFactory connectionFactory = new ConnectionFactory(); 9 connectionFactory.setHost("127.0.0.1"); 10 connectionFactory.setPort(5672);//端口 11 connectionFactory.setUsername("guest");//账户 12 connectionFactory.setPassword("guest");//密码 13 //设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的mq 14 connectionFactory.setVirtualHost("/"); 15 16 //建立新连接 17 Connection connection = connectionFactory.newConnection(); 18 //创建会话通道,生产者和mq服务所有通信都在channel通道中完成 19 Channel channel = connection.createChannel(); 20 21 //监听队列 22 //声明队列,如果队列在mq 中没有则要创建 23 //参数:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments 24 /** 25 * 参数明细 26 * 1、queue 队列名称 27 * 2、durable 是否持久化,如果持久化,mq重启后队列还在 28 * 3、exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建 29 * 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除) 30 * 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间 31 */ 32 channel.queueDeclare(QUEUE,true,false,false,null); 33 34 //实现消费方法 35 DefaultConsumer defaultConsumer = new DefaultConsumer(channel){ 36 37 /** 38 * 当接收到消息后此方法将被调用 39 * @param consumerTag 消费者标签,用来标识消费者的,在监听队列时设置channel.basicConsume 40 * @param envelope 信封,通过envelope 41 * @param properties 消息属性 42 * @param body 消息内容 43 * @throws IOException 44 */ 45 @Override 46 public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { 47 //交换机 48 String exchange = envelope.getExchange(); 49 //消息id,mq在channel中用来标识消息的id,可用于确认消息已接收 50 long deliveryTag = envelope.getDeliveryTag(); 51 //消息内容 52 String message= new String(body,"utf-8"); 53 System.out.println("receive message:"+message); 54 } 55 }; 56 57 //监听队列 58 //参数:String queue, boolean autoAck, Consumer callback 59 /** 60 * 参数明细: 61 * 1、queue 队列名称 62 * 2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复 63 * 3、callback,消费方法,当消费者接收到消息要执行的方法 64 */ 65 channel.basicConsume(QUEUE,true,defaultConsumer); 66 67 } 68 }
工作模式(P:生产者,也就是要发送消息的程序 C:消费者:消息的接受者,会一直等待消息到来 queue:消息队列,图中红色部分 X:交换机)
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
1、Work queues 工作队列
work queues与入门程序相比,多了一个消费端,两个消费端共同消费同一个队列中的消息。
2、Publish/Subscribe 发布订阅
3、Routing 路由
1、每个消费者监听自己的队列,并且设置routingkey。 2、生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列。
案例:用户通知,当用户充值成功或转账完成系统通知用户,通知方式有短信、邮件多种方法。(更好的对代码进行测试)
生产者
声明exchange_routing_inform交换机。
声明两个队列并且绑定到此交换机,绑定时需要指定routingkey
发送消息时需要指定routingkey
1 public class Producer03_routing { 2 //队列名称 3 private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";//队列 4 private static final String QUEUE_INFORM_SMS = "queue_inform_sms";//队列 5 private static final String EXCHANGE_ROUTING_INFORM="exchange_routing_inform";//交换机 6 private static final String ROUTINGKEY_EMAIL="inform_email";//routingKey 路由key 7 private static final String ROUTINGKEY_SMS="inform_sms";//routingKey 路由key 8 public static void main(String[] args) { 9 //通过连接工厂创建新的连接和mq建立连接 10 ConnectionFactory connectionFactory = new ConnectionFactory(); 11 connectionFactory.setHost("127.0.0.1"); 12 connectionFactory.setPort(5672);//端口 13 connectionFactory.setUsername("guest"); 14 connectionFactory.setPassword("guest"); 15 //设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的mq 16 connectionFactory.setVirtualHost("/"); 17 18 Connection connection = null; 19 Channel channel = null; 20 try { 21 //建立新连接 22 connection = connectionFactory.newConnection(); 23 //创建会话通道,生产者和mq服务所有通信都在channel通道中完成 24 channel = connection.createChannel(); 25 //声明队列,如果队列在mq 中没有则要创建 26 //参数:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments 27 /** 28 * 参数明细 29 * 1、queue 队列名称 30 * 2、durable 是否持久化,如果持久化,mq重启后队列还在 31 * 3、exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建 32 * 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除) 33 * 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间 34 */ 35 channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null); 36 channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null); 37 //声明一个交换机 38 //参数:String exchange, String type 39 /** 40 * 参数明细: 41 * 1、交换机的名称 42 * 2、交换机的类型 43 * fanout:对应的rabbitmq的工作模式是 publish/subscribe 44 * direct:对应的Routing 工作模式 45 * topic:对应的Topics工作模式 46 * headers: 对应的headers工作模式 47 */ 48 channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT); 49 //进行交换机和队列绑定 50 //参数:String queue, String exchange, String routingKey 51 /** 52 * 参数明细: 53 * 1、queue 队列名称 54 * 2、exchange 交换机名称 55 * 3、routingKey 路由key,作用是交换机根据路由key的值将消息转发到指定的队列中,在发布订阅模式中调协为空字符串 56 */ 57 channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL); 58 channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,"inform"); 59 channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,ROUTINGKEY_SMS); 60 channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,"inform"); 61 //发送消息 62 //参数:String exchange, String routingKey, BasicProperties props, byte[] body 63 /** 64 * 参数明细: 65 * 1、exchange,交换机,如果不指定将使用mq的默认交换机(设置为"") 66 * 2、routingKey,路由key,交换机根据路由key来将消息转发到指定的队列,如果使用默认交换机,routingKey设置为队列的名称 67 * 3、props,消息的属性 68 * 4、body,消息内容 69 */ 70 /* for(int i=0;i<5;i++){ 71 //发送消息的时候指定routingKey 72 String message = "send email inform message to user"; 73 channel.basicPublish(EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL,null,message.getBytes()); 74 System.out.println("send to mq "+message); 75 } 76 for(int i=0;i<5;i++){ 77 //发送消息的时候指定routingKey 78 String message = "send sms inform message to user"; 79 channel.basicPublish(EXCHANGE_ROUTING_INFORM,ROUTINGKEY_SMS,null,message.getBytes()); 80 System.out.println("send to mq "+message); 81 }*/ 82 for(int i=0;i<5;i++){ 83 //发送消息的时候指定routingKey 84 String message = "send inform message to user"; 85 channel.basicPublish(EXCHANGE_ROUTING_INFORM,"inform",null,message.getBytes()); 86 System.out.println("send to mq "+message); 87 } 88 89 } catch (Exception e) { 90 e.printStackTrace(); 91 } finally { 92 //关闭连接 93 //先关闭通道 94 try { 95 channel.close(); 96 } catch (IOException e) { 97 e.printStackTrace(); 98 } catch (TimeoutException e) { 99 e.printStackTrace(); 100 } 101 try { 102 connection.close(); 103 } catch (IOException e) { 104 e.printStackTrace(); 105 } 106 } 107 108 109 } 110 }
消费者1(邮件发送消费者)
1 public class Consumer03_routing_email { 2 //队列名称 3 private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";//队列 4 private static final String EXCHANGE_ROUTING_INFORM="exchange_routing_inform";//交换机 5 private static final String ROUTINGKEY_EMAIL="inform_email";//routingKey 路由key 和生产者相匹配 6 7 public static void main(String[] args) throws IOException, TimeoutException { 8 //通过连接工厂创建新的连接和mq建立连接 9 ConnectionFactory connectionFactory = new ConnectionFactory(); 10 connectionFactory.setHost("127.0.0.1"); 11 connectionFactory.setPort(5672);//端口 12 connectionFactory.setUsername("guest"); 13 connectionFactory.setPassword("guest"); 14 //设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的mq 15 connectionFactory.setVirtualHost("/"); 16 17 //建立新连接 18 Connection connection = connectionFactory.newConnection(); 19 //创建会话通道,生产者和mq服务所有通信都在channel通道中完成 20 Channel channel = connection.createChannel(); 21 22 /** 23 * 参数明细 24 * 1、queue 队列名称 25 * 2、durable 是否持久化,如果持久化,mq重启后队列还在 26 * 3、exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建 27 * 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除) 28 * 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间 29 */ 30 channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null); 31 //声明一个交换机 32 //参数:String exchange, String type 33 /** 34 * 参数明细: 35 * 1、交换机的名称 36 * 2、交换机的类型 37 * fanout:对应的rabbitmq的工作模式是 publish/subscribe 38 * direct:对应的Routing 工作模式 39 * topic:对应的Topics工作模式 40 * headers: 对应的headers工作模式 41 */ 42 channel.exchangeDeclare(EXCHANGE_ROUTING_INFORM, BuiltinExchangeType.DIRECT); 43 //进行交换机和队列绑定 44 //参数:String queue, String exchange, String routingKey 45 /** 46 * 参数明细: 47 * 1、queue 队列名称 48 * 2、exchange 交换机名称 49 * 3、routingKey 路由key,作用是交换机根据路由key的值将消息转发到指定的队列中,在发布订阅模式中调协为空字符串 50 */ 51 channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_ROUTING_INFORM,ROUTINGKEY_EMAIL); 52 53 //实现消费方法 54 DefaultConsumer defaultConsumer = new DefaultConsumer(channel){ 55 56 /** 57 * 当接收到消息后此方法将被调用 58 * @param consumerTag 消费者标签,用来标识消费者的,在监听队列时设置channel.basicConsume 59 * @param envelope 信封,通过envelope 60 * @param properties 消息属性 61 * @param body 消息内容 62 * @throws IOException 63 */ 64 @Override 65 public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { 66 //交换机 67 String exchange = envelope.getExchange(); 68 //消息id,mq在channel中用来标识消息的id,可用于确认消息已接收 69 long deliveryTag = envelope.getDeliveryTag(); 70 //消息内容 71 String message= new String(body,"utf-8"); 72 System.out.println("receive message:"+message); 73 } 74 }; 75 76 //监听队列 77 //参数:String queue, boolean autoAck, Consumer callback 78 /** 79 * 参数明细: 80 * 1、queue 队列名称 81 * 2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复 82 * 3、callback,消费方法,当消费者接收到消息要执行的方法 83 */ 84 channel.basicConsume(QUEUE_INFORM_EMAIL,true,defaultConsumer); 85 86 } 87 }
消费者2(短信发送消费者(参考邮件发送消费者的代码流程))
4、Topics 通配符
Routingkey一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如:item.insert
通配符规则:#:匹配一个或多个词 *:匹配不多不少恰好1个词
举例:item.#:能够匹配item.insert.abc或者item.insert
item.*:只能匹配item.inser
5、Header Header 转发器
6、RPC 远程过程调用
Spring整合RibbitMQ
maven依赖
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-amqp</artifactId> 4 </dependency> 5 <dependency> 6 <groupId>org.springframework.boot</groupId> 7 <artifactId>spring-boot-starter-test</artifactId> 8 </dependency> 9 <dependency> 10 <groupId>org.springframework.boot</groupId> 11 <artifactId>spring-boot-starter-logging</artifactId> 12 </dependency>
配置
1、配置application.yml
配置连接rabbitmq的参数
1 server: 2 port: 44000 3 spring: 4 application: 5 name: test-rabbitmq-producer 6 rabbitmq: 7 host: 127.0.0.1 8 port: 5672 9 username: guest 10 password: guest 11 virtualHost: /
2、定义RabbitConfig类,配置Exchange、Queue、及绑定交换机。
本例配置Topic交换机
1 @Configuration 2 public class RabbitmqConfig { 3 public static final String QUEUE_INFORM_EMAIL = "queue_inform_email";//队列 4 public static final String QUEUE_INFORM_SMS = "queue_inform_sms"; 5 public static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";//交换机 6 public static final String ROUTINGKEY_EMAIL="inform.#.email.#";//routingKey 7 public static final String ROUTINGKEY_SMS="inform.#.sms.#";//routingKey 8 9 //声明交换机 10 @Bean(EXCHANGE_TOPICS_INFORM) 11 public Exchange EXCHANGE_TOPICS_INFORM(){ 12 //durable(true) 持久化,mq重启之后交换机还在 13 return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM).durable(true).build(); 14 } 15 16 //声明QUEUE_INFORM_EMAIL队列 17 @Bean(QUEUE_INFORM_EMAIL) 18 public Queue QUEUE_INFORM_EMAIL(){ 19 return new Queue(QUEUE_INFORM_EMAIL); 20 } 21 //声明QUEUE_INFORM_SMS队列 22 @Bean(QUEUE_INFORM_SMS) 23 public Queue QUEUE_INFORM_SMS(){ 24 return new Queue(QUEUE_INFORM_SMS); 25 } 26 27 //ROUTINGKEY_EMAIL队列绑定交换机,指定routingKey 28 @Bean 29 public Binding BINDING_QUEUE_INFORM_EMAIL(@Qualifier(QUEUE_INFORM_EMAIL) Queue queue, 30 @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange){ 31 return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_EMAIL).noargs(); 32 } 33 //ROUTINGKEY_SMS队列绑定交换机,指定routingKey 34 @Bean 35 public Binding BINDING_ROUTINGKEY_SMS(@Qualifier(QUEUE_INFORM_SMS) Queue queue, 36 @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange){ 37 return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_SMS).noargs(); 38 } 39 }
生产者
1 public class Producer05_topics_springboot { 2 @Autowired 3 RabbitTemplate rabbitTemplate; 4 5 //使用rabbitTemplate发送消息 6 @Test 7 public void testSendEmail(){ 8 9 String message = "send email message to user"; 10 /** 11 * 参数: 12 * 1、交换机名称 13 * 2、routingKey 14 * 3、消息内容 15 */ 16 rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM,"inform.email",message); 17 18 } 19 20 //使用rabbitTemplate发送消息 21 @Test 22 public void testSendPostPage(){ 23 24 Map message = new HashMap<>(); 25 message.put("pageId","5a795ac7dd573c04508f3a56"); 26 //将消息对象转成json串 27 String messageString = JSON.toJSONString(message); 28 //路由key,就是站点ID 29 String routingKey = "5a751fab6abb5044e0d19ea1"; 30 /** 31 * 参数: 32 * 1、交换机名称 33 * 2、routingKey 34 * 3、消息内容 35 */ 36 rabbitTemplate.convertAndSend("ex_routing_cms_postpage",routingKey,messageString); 37 38 } 39 40 }
消费者
1 @Component 2 public class ReceiveHandler { 3 4 @RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_EMAIL}) 5 public void send_email(String msg,Message message,Channel channel){ 6 System.out.println("receive message is:"+msg); 7 } 8 9 }