自动创建

topic在默认情况下支持自动创建,开关由配置文件的autoCreateTopicEnable(默认true)属性决定,在项目启动后存储在BrokerConfig类中。如果某个Broker的自动创建设置为true,则Broker在启动后会在topicManager中创建名为TBW102的topic并向所有NameServer进行注册。

当Producer向某个topic投递消息时,首先会访问NameServer获取此topic的路由列表,这里假设此topic之前并没有被创建,那么得到的将是一个空列表,Producer会再次访问NameServer获取名为TBW102的topic的路由列表,如果整个集群中没有任何Broker支持自动创建,那么这个路由列表仍然是空的,此时会抛出No route info of this topic异常,如果集群中存在支持自动创建的Broker,那么就返回这些Broker的路由信息。

Producer拿到TBW102的路由列表后,会从中选择(默认轮询)一个Broker进行投递,Broker接收到此消息后会调用msgCheck方法对topic进行校验,先去topicConfigTable中查询此topic是否存在,在目前讨论的场景下是肯定不存在的,那么会以这个不存在的tpoic名称创建一个TopicConfig。这个类包含了topic的具体信息包括队列数、读写权限、同步/异步复制等,基本上沿用TBW102的属性,创建完毕后存入topicConfigTable中,然后将topicConfigTable中的所有数据同步到NameServer中。

topic自动创建流程图:
图片

流程图可以看出,自动创建的topic仅仅在消息投递到的Broker节点中生成queue,一旦刷回注册中心,此topic的负载均衡能力就被固定(排除手动修改的情况)。如果某一时刻不存在的topic被多个Producer同时投递,且集群中支持自动创建的Broker节点数量还算可观,考虑到从投递消息开始直到刷回注册中心这段时间的并发情况,可能会有多个Broker都被分配此topic的queue,此topic仍然有很可观的并行执行能力。

这里换一种假设,不存在的topic第一次被Producer投递时,没有其他Producer同时进行,导致此topic仅仅分配在某一个Broker中。这就相当危险了,并行能力太差,如果消息量上来很容易造成积压。说这么多就是想表达自动创建的topic的配置信息,因为场景的不确定性很难达到理想值,因此在生产环境中最好关闭自动创建,所有topic都由开发人员根据业务场景主观判断并创建。

手动创建

topic的手动创建可以通过linux命令来实现,也可以在console页面进行操作,命令操作相对于页面来说较麻烦一些,并且生产环境除非运维基本上不会有访问服务器的权限,所以这里只写控制台如何创建。

topic在控制台的创建界面:
图片


clusterName与BROKER_NAME clusterName与BROKER_NAME选项必须填写一个,用于指定topic在哪些Broker节点上创建。如果仅填写clusterName选项(支持多选),则选中的集群所属Broker节点都会创建此topic信息,这种方式也叫集群创建。如果仅填写BROKER_NAME选项(支持多选),则只在选中的Broker节点中创建,这种方式也叫Broker创建。如果俩个有存在选项值,则取并集。

writeQueueNums(写队列数)
此参数决定了在每个选中的Broker节点中创建的队列数量,比如写队列数的值为4,在topic被创建后,涉及到的每个Broker对应的consumequeue文件夹下都会创建0、1、2、3四个文件夹(懒创建方式,只有消息投递到此文件夹才会被创建),每个Producer启动后,都会从NameServer中获取到所有Broker的0、1、2、3号队列的路由信息,进行轮询投递消息。

readQueueNums(读队列数)
此参数决定某个Broker负责的某个topic,同一个组内最多可连接的消费者数,在集群正常运行期间此参数似乎没有存在的意义。假设某个Broker上的写队列数是4,那么涉及到的每个Broker节点中都会创建0、1、2、3四个队列。如果设置的读队列数小于写队列数(比如3),那么同一组内的Consumer只会从NameServer拿到1、2、3三个队列的路由信息,队列号3永远无法被消费。如果设置的读队列数大于写队列数(比如5),由于一个队列在同一时刻只会被一个消费者连接(不考虑消费组不同和广播消费模式的情况),即使启动了5个消费者,仍然只有4个消费者可以正常工作,多出来的那个消费者不会被分配任何路由信息,即使启动也没有意义。

扩容与缩容

按照上述的理论,writeQueueNums小于readQueueNums情况下最多造成资源浪费,大于readQueueNums情况下则会导致个别队列无法被Consumer连接消费,造成严重的消息堆积问题,writeQueueNums与readQueueNums的值只有保持一致才是合理的,为什么不将俩个参数选项合并为一个呢?

其实这么设计的目的是方便队列的缩容与扩容,思考一个问题,假设某个topic在每个Broker上创建了128个队列,如果在用户无感知的情况下缩小到64个,或者扩容到256个?

水平扩容
关于扩容,可以先将读队列数修改为256,修改后触发再均衡,将队列重新分配给所有Consumer,并且此时队列号128-155虽然分配了Consumer,但是不会有任何消息进入,因为写队列数仍然是128,Producer投递消息只会路由队列号0-127。然后修改写队列数为256,Producer从NameServer获取到最新的关于此topic的路由信息变为0-255,后续投递消息就会覆盖到扩容的队列,供Consumer进行消费。

水平缩容
关于缩容,可以先将写队列数修改为64,修改后会触发再均衡,将队列重新分配给所有Consumer,并且此时队列号64-127不会被路由到任何Producer,也就是说这些队列不会接收到新消息。由于读队列还是128,仍然可以继续消费队列号64-127的消息。直到队列号64-127全部消费完毕后(通过控制台查看),修改读队列的值与写队列保持一致,Producer从NameServer获取到最新的关于此topic的路由信息变为0-63,后续不会对64-127号队列投递消息。

评论