面试问题
RabbitMQ系列
特点
- 优点
开箱即用,方便部署,支持AMQP协议,支持非常多客户端语言 - 缺点
消息队列种类
- 工作模式,一个发送消息多个接收消息竞争消息
- 发布订阅模式,交换器绑定队列,所有队列都能获得消息
- 路由模式,交换器与队列绑定路由
- 通配符模式。
- Headers模式
- RPC模式
- JMS(activemq)是java提供的一套消息服务API标准,其目的是为所有的java应用程序提供统一的消息通信的标准,类似java的
jdbc,只要遵循jms标准的应用程序之间都可以进行消息通信。它和AMQP有什么 不同,jms是java语言专属的消
息服务标准,它是在api层定义标准,并且只能用于java应用;
RabbitMQ 如何保证消息不丢失
在这里插入图片描述
- 消息持久化
要想做到消息持久化,必须满足以下三个条件,缺一不可。
- Exchange 设置持久化
一般只需要:channel.exchangeDeclare(exchangeName, “direct/topic/header/fanout”, true);即在声明的时候讲durable字段设置为true即可
- Queue 设置持久化
queue的持久化是通过durable=true来实现的
channel.queueDeclare("queue.persistent.name", true, false, false, null);
- Message持久化
发送消息设置发送模式deliveryMode=2,代表持久化消息
如果队列持久化了但是消息没有持久化,那么mq重启后消息就消失了。
channel.basicPublish("exchange.persistent", "persistent", MessageProperties.PERSISTENT_TEXT_PLAIN, "persistent_test_message".getBytes());
这里的关键是:MessageProperties.PERSISTENT_TEXT_PLAIN
- ACK确认机制
多个消费者同时收取消息,比如消息接收到一半的时候,一个消费者死掉了(逻辑复杂时间太长,超时了或者消费被停机或者网络断开链接),如何保证消息不丢?
这个使用就要使用Message acknowledgment 机制,就是消费端消费完成要通知服务端,服务端才把消息从内存删除。
这样就解决了,及时一个消费者出了问题,没有同步消息给服务端,还有其他的消费端去消费,保证了消息不丢的case。 - 消息补偿机制
消息补偿机制需要建立在消息要写入DB日志,发送日志,接受日志,两者的状态必须记录。
然后根据DB日志记录check 消息发送消费是否成功,不成功,进行消息补偿措施,重新发送消息处理。 - mq部署方式
1 简单部署方式,一个挂了就没了
2.普通部署,集群部署,一个挂了等待他恢复
3.镜像部署
RabbitMQ保证不消费重复数据
- 生产消息重复:由于网络问题,ack确认超时,mq内部错误,宕机等原因导致导致消息的发送方不能正常收到确认消息,重新发送消息。
由于生产者发送消息给MQ,在MQ确认的时候出现了网络波动,生产者没有收到确认,实际上MQ已经接收到了消息。这时候生产者就会重新发送一遍这条消息。生产者中如果消息未被确认,或确认失败,我们可以使用定时任务+(redis/db)来进行消息重试。在内存中、redis、数据库中建立一个list消费记录,每次消费消息前先扫描一下这个list, - 消费者获得重复数据:由于网络原因,mq内部问题,消费者消费之后出现内部错误,mq宕机等原因导致mq没有收到消费者的确认消息然后重发消息。
让每个消息携带一个全局的唯一ID,即可保证消息的幂等性,具体消费过程为:
消费者获取到消息后先根据id去查询redis/db是否存在该消息。
如果不存在,则正常消费,消费完毕后写入redis/db。
如果存在,则证明消息被消费过,直接丢弃。
Springboot整合rabbitmq
1.添加amqp依赖
2.创建配置类@Configuration:@Bean申明交换机( ExchangeBuilder.topicExchange())和队列,交换机和队列进行绑定。
3.发送消息
4.消费方接收消息:添加注解: //监听email队列
@RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_EMAIL})
public void receive_email(String msg,Message message,Channel channel){
System.out.println(msg);
}
- 一般情况下,消息发送方只管发送消息,不管队列绑定,由消息接收方绑定
//通配符模式,消息发送
@Test
public void topic() {
rabbitTemplate.convertAndSend(EXCHANGE, "a.info", "ashkdfhaik");
}
//消息接收方,在监听的时候进行队列绑定
@Component
public class topicConsumer {
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,
exchange = @Exchange(value = "exchange1",type = "topic"),
key = {"#.info.#"}
)
})
public void infoGetMsg(String msg) {
System.out.println("info接收到消息:" + msg);
}
声明队列和交换机参数
- 队列
/**
* 参数明细:
* 1、队列名称
* 2、是否持久化,mq重启后还存在
* 3、是否独占此队列
* 4、队列不用是否自动删除,只要发送方和消费方同事没有连接就会删除
* 5、参数
*/
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
- 交换机
/**
* 参数明细
* 1、交换机名称
* 2、交换机类型,fanout、topic、direct、headers
*/
channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
- 消费者
/**
* 监听队列String queue, boolean autoAck,Consumer callback
* 参数明细
* 1、队列名称
* 2、是否自动回复,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置
为false则需要手动回复,只要接收到消息就会发送确认消息,不会等业务完成,如果执行业务中出现问题,消息就没有了,所以不要设置自动确认。
* 3、消费消息的方法,消费者接收到消息后调用此方法,消费消息之后的回调方法。
*/
channel.basicConsume(QUEUE_INFORM_EMAIL, true, defaultConsumer);
手动确认消息
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
connection = connectionFactory.newConnection();
channel = connection.createChannel();
channel.queueDeclare(QUEUE1, true, false, false, null);
channel.exchangeDeclare(EXCHANGE,BuiltinExchangeType.FANOUT);
channel.queueBind(QUEUE1,EXCHANGE,"");
//重点开始
channel.basicQos(1);//关闭消息自动确认,每次接收一个消息,在获取消息之前设置
Channel finalChannel = channel;
DefaultConsumer consumer = new DefaultConsumer(finalChannel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String s1 = new String(body, "utf-8");
System.out.println("fansout1 接收到消息:" + s1);
//参数一:确认当前消息的标志位,参数二:是否开启多个消息同事确认。
finalChannel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(QUEUE1, false, consumer); //消费完消息的回调方法,参数二,false:关闭自动确认消息,参数三:回调方法
//重点结束
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
Kafka、ActiveMQ、RabbitMQ、RocketMQ 对比
Kafka
特点
- 优点
- 性能好,每秒几十万处理
- 支持分布式部署,支持可扩展性
- 支持分区,可复制,可容错
- 异步收发消息性能好
- 缺点
- 发送消息不实时,需要积攒一起发
- topic数量太大之后性能下降
kafka高性能高吞吐的原因
- 磁盘读写,连续的顺序读写,保证了消息能够有效堆积
RocketMQ
相比于rabbitmq,性能上有较大提升,而且用的是java开发,易于阅读和二次扩展
相比于kafka,性能差距不大,而且对于相应的时延有了较大的改进。相应是毫秒级别的。
开发问题
出现shutdown问题
检查发送方和消费方交换器绑定的是否是同一个通信方式,fanout或者topic方式。必须保持一致。
使用junit测试
使用junit测试消费者时,由于junit是单元测试,执行完毕之后所有的资源就释放了,不支持多线程运行,不能持续监听mq消息队列,所以建议使用main方法测试消费方。而且使用main方法不建议将通道和链接关闭,原因和单元测试相同。