各位在面试过程中比较被常问到的中间件可能就有MQ吧,提起MQ,不能说都用过,但是也都听过吧,实在不行叫消息队列也行(开玩笑)。活跃在各种项目中的MQ主要有Kafka、RocketMQ、RabbitMQ、ActiveMQ这些。这次我们学习的是RocketMQ,这款由阿里开发,现如今成为Apache 的顶级项目,完成了双十一的考验,足以证明了它的强大,废话不多说了,我们开始吧!
RocketMQ会在项目中发挥什么作用呢?
1、解耦合:通过rocketMQ将生产者和消费者之间的业务没有联系,互相不受影响。
2、削峰:将大量请求的消息发送到RocketMQ,然后再通过订阅消息队列来处理业务(负载均衡)。
3、异步:使原本同步的业务链异步化,调高业务的响应速度。
4、提供了分布式事务的解决方案。
RocketMQ结构
从官网的结构图来看就很强大,每个角色都是三五成群的,没错,这家伙天生就是支撑分布式的,下边好好看一下这张图。
Broker:这个算是RocketMQ的核心了,别问为什么,因为它不管和谁都有瓜葛。它就是用来存储消息(MQ)和主体(Topic)之间的映射关系的。如图所示,可以作为集群,分master和slave,每个master存储一部分数据,为了数据的高可用,每个master节点可以有多个slave节点。可以有多组master-slave,其中master之间数据是无法同步的,master和自己的slave之间有数据同步。
NameServer:和zookeeper类似,是broker路由的注册中心。如图所示,可以作为集群,多个nameServer之间是互相独立的。
Producer:消息生产者。可以作为组。
Consumer:消息消费者。可以作为组。
我们从图中可以看出来这些角色直接关系有点复杂,我们这里对它们梳理一下。
上边我们重复提到了一个概念就是Topic(主题),在这里我们可以将他理解为是一个相同类型的队列的一个集合。我们在发送消息的时候,由于我们从NameServer获取到了多台Broker的信息并建立连接,我们发送消息的时候通过轮询会将消息发到不同的broker上,会造成一种现象,即使我们的消息是在同一Topic上,但是不能保证他们在同一台broker服务器上,说白了,Topic和Broker他们之间就是多对多的关系。
RocketMQ安装部署
RocketMQ的安装部署其实已经很明了了,在RocketMQ官方网站都有,这里我先说说大家可能会遇到的问题。
1、首先检查服务器端口有没有开放和检查阿里云安全组规则栏中有没有配置。
2、编辑runserver.sh和runbroker.sh文件,修改JVM内存配置,默认的配置太高了。
3、在启动broker的时候指定自动创建Topic,和指定ip端口,即启动命令添加 autoCreateTopicEnable=true 和 -d 192:168:*:*:9876(nameServer地址)。
4、需要指定brokerIP,在配置文件中添加brokerIP1= 你的公网地址,否则会有跨域问题,外网无法访问。
值得注意的几个地方看一下
1、修改runserver.sh和runbroker.sh文件的配置,这俩个脚本都都 /rocketmq/bin 下,主要是它的默认值太大了,大家根据自己的服务器配置进行修改,这里当然是越大性能越好了,如果是自己玩,就和我一样写个256m就行了:
2、然后看一下配置文件:
我们可以从配置文件中就可以看出来,RocketMQ提供了多种模式,有2m-2s-async(双主双从异步)、2m-2s-sync(双主双从同步)和2m-noslave(双主)三种模式,都提供了配置文件的模板,我们直接修改就行了,我们拿出一个配置文件来看一下:
在/conf目录下还有log的配置文件,我们需要对其进行修改,把${user.home}都改了:sed -i 's#${user.home}#/usr/rocketmq#g' *.xml。
RocketMQ API介绍
在使用之前,我先声明一下我这里RocketMQ的版本是4.6,可能在有些地方和大家的会存在差异。
RocketMQ的使用可以分为多种消费模式,包括普通消费模式,顺序消费模式,批量消费模式,广播消费模式,事务消息的消费模式,在将之前,我们先去看一下几个比较重要的对象,包括生产者(Producer),消费者(Consumer),消息载体(Message)等。
先看一下DefaultMQProducer(生产者)的属性都代表什么意思:
看一下消息的载体Message是怎么构造的,其中Topic代表消息将要发到那个主体下,tags指的是过滤条件,keys指的是索引,body指的是消息内容。
在看一下最关键的,如何去发送消息。在API中提供了3中发送方式,分别是同步发送,异步发送,单向发送。
1、同步发送:Producer 发送消息后,在收到 Broker 响应后才继续发下一条消息的通信方式。由于这种同步发送的方式确保了消息的可靠性,同时也能及时得到消息发送的结果,故而适合一些发送比较重要的消息场景。
2、异步发送:异步发送是指 Producer 发出一条消息后,不需要等到Broker响应后再去发消息(通过回调的方式去获取响应),就接着发送下一条消息的通信方式。由于异步发送不需要等待Broker 的响应,因此在一些比较注重响应时间的场景就会比较适用。
3、单向发送:Producer只负责发送消息,无法获取broker的响应。由于单向发送只是简单地发送消息,不需要等待响应,故发送消息所耗时非常短,但是说明消息无法得到验证,导致消息的不可靠性增高。
最后需要了解的就是Consumer(消费者),提到消费者,提供了俩种消息的获取方式DefaultMQPushConsumer和DefaultMQPullConsume,这俩种对象都可以获取消息,不过在4.6版本,给DefaultMQPullConsume来了一刀,已经不建议使用了,在这里还是要给大家介绍一下:
DefaultMQPushConsumer:Broker主动向Consumer推送消息,Consumer会提供一个监听接口,一旦接收到消息,Consumer对象立刻回调监听接口方法消费消息。我们可以看出这种方式实时性高,但增加服务端负载,,如果消费者的速度比发送者的速度慢很多,会造成消息在broker的堆积等问题。
DefaultMQPullConsume:Consumer主动向Broker拉消息,可以批量获取消息。取消息的过程需要用户自己去实现,首先从Topic拿到MessageQueue的集合,遍历MessageQueue集合,然后针对每个MessageQueue批量取消息,一次取完后,记录该队列下一次要取的开始offset,直到取完了,再换另一个MessageQueue。虽说是主动从Broker拉取消息,可控性好,但是时间间隔不好设置,间隔太短,则空请求会多,浪费资源,间隔太长,则消息不能及时处理。
这里我们就只针对DefaultMQPushConsumer来学习了,里边的东西有点多,好多都是查阅资料的,理解的也不是很到位,这里先做个记录:
RocketMQ使用
关于怎么使用,就直接上代码了,其实这样 demo RocketMQ都有提供,大家也可以去参考。
普通消费模式
我们这里体现一下RocketMQ消息的三种发送模式(同步,异步,单向),在测试异步的时候,不要调用producer.shutdown()方法,否则回调的时候无法连接NameServer服务器了。
顺序消费模式
在上代码之前,我们需要了解一下顺序消费的原理,怎么才能做到消费。我们在发送消息的时候,消息会均匀分布到不同的Queue(队列)上,导致我们在消费的时候,无法保证消费的顺序,其实在这我们只需要指定队列就可以了,因为队列本身就是有序,最笨的方法就是我们控制这个Topic下只有一个队列,促使消息都被发送到这个队列上,这样可以实现顺序消费,但是这样在并发量大的情况下,效率会很低,再这样的情况下,MessageQueueSelector就应运而生了,原理其实都是一样的,就是在发送消息的时候去指定队列。说了半天那么RocketMQ顺序消费到底是怎么实现的呢?
1、rocketmq保证消息一定要发送到同一个队列。
2、队列只有一个消费者,也就是说同一个队列不能出现多个消费者并行消费的情况。
批量消费模式
看到批量消息消费的结果后,我们会发现,批量发送的消息他们也都是在同一队列,只不过队列是随机,不是由生产者去指定,到这里我就在想,这个情况和顺序消费的时候很相似,那消费者的监听器换成MessageListenerOrderly,是否也可以做到顺序消费呢?测试了一波,竟然真的可以,这可咋整?找了半天没什么结果,但是我的理解可能就是,使用顺序发送消息的时候可以有多个生产者去发送,但是只有一个消费者消费;而批量发送消息就是只能一个生产者去发送,一个消费者去消费。如果有什么不对的地方,还希望各位能够说出来交流一下。
广播消费模式
广播消费相对于其他的消费模式来说,应该是比较简单的,RocketMQ默认的消费模式是集群模式,我们在这里只需要修改消费模式即可:
事务消息模式
在分布式项目中,事务是一个必须解决的问题。RocketMQ本身也提供了对事务的处理方案,属于二阶段提交,主要发生在Producer和Broker之间,我们先去了解一下RocketMQ事务是怎么解决的,主要分为下边几个步骤:
1、Producer发送消息到Broker,此时消息仅在broker上记录,但是不供消费者消费,即此时消费者获取不到这条消息。
2、Broker存储了消息之后会向Producer返回本次消息的transactionId等信息,之后可以在executeLocalTransaction方法中实现自己本地的事务。
3、执行本地事务,并返回COMMIT_MESSAGE(提交消息),ROLLBACK_MESSAGE(回滚消息)。
4、Broker收到Producer返回的状态,如果是COMMIT_MESSAGE,则之前记录在broker的消息改为对消费者开放,进行消费;如果是ROLLBACK_MESSAGE,则回滚。
5、若Broker长时间未收到Producer返回的状态,则向业务系统询问本地事务状态,即checkLocalTransaction方法中的实现,并做补偿处理。
此处有一个误区,我反正掉进去纠结了小一会。就是在正常发送消息后,进入executeLocalTransaction后处理本地事务,正常返回LocalTransactionState的时候,是不会调用回查方法的;只有在若Broker长时间未收到Producer返回的状态,才会调用,检查本地事务是否执行成功。
从我们打印的日志,我们可以看出在消息真正发送成功之前先执行了executeLocalTransaction方法,并且打印出了transactionId,说明确实是在Broker端生成了一个对Consumer不可见的消息,后经过本地事务之后,才会返回发送成功的状态。
既然是学习,那我们再去看一看返回ROLLBACK_MESSAGE会发生什么事情:
我们可以看到,即使executeLocalTransaction返回的是ROLLBACK_MESSAGE,发送的状态也是SEND_OK,但是消费者端确没有消费记录,大家可以通过返回的本地事务的状态localTransactionState来进行判断本次消息的状态。
最后给大家演示一波调用回查的方法(checkLocalTransaction),消费者端就不贴了,都是一样的:
这里测试发现,如果在executeLocalTransaction中返回 null 或者 UNKNOW 的情况下都会触发回查。如果无法触发回查方法,先去检查一下配置文件中是否开启支持事务回查,即checkTransactionMessageEnable = true。
定时消息(延时)
所谓定时消息(延时),就是在创建Message之后,设置属性DelayTimeLevel即可:
参数是int类型,这个级别需要在服务器的配置文件中体现,即加入下边这行配置:
messageDelayLevel = 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,0代表不延时,后边依次类推。
搭建 rocketmq-console 管理平台
rocketmq-console是RokcetMQ官方提供到的一套消息管理平台,将消息、主题等信息集中管理,通过可视化界面进行操作,进而提高效率。我们可以从官方提供的项目地址获取项目(clone或者下载zip)。
拿到项目,我们只需要修改namesrvAddr即可:
只操作rocketmq-console然后打包,或者直接在本地启动就可以了(我这里就本地跑一下):
这里具体能干什么就不说了,大家配好了自己来点一下就会明白。
到这里,我这次的学习记录就没有了。其实需要我们学习的东西还有很多,比如一些线上发生的消息重复、消息去重等问题的解决方案,奈何自己没有经历过,没有这些问题的解决方案,所以说一些东西也只能从网上学习了,毕竟自己没有处理过,就是没底气啊,好气哦~
写了这么多,基本上全是自己测试过,希望多大家有所帮助。其中可能也会有不足,也会有错误,还是互相交流互相学习吧!加油!