分布式消息通信之RabbitMQ_02
1. 可靠性投递分析
在某些业务实时一致性要求较高的场景,需要确保消息投递的可靠性,可以从RabbitMQ的工作模型的4个主要流程来做处理;并且效率和可靠性不可能同时兼顾,如果要保证每个环节都成功,对消息的收发效率肯定会有影响。
1. 生产者将消息投递至交换机
2. 交换机根据消息的路由关键字和队列的绑定关键字匹配,将消息路由到匹配的队列
3. 队列将消息存储在内存或者磁盘当中
4. 消费者从队列取走消息
5. 其他
1.1 消息投递
有两种方法可以监听生成着投递消息是否成功事物transaction模式
和确认confirm模式
;
事物模式(不推荐使用):可以使用com.rabbitmq.client.Channel#txSelect
来开启事物,当消息由生产者投递到RabbitMQ服务器成功后,事物会提交;不过事物模式会严重影响RabbitMQ特性,一般不建议使用。
ConnectionFactory factory = new ConnectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
try {
// 开启事物
channel.txSelect();
System.out.println("send msg..");
channel.basicPublish("", SimpleConsumer.QUEUE_NAME, null,
"Hello World".getBytes());
channel.txCommit();
System.out.println("消息发送成功!");
} catch (Exception e) {
// 回滚事务
System.out.println("消息发送失败, rollback");
channel.txRollback();
}
确认Confirm模式又分为批量确认,异步确认
Normal:
channel.confirmSelect();
channel.basicPublish("", SimpleConsumer.QUEUE_NAME, null,
"hello world confirm msg!".getBytes());
if (channel.waitForConfirms()) {
System.out.println("消息投递成功!");
}
batch:
try {
channel.confirmSelect();
for (int i = 0; i < 5; i++) {
// 发送消息
// String exchange, String routingKey, BasicProperties props, byte[] body
channel.basicPublish("", QUEUE_NAME, null, (msg +"-"+ i).getBytes());
}
// 批量确认结果,ACK如果是Multiple=True,代表ACK里面的Delivery-Tag之前的消息都被确认了
// 比如5条消息可能只收到1个ACK,也可能收到2个(抓包才看得到)
// 直到所有信息都发布,只要有一个未被Broker确认就会IOException
channel.waitForConfirmsOrDie();
System.out.println("消息发送完毕,批量确认成功");
} catch (Exception e) {
// 发生异常,可能需要对所有消息进行重发
e.printStackTrace();
}
Async
ConnectionFactory factory = new ConnectionFactory();
factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));
// 建立连接
Connection conn = factory.newConnection();
// 创建消息通道
Channel channel = conn.createChannel();
String msg = "Hello world, Rabbit MQ, Async Confirm";
// 声明队列(默认交换机AMQP default,Direct)
// String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
channel.queueDeclare(SimpleConsumer.QUEUE_NAME, false, false, true, null);
// 用来维护未确认消息的deliveryTag
final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
// 这里不会打印所有响应的ACK;ACK可能有多个,有可能一次确认多条,也有可能一次确认一条
// 异步监听确认和未确认的消息
// 如果要重复运行,先停掉之前的生产者,清空队列
channel.addConfirmListener(new ConfirmListener() {
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("Broker未确认消息,标识:" + deliveryTag);
if (multiple) {
// headSet表示后面参数之前的所有元素,全部删除
confirmSet.headSet(deliveryTag + 1L).clear();
} else {
confirmSet.remove(deliveryTag);
}
// 这里添加重发的方法
}
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
// 如果true表示批量执行了deliveryTag这个值以前(小于deliveryTag的)的所有消息,如果为false的话表示单条确认
System.out.println(String.format("Broker已确认消息,标识:%d,多个消息:%b", deliveryTag, multiple));
System.out.println("multiple:"+multiple);
if (multiple) {
System.out.println("deliveryTag:"+deliveryTag);
// headSet表示后面参数之前的所有元素,全部删除
confirmSet.headSet(deliveryTag + 1L).clear();
} else {
// 只移除一个元素
confirmSet.remove(deliveryTag);
}
System.out.println("未确认的消息:"+confirmSet);
}
});
// 开启发送方确认模式
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
long nextSeqNo = channel.getNextPublishSeqNo();
// 发送消息
// String exchange, String routingKey, BasicProperties props, byte[] body
channel.basicPublish("", SimpleConsumer.QUEUE_NAME, null, (msg +"-"+ i).getBytes());
confirmSet.add(nextSeqNo);
}
System.out.println("所有消息:"+confirmSet);
// 这里注释掉的原因是如果先关闭了,可能收不到后面的ACK
//channel.close();
//conn.close();
1.2 消息路由
当消息投递到Exchange后,会根据消息的路由关键字来匹配路由到的队列,当关键字没有匹配到队列或者队列不存在或者路由关键字错误时,消息就会丢失;为了保证消息可以被正确的路由,可以使用两种方式:生产者添加ReturnListener
或者创建队列时指定备份交换机 alternate-exchange
;
public class SimpleConsumer {
public static final String EXCHANGE_NAME = "Simple_Reliability_Exchange";
public static final String QUEUE_NAME = "Simple_Reliability_Queue";
}
- - - - - - - -
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
public class Rmq03ReturnListener {
private static final String BACKUP_EXCHANGE = "MY_alternate_exchange";
private static final String BACKUP_QUEUE = "MY_alternate_queue";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
// factory.setPort(15672);
factory.setUsername("guest");
factory.setPassword("guest");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 设置交换机无法路由的被返回的消息的监听器
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("ReturnListener 接收到无法路由被退回的消息 " + new Date());
System.out.println(String.format("replyCode[%s] replyText[%s] exchange[%s] routingKey[%s] properties[%s] msg[%s]",
replyCode, replyText, exchange, routingKey, properties, new String(body)));
}
});
// 初始化核心交换机和队列
Map<String, Object> exchangeArgs = new HashMap();
exchangeArgs.put("alternate-exchange", BACKUP_EXCHANGE); // 指定交换机的备份交换机,接收无法路由的消息
channel.exchangeDeclare(SimpleConsumer.EXCHANGE_NAME, BuiltinExchangeType.TOPIC, false, true, exchangeArgs);
channel.queueDeclare(SimpleConsumer.QUEUE_NAME, false, false, true, null);
channel.queueBind(SimpleConsumer.QUEUE_NAME, SimpleConsumer.EXCHANGE_NAME, "#.fast.#");
// 初始化备份交换机和队列
initBackup(channel);
// 发送消息
String msg = "Hello World, test msg can not rout ";
// 在发送消息是,可以设置mandatory参数未true,这样当消息在交换器上无法被路由时,服务器将消息返回给生产者,生产者实现回调函数处理被服务端返回的消息。
Boolean mandatory = true;
channel.basicPublish(SimpleConsumer.EXCHANGE_NAME, "v.fast", mandatory,null, msg.getBytes());
channel.basicPublish(SimpleConsumer.EXCHANGE_NAME, "v.slow", mandatory,null, msg.getBytes());
// channel.close();
// connection.close();
}
private static void initBackup(Channel channel) throws IOException {
channel.queueDeclare(BACKUP_QUEUE, false, false, true, null);
channel.exchangeDeclare(BACKUP_EXCHANGE, BuiltinExchangeType.FANOUT);
channel.queueBind(BACKUP_QUEUE, BACKUP_EXCHANGE, "");
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery delivery) throws IOException {
System.out.println("备份交换机 alternate-exchange 接收到无法路由的消息 " + new String(delivery.getBody()) );
// 注释回执
// channel.basicAck(delivery.getEnvelope().getDeliveryTag(), true);
}
};
channel.basicConsume(BACKUP_QUEUE, false, deliverCallback, consumerTag -> {});
}
}
1.3 消息存储
当消息路由至匹配队列,也就是步骤3时,如果消息没有被消费而RabbitMQ服务系统宕机或者重启了,会导致消息丢失,因此可以使用消息的持久化配置。
交换机持久化 消费者在创建交换机时指定存储在磁盘中,系统重启后交换机不被删除
Consumer
// 交换机持久化
// String exchange, String type,
// boolean durable (交换机持久化),
// boolean autoDelete(自动删除,为true时当前连接中断且交换机绑定队列没有消息时,会自动删除),
// Map<String, Object> arguments
channel.exchangeDeclare(EXCHANGE_PERSISTENCE, BuiltinExchangeType.TOPIC, true, false, null);
队列持久化 费者在创建队列时指定存储在磁盘中,系统重启后队列不被删除
consumer
// 队列持久化
// String queue,
// boolean durable, 队列持久化参数,
// boolean exclusive, 排他队列参数
// boolean autoDelete, 是否自动删除,为true时当前连接中断且交换机绑定队列没有消息时,会自动删除
// Map<String, Object> arguments
channel.queueDeclare(QUEUE_PERSISTENCE, true, false, false, null);
消息持久化 生成者在发送消息时指定消息类型为持久化
producer
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.deliveryMode(2)
.build();
channel.basicPublish(EXCHANGE_PERSISTENCE, "bird.fly.fast", properties, new String("just text persistence msg").getBytes());
集群 镜像
1.4 消息消费
消费者在从队列中取走消息进行消费时,如果逻辑处理中出现异常或者服务宕机消息就会丢失,可以在消息时使用手动发送回执的方式进行操作,当消息真正处理完毕后发送回执信息。
producer 发送消息
for (int i = 0; i < 10; i++) {
String msg = i%2 == 0 ? "This is a success msg" : " This is a error msg";
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
}
consumer 消费消息
// 消息消费 autoAck 为false
// DeliverCallback 内进行手工应答
/*
* 1. 成功
* com.rabbitmq.client.Channel#basicAck(long, boolean)
*
* 2. 拒绝,
* 拒绝可以分为单条拒绝 和 批量拒绝
* 拒绝的消息可以设置重新入队,不重新入队的则进入死信交换机,死信队列
* com.rabbitmq.client.Channel#basicReject(long, boolean)
* com.rabbitmq.client.Channel#basicNack(long, boolean, boolean)
*
*/
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery delivery) throws IOException {
String msg = new String(delivery.getBody());
System.out.println(String.format("消费者接收到消息 msg[%s] at %s", msg, new Date()));
if (msg.contains("error")) {
// reject one
channel.basicReject(delivery.getEnvelope().getDeliveryTag(), true);
// reject one or more
// channel.basicNack(delivery.getEnvelope().getDeliveryTag(), true, true);
} else {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), true);
}
}
};
channel.basicConsume(QUEUE_NAME, false, callback, consumerTag -> {});
1.5 其他
消费者回调,补偿机制,消息幂等性,消息顺序性
消费者回调
当跨系统异步通信时,消费者从队列中取走消费并消费成功后,生产者其实并不知道自己发出的消息已经被处理掉了,所以 a) 生产者可以在发送消息时额外添加一个回调API接口,当消费者消费完消息时,调用消息的回调API通知生产者;或者b)消费者在消费完消息后,再发送一条消费成功的消息给生产者; 这样生产者就可以获知了。
补偿机制
当生产者发送的消息一定时间内没有响应时,可以设置一个定时重发的机制,不过必须要规定重发次数如5,避免消息堆积。
消息幂等性
消息的补偿重发机制会发送多次一样的消息给消费者,造成重复消费,可以为消息设置唯一id,将消费者处理完的消息记录下来,入库,每次接收到消息根据库中id判断消息是否已经处理过,如果处理过了就忽略这条消息。
消息顺序性
当一个队列被多个消费者消费时,无法保证消费的顺序性。一个队列只有一个消费者时,顺序性可以得到保障。
2. 高可用架构部署方案
避免单点故障造成数据丢失,可以启用集群来达到高可用和负载均衡。
2.1 集群
集群部署与运维...
通信基础
erlang.cookie,hosts
集群节点
分为磁盘节点和内存节点两种,磁盘节点数据会保存在磁盘中,较安全;内存节点数据保存于内存中,访问效率较快;集群中最少有一个是磁盘节点,用于保存数据。
配置步骤
a) 配置hosts b) 配置erlang.cookie c) 加入集群
2.2 镜像
3. 经验总结
3.1 配置文件与命名规范
a)集中放在配置文件中 b) 体现数据类型 c) 体现数据来源和去向
lei.topicexchange=RISK_TO_PAY_EXCHANGE
3.2 调用封装
在原有基础上封装,减少调用复杂度
@Autowired
@Qualifier("amqpTemplate2")
private AmqpTemplate amqpTemplate2;
/**
* 演示三种交换机的使用
*
* @param message
*/
public void sendMessage(Object message) {
logger.info("Send message:" + message);
// amqpTemplate 默认交换机 MY_DIRECT_EXCHANGE
// amqpTemplate2 默认交换机 MY_TOPIC_EXCHANGE
// Exchange 为 direct 模式,直接指定routingKey
amqpTemplate.convertAndSend("FirstKey", "[Direct,FirstKey] "+message);
amqpTemplate.convertAndSend("SecondKey", "[Direct,SecondKey] "+message);
}
3.3 信息落库(可追溯,可重发) + 定时任务
根据业务场景可以将消息存入库中,可以进行追溯,表中记录消息回执状态,定时轮训库表判断是否收到回执,没有收到可以重发。
同时连接数据库表也会造成发送消息时效率的降低;
3.4 减少连接数
多条消息拼装到一起发送一次,总数据量不要超过4M
3.5 生产者先发送消息还是先登记业务表
先登记业务表;如果先发送消息,后续业务操作异常导致回滚,信息就会不一致。
3.6 谁来创建对象(队列,交换机,绑定关系)
由消费者创建 队列 交换机 绑定关系
3.7 运维监控
3.8 其他插件
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.14\sbin>rabbitmq-plugins list
Listing plugins with pattern ".*" ...
Configured: E = explicitly enabled; e = implicitly enabled
| Status: * = running on rabbit@DESKTOP-2NHH5NJ
|/
[ ] rabbitmq_amqp1_0 3.7.14
[ ] rabbitmq_auth_backend_cache 3.7.14
[ ] rabbitmq_auth_backend_http 3.7.14
[ ] rabbitmq_auth_backend_ldap 3.7.14
[ ] rabbitmq_auth_mechanism_ssl 3.7.14
[ ] rabbitmq_consistent_hash_exchange 3.7.14
[ ] rabbitmq_event_exchange 3.7.14
[ ] rabbitmq_federation 3.7.14
[ ] rabbitmq_federation_management 3.7.14
[ ] rabbitmq_jms_topic_exchange 3.7.14
[E*] rabbitmq_management 3.7.14
[e*] rabbitmq_management_agent 3.7.14
[ ] rabbitmq_mqtt 3.7.14
[ ] rabbitmq_peer_discovery_aws 3.7.14
[ ] rabbitmq_peer_discovery_common 3.7.14
[ ] rabbitmq_peer_discovery_consul 3.7.14
[ ] rabbitmq_peer_discovery_etcd 3.7.14
[ ] rabbitmq_peer_discovery_k8s 3.7.14
[ ] rabbitmq_random_exchange 3.7.14
[ ] rabbitmq_recent_history_exchange 3.7.14
[ ] rabbitmq_sharding 3.7.14
[ ] rabbitmq_shovel 3.7.14
[ ] rabbitmq_shovel_management 3.7.14
[ ] rabbitmq_stomp 3.7.14
[ ] rabbitmq_top 3.7.14
[ ] rabbitmq_tracing 3.7.14
[ ] rabbitmq_trust_store 3.7.14
[e*] rabbitmq_web_dispatch 3.7.14
[ ] rabbitmq_web_mqtt 3.7.14
[ ] rabbitmq_web_mqtt_examples 3.7.14
[ ] rabbitmq_web_stomp 3.7.14
[ ] rabbitmq_web_stomp_examples 3.7.14
分布式消息通信之RabbitMQ_02的更多相关文章
- 分布式消息通信(ActiveMQ)
分布式消息通信(ActiveMQ) 应用场景 异步通信 应用解耦 流量削峰 # ActiveMQ安装 下载 http://activemq.apache.org/ 压缩包上传到Linux系统 apac ...
- 分布式消息通信ActiveMQ
消息中间件 消息中间件是指利用高效可靠的消息传递机制进行平台无关的数据交流,并且基于数据通信来进行分布式系统的集成.通过提供消息传递和消息排队模型,可以在分布式架构下扩展进程之间的通信. 消息中间件能 ...
- 分布式消息通信之RabbitMQ_01
目录 官网 1. RabbitMQ安装 1.1 Window版安装 1.2 Linux版安装 2. 典型应用场景 3. 基本介绍 3.1 AMQP协议 3.2 RabbitMQ的特性 3.3 工作模型 ...
- 分布式消息通信Kafka-原理分析
本文目标 TopicPartition 消息分发策略 消息消费原理 消息的存储策略 Partition 副本机制 1 关于 Topic 和 Partition 1.1 Topic 在 kafka 中, ...
- 分布式消息通信之RabbitMQ Tutorials
目录 官网 1 Hello World! 1.1 生产者demo producer 1.2 消费者demo consumer 1.3 查看queue队列中的信息 页面查看,可看到有4条消息 命令查看 ...
- 分布式消息通信之RabbitMQ_Note
目录 1. RabbitMQ 安装 2. RabbitMQ 应用场景,特性 3. 官网入门指引 4. RabbitMQ 工作模型 5. RabbitMQ 主要的几种交换机类型 6. Java API的 ...
- Netty构建分布式消息队列实现原理浅析
在本人的上一篇博客文章:Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇 中,重点向大家介绍了AvatarMQ主要构成模块以及目前存在的优缺点.最后以一个生产者.消费者传递消息的例子, ...
- C#分布式消息队列 EQueue 2.0 发布啦
前言 最近花了我几个月的业余时间,对EQueue做了一个重大的改造,消息持久化采用本地写文件的方式.到现在为止,总算完成了,所以第一时间写文章分享给大家这段时间我所积累的一些成果. EQueue开源地 ...
- ZeroMQ:云时代极速消息通信库
ZeroMQ:云时代极速消息通信库(大规模|可扩展|低成本|高效率解决之道,大规模分布式|多线程应用程序|消息传递架构构建利器) [美]Pieter Hintjens(皮特.亨特金斯)著 卢涛 李 ...
随机推荐
- 验证码输入自动聚焦下一个input或者删除自动聚焦上一个input
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- python+requests----登录接口reponse中token传递给其他接口使用的一个简单小示例介绍
#!/usr/bin/env python # coding=UTF-8 import requests def login(): url = "https://xxxx.xxx.xxx/v ...
- jaxb读有BOM的XML文件问题
一开始找了半天没找到什么原因,读文件就报错: Content is not allowed in prolog 后来发现是文件是UTF-8带BOM和不带BOM的问题 问题就好解决了,直接读带BOM文件 ...
- 011_Linux驱动之_s3c2410_gpio_getpin
1. 功能:获取引脚状态 2. 函数原型: unsigned int s3c2410_gpio_getpin(unsigned int pin) { void __iomem *base = S3C2 ...
- Django RestFramework (DRF)
准备: 下载 pip install djangorestframework 一 APIView源码解析 1 预备知识 CBV(class based view)FBV(function based ...
- 观察者模式在android网络监控下的运用
github:https://github.com/shonegg/NetMonitor 一.对观察者模式的理解: 1.观察者模式,又叫发布-订阅(Publish/Subscribe)模式,定义的是对 ...
- 从TCP到Socket,彻底理解网络编程是怎么回事
进行程序开发的同学,无论Web前端开发.Web后端开发,还是搜索引擎和大数据,几乎所有的开发领域都会涉及到网络编程.比如我们进行Web服务端开发,除了Web协议本身依赖网络外,通常还需要连接数据库,而 ...
- nginx使用certbot配置https
一般现在的网站都要支持https,即安全的http. 机器:阿里云Ubuntu 16.04.3 LTS 方案一:自己申请证书 配置时需要确保有ssl模块, 之后域名解析下, 之后时申请证书,可以去阿里 ...
- 删除顺序表L中下标为p(0<=p<=length-1)的元素,成功返回1,不成功返回0,并将删除元素的值赋给e
原创:转载请注明出处. [天勤2-2]删除顺序表L中下标为p(0<=p<=length-1)的元素,成功返回1,不成功返回0,并将删除元素的值赋给e 代码: //删除顺序表L中下标为p(0 ...
- 【Phoenix】2、初始化 Phoenix 项目后的 目录结构
1.当我们创建的时候,Phoenix 为我们建立的根目录架构,如下: ├── _build ├── assets // 这个是放一下静态文件的,不如 js.css img 和 node_module ...