简单队列创建(一个生产者,一个队列,一个消费者)
1.创建mq连接
public static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost("192.168.1.6");
factory.setPort(5672);
factory.setVirtualHost("/user_db");
factory.setUsername("user");
factory.setPassword("123");
return factory.newConnection();
}
2.创建生产者
public static void main(String[] args) throws IOException, TimeoutException {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//从链接中获取一个通道
Channel channel = connection.createChannel();
//创建队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String msg="hello____ssss--->";
//发送消息
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
//关闭通道链接
channel.close();
//关闭mq的链接
connection.close();
}
3.创建消费者
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//时间监听模式,如果有消息,就会通过channel,就会触发这个方法。获取消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String mag=new String(body,"utf-8");
System.out.println(mag);
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}
多消费者(轮询分发。一个生产者、两个消费者)
1、生产者
public class SendWork {
private static final String QUEUE_NAME="test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取channel
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
for (int i = 0; i < 50; i++) {
String msg="---->"+i ;
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
channel.close();
connection.close();
}
}
2、第一个消费者(一秒消费一个消息)
public class RecvWork {
private static final String QUEUE_NAME="test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取channel
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("--->"+new String(body,"utf-8"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//开始ack自动应答
boolean autoack=true;
channel.basicConsume(QUEUE_NAME,autoack,consumer);
}
}
3、第二个消费者(两秒消费一个消息)
public class RecvWork2 {
private static final String QUEUE_NAME="test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取channel
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("--->"+new String(body,"utf-8"));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//开始ack自动应答
boolean autoack=true;
channel.basicConsume(QUEUE_NAME,autoack,consumer);
}
}
总结:生产者一共发送50个消息。但是两个消费者消费的消息是一样的。
消费者1:消费奇数。 消费者2:消费偶数 。这种方式叫做轮询分发。结果是不管谁忙,谁清闲。都不会多给谁一个消息。任务消息总是你一个,我一个这么分发。
公平分发(生产者声明只有接收到回应消息,才会发下一条数据。)
1、生产者(声明只有接收到回应消息,才会发下一条数据)
public class SendWork {
private static final String QUEUE_NAME="test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取channel
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/**
* 每个消费者发送确认“收到消息”这个标识之前,消息队列不会发送下一个消息到消费者。一次只处理一个消息
* 限制消费者只能消费一条数据。不会出现消费积压、
*/
int prefetCount=1;
channel.basicQos(prefetCount);
for (int i = 0; i < 50; i++) {
String msg="---->"+i ;
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
channel.close();
connection.close();
}
}
2.消费者1(消费者声明只接收一个消息。并在消费完消息后,给生产者一个回应消息。消费者关闭自动应答改成手动)
public class RecvWork {
private static final String QUEUE_NAME="test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取channel
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//保证只消费 一个消息
channel.basicQos(1);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("--->"+new String(body,"utf-8"));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//消费完消息后,告诉生产者。让生产者发送下一条消息
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//关闭自动应答,开启手动应答
boolean autoack=false;
channel.basicConsume(QUEUE_NAME,autoack,consumer);
}
}
3、消费者2
public class RecvWork2 {
private static final String QUEUE_NAME="test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取channel
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//保证只消费 一个消息
channel.basicQos(1);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("--->"+new String(body,"utf-8"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//消费完消息后,告诉生产者。让生产者发送下一条消息
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//关闭自动应答,开启手动应答
boolean autoack=false;
channel.basicConsume(QUEUE_NAME,autoack,consumer);
}
}
消息应答和消息持久化
消息应答:
boolean autoack=false;
channel.basicConsume(QUEUE_NAME,autoack,consumer);
boolean autoack=true;(自动确认)意思就是rabbitmq把消息发送给消费者后就直接将消息从内存中删除。并不会管你消费者是否真正的消费了这个消息。这样就会导致,在消费者宕机的那一刻,从rabbitmq发过来的消息并没有处理。造成消息丢失。
boolean autoack=false;(默认值为false)(手动模式)如果一个消费者宕机了,那么rabbitmq就会把消息分发给其他消费者。rabbitmq支持消息应答。消费者在处理完这个消息后,会给rabbitmq一个应答,告诉mq这个消息我处理了,你可以从内从中删除了。然后rabbitmq就会从内存中删除这个消息。
消息持久化:
在声明队列的时候,第二个参数为false表示不持久化,改成true表示为持久化
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
但是如果定义了QUEUE_NAME,原来也为false。而且还被我们执行过,这样这个queue就会被定义成未持久化的queue。那么直接改成true是会报错的。因为rabbitmq不允许已经被定义好了的queue再进行修改。解决办法是可以重新写一个,如果不想重新写,也可以在rabbitmq的界面上删除这个queue,改成true后再执行。
订阅模式(Fanout)
在声明交换机的时候,给定Fanout,表示分发。每个消费者都有
解读:
1.一个生产者,对应多个消费者
2.每一个消费者都有自己的队列
3.生产者没有直接把消息发送到队列中,而是发送到交换机(转发器)exchange上
4.每个队列都要绑定到交换机上
5.生产者发送消息经过交换机,到达队列就能实现一个消息被多个消费者消费。
生产者(在声明交换机的时候,第二个参数给fanout)
public class SendPubic {
//定义交换机名字
private static final String EXCHANGE_NAME="test_exchange_fanout";
/**
* 此时生产者只是把消息发送到交换机上,交换机并没有存储消息的能力。
* 如果没有消费者声明队列接收消息,那么消息就会丢失。
* 首先执行一下,先将当前用户存在自己声明的交换机。否则先开消费者会报错。报找不到交换机
* @param args
* @throws IOException
* @throws TimeoutException
*/
public static void main(String[] args) throws IOException, TimeoutException {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//创建channel
Channel channel = connection.createChannel();
//声明交换机 fanout表示分发 ***************
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
channel.basicQos(1);
for (int i = 0; i < 50; i++) {
String msg="hello--->"+i;
//发送消息
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
}
channel.close();
connection.close();
}
}
消费者1
public class RecvPublic2 {
//声明队列
private static final String QUEUE_NAME="test_queue_fanout_aqs";
//声明交换机
private static final String EXCHANGE_NAME="test_exchange_fanout";
/**
* 第一步:获取链接
* 第二步:获取channel
* 第三步:声明队列
* 第四步:将队列绑定到交换机上
* 第五步:明确分发数量
* 第六步:获取消息
* 第七步:执行basicconsumer,开启应答方式
* @param args
* @throws IOException
* @throws TimeoutException
*/
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//将队列绑定到交换机上
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//保证一次只分发一个
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1----》"+new String(body,"utf-8"));
//告诉生产者,发送下一个消息
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//关闭自动应答,开启手动应答
boolean auack=false;
channel.basicConsume(QUEUE_NAME,auack,consumer);
}
}
消费者2(与消费者1不同的只有一点,队列名称不同。)
public class RecvPublic {
//声明队列,与消费者1唯一不同的是队列名称
private static final String QUEUE_NAME="test_queue_fanout_email";
//声明交换机
private static final String EXCHANGE_NAME="test_exchange_fanout";
/**
* 第一步:获取链接
* 第二步:获取channel
* 第三步:声明队列
* 第四步:将队列绑定到交换机上
* 第五步:明确分发数量
* 第六步:获取消息
* 第七步:执行basicconsumer,开启应答方式
* @param args
* @throws IOException
* @throws TimeoutException
*/
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//将队列绑定到交换机上
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//保证一次只分发一个
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1----》"+new String(body,"utf-8"));
//告诉生产者,发送下一个消息
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//关闭自动应答,开启手动应答
boolean auack=false;
channel.basicConsume(QUEUE_NAME,auack,consumer);
}
}
打开rabbitmq控制台,找到当前声明的交换机点进去,就会看到绑定的两个队列
路由模式(direct 根据rockingkey,匹配消息。并发送给相应的消费者)
下图是在声明交换机的时候,给定direct值。表示根据路由key,匹配消费者
生产者(在声明交换机时,第二个参数给direct。设置路由key,根据这个key才会分发指定的队列)
public class SendRock {
//声明交换机名称
private static final String EXCHANGE_NAME="test_rocket_direct";
public static void main(String[] args) throws IOException, TimeoutException {
//获取链接
Connection connection = ConnectionUtils.getConnection();
//获取channel
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
//生产者只发送一个消息,必须等待消费者应答后才发送下一个
// channel.basicQos(1);
//定义消息。
String msg="hello msg11111111";
//交换机是根据routingkey来分发给指定的队列.
String routingkey="info";
channel.basicPublish(EXCHANGE_NAME,routingkey,null,msg.getBytes());
channel.close();
connection.close();
}
}
消费者1(只接收error路由key的消息)
public class RecvRock1 {
//声明交换机名称
private static final String EXCHANGE_NAME="test_rocket_direct";
//声明队列名称
private static final String QUEUE_NAME="test_rocket_queue1_direct";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//******将队列绑定到交换机上,并声明路由key*************
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
//声明只接收一个消息
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("队列1的消息是----》"+new String(body,"utf-8"));
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//关闭自动应答,开启手动应答
boolean auack=false;
channel.basicConsume(QUEUE_NAME,auack,consumer);
}
}
消费者2(接收路由key中error、info、warning的消息)
public class RecvRock2 {
//声明队列名称
private static final String QUEUE_NAME="test_rocket_queue2_direct";
//声明交换机名称
private static final String EXCHANGE_NAME="test_rocket_direct";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//***************将队列绑定到交换机上**************
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"info");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"warning");
//声明只接收一个消息
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("队列2---》"+new String(body,"utf-8"));
//发送给生产者应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
Topic主题模式(通配符模式)
#:匹配一个或者多个
*:只匹配一个
生产者(只需在定义交换机的时候,设置成topic模式,并设置好路由key)
public class SendTopic {
private static final String EXCHANGE_NAME="test_topic";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//设置交换机,并指定topic主题模式**********************
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
//每次只发送一个消息
channel.basicQos(1);
String msg="你方法是毒奶粉是毒奶粉了";
String rockkey="goods.delete";
channel.basicPublish(EXCHANGE_NAME,rockkey,null,msg.getBytes());
channel.close();
connection.close();
}
}
消费者1(只需在将队列绑定到交换机上的同时,指定只接收哪种消息)
public class RecvTopic {
private static final String QUEUE_NAME="test_queue_topic1";
private static final String EXCHANGE_NAME="test_topic";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//定义队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//将队列绑定到交换机上,并声明只接收add的消息********************
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.add");
//只接收一个消息
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("队列1的消息是----》"+new String(body,"utf-8"));
//告诉生产者,回应应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//关闭自动应答
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
消费者2(消费者2,定义路由key的时候,用goods.#,表示接收任何消息)
public class RecvTopic2 {
private static final String QUEUE_NAME="test_queue_topic2";
private static final String EXCHANGE_NAME="test_topic";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到交换机上,并设置路由key************
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");
//只接收一个消息
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("队列1的消息是----》"+new String(body,"utf-8"));
//告诉生产者,回应应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//关闭自动应答
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
对消息进行属性赋值。(增加过期时间,编码格式等)
主要针对在生产者(AMQP.BasicProperties properties=new AMQP.BasicProperties())
public class SendTopic {
private static final String EXCHANGE_NAME="muke_topic_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
String msg="这是topic模式发送的消息";
String rocketkey="muke_send";
//给消息设置属性****************************
AMQP.BasicProperties properties=new AMQP.BasicProperties().builder()
.deliveryMode(2) //持久化,设置2后,就算重启服务,没被消费的消息依旧存在
.expiration("10000") //消息过期时间,超过10秒这个消息没被消费,那么就会自动删除
.contentEncoding("UTF-8") //设置消息的字符集格式
.build();
channel.basicPublish("",rocketkey,properties,msg.getBytes());
channel.close();
connection.close();
}
}