pull类型消息中间件-消息消费者(二)
消费者的实例化
关于consumer的默认实现,metaq有两种:
- DefaultMQPullConsumer:由业务方主动拉取消息
- DefaultMQPushConsumer:通过业务方注册回调方法,由metaq主动推送消息
共同点:
都是消费者,也都提供了start,shutdown方法(吐个槽,这种公用的接口应该MQConsumer接口中,而不是MQPullConsumer与MQPushConsumer各搞一个)
不同点:
具体消费模式不同,PullConsumer提供了各种获取消息的方法,MQPushConsumer提供了各种订阅注册的方法来回调处理
同一类型的消费者通过consumerGroup来区分。
类结构:
Consumer 消息消费者,负责消费消息,一般是后台系统负责异步消费。一般一个应用创建一个Consumer,由应用来维护此对象,可以设置为全局对象或者单例。
consumer的start()方法主要完成以下几件事:
- checkConfig();检查consumer的groupName、Listener等是否设置。consumer的配置需在start()方法前完成。
- copySubscription();复制consumer所有的订阅。
- registerConsumer().注册consumer
- sendHeartbeatToAllBrokerWithLock()。给broker发送心跳
- rebalanceImmediately()订阅消息负载均衡。
底层还是调动netty中com.alibaba.rocketmq.remoting.netty.NettyRemotingClient#invokeAsync
或者com.alibaba.rocketmq.remoting.netty.NettyRemotingClient#invokeSync
无论是consumer还是producer中的send还是pull方法本质上都是调用netty中的invokeSync/invokeAsync进行通信。区别只是在于InvokeCallback回调对象的实现。
消费架构
consumer多余partition
partition多余consumer
消费者可靠性保证
消费者是一条接着一条地消费消息,只有在成功消费一条消息后才会接着消费下一条。如果在消费某条消息失败(如异常),则会尝试重试消费这条消 息(默认最大5次),超过最大次数后仍然无法消费,则将消息存储在消费者的本地磁盘,由后台线程继续做重试。而主线程继续往后走,消费后续的消息。因此, 只有在MessageListener确认成功消费一条消息后,meta的消费者才会继续消费另一条消息。由此来保证消息的可靠消费。消费者的另一个可靠性的关键点是offset的存储,也就是拉取数据的偏移量。默认存储在zoopkeeper上,zookeeper通过集群来保证数据的安全性。Offset会定期保存,并且在每次重新负载均衡前都会强制保存一次,因此可能会存在极端情况下的消息的重复消费。
消息过滤
消息过滤主要使用Message 的Tag字段做的。
- 在服务端,每一条消息对应的Tag被转换成一个8byte的hashcode, 在Broker 端对比Queue中每一个存储单元的的hashcode和 订阅的Tag的hashcode进行对比,不符合,则跳过,继续比对下一个,符合则传输给Consumer。在队列中进行hashcode对比
- Consumer 收到过滤后的消息后,再次将传递过来的Message中的Tag字符串和订阅的Tag字符串进行对比,不是hashcode。这样做可以避免Hash冲突
消息重复性
订阅消息阶段,由于涉及集群订阅,多个订阅方需要使用负载均衡方式订阅,在因负载均衡出现的短暂不一致的情况下可能会重复。
订阅者意外宕机,消费进度未及时存储也会产生息重复。
解决方法:
- Consumer收到消息后,通过Tair,DB去重。
- 使用Pull的方式拉取消息,但是Pull的时候,怎么协调分配队列需要应用控制。
消费时序

- RebalanceService根据NameServer提供的路由信息,执行负载均衡策略。每个consumer都会被尽量平均的分配相应的队列。
- 每一个consumer,RebalanceService都会针对每一个队列派发初始的pullRequest到PullMessageService维护的pullRequest阻塞队列里。
- pullMessageService每次都从pullRequestList里take一个拉消息的请求,并建立与broker的连接,每次默认从Broker获取32条消息。
- 每次pull消息成功后,会异步的调用callback回调函数,callback函数需要做的就是:第一,重新实例化一个新的的pullRequest,将其offset设置为之前已经消费的32条消息之后的offset值,将这个pullRequest重新put到pullRequest队列里。这样就形成了针对一个队列消费的闭环。第二,将之前已经能够获取到的32条消息及其他信息封装成一ConsumeRequest,并submit到ConsumeMessageService维护的ConsumeRequestQueue里。
- ConsumeMessageService 监听到ConsumeRequestQueue里有新的请求时,将会每次起一个线程执行ConsumeRequest里的run函数,这个函数主要是针对每一个消息调用之前Consumer注册的MessageListener函数。
- 顺序消费服务ConsumeMessageConcurrentlyService构建的时候,构建线程池来接收消费请求ConsumeRequest,同时构建一个单线程的本地线程,定时重新消费ConsumeRequest, 用来执行定时周期性锁队列任务
- 周期性锁队列lockMQPeriodically,获取正在消费队列列表ProcessQueueTable所有MesssageQueue,构建根据broker归类成MessageQueue集合Map>;遍历Map>的brokername, 获取broker的master机器地址,将brokerName的Set发送到broker请求锁定这些队列。在broker端锁定队列,其实就是在broker的queue中标记一下消费端,表示这个queue被某个client锁定。 Broker会返回成功锁定队列的集合,根据成功锁定的MessageQueue,设置对应的正在处理队列ProccessQueue的locked属性为true没有锁定设置为false
pull类型消息中间件-消息消费者(二)的更多相关文章
- pull类型消息中间件-消息发布者(一)
消息集群架构 对于发送方来说的关键几要素 topic 消息的主题,由用户定义.类似于知乎的话题,Producer发送消息的时候需要指定发送到某一个topic下面,Consumer从某一个topic下面 ...
- pull类型消息中间件-消息服务端(三)
部署架构 消息存储 存储结构 MetaQ的存储结构是一种物理队列+逻辑队列的结构.如下图所示: Producer生产消息,根据消息的topic选择topic对应某一个分区,然后发送到这个分区对应的Br ...
- push类型消息中间件-消息发布者(二)
1.消息发布者声明 我们以spring的方式来声明一个消息发布者: <bean id="operateLogsMessageManager" class="com. ...
- push类型消息中间件-消息订阅者(一)
1.订阅者的声明方式 我们以spring组件化的方式,声明一个消息订阅者,对于消息订阅者关心的主要有: topic: 一级消息类型(又名消息主题).如TRADE 消息类型:二级消息类型,区别同一Top ...
- push类型消息中间件-消息服务端(三)
1.连接管理 网络架构原来是使用是自己开发的网络框架Gecko,Gecko默认为每个网络连接分配64KB的内存,支持1000个网络连接,就需要大概64MB的内存.后来采用Netty重构了网络服务层. ...
- 消息中间件——RabbitMQ(二)各大主流消息中间件综合对比介绍!
前言 消息队列已经逐渐成为企业IT系统内部通信的核心手段.它具有低耦合.可靠投递.广播.流量控制.最终一致性等一系列功能,成为异步RPC的主要手段之一.当今市面上有很多主流的消息中间件,如老牌的Act ...
- 译MassTransit 创建消息消费者
创建消息消费者一个消息消费者是一个 可以消费一个或多个消息类型的类,指定IConsumer<T>接口,T为消息类型 public class UpdateCustomerConsumer ...
- SpringCloud系列十一:SpringCloudStream(SpringCloudStream 简介、创建消息生产者、创建消息消费者、自定义消息通道、分组与持久化、设置 RoutingKey)
1.概念:SpringCloudStream 2.具体内容 2.1.SpringCloudStream 简介 SpringCloudStream 就是使用了基于消息系统的微服务处理架构.对于消息系统而 ...
- spring参数类型异常输出(二), SpringMvc参数类型转换错误输出(二)
spring参数类型异常输出(二), SpringMvc参数类型转换错误输出(二) >>>>>>>>>>>>>>&g ...
随机推荐
- C语言之结构体
结构体类型 C语言中还有一种类型叫做结构体类型,它是可以保存不同类型数据并且可以把这些不同类型的数据当做一个整体来管理的类型 1).结构体的定义 语法: struct 结构体名{ 成员列表; }; ...
- .Net基于RealProxy实现AOP
一.概述 关于AOP(面向切面编程)还是先讲一个日常经常碰到的场景"错误日志的记录",一般来说我们编码的时候想记录错误日志都是用try..catch来进行捕捉和记录,慢慢的你会发现 ...
- 【CSS学习】字符实体
在html开发中,有一些字符,不适于直接写出,比如:大于号>小于号<<br />一般格式:&+实体名+;实体有很多,记住常用的大于号>小于号<双引号&quo ...
- ipc 入侵步骤
第一步:建立IPC隧道net use \\10.56.204.186\IPC$ "密码" /user:"用户" 第二步:映射对方c盘到本地z盘net u ...
- 实际开发中,后台回传的错误格式的Json数据处理
现在很多学习刚学习移动开发的同学,相信在培训机构,拿到后台数据的时候,格式都是正确的,甚至有的还是plist文件.但是实际开发中,我们获取数据都是通过网络接口从服务器获取数据,这些数据的格式都是后台写 ...
- Python_web框架解析
这一阵正在学习廖雪峰老师的实战课程,这里对其中web.py框架进行一些分析,总结一下学到的东西. 这一部分的课程网站:http://www.liaoxuefeng.com/wiki/001374738 ...
- 在GNU/Linux下使用Lilypond排版简谱
尽管GNU/Linux并非无所不能,但确实能在很多时候提供免费.开放的解决方案.这两天我想做一个简谱,在网上搜索乐谱排版软件,发现了基于GPL协议的Lilypond软件.只不过Lilypond是用来做 ...
- line-height 行高
line-height 行高指一行文字的高度,具体来说是指两行文子间基线间的距离 line-height 与 font-size 的计算值之差(行距)分为两半,分别加到一个文本行内容的顶部和 ...
- class.forname()方法的学习(转)
Class.forName(xxx.xx.xx) 返回的是一个类 首先你要明白在java里面任何class都要装载在虚拟机上才能运行.这句话就是装载类用的(和new 不一样,要分清楚). 至于什么时候 ...
- java基础第三天