1.rabbitMq的队列,工作,订阅,路由,主题模式测试demo 地址:
链接:https://pan.baidu.com/s/1xVkK3i1lSF0s1YW2IFtseA
提取码:u2p5
1.消息中间件的核心设计思想:
采用异步通讯、自动补偿与重试、分布式事务、解决流量削峰问题、系统的解耦
2.消息中间件常用名词:
Broker 消息转发端,消息中间件Server端;
Message 发送的消息内容
roducer 生产者,向Server端投递消息;
Consumer 消费者,向Server端获取消息
MessageId 消息全局id 解决消息幂等性问题
3.主流的MQ对比分析
ActiveMQ: 基本淘汰(老项目使用) 够轻巧(源代码比RocketMQ多),支持持久化到数据库,
对队列数较多的情况支持不好。
RabbitMQ: 结合erlang语言本身的并发优势,支持很多的协议:AMQP,XMPP, SMTP, STOMP,
也正是如此,使的它变的非常重量级,更适合于企业级的开发。
RocketMQ: 阿里系下开源的一款分布式、队列模型的消息中间件,原名Metaq,3.0版本名称改为RocketMQ,
是阿里参照kafka设计思想使用java实现的一套mq,同时将阿里系内部多款mq产品
(Notify、metaq)进行整合,只维护核心功能,去除了所有其他运行时依赖,
保证核心功能最简化,在此基础上配合阿里上述其他开源产品实现不同场景下mq的架构,
目前主要多用于订单交易系统。
Kafka: Apache下的一个子项目,使用scala实现的一个高性能分布式Publish/Subscribe消息队列系统,
具有以下特性:高吞吐:在一台普通的服务器上既可以达到10W/s的吞吐速率;
高堆积:支持topic下消费者较长时间离线,消息堆积量大;
4.RabitMQ环境的基本安装
1.下载并安装erlang,下载地址:http://www.erlang.org/download
2.配置erlang环境变量信息
新增环境变量ERLANG_HOME=erlang的安装地址
将%ERLANG_HOME%\bin加入到path中
3.下载并安装RabbitMQ,下载地址:http://www.rabbitmq.com/download.html
注意: RabbitMQ 它依赖于Erlang,需要先安装Erlang。
5.Virtual Hosts:
像mysql有数据库的概念并且可以指定用户对库和表等操作的权限。那RabbitMQ呢?
RabbitMQ也有类似的权限管理。在RabbitMQ中可以虚拟消息服务器VirtualHost,每
个VirtualHost相当月一个相对独立的RabbitMQ服务器,每个VirtualHost之间是相互
隔离的。exchange、queue、message不能互通。
6.公平队列实现原理
Mq服务器端每次只会给消费者发送一条消息,如果消费者没有返回ack,就不会继续发送消
息。
7.如何保证消息不丢失?
1.生产者 确保我们的生产者将消息投递到MQ成功; 消息确认机制如果开启了消息持久化的机制,必须消息持久化成功才会应 答给生产者
// 开启生产确认消息投递机制 channel.confirmSelect();
channel.waitForConfirms()==true 投递成功
2.消费者 确保我们的消费者消费消息成功 采用手动ack确认
3.MQ服务器端 需要将数据持久化到我们的硬盘
其他情况下: 硬盘坏了、持久化的过程断电了?
如何解决 最好通过表记录每次生产者投递消息,如果长期没有被消费,手动的补偿消费。
8.如果在生产者投递消息失败的情况,在那些场景?
1.MQ挂了 解决做心跳检测(heartbeat),自动重启,多次重启失败发邮件运维
2.Mq拒绝接受消息 (队列满了) 就采用手动补偿或者日志表记录下即可
9.Rabbitmq如何开启持久化的功能?
1.默认的情况下mq服务器端创建队列和交换机都是持久化的
2.如果是代码创建的话,将该值(durablet)设置为true
10.Rabbitmq发布订阅的实现原理:
核心思想:
一个生产者投递消息,可以被多个不同的队列实现消费;
实现原理:
多个不同的队列绑定相同交换机,生产者只需要将消息投递到交换机之后,
在由交换机将消息转发到所有绑定的队列实现消费。
11.Direct exchange(直连(路由)交换机)
Fanout exchange(扇型(广播)交换机)
Topic exchange(主题交换机)
交换机核心作用:分发路由消息、中专
队列:容器存放多个不同消息 遵循先进先出的原则
消息:传递的参数
路由键:交换机根据这样的路由键的值,发送不同的队列中 匹配过程
Fanout(广播) 扇型交换机主要的特征:只要队列绑定同一个交换机,生产者将消息投递到交换机中,交换机会将消息发送给所有绑定的队列进行存放消息。
Direct exchange(路由直连交换机):根据生产者投递不同的路由键,在交换机发送到实现匹配路由键的队列
Topic 主题交换机:根据路由键的key实现模糊匹配到队列存放。
public class ConnectionUtil {
/**
* 1.定义rabbmq地址 ip:端口
* 2.定义虚拟主机
* 3.定义用户名和密码
*/
public static Connection getConnection() throws Exception {
//定义连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置服务地址
factory.setHost("127.0.0.1");
//端口
factory.setPort(5672);
//设置账号信息,用户名、密码、vhost
factory.setVirtualHost("/zsq");
factory.setUsername("zsq");
factory.setPassword("zsq");
// 通过工程获取连接
Connection connection = factory.newConnection();
return connection;
}
}
生产者
public class WorkProducer {
//向队列中发送100条消息。 工作模式:多个人一起消费一个队列消息.内部轮询机制
//定义队列名称
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列 参数依次 队列名称 是否持久化 是否排它(仅此连接) 是否自动删除 其他构造参数
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
for (int i = 0; i < 10; i++) {
// 消息内容
String message = "我是工作模式" + i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(QUEUE_NAME+" == Sent '" + message + "'");
}
channel.close();
connection.close();
}
}
消费者
public class WorkConsumerPoll {
//工作模式:多个人一起消费一个队列消息.内部轮询机制
//定义队列名称
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列 参数依次 队列名称 是否持久化 是否排它(仅此连接) 是否自动删除 其他构造参数
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//***方式一***
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,false表示手动返回完成状态,true表示自动 false表示手动返回ack
channel.basicConsume(QUEUE_NAME, true, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(QUEUE_NAME+" == Received '" + message + "'");
}
//***方式二***
// DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
// @Override
// public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// String msg = new String(body, "UTF-8");
// System.out.println("短信消费者获取消息:" + msg);
// }
// };
// //创建我们的监听的消息 auto Ack 默认自动签收 必须手动ack
// channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
}
}
生产者 ,开启生产投递确认机制(判断投递成功,才会继续投递下一条)
public class WorkProducerMore {
//向队列中发送100条消息。 工作模式:多个人一起消费一个队列消息.能者多劳
//定义队列名称
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列 参数依次 队列名称 是否持久化 是否排它(仅此连接) 是否自动删除 其他构造参数
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// **开启生产确认消息投递机制**
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
String message = "我是工作模式" + i;
if(channel.waitForConfirms()) { //***生产确认消息投递 true投递成功***
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(QUEUE_NAME + " == Sent '" + message + "'");
}
}
channel.close();
connection.close();
}
}
消费者(能者多劳)
public class WorkConsumerMore {
//工作模式:多个人一起消费一个队列消息.能者多劳
//定义队列名称
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列 参数依次 队列名称 是否持久化 是否排它(仅此连接) 是否自动删除 其他构造参数
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//定义消费数 每次只能消费一条记录.当消息执行后需要返回ack确认消息 才能执行下一条
channel.basicQos(1); //******
//***方式一***
// 定义队列的消费者
// QueueingConsumer consumer = new QueueingConsumer(channel);
// // 监听队列,false表示手动返回完成状态,true表示自动 false表示手动返回ack
// channel.basicConsume(QUEUE_NAME, false, consumer); //*****
// // 获取消息
// while (true) {
// QueueingConsumer.Delivery delivery = consumer.nextDelivery();
// String message = new String(delivery.getBody());
// System.out.println(QUEUE_NAME+" == Received '" + message + "'");
// // 表示使用手动确认模式 从队列中删除该消息 deliveryTag 队列下标位置
// channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); //******
// }
//***方式二***
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("短信消费者获取消息:" + msg);
// 手动发送消息告诉给mq服务器端 从队列删除该消息
channel.basicAck(envelope.getDeliveryTag(), false); //*****
}
};
//创建我们的监听的消息 auto Ack 默认自动签收 必须手动ack
channel.basicConsume(QUEUE_NAME, false, defaultConsumer); //*****
}
}
public class FanoutProducer {
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
/*
声明exchange
fanout广播模式 把接收到的消息推送给所有它知道的队列
direct 路由模式
topic 主题模式
*/
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 消息内容
String message = "订阅模式!";
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println(EXCHANGE_NAME+" ==== Sent '" + message + "'");
channel.close();
connection.close();
}
}
public class FanoutConsumer1 {
//发布订阅模式
private final static String QUEUE_NAME = "test_queue_work1"; //定义队列名称
private final static String EXCHANGE_NAME = "test_exchange_fanout";//定义交换机名称
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//定义交换机模式 // 声明exchange fanout广播模式 redirect 路由模式 topic 主题模式
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 声明队列 参数依次 队列名称 是否持久化 是否排它(仅此连接) 是否自动删除 其他构造参数
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,false表示手动返回完成状态,true表示自动 false表示手动返回ack
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("发布订阅模式-消费者1 " + EXCHANGE_NAME+" "+QUEUE_NAME+" ==== Received 1 '" + message + "'");
//创建我们的监听的消息 auto Ack 默认自动签收 必须手动ack
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
生产者
public class DirectProducer {
//定义交换机 路由模式
private final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
/*
声明exchange
fanout广播模式 把接收到的消息推送给所有它知道的队列
direct 路由模式
topic 主题模式
*/
channel.exchangeDeclare(EXCHANGE_NAME, "direct"); //参数分别是(交换机名 , 交换机类型 , 是否持久化)
//开启生产确认消息投递机制
channel.confirmSelect();
//消息内容
String message = "路由模式!";
// routingKey="insert" 匹配路由为insert的
if(channel.waitForConfirms()) { //生产确认消息投递 true投递成功
channel.basicPublish(EXCHANGE_NAME, "insert", null, message.getBytes());
System.out.println(EXCHANGE_NAME + " ==== Sent '" + message + "'");
}
channel.close();
connection.close();
}
}
消费者
public class DirectConsumer {
private final static String QUEUE_NAME = "test_exchange_direct_2"; //定义队列名称
private final static String EXCHANGE_NAME = "test_exchange_direct";//定义交换机名称
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//定义交换机模式 // 声明exchange fanout广播模式 redirect 路由模式 topic 主题模式
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机 路由为update 和 insert
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "update");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "insert"); //******
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("路由模式-消费者2 " + EXCHANGE_NAME+" "+QUEUE_NAME+" ==== Received 2 '" + message + "'");
//创建我们的监听的消息 auto Ack 默认自动签收 必须手动ack
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
生产者
public class TopicProducer {
//主题模式(通配符模式) 定义交换机
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
/*
声明exchange
fanout广播模式 把接收到的消息推送给所有它知道的队列
direct 路由模式
topic 主题模式
*/
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
//消息内容
String message = "主题模式!";
// routingKey="routekey.test.app"
channel.basicPublish(EXCHANGE_NAME, "routekey.test.app", null, message.getBytes());//*****
System.out.println(EXCHANGE_NAME+" ==== Sent '" + message + "'");
channel.close();
connection.close();
}
}
消费者
public class TopicConsumer {
// 主题模式(通配符模式) *号只匹配一个 #号匹配一个词或多个
private final static String QUEUE_NAME = "test_queue_topic_work_3"; //定义队列名称
private final static String EXCHANGE_NAME = "test_exchange_topic";//定义交换机名称
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//定义交换机模式 // 声明exchange fanout广播模式 redirect 路由模式 topic 主题模式
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机 routingKey 通配"routekey.test.app"
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "#.test.*");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("主题模式-消费者3 " + EXCHANGE_NAME+" "+QUEUE_NAME+" ==== Received 3 '" + message + "'");
//创建我们的监听的消息 auto Ack 默认自动签收 必须手动ack
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}