之前有写过啥是消息中间件,如何单机安装RockMQ,接下来让我们看看什么是RockMQ是重点学习的。一些夸它的话就不累赘了。先明白RocketMQ 是基于发布订阅模型的消息中间件。所谓的发布订阅就是说,consumer 订阅了 broker 上的某个 topic,当 producer 发布消息到 broker 上的该 topic 时,consumer 就能收到该条消息。这得看RockMQ部署结构图了。
先来一张图,我们能用图说话,就不用字,能用红包就不开口哈~
画的不好,但是就是表达RockMQ部署结构图里面各个组件相互之间的关联:
一、nameServer是集群管理中心,就像kafka的集群管理中心为zookepper一样,RockMQ有自己的集群管理。nameServer像是一个路由器控制中心,开启之后会监听端口,等待broker、productor、comsumer 的连接。
二、broker和所以的nameServer之间也是使用长连接保持联系的,将其端口、IP、所有的topic信息都放在心跳包里面和nameServer取得联系后并且在其中注册,这样每一个对应的topic在nameServer集群中都可以找到和Broker的映射。
三、在发送消息之前的话,是需要先创建topic的,需要指定Topic存储在哪些Broker上又或者通过在发消息的时候自动创建Topic。
四、Productor 先会和NameServer集群中的其中一个建立连接,从NameServer上查询需要发送的消息的Topic是存储在哪些Broker上,在找到对应的Broker发送消息。单个生产者和该生产者关联的所有broker保持长连接,生产者每隔30s向所有broker发送心跳,broker每隔10s,扫描所有还存活的连接,若某个连接2min内(当前时间与最后更新时间差值超过2min,此时间无法更改)没有发送心跳数据,则关闭连接。连接断开会移除broker上的生产者信息。
五、Consumer也是想和NameServer集群中的其中一个建立长连接。获取在NameServer上订阅的Topic消息,是存储在哪些Broker上,找到对应的Broker,然后消费消息。单个消费者和该消费者关联的所有Broker保持长连接,每隔30秒向所有Broker发送心跳,Broker每隔10s,扫描所有还存活的连接,若某个连接2分钟内没有发送心跳数据,则关闭连接,并向该消费者分组的所有消费者发出通知,分组内消费者重新分配队列继续消费。在消费者挂掉和心跳超时导致Broker主动关闭连接,Broker会立即感知到,并向该消费者分组的所有消费者发出通知,分组内消费者重新分配队列继续消费。
其中RockMQ不支持自动选主,通过指定brokerName和brokerId来指定主从,brokerName相同,broker=0的时候为主,其他时候为从。
也不支持主从切换,不像kafka那样支持主从切换,有N个副本,允许N-1个失效的,并且是Master宕机后,自动找一个slave切换为主。RockMQ是如果Master挂了,就没办法向Master发送消息(?),cusumer会在30秒之后才知道对方挂了,然后Master不能立即恢复,对应的异步复制的数据就会部分丢失。
还有就是RocketMQ支持顺序消费和指定消费(?),消费消息的类型有普通消息、定时消息(Level1)、事务消息等。支持使用Tag来筛选消息,使用MessageId来查询,MessageKey来查询消息。如果消费发送失败支持失败重试,其offset存储在Broker中。
上面说到的特性里面有讲到Tag帮我们筛选消息,在可以直接获取Broker端就帮我们处理好的消息,大大减少了在 Consumer 端的消息过滤处理,一方面减少了代码量,另一方面更减少了不必要消息的网络传输消耗。加上如果一个Topic需要接受和发送的数据量超级大,可以通过增加并行处理的机器,使得一个Topic对应一个或者多个MessageQueue,这样就可以使得消息并行的发送给每一个Message Queue,然后Consumer也可以并行地从多个MessageQueue读取消息并消费。
那么多消息怎么才能算是注册和订阅消费的是一致的呢?我们都知道如果订阅关系不一致就会导致消费消息混乱或者丢失。所以这要求一定要订阅消息一致,其中只有通过使用了同一个Group Name,订阅的Tag也是一样的Consumer实例才能组成Customer集群,其中消费者的Topic和生产者的Topic要一致,并且Topic中的tag一样才算是一致,才能让消费者消费正确的消息。
首先什么样式消息消费失败了呢?如下三种为消费失败
对于已经设置过指定状态值,出现前面两种即为消费者告诉Broker,这条消息消费失败了,请重新投递一下。第三种是在处理消费逻辑的地方报异常,在没有被捕捉异常的情况下,Broker会重新投递此条消息,但是被捕获就不会重新捕获了。当这种情况出现,消息重试就是当消费者消费消息失败后,Broker 会重新投递该消息,直到消费成功,让消费者一定消费成功。
那是一定会有消息重试吗?不是的,消息重试只针对集群消费模式,广播消费没有消息重试的特性,消费失败之后,只会继续消费下一条消息。在 RocketMQ 中,当消费者使用集群消费模式时,消费者接收到消息并进行相应的逻辑处理之后,最后都要返回一个状态值给 Broker。这样Broker 才知道是否消费成功,需不需要重新投递消息。也就是说,我们可以通过设置返回的状态值来告诉 Broker 是否重新投递消息。
还有一点就是消息重试默认只会重试16次,超过16次该消息将不会再投递给消费者,而是被放到对应的死信队列中。对于这个队列的消息出现有可能是消息有问题或者是与消费逻辑的调用有关系,一般需要人工判断之后再处理一次。
RocketMQ会在发送和投递两种情况下出现消息重复,主要是和Broker的连接断开导致。
第一种出现在生产者身上,当生产者发送消息,消息已成功发送到 broker ,但是此时可能发生网络闪断或者生产者宕机了,导致 broker 发回的响应失败。这时候生产者由于没有收到响应,认为消息发送失败,于是尝试再次发送消息给 broker。这样一来,broker 就会再收到一条一摸一样内容的消息,最终造成了消费者也收到两条内容一摸一样的消息。
第二种出现在消费者身上,当消费者消费消,消息已投递到消费者并完成消费逻辑处理,当消费者给 broker 反馈消费状态时可能发生网络闪断。broker 收不到消费者的消费状态,为了保证至少消费一次的语义,broker 将在网络恢复后再次尝试投递之前已经被处理过的消息,最终造成消费者收到两条内容一摸一样的消息。
对于那些不允许消息重复的业务场景来说,需要通过业务上的唯一标识来作为幂等处理的依据来消费消息。
那什么是消费幂等呢?简单来说就是对于一条消息的处理结果,不管这条消息被处理多少次,最终的结果都一样。比如说,你收到一条消息是要更新一个商品的价格为 6.8 元,那么当这条消息执行 1 次,还是执行 100 次,最终在数据库里的该商品价格就是 6.8 元,这就是所谓的幂等。这样的话不管消费同一条消息几次,最终得到的结果一样就不会出现消息重复了。