群集环境中多个角色的领导者选举初始化

时间:2018-05-31 11:27:36

标签: spring spring-integration

我目前正在使用基于以下内容的实现:

org.springframework.integration.support.leader.LockRegistryLeaderInitiator

具有多个候选角色,以便群集中只有一个应用程序实例被选为每个角色的领导者。在初始化集群期间,如果autoStartup属性设置为true,则初始化的第一个应用程序实例将被选为所有角色的领导者。这是我们想要避免的事情,而是在整个集群中公平分配主角。

上面的一个可能的解决方案可能是当集群准备就绪并正确初始化时,然后调用将执行的端点:

lockRegistryLeaderInitiator.start()

对于群集中的所有实例,以便选举过程开始,角色在实例之间公平分配。其中一个缺点是,这需要成为部署过程的一部分,增加了某种程度的复杂性。

上述建议的最佳做法是什么?是否有任何相关附加功能的计划?例如,仅当X应用程序实例可用时才自动启动领导者选举?

3 个答案:

答案 0 :(得分:1)

我建议你看看Spring Cloud Bus项目。我不知道它的详细信息,但看起来您对所有autoStartup = false实例的LockRegistryLeaderInitiator的想法以及通过某些分布式事件启动它们是可行的方法。

不确定我们可以从Spring Integration的角度为您做些什么,但它完全感觉不是它的责任,所有协调和重新平衡都应该通过其他工具完成。幸运的是,我们所有的Spring项目都可以作为一个平台一起使用。

我认为通过总线,您甚至可以跟踪加入群集的实例数量,并决定您自己何时以及如何发布StartLeaderInitiators事件。

答案 1 :(得分:1)

使用Zookeeper LeaderInitiator会相对容易,因为您可以在启动它之前检查zookeeper中的实例计数。

使用锁定注册表并不容易,因为没有关于实例的固有信息;你需要一些外部机制(比如zookeeper,在这种情况下,你也可以使用ZK)。

或者,您可以使用Spring Cloud Bus(使用RabbitMQ或Kafka)向所有实例发送信号,以便开始选举领导。

答案 2 :(得分:0)

我发现这样做的方法非常简单。 您可以向每个节点添加计划任务,如果节点拥有太多领导权,这些任务会定期尝试产生领导权。

例如,如果您有 N 个节点和 2*N 个角色,并且您希望实现完全公平的领导权分配(每个节点试图仅拥有两个领导权),您可以使用以下方法:

@Component
@RequiredArgsConstructor
public class FairLeaderDistributor {
    private final List<LeaderInitiator> initiators;

    @Scheduled(fixedDelay = 300_000) // once per 5 minutes
    public void yieldExcessLeaderships() {
        initiators.stream()
                .map(LeaderInitiator::getContext)
                .filter(Context::isLeader)
                .skip(2) // keep only 2 leaderships
                .forEach(Context::yield);
    }
}

当所有节点都启动时,您将最终获得完全公平的领导权分配。

如果您使用 Zookeeper LeaderInitiator 实现,您还可以根据当前活动节点数实现动态分配。 可以从 Curator LeaderSelector::getParticipants 方法轻松检索当前参与者数量。 您可以通过 LeaderSelector 字段的反射获得 LeaderInitiator.leaderSelector

@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicFairLeaderDistributor {
    final List<LeaderInitiator> initiators;

    @SneakyThrows
    private static int getParticipantsCount(LeaderInitiator leaderInitiator) {
        Field field = LeaderInitiator.class.getDeclaredField("leaderSelector");
        field.setAccessible(true);
        LeaderSelector leaderSelector = (LeaderSelector) field.get(leaderInitiator);
        return leaderSelector.getParticipants().size();
    }

    @Scheduled(fixedDelay = 5_000)
    public void yieldExcessLeaderships() {
        int rolesCount = initiators.size();
        if (rolesCount == 0) return;

        int participantsCount = getParticipantsCount(initiators.get(0));
        if (participantsCount == 0) return;

        int maxLeadershipsCount = (rolesCount - 1) / participantsCount + 1;

        log.info("rolesCount={}, participantsCount={}, maxLeadershipsCount={}", rolesCount, participantsCount, maxLeadershipsCount);

        initiators.stream()
                .map(LeaderInitiator::getContext)
                .filter(Context::isLeader)
                .skip(maxLeadershipsCount)
                .forEach(Context::yield);
    }
}