持久化的唯一BrokerId
现阶段问题
在 RocketMQ 5.0.0 和 5.1.0 版本中,采用BrokerAddress
作为Broker在Controller模式下的唯一标识。导致如下情景出现问题:
- 在容器或者K8s环境下,每次Broker的重启或升级都可能会导致IP发生变化,导致之前的
BrokerAddress
留下的记录没办法和重启后的Broker联系起来,比如说ReplicaInfo
,SyncStateSet
等数据。
改进方案
在Controller侧采用BrokerName:BrokerId
作为唯一标识,不再以BrokerAddress
作为唯一标识。并且需要对BrokerId
进行持久化存储,由于ClusterName
和BrokerName
都是启动的时候在配置文件中配置好的,所以只需要处理BrokerId
的分配和持久化问题。
Broker第一次上线的时候,只有配置文件中配置的ClusterName
和BrokerName
,以及自身的BrokerAddress
。那么我们需要和Controller
协商出一个在整个集群生命周期中都唯一确定的标识:BrokerId
,该BrokerId
从1开始分配。当某一个Broker被选为Master的时候,在向Name Server中重新注册时,将更改为BrokerId
为0 (兼容之前逻辑 brokerId为0代表着Broker是Master身份)。
上线流程
1. GetNextBrokerId Request
这时候发起一个GetNextBrokerId
的请求到Controller,为了拿到当前的下一个待分配的BrokerId
(从1开始分配)。
1.1 ReadFromDLedger
此时Controller接收到请求,然后走DLedger去获取到状态机的NextBrokerId
数据。
2. GetNextBrokerId Response
Controller将NextBrokerId
返回给Broker。
2.1 CreateTempMetaFile
Broker拿到NextBrokerId
之后,创建一个临时文件.broker.meta.temp
,里面记录了NextBrokerId
(也就是期望应用的BrokerId
),以及自己生成一个RegisterCode
(用于之后的身份校验)也持久化到临时文件中。
3. ApplyBrokerId Request
Broker携带着当前自己的基本数据(ClusterName
、BrokerName
和BrokerAddress
)以及此时期望应用的BrokerId
和RegisterCode
,发送一个ApplyBrokerId
的请求到Controller。
3.1 CASApplyBrokerId
Controller通过DLedger写入该事件,当该事件(日志)被应用到状态机的时候,判断此时是否可以应用该BrokerId
(若BrokerId
已被分配并且也不是分配给该Broker时则失败)。并且此时会记录下来该BrokerId
和RegisterCode
之间的关系。
4. ApplyBrokerId Response
若上一步成功应用了该BrokerId
,此时则返回成功给Broker,若失败则返回当前的NextBrokerId
。
4.1 CreateMetaFileFromTemp
若上一步成功的应用了该BrokerId
,那么此时可以视为Broker侧成功的分配了该BrokerId,那么此时我们也需要彻底将这个BrokerId的信息持久化,那么我们就可以直接原子删除.broker.meta.temp
并创建.broker.meta
。删除和创建这两步需为原子操作。
经过上述流程,第一次上线的broker和controller成功协商出一个双方都认同的brokeId并持久化保存起来。
5. RegisterBrokerToController Request
之前的步骤已经正确协商出了BrokerId
,但是这时候有可能Controller侧保存的BrokerAddress
是上次Broker上线的时候的BrokerAddress
,所以现在需要更新一下BrokerAddress
,发送一个RegisterBrokerToController
请求并带上当前的BrokerAddress
。
5.1 UpdateBrokerAddress
Controller比对当前该Broker在Controller状态机中保存的BrokerAddress
,若和请求中携带的不一致则更新为请求中的BrokerAddress
。
6. RegisterBrokerToController Response
Controller侧在更新完BrokerAddress
之后可携带着当前该Broker所在的Broker-set
的主从信息返回,用于通知Broker进行相应的身份转变。
注册状态轮转
故障容错
如果在正常上线流程中出现了各种情况的宕机,则以下流程保证正确的
BrokerId
分配
正常重启后的节点上线
若是正常重启,那么则已经在双方协商出唯一的BrokerId
,并且本地也在broker.meta
中有该BrokerId
的数据,那么就该注册流程不需要进行,直接继续后面的流程即可。即从RegisterBrokerToController
处继续上线即可。
CreateTempMetaFile失败
如果是上图中的流程失败的话,那么Broker重启后,Controller侧的状态机本身也没有分配任何BrokerId
。Broker自身也没有任何数据被保存。因此直接重新按照上述流程从头开始走即可。
CreateTempMetaFile成功,ApplyBrokerId未成功
若是Controller侧已经认为本次ApplyBrokerId
请求不对(请求去分配一个已被分配的BrokerId
并且该 RegisterCode
不相等),并且此时返回当前的NextBrokerId
给Broker,那么此时Broker直接删除.broker.meta.temp
文件,接下来回到第2步,重新开始该流程以及后续流程。
ApplyBrokerId成功,CreateMetaFileFromTemp未成功
上述情况可以出现在ApplyResult
丢失、CAS删除并创建broker.meta
失败,这俩流程中。
那么重启后,Controller侧是已经认为我们ApplyBrokerId
流程是成功的了,而且也已经在状态机中修改了BrokerId的分配数据,那么我们这时候重新直接开始步骤3,也就是发送ApplyBrokerId
请求的这一步。
因为我们有.broker.meta.temp
文件,可以从中拿到我们之前成功在Controller侧应用的BrokerId
和RegisterCode
,那么直接发送给Controller,如果Controller中存在该BrokerId
并且RegisterCode
和请求中的RegisterCode
相等,那么视为成功。
正确上线后使用BrokerId作为唯一标识
当正确上线之后,之后Broker的请求和状态记录都以BrokerId
作为唯一标识。心跳等数据的记录都以BrokerId
为标识。
同时Controller侧也会记录当前该BrokerId
的BrokerAddress
,在主从切换等时候用于通知Broker状态变化。
默认持久化ID的文件在~/store/brokerIdentity,也可以设置storePathBrokerIdentity参数来决定存储路径。在自动主备切换模式下,不要随意删除该文件,否则该 Broker 会被当作新 Broker 上线。
升级方案
4.x 版本升级遵守 5.0 升级文档流程即可。 5.0.0 和 5.1.0 非持久化BrokerId版本升级到 5.1.1 及以上持久化BrokerId版本按照如下流程:
升级Controller
- 将旧版本Controller组停机。
- 清除Controller数据,即默认在
~/DLedgerController
下的数据文件。 - 上线新版Controller组。
在上述升级Controller流程中,Broker仍可正常运行,但无法切换。
升级Broker
- 将Broker从节点停机。
- 将Broker主节点停机。
- 将所有的Broker的Epoch文件删除,即默认为
~/store/epochFileCheckpoint
和~/store/epochFileCheckpoint.bak
。 - 将原先的主Broker先上线,等待该Broker当选为master。(可使用
admin
命令的getSyncStateSet
来观察) - 将原来的从Broker全部上线。
建议停机时先停从再停主,上线时先上原先的主再上原先的从,这样可以保证原来的主备关系。 若需要改变升级前后主备关系,则需要停机时保证主、备的CommitLog对齐,否则可能导致数据被截断而丢失。
兼容性
5.1.0 及以下版本 Controller | 5.1.1 及以上版本 Controller | |
---|---|---|
5.1.0 及以下版本 Broker | 正常运行,可切换 | 若已主备确定则可正常运行,不可切换。若broker重新启动则无法上线 |
5.1.1 及以上版本 Broker | 无法正常上线 | 正常运行,可切换 |
更新时间:2023-08-22 16:10