文章目录
五种测试消息模型概况
如果没有rabbitmq服务器,需要调试安装rabbitmq的同学,可以参考
rabbitmq安装
总体概括
五种消息模型都是同过消息的生产者将消息发送给交换机(交换机不负责消息的保存),交换机根据特定的路由规则配合消息队列与交换机的绑定规则将消息投递到特定的消息队列(消息队列负责消息的保存),消息的消费者监听特定消息队列来消费处理消息。只不过前两种消息队列使用的是默认的交换机。
消息模型准备pom和工具类
需要依赖的pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xym</groupId>
<artifactId>xym-rabbitmq</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
</project>
获取连接的工具类
public class ConnectionUtil {
public static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory cf = new ConnectionFactory();
cf.setUsername("rabbitmq");//设置用户名
cf.setPassword("rabbitmq");//设置密码
cf.setHost("192.168.2.66");//设置连接url
cf.setVirtualHost("/rabbit");//设置虚拟机名称
return cf.newConnection();
}
}
简单消息模型
消息发送者
public class Send {
private final static String QUEUE_NAME = "xym_test_simple";
public static void main(String[] argv) throws Exception {
//获取连接
Connection connection = ConnectionUtil.getConnection();
//创建通道 消息的投递获取依赖通道
Channel channel = connection.createChannel();
/*// 声明(创建)队列
//简单队列的声明方式
//第一个参数为队列名称
//第二个参数为是否是持久化队列 非持久话的队列当服务重启后,消息会丢失
//第三个参数为队列是否排他,即如果为true,非本链接创建的channel不可以访问该队列,并且此连接关闭以后队列清除
//第四个参数为队列是否为自动删除,最后一个connection断开的时候自动清除队列
//第五个参数:其他参数
//Map<String,Object> args= new HashMap<String,Object>();
* args.add("x-message-ttl",6000);设置了这个参数,发布的消息在queue时间超过了你设定的时间就会被删除掉。单位是毫秒
* args.add("x-expires",6000);当前的queue在指定的时间内,没有consumer、basic.get也就是未被访问,就会被删除。
* args.add("x-max-length",6000);设置队列可以放的最大消息数。
* args.add("x-max-length-bytes",6000);设置队列可以放的最大字节数。
* args.add("x-dead-letter-exchange","dead-exchange");设置死信交换器的名称。
* args.add("x-dead-letter-routing-key","dead-exchange-routing");设置死信交换器的路由键
*/
/*Map<String,Object> argss= new HashMap<String,Object>();
argss.put("x-message-ttl",600);*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//channel.queueDeclare(QUEUE_NAME, true, false, false, null);
String message = "Hello World!";
/** @param exchange the exchange to publish the message to
*要发布到的交换机的名称,如果为空则进入默认交换机(amqp default)
* @param routingKey the routing key
* 路由关键字
* @param props other properties for the message - routing headers etc
* 发送消息头的一些其他属性
* @param body the message body
*发送消息体的内容
* */
//为了保证消息是持久化的,需要交换机、队列和发送方式都是持久化的。
//channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
}
消息接收者
public class Recv {
private final static String QUEUE_NAME = "xym_test_simple";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
//int i = 1/0;
//手动恢复确认通知
//channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
//关闭消息的自动应答,改为手动应答
//channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
}
}
消息的确认机制
当下面的第二个参数为true时,表示消息是自动确认的只要消息被消费者处理,不管处理结果是否抛出异常,消息都会从消息队列中删除。此种方式容易在消息出现异常时,消息丢失。
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
解决消息丢失的方法:采用手动消息确认机制
- 将处理方式消息的应答机制设为false
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
- 在消息的处理体内完成消息处理成功应答
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
这样在应答前消息出现异常时消息不会丢失。
工作队列模型
将同一个消息队列的消息,分发给不同的消费处理(消息只有一份)
消息发送者
消息生成者生产100条消息供消费者消费
public class Send {
private final static String QUEUE_NAME = "xym_test_work";
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for(int i =0 ;i<100;i++){
String message = "Hello World!"+i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
channel.close();
connection.close();
}
}
消息消费者
消费者一模拟性能良好服务器.
public class Recv {
private final static String QUEUE_NAME = "xym_test_work";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//设置预拉取数量,防止消费者均分队列消息,保证性能强的消费者处理更多的消息。
//channel.basicQos(1);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
消费者2通过sleep模拟性能较差服务
public class Recv2 {
private final static String QUEUE_NAME = "xym_test_work";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//设置预拉取数量,防止消费者均分队列消息,保证性能强的消费者处理更多的消息。
//channel.basicQos(1);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
}
}
最终运行发现消息是被两个队列均分
设置预拉取数量,保证能者多劳。
在两个消费者中都设置预拉取数量为1,设置后性能高的服务器把大部分消息给处理
//设置预拉取数量,防止消费者均分队列消息,保证性能强的消费者处理更多的消息。
channel.basicQos(1);
广播模型
广播模式可以完成消息的复制把同样消息复制多份发送到绑定的队列上。
消息发送者
public class Send {
private final static String EXCHANGE_NAME = "xym_exchange_faout";
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//定义交换机
//第一个参数为交换机名称
//第二个参数为交换机类型
//第三个参数为是否持久化
channel.exchangeDeclare(EXCHANGE_NAME,"fanout",true);
channel.basicPublish(EXCHANGE_NAME, "", null, "test_fanout".getBytes(StandardCharsets.UTF_8));
channel.close();
connection.close();
}
}
消息接收者
public class Recv {
private final static String QUEUE_NAME = "xym_queue_fanout1";
private final static String EXCHANGE_NAME = "xym_exchange_faout";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
public class Recv2 {
private final static String QUEUE_NAME = "xym_queue_fanout2";
private final static String EXCHANGE_NAME = "xym_exchange_faout";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
消息的持久化
消费发送后,重启rabbitmq 服务,结果消息丢失。
解决办法:
- 保证定义的交换机是持久化的
channel.exchangeDeclare(EXCHANGE_NAME,"fanout",true);
- 保证消息的发送我持久化的
channel.basicPublish(EXCHANGE_NAME, "", MessageProperties.PERSISTENT_TEXT_PLAIN, "test_fanout".getBytes(StandardCharsets.UTF_8));
- 保证队列是持久化的
//保证第二个参数为true
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
重启rabbitmq 服务,服务中的消息不会丢失。
路由消息模式
保证了消息可以选择性的发送到队列上.
消息生产者
public class Send {
private final static String EXCHANGE_NAME = "xym_exchange_direct";
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME,"direct",true);
channel.basicPublish(EXCHANGE_NAME, "key1", null, "test_direct_1".getBytes(StandardCharsets.UTF_8));
channel.basicPublish(EXCHANGE_NAME, "key2", null, "test_direct_2".getBytes(StandardCharsets.UTF_8));
channel.basicPublish(EXCHANGE_NAME, "key2", null, "test_direct_2".getBytes(StandardCharsets.UTF_8));
channel.close();
connection.close();
}
}
消息消费者
public class Recv {
private final static String QUEUE_NAME = "xym_queue_direct1";
private final static String EXCHANGE_NAME = "xym_exchange_direct";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"key1");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
public class Recv2 {
private final static String QUEUE_NAME = "xym_queue_direct2";
private final static String EXCHANGE_NAME = "xym_exchange_direct";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"key2");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
topic消息模式
保证了消息可以选择性的发送到队列上,消息的绑定支持模板匹配
消息的生产
public class Send {
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME,"topic",true);
channel.basicPublish(EXCHANGE_NAME, "key1.#", null, "key1.#".getBytes(StandardCharsets.UTF_8));
channel.basicPublish(EXCHANGE_NAME, "key2.banana", null, "key2.banana".getBytes(StandardCharsets.UTF_8));
channel.basicPublish(EXCHANGE_NAME, "key2.rule", null, "key2.rule".getBytes(StandardCharsets.UTF_8));
channel.close();
connection.close();
}
}
消息消费者
public class Recv {
private final static String QUEUE_NAME = "xym_queue_topic1";
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"key2.#");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"key1.*");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
public class Recv2 {
private final static String QUEUE_NAME = "xym_queue_topic2";
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"key2.*");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
springboot 中的应用
spring:
rabbitmq:
username: rabbitmq
password: rabbitmq
virtual-host: /rabbit
host: 192.168.2.66
忽略启动类,消费者
@Component
public class Listener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(
value = "spring.amqp.test",
durable = "true",
autoDelete = "false"
),
exchange = @Exchange(
value = "spring.amqp.exchange",
type = ExchangeTypes.TOPIC,
ignoreDeclarationExceptions = "true"
),
key = {"#.#"}
))
public void listen(String msg){
System.out.println(msg);
}
}
通过测试类模拟生产者
@RunWith(SpringRunner.class)
@SpringBootTest
public class ListenerTest {
@Autowired
private AmqpTemplate amqpTemplate;
@Test
public void test() throws InterruptedException {
String msg = "spring boot amqp send";
amqpTemplate.convertAndSend("spring.amqp.exchange","dd.d",msg);
Thread.sleep(5000);
}
}