目前主流的消息队列讲解及面试

一、目前主流的消息队列

特性

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消息,生产者会自动重发。

         那么如何持久化呢,其实很简单,以下两 步

  1. 将queue的持久化标识durable设置为True,则代表是一个持久的队列
  2. 发送消息的时候将 deliveryMode= 2

这样设置以后,RabbitMQ就算挂了,重启后也能恢复数据

后面持续更新

3、

   RocketMQ

1、生产者丢数据

 

2、RocketMQ 丢数据

 

3、消费者丢数据

        

kafka

1、生产者丢数据

 

2、kafka丢数据

 

3、消费者丢数据

你可能感兴趣的:(消息队列)