四、java操作简单的simple简单队列
- 模型
P:消息的生产者
红色:队列
C:消费者
- 获取Mq的链接


1 import java.io.IOException; 2 import java.util.concurrent.TimeoutException; 3 4 import com.rabbitmq.client.Connection; 5 import com.rabbitmq.client.ConnectionFactory; 6 7 public class ConnectionUtils { 8 9 /** 10 * 11 * 方法功能说明:获取MQ的链接 12 * 参数说明: 13 * @return 14 * 作者:Administrator 15 * 创建时间:2019年1月22日-下午7:51:48 16 * @throws TimeoutException 17 * @throws IOException 18 * 19 * 20 */ 21 public static Connection getConnection() throws IOException, TimeoutException { 22 //定义一个链接工厂 23 ConnectionFactory factory = new ConnectionFactory(); 24 //设置服务地址 25 factory.setHost("192.168.0.116"); 26 //设定端口 27 factory.setPort(8244); 28 //设定vhost 29 factory.setVirtualHost("vhost"); 30 //设定用户名 31 factory.setUsername("admin"); 32 //设定密码 33 factory.setPassword("jorudan0407"); 34 return factory.newConnection(); 35 } 36 }
- 生产者生产消息


1 package com.mmr.rabbitmq.simple; 2 3 import java.io.IOException; 4 import java.util.concurrent.TimeoutException; 5 6 import com.mmr.rabbitmq.utils.ConnectionUtils; 7 import com.rabbitmq.client.Channel; 8 import com.rabbitmq.client.Connection; 9 10 public class Send { 11 private static final String QUEUE_NAME="test_simple_queue"; 12 13 public static void main(String[] args) throws IOException, TimeoutException { 14 //获取一个链接 15 Connection connection = ConnectionUtils.getConnection(); 16 //获取一个通道 17 Channel channel = connection.createChannel(); 18 //创建队列声明 19 channel.queueDeclare(QUEUE_NAME, false, false, false, null); 20 String msg = "hello simple!"; 21 channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); 22 System.out.println("-- send msg: "+msg); 23 24 channel.close(); 25 } 26 }
- 消费者消费消息


1 package com.mmr.rabbitmq.simple; 2 3 import java.io.IOException; 4 import java.util.concurrent.TimeoutException; 5 6 import com.mmr.rabbitmq.utils.ConnectionUtils; 7 import com.rabbitmq.client.Channel; 8 import com.rabbitmq.client.Connection; 9 import com.rabbitmq.client.DefaultConsumer; 10 import com.rabbitmq.client.Envelope; 11 import com.rabbitmq.client.AMQP.BasicProperties; 12 13 /** 14 * 15 * 公司:若尔丹软件开发公司 16 * 类描述:获取消息 17 * 作者:lizongti 18 * 创建时间:2019年1月22日-下午8:09:24 19 * 更新时间: 20 */ 21 public class Recv { 22 private static final String QUEUE_NAME="test_simple_queue"; 23 public static void main(String[] args) throws IOException, TimeoutException { 24 //获取链接 25 Connection connection = ConnectionUtils.getConnection(); 26 //创建链接 27 Channel channel = connection.createChannel(); 28 //队列声明 29 channel.queueDeclare(QUEUE_NAME, false, false, false, null); 30 //监听队列 31 channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) { 32 @Override 33 public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) 34 throws IOException { 35 // TODO Auto-generated method stub 36 System.out.println("接收到消息:"+new String(body, "UTF-8")); 37 } 38 }); 39 } 40 }
- 简单队列不足
- 耦合性告,生产者意义对应消费者,如果我想有多个消费者,就不行了
- 对列明变更,这时候的同时变更
五、Work queues工作队列
- 模型
为什么会出现工作队列
simple队列是意义对应的,而且实际开发,生产者生产消息是很快的,而消费者一般是同业务结合的,处理逻辑慢。可能花费时间较多,这时候队列就会积压很多消息。
- 生产者生产消息


1 package com.mmr.rabbitmq.work; 2 3 import com.mmr.rabbitmq.utils.ConnectionUtils; 4 import com.rabbitmq.client.Channel; 5 import com.rabbitmq.client.Connection; 6 7 public class Send { 8 private static final String QUEUE_NAME="test_work_query"; 9 public static void main(String[] args) throws Exception { 10 Connection connection = ConnectionUtils.getConnection(); 11 Channel channel = connection.createChannel(); 12 channel.queueDeclare(QUEUE_NAME, false, false, false, null); 13 for(int i=0;i<50;i++) { 14 String msg = "hello"+i; 15 channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); 16 Thread.sleep(i*20); 17 } 18 19 channel.close(); 20 connection.close(); 21 } 22 }
- 消费者1消费消息


1 package com.mmr.rabbitmq.work; 2 3 import java.io.IOException; 4 5 import com.mmr.rabbitmq.utils.ConnectionUtils; 6 import com.rabbitmq.client.Channel; 7 import com.rabbitmq.client.Connection; 8 import com.rabbitmq.client.DefaultConsumer; 9 import com.rabbitmq.client.Envelope; 10 import com.rabbitmq.client.AMQP.BasicProperties; 11 12 public class Recv1 { 13 private static final String QUEUE_NAME="test_work_query"; 14 public static void main(String[] args) throws Exception { 15 //获取链接 16 Connection con = ConnectionUtils.getConnection(); 17 //获取channel 18 Channel channel = con.createChannel(); 19 //声明队列 20 channel.queueDeclare(QUEUE_NAME, false, false, false, null); 21 Boolean autoAck = true; 22 channel.basicConsume(QUEUE_NAME, autoAck, new DefaultConsumer(channel) { 23 @Override 24 public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) 25 throws IOException { 26 System.out.println("Recv1 触发消息:"+new String(body,"utf-8")); 27 try { 28 Thread.sleep(2000); 29 } catch (InterruptedException e) { 30 // TODO Auto-generated catch block 31 e.printStackTrace(); 32 }finally { 33 System.out.println("Recv1 消息处理完成!"); 34 } 35 } 36 }); 37 } 38 }
- 消费者2消费消息


1 package com.mmr.rabbitmq.work; 2 3 import java.io.IOException; 4 5 import com.mmr.rabbitmq.utils.ConnectionUtils; 6 import com.rabbitmq.client.Channel; 7 import com.rabbitmq.client.Connection; 8 import com.rabbitmq.client.DefaultConsumer; 9 import com.rabbitmq.client.Envelope; 10 import com.rabbitmq.client.AMQP.BasicProperties; 11 12 public class Recv2 { 13 private static final String QUEUE_NAME="test_work_query"; 14 public static void main(String[] args) throws Exception { 15 //获取链接 16 Connection con = ConnectionUtils.getConnection(); 17 //获取channel 18 Channel channel = con.createChannel(); 19 //声明队列 20 channel.queueDeclare(QUEUE_NAME, false, false, false, null); 21 Boolean autoAck = true; 22 channel.basicConsume(QUEUE_NAME, autoAck, new DefaultConsumer(channel) { 23 @Override 24 public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) 25 throws IOException { 26 System.out.println("Recv2 触发消息:"+new String(body,"utf-8")); 27 try { 28 Thread.sleep(1000); 29 } catch (InterruptedException e) { 30 // TODO Auto-generated catch block 31 e.printStackTrace(); 32 }finally { 33 System.out.println("Recv2 消息处理完成!"); 34 } 35 } 36 }); 37 } 38 }
现象:
消费者1和消费者2处理的数据消息是一样多的,消费者1都是偶数,消费者2都是基数,这种方式叫做轮询分发(round-robin)结果就不管谁处理快,都不会都给一个消息,任务消息总是你一个我一个。
公平分发:
使用basicQos(perfetch=1),使用自动分发,必需干逼ack,手动应答
生产者


1 package com.mmr.rabbitmq.work.workfair; 2 3 import com.mmr.rabbitmq.utils.ConnectionUtils; 4 import com.rabbitmq.client.Channel; 5 import com.rabbitmq.client.Connection; 6 7 public class Send { 8 private static final String QUEUE_NAME="test_work_query"; 9 public static void main(String[] args) throws Exception { 10 Connection connection = ConnectionUtils.getConnection(); 11 Channel channel = connection.createChannel(); 12 channel.queueDeclare(QUEUE_NAME, false, false, false, null); 13 /** 14 * 每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者,一次只能处理一个消息 15 * 限制发送给同一个消费者不超过一条消息 16 */ 17 int prefetchCount=1; 18 channel.basicQos(prefetchCount); 19 for(int i=0;i<50;i++) { 20 String msg = "hello"+i; 21 channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); 22 Thread.sleep(i*20); 23 } 24 25 channel.close(); 26 connection.close(); 27 } 28 }
消费者1


1 package com.mmr.rabbitmq.work.workfair; 2 3 import java.io.IOException; 4 5 import com.mmr.rabbitmq.utils.ConnectionUtils; 6 import com.rabbitmq.client.Channel; 7 import com.rabbitmq.client.Connection; 8 import com.rabbitmq.client.DefaultConsumer; 9 import com.rabbitmq.client.Envelope; 10 import com.rabbitmq.client.AMQP.BasicProperties; 11 12 public class Recv1 { 13 private static final String QUEUE_NAME="test_work_query"; 14 public static void main(String[] args) throws Exception { 15 //获取链接 16 Connection con = ConnectionUtils.getConnection(); 17 //获取channel 18 Channel channel = con.createChannel(); 19 //声明队列 20 channel.queueDeclare(QUEUE_NAME, false, false, false, null); 21 channel.basicQos(1);//保证一次只发一个 22 Boolean autoAck = false;//自动应答 false 23 channel.basicConsume(QUEUE_NAME, autoAck, new DefaultConsumer(channel) { 24 @Override 25 public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) 26 throws IOException { 27 System.out.println("Recv1 触发消息:"+new String(body,"utf-8")); 28 try { 29 Thread.sleep(2000); 30 } catch (InterruptedException e) { 31 // TODO Auto-generated catch block 32 e.printStackTrace(); 33 }finally { 34 System.out.println("Recv1 消息处理完成!"); 35 channel.basicAck(envelope.getDeliveryTag(), false); 36 } 37 } 38 }); 39 } 40 }
消费者2


1 package com.mmr.rabbitmq.work.workfair; 2 3 import java.io.IOException; 4 5 import com.mmr.rabbitmq.utils.ConnectionUtils; 6 import com.rabbitmq.client.Channel; 7 import com.rabbitmq.client.Connection; 8 import com.rabbitmq.client.DefaultConsumer; 9 import com.rabbitmq.client.Envelope; 10 import com.rabbitmq.client.AMQP.BasicProperties; 11 12 public class Recv2 { 13 private static final String QUEUE_NAME="test_work_query"; 14 public static void main(String[] args) throws Exception { 15 //获取链接 16 Connection con = ConnectionUtils.getConnection(); 17 //获取channel 18 Channel channel = con.createChannel(); 19 //声明队列 20 channel.queueDeclare(QUEUE_NAME, false, false, false, null); 21 channel.basicQos(1);//保证一次只发一个 22 Boolean autoAck = false;//自动应答 false 23 channel.basicConsume(QUEUE_NAME, autoAck, new DefaultConsumer(channel) { 24 @Override 25 public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) 26 throws IOException { 27 System.out.println("Recv2 触发消息:"+new String(body,"utf-8")); 28 try { 29 Thread.sleep(1000); 30 } catch (InterruptedException e) { 31 // TODO Auto-generated catch block 32 e.printStackTrace(); 33 }finally { 34 System.out.println("Recv2 消息处理完成!"); 35 //手动回执一个消息 36 channel.basicAck(envelope.getDeliveryTag(), false); 37 } 38 } 39 }); 40 } 41 }
现象:消费者2比消费者消费的多,也叫能者多劳
六、消息应答与消息持久化
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
boolean autoAck = true;(自动确认模式),一旦rabbitmq将消息分发给消费者,就会从内存删除,这种情况,如果杀死正在执行的消费者,就会丢失正在处理的消息
boolean autoAck = false;(手动模式),如果有一个消费者挂掉,就会交付给其他的消费者,消息不会丢失,rabbitmq支持消息应答,消费者发送一个消息应答告诉rabbitmq这个消息我已经处理完成,你可以删除了,然后rabbitmq就会删除内存中的消息。
消息应答默认是打开的,即false
消息的持久化:
boolean durable = true;
channel.queueDeclare(QUEUE_NAME, durable , false, false, null);
我们将程序的dureable=false改成true,是不可以的,应为代码景观是正确的,它也不会运行成功,因为我们定义了一个test_work_queue这个queue是为持久化的,rabbitmq不允许重新定义不同参数的一个已存在的队列
解决方法:在控制台将原来的队列删除即可
七、订阅模式
- 模型
一个生产者、多个消费者
每个消费者都有自己的队列
生产者没有将消息直接发给队列,而是发给交换机(转发器 exchange)里面
每个队列都要绑定到交换机上
生产者发送的消息经过交换机到达多个队列,就能实现一个消息被多个消费者消费