特性 |
ActiveMQ |
RabbitMQ |
RocketMQ(阿里) |
kafka |
开发语言 |
java |
erlang |
java |
scala |
单机吞吐量 |
万级 |
万级 |
十万级 |
十万级 |
时效性 |
ms级 |
us级 |
ms级 |
ms级内 |
可用性 |
高(主从架构) |
高(主从架构) |
非常高(分布式架构) |
非常高(分布式架构) |
1、分布式系统如何保证发送消息不重复
答:发送消息可以加上redis分布式锁解决集群环境同时执行是发相同的消息。
2、消息队列如何确定消息被消费成功了
答:消息成功时RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,kafka实际上有个offset,需要提交offset
3、分布式系统如何如何保证消费者不重复消费消息
答:结合业务,1、插入数据,先根据主键查询一下。有数据则做更新操作 2、如果是写redis,那没有问题,反正每次都是set,天然幂等性 3、让生产者发生每条数据的时候,里面加一个全局的唯一的id,消费的时候,先根据id去比如edis里面查询,没有查询到就处理,然后将这个id写redis。查询到就不处理。
4、如何保证消费的可靠性传输?
答:其实这个可靠性传输,每种MQ都要从三个角度来分析:生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据
RabbitMQ
1、生产者丢数据
从生产者弄丢数据,RabbitMQ提供了transaction 和confirm模式来确保生产者不丢消息。Transacion机制就是说,发送消息前,开启事务channel.txSelect(),然后发送消息,如果发送过程中出现什么异常,事务就回滚channel.txRollback(),如果发送成功则提交事务channel.txCommit()。这个缺点就是吞吐量下降了。一般经验,生产上使用confirm模式居多, confirm模式有三种:一般都用异步监听发送方确认模式(addConfirmListener)。一旦channel进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被发送到所匹配的队列中,RabbitMQ就发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经被正确到队列中,如果RabbitMQ没有处理该消息,则会发一个Nack消息给你,你可用重试操作。
Transacion 事务模式
channel.txSelect() 声明启动事务模式;
channel.txComment() 提交事务;
channel.txRollback() 回滚事务;
// 创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(config.UserName); factory.setPassword(config.Password); factory.setVirtualHost(config.VHost);
factory.setHost(config.Host);
factory.setPort(config.Port);
Connection conn = factory.newConnection();
// 创建信道
Channel channel = conn.createChannel();
// 声明队列
channel.queueDeclare(_queueName, true, false, false, null);
String message = String.format("时间 => %s", new Date().getTime());
try {
channel.txSelect();// 声明事务
// 发送消息
channel.basicPublish("",_queueName,MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));
channel.txCommit(); // 提交事务
} catch (Exception e) {
channel.txRollback();
} finally {
channel.close();
conn.close();
}
Confirm 发送方确认模式
方式一: channel.waitForConfirms() 普通发送方式确认模式
方式二: channel.waitForConfirmsOrDie() 批量确认模式
方式三: channel.addConfirmListener() 异步监听发送方确认模式
具体实现
方式一:channel.waitForConfirms()普通发送方式确认模式
// 创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(config.UserName);
factory.setPassword(config.Password);
factory.setVirtualHost(config.VHost);
factory.setHost(config.Host);
factory.setPort(config.Port);
Connection conn = factory.newConnection();
// 创建信道
Channel channel = conn.createChannel();
// 声明队列
channel.queueDeclare(config.QueueName, false, false, false, null);
// 开启发送方确认模式
channel.confirmSelect();
String message = String.format("时间 => %s", new Date().getTime());
channel.basicPublish("",config.QueueName,null,message.getBytes("UTF-8");
if (channel.waitForConfirms()) {
System.out.println("消息发送成功" );
}
看代码可以知道,我们只需要在推送消息之前,channel.confirmSelect()声明开启发送方确认模式,再使用channel.waitForConfirms()等待消息被服务器确认即可。
方式二: channel.waitForConfirmsOrDie() 批量确认模式
// 创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(config.UserName);
factory.setPassword(config.Password);
factory.setVirtualHost(config.VHost);
factory.setHost(config.Host);
factory.setPort(config.Port); Connection conn = factory.newConnection();
// 创建信道
Channel channel = conn.createChannel();
// 声明队列
channel.queueDeclare(config.QueueName, false, false, false, null);
// 开启发送方确认模式
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
String message = String.format("时间 => %s", new Date().getTime());
channel.basicPublish("",config.QueueName,null,message.getBytes("UTF-8"));
}
channel.waitForConfirmsOrDie();//直到所有信息都发布,只要有一个未确认就会IOException
System.out.println("全部执行完成");
以上代码可以看出来channel.waitForConfirmsOrDie(),使用同步方式等所有的消息发送之后才会执行后面代码,只要有一个消息未被确认就会抛出IOException异常。
方式三: channel.addConfirmListener() 异步监听发送方确认模式
// 创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(config.UserName);
factory.setPassword(config.Password);
factory.setVirtualHost(config.VHost);
factory.setHost(config.Host);
factory.setPort(config.Port);
Connection conn = factory.newConnection();
// 创建信道
Channel channel = conn.createChannel();
// 声明队列
channel.queueDeclare(config.QueueName, false, false, false, null);
// 开启发送方确认模式
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
String message = String.format("时间 => %s", new Date().getTime()); channel.basicPublish("",config.QueueName,null,message.getBytes("UTF-8"));
} //异步监听确认和未确认的消息
channel.addConfirmListener(new ConfirmListener() {
//消息没有确认,则执行handleNack函数 重试发送消息
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("未确认消息,标识:" + deliveryTag);
}
// 发送成功的消息handleAck
@Override
public void handleAck (long deliveryTag, boolean multiple) throws IOException {
System.out.println(String.format("已确认消息,标识:%d,多个消息:%b", deliveryTag, multiple));
}
});
异步模式的优点,就是执行效率高,不需要等待消息执行完,只需要监听消息即可,
可以看出,代码是异步执行的,消息确认有可能是批量确认的,是否批量确认在于返回的multiple的参数,此参数为bool值,如果true表示批量执行了deliveryTag这个值以前的所有消息,如果为false的话表示单条确认。
2、消息队列丢数据
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁后,再给生产者发送ACK信号。这样如果消息持久化磁盘之前,RabbitMQ挂了,那么生产者收不到ACK消息,生产者会自动重发。
那么如何持久化呢,其实很简单,以下两 步
这样设置以后,RabbitMQ就算挂了,重启后也能恢复数据
后面持续更新
3、
RocketMQ
1、生产者丢数据
2、RocketMQ 丢数据
3、消费者丢数据
kafka
1、生产者丢数据
2、kafka丢数据
3、消费者丢数据