DefaultMQProducer
DefaultMQProducerImpl
MQClientInstance
brokerAddrTable(ConcurrentMap<String/* Broker Name */, HashMap<Long/* brokerId */, String/* address */>>)
topicRouteTable(ConcurrentMap<String/* Topic */, TopicRouteData>)
MQConsumerInner(ConcurrentMap<String/* group */, MQConsumerInner>)
MQProducerInner(ConcurrentMap<String/* group */, MQProducerInner>)
TopicPublishInfo(Map对象,key:topic,value:TopicPublishInfo)
1、创建DefaultMQProducer实例
2、start启动生产者(持有关系DefaultMQProducer-》DefaultMQProducerImpl=》MQClientInstance=》MQProducerInner(Map对象,key:group, value:MQProducerInner)=》TopicPublishInfo(Map对象,key:topic,value:TopicPublishInfo))
DefaultMQProducer-》DefaultMQProducerImpl(门面模式设计模式)的start启动
调用checkConfig方法检查生产者的ProducerGroup是否符合规范,如果ProducerGroup为空,或者长度大于255个字符,或者包含非法字符(正常的匹配模式为 ^[%|a-zA-Z0-9_-]+$),或者生产者组名为默认组名DEFAULT_PRODUCER,满足以上任意条件都校验不通过抛出异常。
调用getOrCreateMQClientInstance方法,然后根据clientId获取或者创建MQClientInstance实例。
该方法会先生成clientId,格式为 clientIP@instanceName@unitName。然后从本地缓存factoryTable中,查找该clientId的MQClientInstance实例。如果缓存中没有找到,则创建实例并存入缓存中。
MQClientInstance封装了RocketMQ底层网络处理API,Producer、Consumer都会使用到这个类,是Producer、Consumer与NameServer、Broker 打交道的网络通道。因此,同一个clientId对应同一个MQClientInstance实例就可以了,即同一个应用中的多个producer和consumer使用同一个MQClientInstance实例即可。
将当前生产者注册到MQClientInstance实例的producerTable属性中。
ConcurrentMap<String/* group */, MQProducerInner> producerTable
key: 生产者组名 value:生产者实例
添加一个默认topic “TBW102”,将会在isAutoCreateTopicEnable属性开启时在broker上自动创建,RocketMQ会基于该Topic的配置创建新的Topic。
调用mQClientFactory变量(MQClientInstance)#start方法启动MQClientInstance客户端通信实例,初始化netty服务、各种定时任务、拉取消息服务、rebalanceService服务等等。
初始化netty服务:
初始化netty客户端(服务请求处理器,处理RemotingCommand消息,即请求和响应的业务处理,并且返回相应的处理结果。)
启动定时任务,初始启动3秒后,扫描responseTable,将超时的ResponseFuture直接移除,并且执行这些超时ResponseFuture的回调
startScheduledTask启动各种定时任务:
如果没有手动指定namesrvAddr,那么每隔2m从nameServer地址服务器拉取最新的nameServer地址并更新。
每隔30S尝试从nameServer更新topic路由信息。
从MQClientInstance内部的consumerTable以及producerTable这两个map中获取配置的所有topic集合topicList,包括consumer订阅的topic集合以及producer中topicPublishInfoTable集合中的数据。
从nameSerer拉取到topic路由信息之后,调用topicRouteDataIsChange方法与本地的旧topic路由信息比较看是否更改,比较的数据包括:topic的队列信息queueDatas,topic的broker信息brokerDatas,顺序topic配置orderTopicConf,消费过滤信息filterServerTable。
当判断需要更新的时候,会更新本地的topic缓存,包括:
更新brokerName到brokerAddr的地址的映射关系,即brokerAddrTable;
更新生产者的producerTable集合,更新MQProducerInner的topicPublishInfoTable属性。
更新消费者的consumerTable集合,更新MQConsumerInner的rebalanceImpl.topicSubscribeInfoTable属性。
更新topicRouteTable集合,更新本地topic路由信息。
首先获取一个随机位于nameServer集合长度范围内的整数,作为第一个被选取的nameServer的地址,然后夯实建立长连接,如果建立成功,那么该长连接还会被放入缓存中,以后可以直接使用,这说明,同一个客户端应用中,不同的consumer和produer可以共用同一个nameServer的长连接。
如果没有建立连接成功,那么选择当前位置开始的下一个nameServer地址继续尝试建立长连接,直到最后成功为止。如果所有nameServer都建立长连接失败,那么最终会抛出异常。
每隔30S尝试清除无效的broker信息,以及发送心跳信息给所有broker。
遍历并且更新brokerAddrTable这个map集合,该集合类型为ConcurrentMap<String/* Broker Name */, HashMap<Long/* brokerId */, String/* address */>>。
其主要步骤就是获取每一个address,然后去本地路由信息集合topicRouteTable中查找判断broker地址是否存在于topicRouteTable的任意一个topic的路由信息中,如果不存在,则表示该broker已下线,那么清除该broker地址,否则保留。如果brokerAddrTable中的value集合也是空的,那么直接删除键值对。
topicRouteTable的数据是拉取的最新的,而brokerAddrTable里的数据会同步topicRouteTable里的新数据,但是存在的旧数据没有删除,所以要定时清除。
每隔5S尝试持久化消费者偏移量,即消费进度。广播消费模式下持久化到本地,集群消费模式下推送到broker端。该定时任务针对消费者,后面学习消费者的时候再学习源码。
每隔1m尝试调整push模式的消费线程池的线程数量,该定时任务针对消费者,目前默认没有实现该功能,是一个空方法实现。
主动调用一次sendHeartbeatToAllBrokerWithLock发送心跳信息给所有broker。
启动一个定时任务,移除超时的request方法的请求,并执行异常回调,任务间隔1s。
总结:
RocketMQ的生产者客户端的源码并不是很难,入口就是DefaultMQProducer的构造器和start方法。
在Producer启动和初始化过程中,会获取或者创建一个非常重要的对象:MQClientInstance。MQClientInstance封装了RocketMQ底层网络处理API,其本身位于通信层,客户端层的Producer、Consumer都会使用到这个类,是Producer、Consumer与NameServer、Broker 打交道的网络通道。因此,同一个clientId对应同一个MQClientInstance实例就可以了,即同一个应用中的多个producer和consumer使用同一个MQClientInstance实例即可。
MQClientInstance的start方法源码也是本次学习的重中之重,但是,在同一个应用中,即使存在多个不同的consume和producer实例,该MQClientInstance实例的start方法中的初始化操作也仅会被调用一次。
MQClientInstance的start方法将会初始化客户端的netty服务、启动各种定时任务(例如定时更新topic信息、定时发送心跳数据等等)、拉取消息服务(后面消费者模块再学习)、rebalanceService负载均衡服务(后面消费者模块再学习)、内部的生产者服务等等服务,这些服务只需要启动一次就好。
由于同一个应用中不同的consume和producer实例共享这个MQClientInstance实例,因此MQClientInstance中的很多方法均需加锁。
生产者的启动源码中使用了一些设计模式,例如:
门面设计模式,DefaultMQProducer是我们使用的生产者,但是其方法几乎均委托给内部的DefaultMQProducerImpl来实现。
单例模式,例如MQClientManager,典型的饿汉单例模式。
状态模式:MQClientInstance和DefaultMQProducerImpl中均有状态字段serviceState,根据不同的状态,其方法的调用会做出不同的行为。