目录
一、队列的使用场景
java中的队列
定时任务
服务解耦
流量削峰
异步调用
二、交换机(Exchange)
Direct
Topic
Fanout
三、案例应用
基础简单的应用
工作模式 (可以均衡的分发消息)
RPC异步调用模式(可以等待获取返回值)
交换机Direct模式(直接绑定)
交换机Fanout(广播模式)
交换机Topic(通配符模式)
额外篇(死信队列的使用,也是定时器的使用方式)
四、参考
在java中也会常听说一些队列,比如我们在多线程中使用的阻塞队列等等,都是属于队列的范畴,不过这种只是对队列的简单使用,属于队列特点先进先出特征的使用。
使用rabbtimq,会使用到一些死信队列等等,使用这些队列就可以完成定时任务。
在微服务使用越来越多的情况下,使用队列作为中间件进行消息传递,服务解耦也是比较多的情况。生产者只需生产数据,消费者服务可以自己订阅消息。
这种应用类似于java中的多线程的阻塞队列的使用。当访问请求流量高峰每天会有瞬间比较高的情况,我们可以使用队列来排队任务,然后读取队列中的任务进行处理。
有时候有些任务不需要立马就执行的,我们可以扔到队列中等待执行。例如业务日志,有些不需要直接写进数据库,可以先放到队列中,等资源充足的时候再写到数据库中。
以上这些只是对队列使用的一些简单应用场景介绍,还有其他变换使用方式也是可以使用的
接受生产者发送的消息,并根据Binding规则将消息路由给服务器中的队列。ExchangeType决定了Exchange路由消息的行为。在RabbitMQ中,ExchangeType常用的有direct、Fanout和Topic三种。
首先这里声明一点,消息的最终去向就是队列,交换机就是根据规则进行消息的分发
这个表示的是交换机通过绑定键直接对应到队列进行信息的交换。
该模式表示的是也是交换机通过绑定键直接对应到队列进行信息的交换。但是这个绑定键可以通过通配符进行配置,通配符如下:
该模式的交换机会对其下面的队列进行广播发送。
关于如何安装rabbitmq,这里就不作过多的说明,可以采用直接安装的方式,也可以采用docker安装方式。
测试使用的jar包如下:
com.rabbitmq
amqp-client
5.14.2
package org.loulan.application.amqp;
import com.rabbitmq.client.*;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*********************************************************
** rabbitmq的基础使用
**
** Date: Created in 2022/5/6 11:26
** @author loulan
** @version 0.0.0
*********************************************************/
public class Base {
private Channel ch = null;
@Before
public void ready() throws IOException, TimeoutException {
//创建连接工厂,并设置连接信息
ConnectionFactory f = new ConnectionFactory();
f.setHost("47.95.114.22");
f.setPort(5672);//可选,5672是默认端口
f.setUsername("admin");
f.setPassword("admin");
/*
* 与rabbitmq服务器建立连接,
* rabbitmq服务器端使用的是nio,会复用tcp连接,
* 并开辟多个信道与客户端通信
* 以减轻服务器端建立连接的开销
*/
Connection c = f.newConnection();
//建立信道
ch = c.createChannel();
/*
* 声明队列,会在rabbitmq中创建一个队列
* 如果已经创建过该队列,就不能再使用其他参数来创建
*
* 参数含义:
* -queue: 队列名称
* -durable: 队列持久化,true表示RabbitMQ重启后队列仍存在
* -exclusive: 排他,true表示限制仅当前连接可用
* -autoDelete: 当最后一个消费者断开后,是否删除队列
* -arguments: 其他参数
*/
ch.queueDeclare("test-base", false, false, false, null);
}
@Test
public void product() throws IOException, TimeoutException {
/*
* 发布消息
* 这里把消息向默认交换机发送.
* 默认交换机隐含与所有队列绑定,routing key即为队列名称
*
* 参数含义:
* -exchange: 交换机名称,空串表示默认交换机"(AMQP default)",不能用 null
* -routingKey: 对于默认交换机,路由键就是目标队列名称
* -props: 其他参数,例如头信息
* -body: 消息内容byte[]数组
*/
ch.basicPublish("", "test-base", null, "Hello world!".getBytes());
System.out.println("消息已发送");
//c.close();
}
@Test
public void consumer() throws IOException, TimeoutException, InterruptedException {
System.out.println("等待接收数据");
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("收到: " + msg);
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
ch.basicConsume("test-base", true, callback, cancel);
Thread.sleep(5000);
}
}
package org.loulan.application.amqp;
import com.rabbitmq.client.*;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
/*********************************************************
** 工作模式
**
** Date: Created in 2022/5/6 12:33
** @author loulan
** @version 0.0.0
*********************************************************/
public class Work {
private final static String queue = "test-work";
private Channel ch = null;
@Before
public void ready() throws IOException, TimeoutException {
//创建连接工厂,并设置连接信息
ConnectionFactory f = new ConnectionFactory();
f.setHost("47.95.114.22");
f.setPort(5672);//可选,5672是默认端口
f.setUsername("admin");
f.setPassword("admin");
/*
* 与rabbitmq服务器建立连接,
* rabbitmq服务器端使用的是nio,会复用tcp连接,
* 并开辟多个信道与客户端通信
* 以减轻服务器端建立连接的开销
*/
Connection c = f.newConnection();
//建立信道
ch = c.createChannel();
/*
* 声明队列,会在rabbitmq中创建一个队列
* 如果已经创建过该队列,就不能再使用其他参数来创建
*
* 参数含义:
* -queue: 队列名称
* -durable: 队列持久化,true表示RabbitMQ重启后队列仍存在
* -exclusive: 排他,true表示限制仅当前连接可用
* -autoDelete: 当最后一个消费者断开后,是否删除队列
* -arguments: 其他参数
*/
ch.queueDeclare(queue, false, false, false, null);
}
@Test
public void product() throws IOException, InterruptedException {
String str = "YXQ";
int index = 1;
do {
//控制台输入的消息发送到rabbitmq
System.out.print("输入消息: ");
String msg = str + "-" + index++;
//如果输入的是"exit"则结束生产者进程
if (index==100) {
break;
}
//参数:exchage,routingKey,props,body
ch.basicPublish("", queue, null, msg.getBytes());
System.out.println("消息已发送: " + msg);
Thread.sleep(1000);
} while (true);
}
@Test
public void consumer_1() throws IOException, InterruptedException {
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("1收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
ch.basicConsume(queue, true, callback, cancel);
Thread.sleep(100000);
}
@Test
public void consumer_2() throws IOException, InterruptedException {
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("2收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
ch.basicConsume(queue, true, callback, cancel);
Thread.sleep(100000);
}
/**
* 手动应答,需要代码手动告诉队列,任务处理完毕
* @param
* @return
* @exception
* @author :loulan
* */
@Test
public void ack_consumer_1() throws IOException, TimeoutException, InterruptedException {
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("1-ack-收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
//发送回执
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
//autoAck设置为false,则需要手动确认发送回执
ch.basicConsume(queue, false, callback, cancel);
Thread.sleep(100000);
}
/**
* 手动应答,需要代码手动告诉队列,任务处理完毕
* @param
* @return
* @exception
* @author :loulan
* */
@Test
public void ack_consumer_2() throws IOException, TimeoutException, InterruptedException {
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("2-ack-收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
//发送回执
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
//autoAck设置为false,则需要手动确认发送回执
ch.basicConsume(queue, false, callback, cancel);
Thread.sleep(100000);
}
}
package org.loulan.application.amqp;
import com.rabbitmq.client.*;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
/*********************************************************
** 工作模式
**
** Date: Created in 2022/5/6 12:33
** @author loulan
** @version 0.0.0
*********************************************************/
public class Rpc {
private final static String queue = "test-rpc";
private Channel ch = null;
@Before
public void ready() throws IOException, TimeoutException {
//创建连接工厂,并设置连接信息
ConnectionFactory f = new ConnectionFactory();
f.setHost("47.95.114.22");
f.setPort(5672);//可选,5672是默认端口
f.setUsername("admin");
f.setPassword("admin");
/*
* 与rabbitmq服务器建立连接,
* rabbitmq服务器端使用的是nio,会复用tcp连接,
* 并开辟多个信道与客户端通信
* 以减轻服务器端建立连接的开销
*/
Connection c = f.newConnection();
//建立信道
ch = c.createChannel();
/*
* 声明队列,会在rabbitmq中创建一个队列
* 如果已经创建过该队列,就不能再使用其他参数来创建
*
* 参数含义:
* -queue: 队列名称
* -durable: 队列持久化,true表示RabbitMQ重启后队列仍存在
* -exclusive: 排他,true表示限制仅当前连接可用
* -autoDelete: 当最后一个消费者断开后,是否删除队列
* -arguments: 其他参数
*/
ch.queueDeclare(queue, false, false, false, null);
}
@Test
public void product() throws IOException, InterruptedException {
String str = "YXQ";
//自动生成对列名,非持久,独占,自动删除
String replyQueueName = ch.queueDeclare().getQueue();
//生成关联id
String corrId = UUID.randomUUID().toString();
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
//参数:exchage,routingKey,props,body
ch.basicPublish("", queue, basicProperties, str.getBytes());
System.out.println("消息已发送: " + str);
//用来保存结果的阻塞集合,取数据时,没有数据会暂停等待
BlockingQueue response = new ArrayBlockingQueue<>(1);
//接收响应数据的回调对象
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
//如果响应消息的关联id,与请求的关联id相同,我们来处理这个响应数据
if (message.getProperties().getCorrelationId().contentEquals(corrId)) {
//把收到的响应数据,放入阻塞集合
response.offer(new String(message.getBody(), "UTF-8"));
}
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
//开始从队列接收响应数据
ch.basicConsume(replyQueueName, true, deliverCallback, cancelCallback);
System.out.println(response.take());
System.err.println("发送响应结束");
}
/**
* 手动应答,需要代码手动告诉队列,任务处理完毕
*
* @param
* @return
* @throws
* @author :loulan
*/
@Test
public void ack_consumer_1() throws IOException, TimeoutException, InterruptedException {
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("1-ack-收到: " + msg);
try {
System.err.println("正在处理任务");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//设置发回响应的id, 与请求id一致, 这样客户端可以把该响应与它的请求进行对应
AMQP.BasicProperties replyProps = new AMQP.BasicProperties.Builder()
.correlationId(message.getProperties().getCorrelationId())
.build();
//发送回执
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
/*
* 发送响应消息
* 1. 默认交换机
* 2. 由客户端指定的,用来传递响应消息的队列名
* 3. 参数(关联id)
* 4. 发回的响应消息
*/
ch.basicPublish("",message.getProperties().getReplyTo(), replyProps, "666666".getBytes("UTF-8"));
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
//autoAck设置为false,则需要手动确认发送回执
ch.basicConsume(queue, false, callback, cancel);
Thread.sleep(100000);
}
}
package org.loulan.application.amqp;
import com.rabbitmq.client.*;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Exchange_Direct {
private final static String exchange = "test-exchange-direct";
private Channel ch = null;
@Before
public void ready() throws IOException, TimeoutException {
//创建连接工厂,并设置连接信息
ConnectionFactory f = new ConnectionFactory();
f.setHost("47.95.114.22");
f.setPort(5672);//可选,5672是默认端口
f.setUsername("admin");
f.setPassword("admin");
/*
* 与rabbitmq服务器建立连接,
* rabbitmq服务器端使用的是nio,会复用tcp连接,
* 并开辟多个信道与客户端通信
* 以减轻服务器端建立连接的开销
*/
Connection c = f.newConnection();
//建立信道
ch = c.createChannel();
//定义名字为logs的交换机,交换机类型为fanout
//这一步是必须的,因为禁止发布到不存在的交换。
ch.exchangeDeclare(exchange, BuiltinExchangeType.DIRECT);
}
@Test
public void product() throws IOException, InterruptedException {
String str = "YXQ";
int index = 1;
do {
//控制台输入的消息发送到rabbitmq
System.out.print("输入消息: ");
String msg = str + "-" + index++;
//如果输入的是"exit"则结束生产者进程
if (index==100) {
break;
}
//第一个参数,向指定的交换机发送消息
//第二个参数,不指定队列,由消费者向交换机绑定队列
//如果还没有队列绑定到交换器,消息就会丢失,
//但这对我们来说没有问题;即使没有消费者接收,我们也可以安全地丢弃这些信息。
ch.basicPublish(exchange, "test", null, msg.getBytes("UTF-8"));
ch.basicPublish(exchange, "test1", null, msg.getBytes("UTF-8"));
System.out.println("消息已发送: " + msg);
Thread.sleep(1000);
} while (true);
}
/**
* 手动应答,需要代码手动告诉队列,任务处理完毕
* @param
* @return
* @exception
* @author :loulan
* */
@Test
public void ack_consumer_1() throws IOException, TimeoutException, InterruptedException {
//自动生成对列名,
//非持久,独占,自动删除
String queueName = ch.queueDeclare().getQueue();
//把该队列,绑定到 logs 交换机
//对于 direct,指定routeing_key
ch.queueBind(queueName, exchange, "test1");
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("1-ack-收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
//发送回执
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
//autoAck设置为false,则需要手动确认发送回执
ch.basicConsume(queueName, false, callback, cancel);
Thread.sleep(100000);
}
/**
* 手动应答,需要代码手动告诉队列,任务处理完毕
* @param
* @return
* @exception
* @author :loulan
* */
@Test
public void ack_consumer_2() throws IOException, TimeoutException, InterruptedException {
//自动生成对列名,
//非持久,独占,自动删除
String queueName = ch.queueDeclare().getQueue();
//把该队列,绑定到 logs 交换机
//对于 direct,指定routeing_key
ch.queueBind(queueName, exchange, "test");
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("2-ack-收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
//发送回执
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
//autoAck设置为false,则需要手动确认发送回执
ch.basicConsume(queueName, false, callback, cancel);
Thread.sleep(100000);
}
}
package org.loulan.application.amqp;
import com.rabbitmq.client.*;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Exchange_Fanout {
private final static String exchange = "test-exchange";
private Channel ch = null;
@Before
public void ready() throws IOException, TimeoutException {
//创建连接工厂,并设置连接信息
ConnectionFactory f = new ConnectionFactory();
f.setHost("47.95.114.22");
f.setPort(5672);//可选,5672是默认端口
f.setUsername("admin");
f.setPassword("admin");
/*
* 与rabbitmq服务器建立连接,
* rabbitmq服务器端使用的是nio,会复用tcp连接,
* 并开辟多个信道与客户端通信
* 以减轻服务器端建立连接的开销
*/
Connection c = f.newConnection();
//建立信道
ch = c.createChannel();
//定义名字为logs的交换机,交换机类型为fanout
//这一步是必须的,因为禁止发布到不存在的交换。
ch.exchangeDeclare(exchange, "fanout");
}
@Test
public void product() throws IOException, InterruptedException {
String str = "YXQ";
int index = 1;
do {
//控制台输入的消息发送到rabbitmq
System.out.print("输入消息: ");
String msg = str + "-" + index++;
//如果输入的是"exit"则结束生产者进程
if (index==100) {
break;
}
//第一个参数,向指定的交换机发送消息
//第二个参数,不指定队列,由消费者向交换机绑定队列
//如果还没有队列绑定到交换器,消息就会丢失,
//但这对我们来说没有问题;即使没有消费者接收,我们也可以安全地丢弃这些信息。
ch.basicPublish(exchange, "", null, msg.getBytes("UTF-8"));
System.out.println("消息已发送: " + msg);
Thread.sleep(1000);
} while (true);
}
/**
* 手动应答,需要代码手动告诉队列,任务处理完毕
* @param
* @return
* @exception
* @author :loulan
* */
@Test
public void ack_consumer_1() throws IOException, TimeoutException, InterruptedException {
//自动生成对列名,
//非持久,独占,自动删除
String queueName = ch.queueDeclare().getQueue();
//把该队列,绑定到 logs 交换机
//对于 fanout 类型的交换机, routingKey会被忽略,不允许null值
ch.queueBind(queueName, exchange, "");
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("1-ack-收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
//发送回执
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
//autoAck设置为false,则需要手动确认发送回执
ch.basicConsume(queueName, false, callback, cancel);
Thread.sleep(100000);
}
/**
* 手动应答,需要代码手动告诉队列,任务处理完毕
* @param
* @return
* @exception
* @author :loulan
* */
@Test
public void ack_consumer_2() throws IOException, TimeoutException, InterruptedException {
//自动生成对列名,
//非持久,独占,自动删除
String queueName = ch.queueDeclare().getQueue();
//把该队列,绑定到 logs 交换机
//对于 fanout 类型的交换机, routingKey会被忽略,不允许null值
ch.queueBind(queueName, exchange, "");
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("2-ack-收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
//发送回执
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
//autoAck设置为false,则需要手动确认发送回执
ch.basicConsume(queueName, false, callback, cancel);
Thread.sleep(100000);
}
}
package org.loulan.application.amqp;
import com.rabbitmq.client.*;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Exchange_Topic {
private final static String exchange = "test-exchange-topic";
private Channel ch = null;
@Before
public void ready() throws IOException, TimeoutException {
//创建连接工厂,并设置连接信息
ConnectionFactory f = new ConnectionFactory();
f.setHost("47.95.114.22");
f.setPort(5672);//可选,5672是默认端口
f.setUsername("admin");
f.setPassword("admin");
/*
* 与rabbitmq服务器建立连接,
* rabbitmq服务器端使用的是nio,会复用tcp连接,
* 并开辟多个信道与客户端通信
* 以减轻服务器端建立连接的开销
*/
Connection c = f.newConnection();
//建立信道
ch = c.createChannel();
//定义名字为logs的交换机,交换机类型为fanout
//这一步是必须的,因为禁止发布到不存在的交换。
ch.exchangeDeclare(exchange, BuiltinExchangeType.TOPIC);
}
@Test
public void product() throws IOException, InterruptedException {
String str = "YXQ";
int index = 1;
do {
//控制台输入的消息发送到rabbitmq
System.out.print("输入消息: ");
String msg = str + "-" + index++;
//如果输入的是"exit"则结束生产者进程
if (index==100) {
break;
}
//第一个参数,向指定的交换机发送消息
//第二个参数,不指定队列,由消费者向交换机绑定队列
//如果还没有队列绑定到交换器,消息就会丢失,
//但这对我们来说没有问题;即使没有消费者接收,我们也可以安全地丢弃这些信息。
ch.basicPublish(exchange, "test.test.test", null, msg.getBytes("UTF-8"));
System.out.println("消息已发送: " + msg);
Thread.sleep(1000);
} while (true);
}
/**
* 手动应答,需要代码手动告诉队列,任务处理完毕
* @param
* @return
* @exception
* @author :loulan
* */
@Test
public void ack_consumer_1() throws IOException, TimeoutException, InterruptedException {
//自动生成对列名,
//非持久,独占,自动删除
String queueName = ch.queueDeclare().getQueue();
/*
* * 可以通配单个单词。
# 可以通配零个或多个单
* */
//把该队列,绑定到 logs 交换机
//对于 direct,指定routeing_key
ch.queueBind(queueName, exchange, "*.test.*");
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("1-ack-收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
//发送回执
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
//autoAck设置为false,则需要手动确认发送回执
ch.basicConsume(queueName, false, callback, cancel);
Thread.sleep(100000);
}
/**
* 手动应答,需要代码手动告诉队列,任务处理完毕
* @param
* @return
* @exception
* @author :loulan
* */
@Test
public void ack_consumer_2() throws IOException, TimeoutException, InterruptedException {
//自动生成对列名,
//非持久,独占,自动删除
String queueName = ch.queueDeclare().getQueue();
/*
* * 可以通配单个单词。
# 可以通配零个或多个单
* */
//把该队列,绑定到 logs 交换机
//对于 direct,指定routeing_key
ch.queueBind(queueName, exchange, "*.*.test");
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("2-ack-收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
//发送回执
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
//autoAck设置为false,则需要手动确认发送回执
ch.basicConsume(queueName, false, callback, cancel);
Thread.sleep(100000);
}
}
所谓的死信队列其实和正常的队列一摸一样,没有任何的区别。测试死信队列需要使用到2条队列,一个是正常队列(反而不太正常,需要配置死信交换机内容,并且该队列信息不能被消费,否则信息无法到达死信队列),一个死信队列(反而该队列和正常的一摸一样)。
其实就是正常队列的消息无法正常的进行消费,消息超出该队列的要求,那么就会被丢掉垃圾桶(死信交换机),死信交换机再把该信息交给死信队列。
package org.loulan.application.amqp;
import com.rabbitmq.client.*;
import io.gitee.loulan_yxq.owner.core.map.MapTool;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 死信队列和死信交换机其实就是普通的队列和交换机,而且是非常普通的那种
*
* 那么死信对队列和交换机什么时候起作用呢,主要体现在正常队列上,其实这个所谓的正常队列并不正常,
* 首先该正常队列必须要配置和死信交换机之间的连接关系,也就是说要告诉正常队列,你无法消化的信息往哪里丢(其实就是丢到死信队列),
*
* 死信队列可以用作定时任务的作用,就是要在正常队列设置信息的过期时间并且正常队列不能进行消费,这样过期时间一到
* 就会进入到死信队列,然后由死信队列来进行消费。 这样体现出来的效果就是定时任务的效果。
*
*
* @param
* @return
* @exception
* @author :loulan
* */
public class Exchange_Topic_deadletter {
private final static String exchange = "test-exchange-topic";
private final static String deadExchange = "test-exchange-topic-dead";
private Channel ch = null;
@Before
public void ready() throws IOException, TimeoutException {
//创建连接工厂,并设置连接信息
ConnectionFactory f = new ConnectionFactory();
f.setHost("47.95.114.22");
f.setPort(5672);//可选,5672是默认端口
f.setUsername("admin");
f.setPassword("admin");
/*
* 与rabbitmq服务器建立连接,
* rabbitmq服务器端使用的是nio,会复用tcp连接,
* 并开辟多个信道与客户端通信
* 以减轻服务器端建立连接的开销
*/
Connection c = f.newConnection();
//建立信道
ch = c.createChannel();
//定义名字为logs的交换机,交换机类型为fanout
//这一步是必须的,因为禁止发布到不存在的交换。
ch.exchangeDeclare(exchange, BuiltinExchangeType.TOPIC);
ch.exchangeDeclare(deadExchange, BuiltinExchangeType.TOPIC);
}
@Test
public void product() throws IOException, InterruptedException {
String str = "YXQ";
int index = 1;
do {
//控制台输入的消息发送到rabbitmq
System.out.print("输入消息: ");
String msg = str + "-" + index++;
//如果输入的是"exit"则结束生产者进程
if (index==100) {
break;
}
// 设置定时时间为5s
AMQP.BasicProperties properties =
new AMQP.BasicProperties()
.builder().expiration("5000").build();
//第一个参数,向指定的交换机发送消息
//第二个参数,不指定队列,由消费者向交换机绑定队列
//如果还没有队列绑定到交换器,消息就会丢失,
//但这对我们来说没有问题;即使没有消费者接收,我们也可以安全地丢弃这些信息。
ch.basicPublish(exchange, "test.test.test", properties, msg.getBytes("UTF-8"));
System.out.println("消息已发送: " + msg);
Thread.sleep(1000);
} while (true);
}
/**
* 手动应答,需要代码手动告诉队列,任务处理完毕
* @param
* @return
* @exception
* @author :loulan
* */
@Test
public void ack_consumer_dead() throws IOException, TimeoutException, InterruptedException {
//自动生成对列名,
//非持久,独占,自动删除
String queueName = ch.queueDeclare().getQueue();
/*
* * 可以通配单个单词。
# 可以通配零个或多个单
* */
//把该队列,绑定到 logs 交换机
//对于 direct,指定routeing_key
ch.queueBind(queueName, deadExchange, "dead.test");
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("1-ack-收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
//发送回执
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
//autoAck设置为false,则需要手动确认发送回执
ch.basicConsume(queueName, false, callback, cancel);
Thread.sleep(100000);
}
/**
* 手动应答,需要代码手动告诉队列,任务处理完毕
* @param
* @return
* @exception
* @author :loulan
* */
@Test
public void ack_consumer_normal() throws IOException, TimeoutException, InterruptedException {
//声名普通队列
Map var5 = MapTool.map();
//设置普通队列消息过期时间 10s=10000ms 最好由生产者指定
//var5.put("x-message-ttl",10000);
//给正常队列设置死信交换机
var5.put("x-dead-letter-exchange",deadExchange);
//设置死信队列的 routingKey
var5.put("x-dead-letter-routing-key","dead.test");
//自动生成对列名,
//非持久,独占,自动删除
String queueName = ch.queueDeclare("dead-test-normalQueue",false,false,false,var5).getQueue();
/*
* * 可以通配单个单词。
# 可以通配零个或多个单
* */
//把该队列,绑定到 logs 交换机
//对于 direct,指定routeing_key
ch.queueBind(queueName, exchange, "*.*.test");
//收到消息后用来处理消息的回调对象
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String msg = new String(message.getBody(), "UTF-8");
System.out.println("2-ack-收到: "+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理结束");
//发送回执
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
//消费者取消时的回调对象
CancelCallback cancel = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
// 保证一次只给消费者一条消息,不会给太多导致一些消费者空闲,有些很忙
ch.basicQos(1);
//autoAck设置为false,则需要手动确认发送回执
ch.basicConsume(queueName, false, callback, cancel);
Thread.sleep(100000);
}
}
该文章参考自:(34条消息) RabbitMQ_Wanght6的博客-CSDN博客_rabbitmq