RocketMQ Connect Runtime集群配置同步架构

在RocketMQ Connect遵循Message Connector的设计理念,依托RocketMQ实现。主要包括Source Connector、Sink Connector、Runtime。RocketMQ Connect Runtime 是 Source、Sink Connector的运行时环境,同时,它也是一个分布式系统,支持集群节点之间的服务发现、配置同步、负载均衡等功能。

RocketMQConnect 模块

原Connect Runtime设计思路

首先,关于集群配置相关的实现都在ConfigManagementService中,当配置变化时主要涉及两种ConfigChangeKey,分别是ONLINE_KEY和CONFIG_CHANG_KEY

public void onCompletion(Throwable error, String key, ConnAndTaskConfigs result) {
	boolean changed = false;
    switch (ConfigChangeEnum.valueOf(key)) {
		case ONLINE_KEY:
			mergeConfig(result);
			changed = true;
			sendSynchronizeConfig();
			break;
		case CONFIG_CHANG_KEY:
			changed = mergeConfig(result);
			break;
		default:
			break;
	}
	if (changed) {
		triggerListener();
	}
}

其中ONLINE_KEY只会在ConfigManagementService开启时会执行一次,表示有新的节点上线了并将其配置发送到RocketMQ,以便其他节点获取并更新配置,同时,从代码中我们可以看到,在节点收到ONLINE_KEY后,会merge自己的配置并发送到RocketMQ中,这里有两个问题:一是会造成信息冗余,因为当集群中节点过多时,当一个节点收到ONLINE_KEY后,继而继续发送自己的CONFIG_CHANG_KEY到RocketMQ中,然后其他节点又会收到它的消息。通常情况下,我们认为集群的配置是同步的,所以每个节点在收到ONLINE_KEY都接收、merge、并发送消息到RocketMQ是没有必要的,造成了信息的浪费。二是如果在merge后发送CONFIG_CHANG_KEY到集群的过程失败时,本地的配置和其他节点的配置就不一致了。

private void sendOnlineConfig() {

    ConnAndTaskConfigs configs = new ConnAndTaskConfigs();
    configs.setConnectorConfigs(connectorKeyValueStore.getKVMap());
    configs.setTaskConfigs(taskKeyValueStore.getKVMap());
    dataSynchronizer.send(ConfigChangeEnum.ONLINE_KEY.name(), configs);
}

private void sendSynchronizeConfig() {

    ConnAndTaskConfigs configs = new ConnAndTaskConfigs();
    configs.setConnectorConfigs(connectorKeyValueStore.getKVMap());
    configs.setTaskConfigs(taskKeyValueStore.getKVMap());
    dataSynchronizer.send(ConfigChangeEnum.CONFIG_CHANG_KEY.name(), configs);
}

当用户通过RESTful接口创建新的connector时,通过putConnectorConfig处理新的Connector并分配task,然后通过sendSynchronizeConfig()向RocketMQ中发送CONFIG_CHANG_KEY,这里同样,当发送CONFIG_CHANG_KEY消息到RocketMQ集群失败或者发送过程中该worker挂掉时,会产生一致性问题。

问题描述&目标

通过上述问题可以看出,我们需要一种方案来解决当发送消息到RocketMQ失败时产生的一致性问题。解决方案如下:

  • 把RocketMQ本身当作持久化日志存储
  • 增加leader、follower的概念,只允许leader往RocketMQ中写入消息,其他follower从topic消费数据
  • 将follower的RESTful接口请求将直接重定向到leader

总而言之,就是只允许一个worker往RocketMQ中发送消息,其他worker消费RocketMQ中的消息并进行connector/task的更新。注意,和Raft中的leader不同,RocketMQ Connect Runtime中的leader的功能只有把config update写入RocketMQ消息队列,所以我们称之为轻量化leader的概念。

当某个follower挂掉并重启后,重新从头到尾读一遍topic里的所有消息,把最新的config构建起来,保证配置与RocketMQ中的消息一致,就实现了配置的全局一致性。

另外,由于目前没有leader选举算法,所以我们采用直接指派leader,并指定某个follower在leader挂掉后进行主从切换,目前这种方式会丧失一定的可用性,所以打算后续会实现类似Raft/Dledger中的强一致性leader选举算法

配置同步解决方案架构

下面是该配置同步优化的解决方案架构图

在这里插入图片描述

Class Design

  • ConnectorConfig
    • 增加leaderID、workerID
  • ConfigConverter/ConfigWrapper
    • ConfigWrapper将ConnAndTaskConfig和leaderID封装
    • ConfigConverter实现了将leader、connentor、task转化为json以及从json解析为config的方法
  • ConfigManagementService
    • checkLeaderState() 方法进行leader指定并检查
  • ClusterManagementService
    • 在onWorkerChangeListener中实现主从切换的逻辑
  • MQConnectRuntimeAdminStartup
    • 实现与RESTful相同功能的命令行工具(CLI),优化用户体验
  • RestServer
    • 增加对follower进行请求重定向的方法
  • RestSender
    • 实现对在CLI接口中请求的转发功能

Leader选举

首先,在配置文件connect.conf中设置leader以及主从切换

# Is the current node a Leader ("1" is a leader,null or "0" is a follower)
isLeader=1

# Is the current node a Candidate (When the leader down, it automatically becomes the leader)
isCandidate=0

重构了ConfigManagementService中的设计,取消worker启动时会发送ONLINE_KEY的设定,而是在开启ConfigManagementService时进行判断,如果是leader才会发送ONLINE_KEY

/**
 * Check if Cluster has leader
 *
 * @return workerID if Cluster has leader or this worker is leader
 */
public void checkLeaderState() throws ConnectException {
    if (connectConfig.getIsLeader() == 1){
        log.info("This worker is a leader, leaderID is " + connectConfig.getWorkerID());
        connectConfig.setLeaderID(connectConfig.getWorkerID());
        sendOnlineConfig();
    }
    else {
        //因为正常情况下,follow已经通过leader的CONFIG_CHANG_KEY设置了leader
        if (connectConfig.getLeaderID() != null){
            log.info("This worker is a follower, leader is " + connectConfig.getLeaderID());
            connectConfig.setLeaderID(connectConfig.getLeaderID());
        }
        else
            throw new ConnectException("leader status error");
    }
}

另外,由于只要leader才会发送ONLINE_KEY,所以在收到ONLINE_KEY后,不再向集群中发送配置更新消息,而是只有当收到关于更新配置的RESTful请求,才会发送CONFIG_CHANG_KEY到RocketMQ中

public void onCompletion(Throwable error, String key, ConfigWrapper result) {

    boolean changed = false;
    switch (ConfigChangeEnum.valueOf(key)) {
        case ONLINE_KEY:
            log.info("Receive ON_LINE key, leader ip is {}", result.getLeader());
            mergeConfig(result);
            changed = true;
            break;
        case CONFIG_CHANG_KEY:
            if (!checkLeaderState(result.getLeader()))
                break;
            changed = mergeConfig(result);
            break;
        default:
            break;
    }
    if (changed) {
        triggerListener();
    }
}

原有的配置信息是ConnAndTaskConfigs,包含所有connector和task的配置信息,现在我们将其与leader信息封装到一起,作为配置信息转化为json并发送到RocketMQ中

主从切换

在ClusterManagementService中,每个worker都subscribe了cluster-topic,当其中的consumer(worker)数量减少时,每个worker的WorkerChangeListener的onWorkerChange就会触发,然后唤醒负载均衡线程,所以就会感知到集群中节点的变化。

/**
 * Check whether the leader is down and the master-slave switch occurs
 *
 */
private void checkClusterLeader(){
    if (connectConfig.getLeaderID() == null) return;
    List<String> workers = getAllAliveWorkers();
    if (connectConfig.getIsLeader() == 1 && !workers.contains(connectConfig.getLeaderID() + "")){
        log.error("This condition should not happen!");
    }
    if (connectConfig.getIsLeader() == 0 && connectConfig.getIsCandidate() == 1 && !workers.contains(connectConfig.getLeaderID())){
        for (LeaderStatusListener leaderStatusListener : leaderStatusListeners){
            leaderStatusListener.onLeaderChange();
        }
        log.info("Finish the master-slave switch");
    }
}

这里,我们在WorkerChangeListener加入判断主从切换的逻辑,当follower检测到leader挂掉后,立刻通过LeaderStatusListener的onLeaderChange方法触发主从切换。在主从切换中,首先将当前worker设置为leader,然后发送CONFIG_CHANG_KEY到RocketMQ中。

一个leader挂掉,另外一个follower切换成leader。当原leader重启后,WorkerChangeListener会检测到集群中有leader冲突,然后返回错误。

消息转发

当leader接收到RESTful请求后,直接处理请求。当follower接收到RESTful请求后,将请求重定向到leader中。那么是如何获得leader的IP地址的呢?目前我们是通过ClusterManagementService中的getAllAliveWorkers获取到leader的ClientID,而ClientID是由ip地址+端口组成。

if (this.connectController.getConnectConfig().getIsLeader() != 1){
    app.get("/*", this::RedirectionToLeader);
}

/**
 * Redirect to the Leader
 *
 * @param context
 */
private void RedirectionToLeader(Context context) {
    String address = this.connectController.getConnectConfig().getLeaderID().replace(":", "@");
    String parm = context.queryString() == null ? "" : "?" + context.queryString();
    String url = "http://" + address + context.path() + parm;
    context.redirect(url);
}

在RESTHandler接收到RESTful请求后,会判断当前worker是leader还是follower,如果不是leader,就将请求重定向到leader中。

问题

  • 目前这种获取leader的ip地址的方式在云原生环境显地不太优雅,后续我们也会寻找更好的解决方案
  • 关于集群中中,当主从切换后原leader又开启、以及两个leader woker同时开启的逻辑部分目前还没有测试,还应该多加考虑
  • 强一致性leader选举算法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值