消息队列相关面试题

巩固基础,砥砺前行 。

只有不断重复,才能做到超越自己。

能坚持把简单的事情做到极致,也是不容易的。

面试题

项目上用过消息队列吗?用过哪些?当初选型基于什么考虑的呢?

面试官心理分析

第一,你知不知道你们系统里为什么要用消息队列这个东西?

不少候选人,说自己项目里用了Redis、MQ,但是其实他并不知道自己为什么要用这个东西。其实说白了,就是为了用而用,或者是别人

设计的架构,他从头到尾都没思考过。

没有对自己的架构问过为什么的人,一定是平时没有思考的人,面试官对这类候选人印象通常很不好。因为面试官担心你进了团队之后只会木头木脑的干呆活儿,不会自己思考。

第二,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?

你要是没考虑过这个,那你盲目弄个MQ进系统里,后面出了问题你是不是就自己溜了给公司留坑?你要是没考虑过引入一个技术可能存

在的弊端和风险,面试官把这类候选人招进来了,基本可能就是挖坑型选手。就怕你干 1年挖一堆坑,自己跳槽了,给公司留下无穷后

第三,既然你用了MQ,可能是某一种MQ,那么你当时做没做过调研?

你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个MQ,比如Kafka,甚至都从没调研过业界流行的MQ到底有哪几种。每一个MQ的优点

和缺点是什么。每一个MQ没有绝对的好坏,但是就是看用在哪个场景可以扬长避短,利用其优势,规避其劣势。

如果是一个不考虑技术选型的候选人招进了团队,leader交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选

型,最后选的技术可能并不一定合适,一样是留坑。

为什么使用消息队列

其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么?

面试官问你这个问题,期望的一个回答是说,你们公司有个什么业务场景,这个业务场景有个什么技术挑战,如果不用MQ可能会很麻烦,但

是你现在用了MQ之后带给了你很多的好处。

先说一下消息队列常见的使用场景吧,其实场景有很多,但是比较核心的有3个:解耦、异步、削峰

消息队列有什么优缺点

缺点有以下几个:

系统可用性降低

系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,ABCD 四个系统还好好的,没啥问题,你偏

加个MQ进来,万一MQ挂了咋整?MQ一挂,整套系统崩溃,你不就完了?如何保证消息队列的高可用,可以点击这里查看。

系统复杂度提高

硬生生加个MQ进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?头大头,问题一大堆,痛

苦不已。

一致性问题

A系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果C

系统写库失败了,咋整?你这数据就不一致了。

所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好

之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了10倍。但是关键时刻,用,还是得用的。

Kafka、ActiveMQ、RabbitMQ、RocketMQ有什么优缺点?


综上,各种对比之后,有如下建议:

::一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,

所以大家还是算了吧,我个人不推荐用这个了。|

后来大家开始用RabbitMQ,但是确实erlang语言阻止了大量的Java工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是

确实人家是开源的,比较稳定的支持,活跃度也高。

不过现在确实越来越多的公司会去用RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黄掉的风险(目前RocketMQ已捐给Apache,但GitHub上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用RocketMQ,否则回去老老实实用RabbitMQ吧,人家有活跃的开源社区,绝对不会黄。

所以中小型公司,技术实力较为一般,技术挑战不是特别高,用RabbitMQ是不错的选择;大型公司,基础架构研发实力较强,用RocketMQ

是很好的选择。

如果是大数据领域的实时计算、日志采集等场景,用Kafka是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这

个领域的事实性规范。

部署是单机还是集群呢?你们高可用是怎么保证的呢?

如果有人问到你MQ的知识,高可用是必问的。上一讲提到,MQ会导致系统可用性降低。所以只要你用了MQ,接下来问的一些要点肯定就

是围绕着MQ的那些缺点怎么来解决了。

要是你傻乎乎的就干用了一个MQ,各种问题从来没考虑过,那你就杯具了,面试官对你的感觉就是,只会简单使用一些技术,没任何思考,马上对你的印象就不太好了。这样的同学招进来要是做个20k薪资以内的普通小弟还凑合,要是做薪资20k+的高工,那就惨了,让你设计个系统,里面肯定一堆坑,出了事故公司受损失,团队一起背锅。

这个问题这么问是很好的,因为不能问你Kafka的高可用性怎么保证?ActiveMQ的高可用性怎么保证?一个面试官要是这么问就显得很没水

平,人家可能用的就是RabbitMQ,没用过Kafka,你上来问人家Kafka干什么?这不是摆明了刁难人么。

所以有水平的面试官,问的是MQ的高可用性怎么保证?这样就是你用过哪个MQ,你就说说你对那个MQ的高可用性的理解。

RabbitMQ的高可用性

RabbitMQ是比较有代表性的,因为是基于主从(非分布式)做高可用性的,我们就以RabbitMQ为例子讲解第一种 MQ的高可用性怎么实

现。

RabbitMQ有三种模式:单机模式、普通集群模式、镜像集群模式。

单机模式

单机模式,就是Demo级别的,一般就是你本地启动了玩玩儿的,没人生产用单机模式。

普通集群模式(无高可用性)

普通集群模式,意思就是在多台机器上启动多个RabbitMQ实例,每台机器启动一个。你创建的queue,只会放在一个RabbitMQ 实例上,但是每个实例都同步queue的元数据(元数据可以认为是queue的一些配置信息,通过元数据,可以找到queue所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从queue所在实例上拉取数据过来。

镜像集群模式(高可用性)

这种模式,才是所谓的RabbitMQ的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的queue,无论是元数据还是queue 里的消息都会存在于多个实例上,就是说,每个RabbitMQ节点都有这个queue的一个完整镜像,包含queue 的全部数据的意思。然后每次你写消息到queue的时候,都会自动把消息同步到多个实例的queue上。

那么如何开启这个镜像集群模式呢?其实很简单,RabbitMQ有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。

这样的话,好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个queue的完整数据,别的consumer都可以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!第二,这么玩儿,不是分布式的,就没有扩展性可言了,如果某个queue负载很重,你加机器,新增的机器也包含了这个queue的所有数据,并没有办法线性扩展你的queue。你想,如果这个queue的数据量很大,大到这个机器上的容量无法容纳了,此时该怎么办呢?

有遇到过重复消费的问题吗?怎么解决的呢?

其实这是很常见的一个问题,这俩问题基本可以连起来问。既然是消费消息,那肯定要考虑会不会重复消费?能不能避免重复消费?或者重复消费了也别造成系统异常可以吗?这个是 MQ领域的基本问题,其实本质上还是问你使用消息队列郊何保证幂等性,这个是你架构里要考虑的一个问题。

回答这个问题,首先你别听到重复消息这个事儿,就一无所知吧,你先大概说一说可能会有哪些重复消费的问题。

首先,比如RabbitMQ、RocketMQ、Kafka,都有可能会出现消息重复消费的问题,正常。因为这问题通常不是MQ自己保证的,是由我们开

发来保证的。挑一个Kafka 来举个例子,说说怎么重复消费吧。

Kafka 实际上有个offset 的概念,就是每个消息写进去,都有一个offset,代表消息的序号,然后consumer 消费了数据之后,每隔一段时间(定时定期),会把自己消费过的消息的offset 提交一下,表示"我已经消费过了,下次我要是重启啥的,你就让我继续从上次消费到的offset来继续消费吧"。

但是凡事总有意外,比如我们之前生产经常遇到的,就是你有时候重启系统,看你怎么重启了,如果碰到点着急的,直接 kill 进程了,再重启。

这会导致consumer有些消息处理了,但是没来得及提交offset,尴尬了。重启之后,少数消息会再次消费一次。

举个栗子。

有这么个场景。数据 1/2/3依次进入Kafka,Kafka会给这三条数据每条分配一个offset,代表这条数据的序号,我们就假设分配的offset 依次是152/153/154。消费者从Kafka 去消费的时候,也是按照这个顺序去消费。假如当消费者消费了 offset=153的这条数据,刚准备去提交offset 到Zookeeper,此时消费者进程被重启了。那么此时消费过的数据1/2的offset并没有提交,Kafka 也就不知道你已经消费了

offset=153这条数据。那么重启之后,消费者会找Kafka说,嘿,哥儿们,你给我接着把上次我消费到的那个地方后面的数据继续给我传

递过来。由于之前的offset 没有提交成功,那么数据1/2会再次传过来,如果此时消费者没有去重的话,那么就会导致重复消费。

注意:新版的Kafka 已经将offset的存储从Zookeeper 转移至 Kafka brokers,并使用内部位移主题

如果消费者干的事儿是拿一条数据就往数据库里写一条,会导致说,你可能就把数据1/2在数据库里插入了2次,那么数据就错啦。

其实重复消费不可怕,可怕的是你没考虑到重复消费之后,怎么保证幂等性。

举个例子吧。假设你有个系统,消费一条消息就往数据库里插入一条数据,要是你一个消息重复两次,你不就插入了两条,这数据不就错了?

但是你要是消费到第二次的时候,自己判断一下是否已经消费过了,若是就直接扔了,这样不就保留了一条数据,从而保证了数据的正确性。

一条数据重复出现两次,数据库里就只有一条数据,这就保证了系统的幂等性。

幂等性,通俗点说,就一个数据,或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,不能出错。

所以第二个问题来了,怎么保证消息队列消费的幂等性?

其实还是得结合业务来思考,我这里给几个思路:

。比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下好吧。

比如你是写Redis,那没问题了,反正每次都是set,天然幂等性。

比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单 id之类的东西,然后你这里消费到了之后,先根据这个id 去比如Redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个id写Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。

比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。当然,如何保证MQ的消费是幂等性的,在实际应用中需要结合具体的业务来看。

消息队列有哪些作用

1.解耦:使用消息队列来作为两个系统直接的通讯方式,两个系统不需要相互依赖了

2.异步:系统A给消费队列发送完消息之后,就可以继续做其他事情了

3.流量削峰:如果使用消息队列的方式来调用某个系统,那么消息将在队列中排队,有消费者自己控制消费速度

死信队列是什么?延时队列是什么?

1.死信队列也是一个消息队列,它是用来存放那些没有成功消费的消息的,通常可以用来作为消息重试

2.延时队列就是用来存放需要在指定时间被处理的元素的队列,通常可以用来处理一些具有过期性操作的业务,比如十分钟内未支付则取消订单

有遇到过消息丢失吗?可靠性怎么保证呢?

这个是肯定的,用MQ有个基本原则,就是数据不能多一条,也不能少一条,不能多,就是前面说的重复消费和幂等性问题。不能少,就是说

这数据别搞丢了。那这个问题你必须得考虑一下。

如果说你这个是用MQ来传递非常核心的消息,比如说计费、扣费的一些消息,那必须确保这个MQ传递过程中绝对不会把计费消息给弄丢。

数据的丢失问题,可能出现在生产者、MQ、消费者中,咱们从RabbitMQ来分析一下吧。

生产者弄丢了数据

生产者将数据发送到RabbitMQ的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能。

此时可以选择用RabbitMQ提供的事务功能,就是生产者发送数据之前开启RabbitMQ事务 channel.txSelect(),然后发送消息,如果消

息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务

channel.txRollback(),然后重试发送消息;如果

收到了消息,那么可以提交事务 channel.txCommit()

try{

通过工厂创建连接

connection = factory.newConnection();

// 获取通道

channel = connection.createChannel();

//开启事务

channel.txSelect();//这里发送消息

channel.basicPublish(exchange,routingKey,MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes())

// 模拟出现异常

int result =1/0;

|/ 提交事务

channel.txCommit();

}catch (IOException | TimeoutException e)

//捕捉异常,回滚事务

channel.txRollback();

}

但是问题是,RabbitMQ事务机制(同步)一搞,基本上吞吐量会下来,因为太耗性能。

所以一般来说,如果你要确保说写RabbitMQ的消息别丢,可以开启confirm 模式,在生产者那里设置开启

confirm模式之后,你每次写

的消息都会分配一个唯一的id,然后如果写入了RabbitMQ中,RabbitMQ会给你回传一个ack消息,告诉你说这个消息ok了。如果RabbitMQ没能处理这个消息,会回调你的一个nack接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。

事务机制和confirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm 机制是异步的,你发送

个消息之后就可以发送下一个消息,然后那个消息RabbitMQ接收了之后会异步回调你的一个接口通知你这个消息接收到了。

所以一般在生产者这块避免数据丢失,都是用 confirm机制的。

已经在transaction事务模式的channel是不能再设置成confirm 模式的,即这两种模式是不能共存的。

客户端实现生产者 confirm 有3种方式:

1.普通confirm 模式:每发送一条消息后,调用 waitForConfirms()方法,等待服务器端confirm,如果服务端返回false或者在一段时间

内都没返回,客户端可以进行消息重发。

channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingkey, MessageProperties. PERSISTENT_TEXT_PLAIN, Cor if (!channel.waitForConfirms()){

|/消息发送失败

}

2.批量confirm 模式:每发送一批消息后,调用 waitForConfirms()方法,等待服务端confirm。

channel.confirmSelect();

for (int i= 0;i< batchCount;++i) {

channel.basicPubLish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties. PERSISTENT_TEXT_PLAIN,

if (!channel.waitForConfirms())f

//消息发送失败

}

3.异步confirm模式:提供一个回调方法,服务端confirm了一条或者多条消息后客户端会回调这个方法。

SortedSet confirmset = Collections.synchronizedSortedtet(new TreeSet());

channel.confirmSelect();

channel.addConfirmListener(new ConfirmListener()f

public void handleAck(long deliveryTag, boolean multiple) throws IOException (

if(multiple){

confirmset.headSet(deliveryTag + 1).clear();

}else {

confirmset.remove(deliveryTag);

}

public void handleNack(long deliveryTag, boolean multiple) throws I0Exception System.out.println("Nack, SeqNo:"+ deliveryTag + ", multiple:"+ multiple)if (multiple){

confirmSet.headSet(deliveryTag + 1).clear();

1 else {

confirmset.remove(deliveryTag);

}

while (true){

Long nextSeqNo = channel.getNextPubLishSeqNo();

channel.basicPublish(ConfirmConfig.exchangeName,ConfirmConfig.routingKey,MessageProperties.PERSISTENT_TEXT_PLAIN,

confirmSet.add(nextSeqNo);}

RabbitMQ弄丢了数据

就是RabbitMQ自己弄丢了数据,这个你必须开启RabbitMQ的持久化,就是消息写入之后会持久化到磁盘,哪怕是RabbitMQ自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,RabbitMQ还没持久化,自己就挂了,可能导致少量数据丢失,但是这个概率较小。

设置持久化有两个步骤:

创建queue的时候将其设置为持久化。这样就可以保证RabbitMQ持久化queue的元数据,但是它是不会持久化queue里的数据的。第二个是发送消息的时候将消息的 deliveryMode 设置为2。就是将消息设置为持久化的,此时RabbitMQ就会将消息持久化到磁盘上去。

必须要同时设置这两个持久化才行,RabbitMQ哪怕是挂了,再次重启,也会从磁盘上重启恢复queue,恢复这个queue里的数据。

注意,哪怕是你给RabbitMQ开启了持久化机制,也有一种可能,就是这个消息写到了RabbitMQ中,但是还没来得及持久化到磁盘上,结果

不巧,此时RabbitMQ挂了,就会导致内存里的一点点数据丢失。

所以,持久化可以跟生产者那边的 confirm 机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者ack了,所以哪怕是在持久化

到磁盘之前,RabbitMQ挂了,数据丢了,生产者收不到ack,你也是可以自己重发的。

消费端弄丢了数据

RabbitMQ如果丢失了数据,主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,RabbitMQ认为你都消费了,这数据就丢了。

这个时候得用RabbitMQ提供的ack 机制,简单来说 就是你必须关闭RabbitMQ的自动 aick

,可以通过一个api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 ack一把。这样的话,如果你还没处理完,不就没有ack了?那RabbitMQ就认为你还没处理完,这个时候RabbitMQ会把这个消费分配给别的consumer 去处理,消息是不会丢的。

为了保证消息从队列中可靠地到达消费者,RabbitMQ提供了消息确认机制。消费者在声明队列时,可以指定noAck参数,当

noAck=false,RabbitMQ会等待消费者显式发回ack信号后,才从内存(和磁盘,如果是持久化消息)中移去消息。否则,一旦消息被消费

者消费,RabbitMQ会在队列中立即删除它。

大量消息在mq里积压了几个小时了还没解决

几千万条数据在MQ里积压了七八个小时,从下午4点多,积压到了晚上11点多。这个是我们真实遇到过的一个场景,确实是线上故障了,这个时候要不然就是修复consumer的问题,让它恢复消费速度,然后傻傻的等待几个小时消费完毕。这个肯定不能在面试的时候说吧。::一个消费者一秒是1000条,一秒3个消费者是3000条,一分钟就是18万条。所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概1小时的时间才能恢复过来。

一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下:

先修复consumer的问题,确保其恢复消费速度,然后将现有consumer都停掉。

·新建一个topic,partition是原来的10倍,临时建立好原先10倍的queue 数量。

然后写一个临时的分发数据的consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建

立好的10倍数量的queue。

接着临时征用10倍的机器来部署consumer,每一批consumer 消费一个临时queue的数据。这种做法相当于是临时将queue资源和

consumer 资源扩大10倍,以正常的10倍速度来消费数据。

等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的consumer 机器来消费消息。

MQ中的消息过期失效了

假设你用的是RabbitMQ,RabbtiMQ是可以设置过期时间的,也就是TTL。如果消息在queue中积压超过一定的时间就会被RabbitMQ给清

理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在mq里,而是大量的数据会直接搞丢。

这个情况下,就不是说要增加consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入mq里面去,把白天丢的数据给他补回来。也只能是这样了。

假设1万个订单积压在mq里面,没有处理,其中1000个订单都丢了,你只能手动写程序把那1000个订单给查出来,手动发到mq里去再

补一次。

MQ都快写满了

如果消息积压在mq里,你很长时间都没有处理掉,此时导致mq都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。

对于RocketMQ,官方针对消息积压问题,提供了解决方案。

1.提高消费并行度

绝大部分消息消费行为都属于10密集型,即可能是操作数据库,或者调用RPC,这类消费行为的消费速度在于后端数据库或者外系统的吞吐量,通过增加消费并行度,可以提高总的清费吞吐量,但是并行度增加到一定程度,反而会下降。所以,应用必须要设置合理的并行度。如下有几种修改消费并行度的方法:

同一个ConsumerGroup下,通过增加Consumer 实例数量来提高并行度(需要注意的是超过订阅队列数的Consumer 实例无效)。可以通过

加机器,或者在已有机器启动多个进程的方式。提高单个Consumer的消费并行线程,通过修改参数consumeThreadMin.

consumeThreadMax实现。

2.批量方式消费

某些业务流程如果支持批量方式消费,则可以很大程度上提高消费吞吐量,例如订单扣款类应用,一次处理一个订单耗时1s,一次处理 10个订单可能也只耗时2s,这样即可大幅度提高消费的吞吐量,通过设置consumer 的consumeMessageBatchMaxSize 返个参数,默认是1,即一次只消费一条消息,例如设置为N,那么每次消费的消息数小于等于N。

3.跳过非重要消息

发生消息堆积时,如果消费速度一直追不上发送速度,如果业务对数据要求不高的话,可以选择丢弃不重要的消息。例如,当某个队列的消息

数堆积到100000条以上,则尝试丢弃部分或全部消息,这样就可以快速追上发送消息的速度。示例代码如下:

public ConsumeConcurrentlyStatus consumeMessage(

Listmsgs

ConsumeConcurrentlyContext context)long offset = msgs.get(0).getQueue0ffset();String maxoffset =

msgs.get(0).getProperty(Message.PROPERTY_MAX_OFFSET);Long diff = Long.parseLong(max0ffset)- offset;

if (diff >100000)

//TODO 消息堆积情况的特殊处理

return ConsumeConcurrentyStatus.CONSUME SUCCESS

// TODO正常消费过程

return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;

}

4.优化每条消息消费过程举例如下,某条消息的消费过程如下:

•根据消息从DB查询【数据1]

根据消息从DB查询【数据2】复杂的业务计算•向DB插入【数据 3]向DB插入【数据 4】

这条消息的消费过程中有 4次与 DB的交互,如果按照每次 5ms 计算,那么总共耗时 20ms,假设业务计算耗时 5ms,那么总过耗时25ms,所以如果能把4次DB交互优化为2次,那么总耗时就可以优化到15ms,即总体性能提高了40%。所以应用如果对时延敏感的话,可以把DB部署在SSD硬盘,相比于SCSI磁盘,前者的RT会小很多。

如果让你写一个消息队列,该如何进行架构设计?说一下你的思路。

其实聊到这个问题,一般面试官要考察两块:

你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。

看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。说实话,问类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,大多数人就是平时埋头用,从来不去思考背后的一些东西。类似的问题,比如,如果让你来设计一个Spring 框架你会怎么做?如果让你来设计一个Dubbc框架你会怎么做?如果让你来设计一个MyBatis 框架你会怎么做?

其实回答这类问题,说白了,不求你看过那技术的源码,起码你要大概知道那个技术的基本原理、核心组成部分、基本架构构成,然后参照一

些开源的技术把一个系统设计出来的思路说一下就好。

比如说这个消息队列系统,我们从以下几个角度来考虑一下:

首先这个mq得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下kafka的设计理念,broker->topic->partition,每个partition放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给topic增加partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?

其次你得考虑一下这个mq的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?

顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是kafka的思路。

其次你考虑一下你的mq的可用性啊?这个事儿,具体参考之前可用性那个环节讲解的kafka的高可用保障机制。多副本->leader&

follower->broker 挂了重新选举leader即可对外服务。

能不能支持数据0丢失啊?可以的,参考我们之前说的那个kafka数据零丢失方案。

mq 肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题

可以刷掉一大批人,因为大部分人平时不思考这些东西。

ActiveMQ

消息队列(第一节)

1.消息队列产品有好多种,kafka、rabbitMQ 、rocketMQ 、activeMQ 等

在学习这些产品时,都需要从以下几个方面来着手
1)常用的API 如何发送接收消息
2)如何实现MQ高可用
3)MQ的集群和容错机制
4)MQ的持久化
5)MQ如何延迟和定时发送消息,如何保证消息有序
6)MQ的签收机制
7)这些MQ如何和Spring、SpringBoot 整合
8)这些消息队列有什么不同,使用场景有那些差异?
9)他们是用哪些语音开发的?
kafka(java、scale)、rabbitMQ(erlang)、rocketMQ(java)、activeMQ(java)

2.电商业务中的秒杀模块的操作:

读取订单、库存检查、库存冻结、余额查询、余额冻结、订单生成、余额扣减、库存扣减、生成流水、余额解冻、库从解冻

3. activeMQ 的两个端口 61616 后台端口,8161 web页面端口

4.查看后台程序是否存活

ps -ef|grep activemq | grep -v activemq
netstart -anp|grep 61616
lsof -i:61616

5.linux关闭防火墙命令

1)查看防火墙状态:
service iptables status
systemctl status firewalld
2)暂时关闭防火墙
systemctl stop firewalld
service iptables stop
3)永久关闭防火墙
systemctl disable firewalld
service iptables off
4)重启防火墙
systemctl enable firewalld
service iptables restart
5)查看版本
forewalld -cmd -version

6.消息队列工作流程

1)创建连接工厂
2)连接工厂创建连接,得到连接
3)连接创建session
4)session创建消息生产者或者消息消费者
5)消息生产者组装消息,并发送 

activeMQ 使用场景(第二节)

问题引入

1.在什么情况下使用消息中间件?
2.为什么要使用消息中间件?

解耦 系统之间接口耦合太高
异步 同步操作太费时间,例如 注册发送邮件 XXX
消峰 双十一 春运等高并发场景  

activeMQ 官网地址

activeMQ Java简单实现(第三节)

两种通讯方式

点对点(队列) ;
订阅发布(主题)

pom.xml

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
  	
  	<dependencies>
        <!--activemq所需要的jar包-->
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-all</artifactId>
            <version>5.15.9</version>
        </dependency>
 
        <dependency>
            <groupId>org.apache.xbean</groupId>
            <artifactId>xbean-spring</artifactId>
            <version>3.16</version>
        </dependency>
 
 
        <!-- 下面是通用jar包-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
 
        <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>
 
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
 
    </dependencies>

队列生产者

package com.ttzz.activemq;


import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;
/**
 * 创建生产者
 * @Description: 
 * @author: tangzhong
 * @date: 2021年3月15日 下午6:31:41
 */
public class ActiveMQProduceByQueue {
	
	public static String url = "tcp://localhost:61616";
	public static String queueName = "myQueue";
	
	public static void main(String[] args) throws JMSException {
		//1.获取工厂
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(url);
		//2. 创建连接
		Connection connection = activeMQConnectionFactory.createConnection();
		connection.start();
		//3.创建会话
		// 第一个参数 是否开启开启事务
		// 第二个参数 是签收模式
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		//4. 创建目的地 Queue 
		Queue queue =session.createQueue(queueName);
		//5. 创建生产者
		MessageProducer messageProducer = session.createProducer(queue);
		//6. 发送消息
		for (int i = 0; i < 4; i++) {
			TextMessage textMessage = session.createTextMessage("queue......"+ i );
			messageProducer.send(textMessage);
		}
		//关闭资源
		messageProducer.close();
		session.close();
		connection.close();
		System.out.println("OOKK");
	}
}

队列消费者

有两种接收方式:
同步阻塞
异步非阻塞

package com.ttzz.activemq;

import java.io.IOException;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;

/**
 * 创建消费者
 * @Description: 
 * @author: tangzhong
 * @date: 2021年3月15日 下午6:32:57
 */
public class ActiveMQConsumerByQueue {
	public static String url = "tcp://localhost:61616";
	public static String queueName = "myQueue";
	public static void main(String[] args) throws JMSException, IOException {
		//1.获取工厂
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(url);
		//2. 创建连接
		Connection connection = activeMQConnectionFactory.createConnection();
		connection.start();
		//3.创建会话
		// 第一个参数 是否开启开启事务
		// 第二个参数 是签收模式
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		//4. 创建目的地 Queue 
		Queue queue =session.createQueue(queueName);
		//5. 创建消费者
		MessageConsumer messageConsumer = session.createConsumer(queue);
		//使用同步阻塞的方式
//		while(true) {
//			TextMessage textMessage = (TextMessage) messageConsumer.receive();
//			if(textMessage!=null) {
//				System.out.println("****消费者接收到消息:"+textMessage.getText());
//			} else {
//				break;
//			}
//			System.out.println(textMessage.getText());
//		}
		//使用异步非阻塞的方式  监听器 
		messageConsumer.setMessageListener(new MessageListener() {

			public void onMessage(Message arg0) {
				TextMessage textMessage = (TextMessage) arg0;
				if(textMessage!=null) {
					try {
						System.out.println("****消费者接收到消息:"+textMessage.getText());
					} catch (JMSException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				} 
				try {
					System.out.println(textMessage.getText());
				} catch (JMSException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
			
		});
		 System.in.read();  //保证控制台不关
		//关闭资源
		messageConsumer.close();
		session.close();
		connection.close();
		System.out.println("OOKK2");
	}
}

主题生产者

package com.ttzz.activemq;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;

import org.apache.activemq.ActiveMQConnectionFactory;

public class ActiveMQProduceByTopic {
	public static String url = "tcp://localhost:61616";
	public static String topicName = "myTopic";
	
	public static void main(String[] args) throws JMSException {
		//1.获取工厂
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(url);
		//2. 创建连接
		Connection connection = activeMQConnectionFactory.createConnection();
		connection.start();
		//3.创建会话
		// 第一个参数 是否开启开启事务
		// 第二个参数 是签收模式
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		//4. 创建目的地 Topic 
		Topic queue =session.createTopic(topicName);
		//5. 创建生产者
		MessageProducer messageProducer = session.createProducer(queue);
		//6. 发送消息
		for (int i = 0; i < 4; i++) {
			TextMessage textMessage = session.createTextMessage("myTopic......"+ i );
			messageProducer.send(textMessage);
		}
		//关闭资源
		messageProducer.close();
		session.close();
		connection.close();
		System.out.println("OOKK");
	}
}

主题消费者

package com.ttzz.activemq;

import java.io.IOException;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;

import org.apache.activemq.ActiveMQConnectionFactory;

public class ActiveMQConsumerByTopic {
	public static String url = "tcp://localhost:61616";
	public static String topicName = "myTopic";
	public static void main(String[] args) throws JMSException, IOException {
		//1.获取工厂
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(url);
		//2. 创建连接
		Connection connection = activeMQConnectionFactory.createConnection();
		connection.start();
		//3.创建会话
		// 第一个参数 是否开启开启事务
		// 第二个参数 是签收模式
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		//4. 创建目的地Topic
		Topic queue =session.createTopic(topicName);
		//5. 创建消费者
		MessageConsumer messageConsumer = session.createConsumer(queue);
		//使用同步阻塞的方式
//		while(true) {
//			TextMessage textMessage = (TextMessage) messageConsumer.receive();
//			if(textMessage!=null) {
//				System.out.println("****消费者接收到消息:"+textMessage.getText());
//			} else {
//				break;
//			}
//			System.out.println(textMessage.getText());
//		}
		//使用异步非阻塞的方式  监听器 
		messageConsumer.setMessageListener(new MessageListener() {

			public void onMessage(Message arg0) {
				TextMessage textMessage = (TextMessage) arg0;
				if(textMessage!=null) {
					try {
						System.out.println("****消费者接收到消息:"+textMessage.getText());
					} catch (JMSException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				} 
				try {
					System.out.println(textMessage.getText());
				} catch (JMSException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
			
		});
		 System.in.read();  //保证控制台不关
		//关闭资源
		messageConsumer.close();
		session.close();
		connection.close();
		System.out.println("OOKK2");
	}
}

消费者的三种情况

	/**
	 * 1. 先生成,只启动一个消费者,第1个消费者能消费吗 ?  能
	 * 2. 先生成,先启动一个消费者,再启动一个消费者,第2个消费者能消费吗 ?  no
	 * 3. 先启动两个消费者,然后再启动生成着,第二个消费者可以消费吗 ?Y 采用轮询的方式进行消费
	 */

topic 简介

前提

1.先启动消费者 然后再启动生成者,只有订阅了,才能接收到订阅的消息

生成者

java 复制代码
package com.ttzz.activemq;

import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;

import org.apache.activemq.ActiveMQConnectionFactory;

public class ActiveMQProduceByTopic {
	public static String url = "tcp://localhost:61616";
	public static String topicName = "myTopic";
	
	public static void main(String[] args) throws JMSException {
		//1.获取工厂
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(url);
		//2. 创建连接
		Connection connection = activeMQConnectionFactory.createConnection();
		
		//3.创建会话
		// 第一个参数 是否开启开启事务
		// 第二个参数 是签收模式
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		//4. 创建目的地 Topic 
		Topic topic =session.createTopic(topicName);
		//5. 创建生产者
		MessageProducer messageProducer = session.createProducer(topic);
		
		messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
		connection.start();
		//6. 发送消息
		for (int i = 0; i < 4; i++) {
			TextMessage textMessage = session.createTextMessage("myTopic......"+ i );
			textMessage.setStringProperty("自定义消息的key", "自定义消息的value");
			messageProducer.send(textMessage);
		}
		//关闭资源
		messageProducer.close();
		session.close();
		connection.close();
		System.out.println("OOKK");
	}
}

消费者

java 复制代码
package com.ttzz.activemq;

import java.io.IOException;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;

import org.apache.activemq.ActiveMQConnectionFactory;

public class ActiveMQConsumerByTopic {
	public static String url = "tcp://localhost:61616";
	public static String topicName = "myTopic";

	public static void main(String[] args) throws JMSException, IOException {
		// 1.获取工厂
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(url);
		// 2. 创建连接
		Connection connection = activeMQConnectionFactory.createConnection();
		
		connection.setClientID("消费者2");
		System.out.println("消费者2");
		
		// 3.创建会话
		// 第一个参数 是否开启开启事务
		// 第二个参数 是签收模式
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		// 4. 创建目的地Topic
		Topic topic = session.createTopic(topicName);
		// 5. 创建消费者
		TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "remark..."); // 创建持久化的订阅

		connection.start();

		Message message = topicSubscriber.receive();
		while (message != null) {
			TextMessage textMessage = (TextMessage) message;
			System.out.println(textMessage.getText());
			message = topicSubscriber.receive();
		}

		session.close();
		connection.close();
		System.out.println("OOKK2");
	}
}

先启动两个消费者,然后再启动生产者

消费者控制台

消费者1

消费者2

MQ界面

JMS 规范以及消息特性

JMS规范是什么

它是JavaEE体系中的一项Message Service

常用消息中间件比较

JMS组成和特点

JMS provider
实现jms接口的消息中间件
JMS Producer 、JMS Constomer
复制代码
JMS Message 消息头
1)jms destination 消息目的地 队列或者主题
2)jms deviverymode 持久化方式
3)jms expiration 消息过期时间 
4)jms 优先级 1到4是普通消息  5-9是加急消息
5)消息id  唯一识别每个消息的标识,是有MQ自己生成
消息头之destination

当然,也可以通过消息进行设置

四种重载:目的地,消息,优先级,存活时间,是否持久化

消息的目的地:队列 和 主题

持久性

消息的过期时间 默认是永不过期的

消息体
发送的消息类型有哪些:
StringMessage MapMessage ByteMessage StringMessage ObjectMessage 五中类型

要求:发送的消息体和接受的消息体要求类型一致。

要求:发送的消息体和接受的消息体要求类型一致。

自定义的消息属性
自定义的消息属性能有什么还用呢 ?
去重、识别、重点标注等	

TextMessage textMessage = session.createTextMessage("myTopic......"+ i );
messageProducer.send(textMessage);
textMessage.setStringProperty("自定义消息的key", "自定义消息的value");

如何保证消息的可靠性???

消息的可靠性可以从以下四个方面来回答:
1)消息的持久性
2)消息的事务特性
3)消息的签收机制
4)消息持久化

(队列)消息的持久性

验证1:

设置消息为非持久性,然后生产消息,(服务器不关闭),再去消费消息

消息被正常消费

验证2:

设置消息为非持久性,然后生产消息,(服务器关闭),再去消费消息

生成出的消息

服务器关闭之后,再去消费消息

消息丢失,不能被消费。

刚刚生成的消息被丢失了

验证3:设置消息为持久性,然后生成消息。这个是刚刚生成的消息。

关闭消息服务器,再次重新启动消息,消息依旧存在

去消费消息。消息成功被消费

(topic)消息的持久性

对于topic消息,持久性没有多大意义,因为在topic模式中,需要先启动消费者,然后再启动生产者,如果设置了消息持久性,但是,还没有启动动消费者,则这些消息就会被丢失,不能被消费者消费

设置持久化的topic生成者

java 复制代码
package com.ttzz.activemq;

import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;

import org.apache.activemq.ActiveMQConnectionFactory;

public class ActiveMQProduceByTopic {
	public static String url = "tcp://localhost:61616";
	public static String topicName = "myTopic";
	
	public static void main(String[] args) throws JMSException {
		//1.获取工厂
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(url);
		//2. 创建连接
		Connection connection = activeMQConnectionFactory.createConnection();
		
		//3.创建会话
		// 第一个参数 是否开启开启事务
		// 第二个参数 是签收模式
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		//4. 创建目的地 Topic 
		Topic topic =session.createTopic(topicName);
		//5. 创建生产者
		MessageProducer messageProducer = session.createProducer(topic);
		
		messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
		connection.start();
		//6. 发送消息
		for (int i = 0; i < 4; i++) {
			TextMessage textMessage = session.createTextMessage("myTopic......"+ i );
			textMessage.setStringProperty("自定义消息的key", "自定义消息的value");
			messageProducer.send(textMessage);
		}
		//关闭资源
		messageProducer.close();
		session.close();
		connection.close();
		System.out.println("OOKK");
	}
}

topic 消费者

java 复制代码
package com.ttzz.activemq;

import java.io.IOException;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;

import org.apache.activemq.ActiveMQConnectionFactory;

public class ActiveMQConsumerByTopic {
	public static String url = "tcp://localhost:61616";
	public static String topicName = "myTopic";

	public static void main(String[] args) throws JMSException, IOException {
		// 1.获取工厂
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(url);
		// 2. 创建连接
		Connection connection = activeMQConnectionFactory.createConnection();
		
		connection.setClientID("消费者1");
		System.out.println("topic消费者1");
		
		// 3.创建会话
		// 第一个参数 是否开启开启事务
		// 第二个参数 是签收模式
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		// 4. 创建目的地Topic
		Topic topic = session.createTopic(topicName);
		// 5. 创建消费者
		TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "remark..."); // 创建持久化的订阅

		connection.start();

		Message message = topicSubscriber.receive();
		while (message != null) {
			TextMessage textMessage = (TextMessage) message;
			System.out.println(textMessage.getText());
			message = topicSubscriber.receive();
		}

		session.close();
		connection.close();
		System.out.println("OOKK2");
	}
}

验证1:启动一个消费者

Active Durable Topic Subscribers :处于激活状态的持久化的topic消费者

Offline Durable Topic Subscribers:处于离线状态的持久化的topic消费者

启动持久化的topic生成者:

topic消费者消费消息

消息服务器

验证2:将消费者1 关闭,启动消费者2

消费者1处于离线状态,启动生成者消费

消费者2能够正常消费消息

再次启动消费者1,消费者1也能正常消费消息

消息的事务特性

事务主要是针对生产者,签收主要针对消费者

	//3.创建会话
	// 第一个参数 是否开启开启事务
	// 第二个参数 是签收模式
	Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

验证1:

事务设置为 false,执行发送消息,消息自动到服务器

因为设置了消息的持久性,关闭服务器,再次重启启动,该消息依旧存在

验证2:

事务设置为 true,执行发送消息,

看看服务器有没有收到消息。服务器中没有收到刚刚发送的消息。因为没有做消息的提交操作

提交事务

消息入队

事务对于多个消息同时发送,能够保证原子性

session.rollback();

为了验证的需要,需要重启的时候,删除持久化的消息

操作:在配置文件activemq.xml的broker字段添加deleteAllMessagesOnStartup="true"

可以看到持久化的消息被删除,

生成者开启事务,将消息发送到消息服务器

看到消息

消费者消费掉消息

再次启动一个消费者,发现消息已经被消费,说明消息不能被重新消费

验证2:设置消费者开启事务,但是,没有提交事务【消息被重复消费】。第一次,消费者1正常消费消息,

但是在服务器看到:消息没有被消费

再次启动另一个消费者,发现消息可以被多次消费

一个有趣的现象

生成者以事务的方式将生成者发送到服务器

消费者开启事务进行消费,但是,没有提交事务。保证控制到不灭。再次启动2号消费者,发现不能重复消费???

如果(去掉: System.in.read();)设置消费者4秒之后,没有消息,自动关闭。则启动2号消费者,可以重复消费。

原因呢???哈哈哈哈

消息的签收

非事务的签收有三种

1)自动签收 Session.AUTO_ACKNOWLEDGE

2)手动签收 Session.CLIENT_ACKNOWLEDGE

3)允许重复消息 Session.DUPS_OK_ACKNOWLEDGE ???这个我没有验证通过

对生产者而言,如果开启了事务,则签收机制可以随便选择,事务的优先级高于签收机制

验证1:生产者未开启事务,采用自动签收的方式将消息发送到服务器

消费者采用手动签收,发现消息可以重复消费。

消息发送到服务器,运行消费者程序,消息没有被消费掉

开启消息签收机制后,消息不能重复消费

事务模式下的签收

生产者开启事务,签收模式为自动签收,将消息发送到服务器

消费者开启事务,采用手动签收模式,但是消息没有使用ack机制。

消息仍然被消费掉

验证:

生产者开启事务,签收模式为自动签收,将消息发送到服务器;

消费者开启事务,采用手动签收模式,消息使用ack机制。但是没有commit。消息能被重复消费

结论:

在事务性会话中,当一个事物被成功提交则消息被自动签收。如果事物回滚,则消息会被再次传送。

非事物性会话中,消息何时被确认取决于创建会话时的应答模式(acknowledgement)

activeMQ两种模式比较

1)工作模式上来说,主题采用订阅发布模式,如果没有订阅者消息就会被丢弃;如果有多个订阅者,则
就会被多个订阅者接收;队列采用一对一的模式,如果当前消息没有消费者,则该
消息也不会丢弃,如果有多个消费者,那么该消息只能被一个消费者消费,同时要求
消费者发送ack确认信息
2)从有无状态上来看,主题是无状态的,队列会默认在服务器上以文件的形式保存,
也可以配置DB存储
3)从消息传递的完整性来看,主题如果没有订阅者,则消息会丢弃,而队列不会
4) 处理效率,主题会随着订阅者的增多效率减低,而队列不会

activemq传输协议

http://activemq.apache.org/configuring-version-5-transports.html

activemq 默认采用tcp协议

在网络传输之前,序列化数据为字节流 open wire

tcp协议的优点

可靠性高稳定性强;效率高,采用字节流的方式传递;高效性、可用性支持所有平台

nio协议:

auto+nio

java 复制代码
//	public static String url = "tcp://localhost:61616";
//	public static String url = "nio://localhost:61618";
	public static String url = "auto+nio://localhost:5671";
相关推荐
翱翔-蓝天20 小时前
Spring Boot 3 集成 RabbitMQ 实践指南
spring boot·rabbitmq·java-rabbitmq
luckilyil21 小时前
RabbitMQ学习—day6—springboot整合
spring boot·rabbitmq·java-rabbitmq
天天向上杰1 天前
简识MQ之Kafka、ActiveMQ、RabbitMQ、RocketMQ传递机制
kafka·rabbitmq·rocketmq·activemq
一个假的前端男1 天前
RabbitMQ 消息队列
分布式·rabbitmq
布谷歌3 天前
Oops! 更改field的数据类型,影响到rabbitmq消费了...(有关于Java序列化)
java·开发语言·分布式·rabbitmq·java-rabbitmq
一个假的前端男3 天前
RabbitMQ 消息队列 优化发送邮件
分布式·rabbitmq·ruby
A尘埃3 天前
关闭超时订单和七天自动确认收货+RabbitMQ规范
分布式·rabbitmq
m0_748241233 天前
RabbitMq 基础
分布式·rabbitmq·ruby
星星点点洲3 天前
【RabbitMQ业务幂等设计】RabbitMQ消息是幂等的吗?
rabbitmq
劉煥平CHN4 天前
RabbitMQ的脑裂(网络分区)问题
网络·分布式·rabbitmq