本文架构
具体可参考文章:
https://www.cnblogs.com/linjiqin/p/12683076.html
https://blog.csdn.net/u013256816/article/details/60875666/
消息可靠性
生产者端
如何保证生产者消息能投递到rabbitMQ服务端?
1.事务 + 持久化exchange,queue,message
说明:
- 通过事务,来保证生产者将消息投递到服务端
- 性能比较差
/**
* 生产者---开启事务的方式发送消息,确保服务端收到消息
* 事务模式控制发送消息的确认
* 效率最低
*/
@Test
public void producer_transaction() throws IOException, TimeoutException {
//1.通过util对象创建connection对象
Connection connection = ConnUtil.getConnection();
//2.获取channel
Channel channel = connection.createChannel();
//3.声明队列,这里就需要声明两个队列了,
/**
* queue:声明队列的名称
* durable: 是否持久化 ----> 持久化queue
* exclusive: 是否独占连接,如果连接关闭则自动删除,如果将此参数设置为true可用于临时队列的创建
* autoDelete: 是否自动删除此队列,如果将自参数和exclusive都设置为true就可实现临时队列.
* arguments: 可以设置一个队列的扩展参数,比如: 设置队列存活时间
*/
channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
//4 声明交换机
/**
* 参数明细
* 参数1: 交换机名称
* 参数2: 交换机类型
* fanout: 对应发布订阅模式
* direct: 对应routing路由模式
* topic: 对应通配符模式
* header: 对应header模式
* 参数3: 是否持久化 ---->持久化exchange
*/
channel.exchangeDeclare(EXCHANGE_TOPIC_INFORM,BuiltinExchangeType.TOPIC,true);
//5.将2个队列绑定在交换机上面
/**
* 参数1: 队列名字
* 参数2: 交换机名字
* 路由key: 在发布订阅模式中使用空串;在路由模式和通配符模式中会根据路由key将消息发送到指定的queue中去
*/
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPIC_INFORM,ROUTING_EMAIL);
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPIC_INFORM,ROUTING_SMS);
//同一个queue可以绑定多个routingKey
//如果两个queue绑定相同的exchange交换机,相同的routingKey,则我们就可以实现发发布订阅的功能
//只需要我们发送消息时,指定发送到这个交换机的这个routingKey下节课
//channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_ROUTING_INFORM,"inform");
//channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_ROUTING_INFORM,"inform");
//6.发送消息
/**
* channel.basicPublish();
* String exchange, String routingKey, BasicProperties props, byte[] body
* 1.exchange: 交换机,如果传递""空串表示使用默认的交换机
* 2.routingKey: 路由key,交换机根据路由key来将消息转发到指定的队列,如果使用默认的交换机,routingKey设置队列的名字
* 3.props: 消息的属性,这里使用 MessageProperties.PERSISTENT_TEXT_PLAIN ---->表示持久化消息
* 消息的属性有这些内容
* public BasicProperties(
* 3.1 String contentType,//消息类型如:text/plain
* 3.2 String contentEncoding,//编码
* 3.3 Map<String,Object> headers,
* 3.4 Integer deliveryMode,//1:不持久化 2:持久化消息
* 3.5 Integer priority,//优先级
* 3.6 String correlationId,
* 3.7 String replyTo,//反馈队列
* 3.8 String expiration,//expiration到期时间
* ...
* 4.body: 消息的内容
*/
try {
String msg_email = "发送消息到email!!";
String msg_sms = "发送消息到sms!!!";
/**
* 因为这里采用的是发布订阅模式,我们把消息是发给交换机,交换机去发给queue,所以这里指定交换机及即可
* ---->1.开启事务
* ---->2.持久化消息
*/
channel.txSelect();//开启事务
//MessageProperties.PERSISTENT_TEXT_PLAIN 这是把消息持久化了,服务器宕机重启,消息依然存在
channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.email",MessageProperties.PERSISTENT_TEXT_PLAIN,msg_email.getBytes());
//这是没有持久化,服务器宕机重启,消息就会丢失
channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.sms",null,msg_sms.getBytes());
System.out.println("发送成功!!消息内容:" + msg_email );
System.out.println("发送成功!!消息内容:" + msg_sms );
channel.txCommit();//提交事务
}catch (Exception e){
e.printStackTrace();
channel.txRollback();//事务回滚
}finally {
channel.close();
}
}
2.普通confirm + 持久化exchange,queue,message
说明:
- 这是消息投递开启confirm确认,通过查询消息的投递状态,来保证消息投递的到rabbitMQ服务器的可靠性.
- 性能比事务稍微好一点
/**
* 生产者---以confirm模式,确保服务端收到消息
* 普通模式的confirm
* 效率仅仅比事务快一点
*/
@Test
public void producer_confirm_普通() throws Exception {
Connection connection = ConnUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
channel.exchangeDeclare(EXCHANGE_TOPIC_INFORM,BuiltinExchangeType.TOPIC,true);
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPIC_INFORM,ROUTING_EMAIL);
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPIC_INFORM,ROUTING_SMS);
String msg_email = "发送消息到email!!";
String msg_sms = "发送消息到sms!!!";
//开启普通模式的confirm
channel.confirmSelect();
channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.email",MessageProperties.PERSISTENT_TEXT_PLAIN,msg_email.getBytes());
//检查是否服务端给我们确认消息是否发送成功了
if(!channel.waitForConfirms()){
System.out.println("消息发送失败了");
}
System.out.println("发送成功!!消息内容:" + msg_email );
System.out.println("发送成功!!消息内容:" + msg_sms );
channel.close();
}
3.批量confirm + 持久化exchange,queue,message
说明:
- 这是消息投递开启confirm确认,通过查询消息的投递状态,来保证消息投递的到rabbitMQ服务器的可靠性.
- 性能比普通confirm好的多.
/**
* 生产者---以confirm模式,确保服务端收到消息
* 批量模式的confirm
* 效率次之
*/
@Test
public void producer_confirm_批量() throws Exception {
Connection connection = ConnUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
channel.exchangeDeclare(EXCHANGE_TOPIC_INFORM,BuiltinExchangeType.TOPIC,true);
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPIC_INFORM,ROUTING_EMAIL);
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPIC_INFORM,ROUTING_SMS);
String msg_email = "发送消息到email!!";
String msg_sms = "发送消息到sms!!!";
//开启普通模式的confirm
channel.confirmSelect();
for(int i = 0; i < 10; i ++){
channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.email",MessageProperties.PERSISTENT_TEXT_PLAIN,msg_email.getBytes());
}
//批量发送之后,检查是否服务端给我们确认消息是否发送成功了
if(!channel.waitForConfirms()){
System.out.println("消息发送失败了");
}
System.out.println("发送成功!!消息内容:" + msg_email );
System.out.println("发送成功!!消息内容:" + msg_sms );
channel.close();
}
4.异步confirm + 持久化exchange,queue,message
说明:
- 这是消息投递开启confirm确认,通过异步回调确认机制,来保证消息投递的到rabbitMQ服务器的可靠性.
- 性能最好.
/**
* 生产者---以confirm模式,确保服务端收到消息
* 异步模式的confirm
* 效率最高
*/
@Test
public void producer_confirm_异步() throws Exception {
Connection connection = ConnUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
channel.exchangeDeclare(EXCHANGE_TOPIC_INFORM,BuiltinExchangeType.TOPIC,true);
channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_TOPIC_INFORM,ROUTING_EMAIL);
channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_TOPIC_INFORM,ROUTING_SMS);
String msg_email = "发送消息到email!!";
String msg_sms = "发送消息到sms!!!";
//开启普通模式的confirm
channel.confirmSelect();
//添加异步的confirm确认的监听
channel.addConfirmListener(new ConfirmListener() {
/**
* 消息发送成功之后的回调方法
*/
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("发送成功!!!deliveryTag:" + deliveryTag + ",multiple:" + multiple );
}
/**
* 消息发送失败之后的回调方法
*/
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("发送失败!!!deliveryTag:" + deliveryTag + ",multiple:" + multiple );
}
});
for(int i = 0; i < 10; i ++){
channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.email",MessageProperties.PERSISTENT_TEXT_PLAIN,msg_email.getBytes());
channel.basicPublish(EXCHANGE_TOPIC_INFORM,"inform.sms",null,msg_sms.getBytes());
System.out.println("发送成功!!消息内容:" + msg_email );
System.out.println("发送成功!!消息内容:" + msg_sms );
}
Thread.sleep(3000);
channel.close();
}
消费者端
如何确保消费者消费消息的时候,消息不会丢失?
问题说明:
- 1.如果开启自动回复,如果客户端收到消息了,就会自动回复服务端收到消息,服务端就会自动删除这个消息; 但是如果在处理业务流程的时候出现问题,这个消息最后在业务中处理未成功,则这个消息将会丢失.
- 2.如果改为手动应答,不是在收到消息的时候就回复确认,而一般在我们的业务中是:收到消息,并且处理完成这个业务流程之后,再确认消息.这样就能保证我们消息不会丢失
@Test
public void consumerSMS() throws IOException, TimeoutException, InterruptedException {
publishSuvscirbeConsume(QUEUE_INFORM_SMS,EXCHANGE_TOPIC_INFORM,ROUTING_SMS);
}
/**
* 这是提取公共的发布订阅的consumer方法
*/
public void publishSuvscirbeConsume(String queue,String exchange,String routingKey) throws IOException, TimeoutException, InterruptedException {
//1.通过util对象创建connection对象
Connection connection = ConnUtil.getConnection();
//2.获取channel
Channel channel = connection.createChannel();
//3.声明队列
/**
* 这里可以不用声明队列,但是要确定该队列一定是在mq中存在的,不然就会代码报错.
* queue:声明队列的名称
* durable: 是否持久化
* exclusive: 是否独占连接,如果连接关闭则自动删除,如果将此参数设置为true可用于临时队列的创建
* autoDelete: 是否自动删除此队列,如果将自参数和exclusive都设置为true就可实现临时队列.
* arguments: 可以设置一个队列的扩展参数,比如: 设置队列存活时间
*/
channel.queueDeclare(queue,true,false,false,null);
//4.声明交换机,并设置属性持久化
channel.exchangeDeclare(exchange,BuiltinExchangeType.TOPIC,true);
//5.绑定queue到exchange上
channel.queueBind(queue,exchange,routingKey);
//声明一个消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
* 接收到消息时,调用此方法
* @param consumerTag 消费者标签
* @param envelope 信封---其实就是mq对于此消息的封装的东西,比如消息id,exchange等
* @param properties 消息属性
* @param body 消息内容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//交换机
String exchange = envelope.getExchange();
long deliveryTag = envelope.getDeliveryTag();
System.out.println("exchange:" + exchange);
System.out.println("deliveryTag:" + deliveryTag);
//接收消息
String msg = new String(body, "utf-8");
System.out.println("receive msg: "+ msg);
/**
* 开启了手动确认
* 1.如果消费者不确认,则该queue中的消息进入unacked状态,并且不会被删除,再次去拉取消息的时候依然可以消费这些消息
* 2.如果消费者确认,则该queue中的消息将被删除.
*/
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//4.监听队列,消费消息
/**
* String queue, boolean autoAck, Consumer callback
* 参数明细:
* queue: 队列名
* autoAck: 自动回复,当消费者受到消息之后,要告诉mq已接收到消息
* true:表示开启自动回复,如果客户端收到消息了,就会自动回复服务端收到消息,服务端就会自动删除这个消息;
* 但是如果在处理业务流程的时候出现问题,这个消息最后在业务中处理未成功,则这个消息将会丢失.
* false:改为手动应答,不是在收到消息的时候就回复确认,而一般在我们的业务中是:
* 收到消息,并且处理完成这个业务流程之后,再确认消息.
* callback: 消费方法,当前消费者受到消息后要执行的方法.
*/
channel.basicConsume(queue,false,consumer);
//这是模拟,客户端阻塞,因为不这样做,客户端程序直接执行完毕,则不能执行consumer中的handleDelivery回调方法.
//阻塞当前线程
while (true){
}
}
rabbitMQ服务端
服务端如何保证,在服务器故障的情况下,消息不会丢失?
- 1.exchange,queue,message在声明和传入的时候就持久化.
- 2.服务器采用高可用的镜像集群模式,如果一个服务器宕机,其他服务器照样可以提供服务.
- 3.如果服务器全部宕机,我们还有持久化的方式,保证消息的不会丢失.
- 4.由于rabbitMQ持久化需要一定间隔时间,我们在持久化可能对于间隔时间内的消息不能持久化到,我们还可以采用镜像队列的方式,其实这个就是rabbitMQ高可用集群的模式.
综上所述,服务端保证消息的不会丢失,总结:
- 配置高可用的rabbitMQ镜像集群(这个是服务器搭建).
- 传入的exchange,queue,message一定要持久化(这个是业务调用).