All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.flink.runtime.highavailability.nonha.embedded.EmbeddedLeaderService Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.flink.runtime.highavailability.nonha.embedded;

import org.apache.flink.runtime.leaderelection.LeaderContender;
import org.apache.flink.runtime.leaderelection.LeaderElectionService;
import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalListener;
import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executor;

import static org.apache.flink.util.Preconditions.checkNotNull;
import static org.apache.flink.util.Preconditions.checkState;

/**
 * A simple leader election service, which selects a leader among contenders and notifies listeners.
 *
 * 

An election service for contenders can be created via {@link #createLeaderElectionService()}, * a listener service for leader observers can be created via {@link #createLeaderRetrievalService()}. */ public class EmbeddedLeaderService { private static final Logger LOG = LoggerFactory.getLogger(EmbeddedLeaderService.class); private final Object lock = new Object(); private final Executor notificationExecutor; private final Set allLeaderContenders; private final Set listeners; /** proposed leader, which has been notified of leadership grant, but has not confirmed. */ private EmbeddedLeaderElectionService currentLeaderProposed; /** actual leader that has confirmed leadership and of which listeners have been notified. */ private EmbeddedLeaderElectionService currentLeaderConfirmed; /** fencing UID for the current leader (or proposed leader). */ private volatile UUID currentLeaderSessionId; /** the cached address of the current leader. */ private String currentLeaderAddress; /** flag marking the service as terminated. */ private boolean shutdown; // ------------------------------------------------------------------------ public EmbeddedLeaderService(Executor notificationsDispatcher) { this.notificationExecutor = checkNotNull(notificationsDispatcher); this.allLeaderContenders = new HashSet<>(); this.listeners = new HashSet<>(); } // ------------------------------------------------------------------------ // shutdown and errors // ------------------------------------------------------------------------ /** * Shuts down this leader election service. * *

This method does not perform a clean revocation of the leader status and * no notification to any leader listeners. It simply notifies all contenders * and listeners that the service is no longer available. */ public void shutdown() { synchronized (lock) { shutdownInternally(new Exception("Leader election service is shutting down")); } } private void fatalError(Throwable error) { LOG.error("Embedded leader election service encountered a fatal error. Shutting down service.", error); synchronized (lock) { shutdownInternally(new Exception("Leader election service is shutting down after a fatal error", error)); } } @GuardedBy("lock") private void shutdownInternally(Exception exceptionForHandlers) { assert Thread.holdsLock(lock); if (!shutdown) { // clear all leader status currentLeaderProposed = null; currentLeaderConfirmed = null; currentLeaderSessionId = null; currentLeaderAddress = null; // fail all registered listeners for (EmbeddedLeaderElectionService service : allLeaderContenders) { service.shutdown(exceptionForHandlers); } allLeaderContenders.clear(); // fail all registered listeners for (EmbeddedLeaderRetrievalService service : listeners) { service.shutdown(exceptionForHandlers); } listeners.clear(); shutdown = true; } } // ------------------------------------------------------------------------ // creating contenders and listeners // ------------------------------------------------------------------------ public LeaderElectionService createLeaderElectionService() { checkState(!shutdown, "leader election service is shut down"); return new EmbeddedLeaderElectionService(); } public LeaderRetrievalService createLeaderRetrievalService() { checkState(!shutdown, "leader election service is shut down"); return new EmbeddedLeaderRetrievalService(); } // ------------------------------------------------------------------------ // adding and removing contenders & listeners // ------------------------------------------------------------------------ /** * Callback from leader contenders when they start their service. */ void addContender(EmbeddedLeaderElectionService service, LeaderContender contender) { synchronized (lock) { checkState(!shutdown, "leader election service is shut down"); checkState(!service.running, "leader election service is already started"); try { if (!allLeaderContenders.add(service)) { throw new IllegalStateException("leader election service was added to this service multiple times"); } service.contender = contender; service.running = true; updateLeader(); } catch (Throwable t) { fatalError(t); } } } /** * Callback from leader contenders when they stop their service. */ void removeContender(EmbeddedLeaderElectionService service) { synchronized (lock) { // if the service was not even started, simply do nothing if (!service.running || shutdown) { return; } try { if (!allLeaderContenders.remove(service)) { throw new IllegalStateException("leader election service does not belong to this service"); } // stop the service service.contender = null; service.running = false; service.isLeader = false; // if that was the current leader, unset its status if (currentLeaderConfirmed == service) { currentLeaderConfirmed = null; currentLeaderSessionId = null; currentLeaderAddress = null; } if (currentLeaderProposed == service) { currentLeaderProposed = null; currentLeaderSessionId = null; } updateLeader(); } catch (Throwable t) { fatalError(t); } } } /** * Callback from leader contenders when they confirm a leader grant. */ void confirmLeader(final EmbeddedLeaderElectionService service, final UUID leaderSessionId) { synchronized (lock) { // if the service was shut down in the meantime, ignore this confirmation if (!service.running || shutdown) { return; } try { // check if the confirmation is for the same grant, or whether it is a stale grant if (service == currentLeaderProposed && currentLeaderSessionId.equals(leaderSessionId)) { final String address = service.contender.getAddress(); LOG.info("Received confirmation of leadership for leader {} , session={}", address, leaderSessionId); // mark leadership currentLeaderConfirmed = service; currentLeaderAddress = address; currentLeaderProposed = null; // notify all listeners for (EmbeddedLeaderRetrievalService listener : listeners) { notificationExecutor.execute( new NotifyOfLeaderCall(address, leaderSessionId, listener.listener, LOG)); } } else { LOG.debug("Received confirmation of leadership for a stale leadership grant. Ignoring."); } } catch (Throwable t) { fatalError(t); } } } @GuardedBy("lock") private void updateLeader() { // this must be called under the lock assert Thread.holdsLock(lock); if (currentLeaderConfirmed == null && currentLeaderProposed == null) { // we need a new leader if (allLeaderContenders.isEmpty()) { // no new leader available, tell everyone that there is no leader currently for (EmbeddedLeaderRetrievalService listener : listeners) { notificationExecutor.execute( new NotifyOfLeaderCall(null, null, listener.listener, LOG)); } } else { // propose a leader and ask it final UUID leaderSessionId = UUID.randomUUID(); EmbeddedLeaderElectionService leaderService = allLeaderContenders.iterator().next(); currentLeaderSessionId = leaderSessionId; currentLeaderProposed = leaderService; LOG.info("Proposing leadership to contender {} @ {}", leaderService.contender, leaderService.contender.getAddress()); notificationExecutor.execute( new GrantLeadershipCall(leaderService, leaderSessionId, LOG)); } } } void addListener(EmbeddedLeaderRetrievalService service, LeaderRetrievalListener listener) { synchronized (lock) { checkState(!shutdown, "leader election service is shut down"); checkState(!service.running, "leader retrieval service is already started"); try { if (!listeners.add(service)) { throw new IllegalStateException("leader retrieval service was added to this service multiple times"); } service.listener = listener; service.running = true; // if we already have a leader, immediately notify this new listener if (currentLeaderConfirmed != null) { notificationExecutor.execute( new NotifyOfLeaderCall(currentLeaderAddress, currentLeaderSessionId, listener, LOG)); } } catch (Throwable t) { fatalError(t); } } } void removeListener(EmbeddedLeaderRetrievalService service) { synchronized (lock) { // if the service was not even started, simply do nothing if (!service.running || shutdown) { return; } try { if (!listeners.remove(service)) { throw new IllegalStateException("leader retrieval service does not belong to this service"); } // stop the service service.listener = null; service.running = false; } catch (Throwable t) { fatalError(t); } } } // ------------------------------------------------------------------------ // election and retrieval service implementations // ------------------------------------------------------------------------ private class EmbeddedLeaderElectionService implements LeaderElectionService { volatile LeaderContender contender; volatile boolean isLeader; volatile boolean running; @Override public void start(LeaderContender contender) throws Exception { checkNotNull(contender); addContender(this, contender); } @Override public void stop() throws Exception { removeContender(this); } @Override public void confirmLeaderSessionID(UUID leaderSessionID) { checkNotNull(leaderSessionID); confirmLeader(this, leaderSessionID); } @Override public boolean hasLeadership(@Nonnull UUID leaderSessionId) { return isLeader && leaderSessionId.equals(currentLeaderSessionId); } void shutdown(Exception cause) { if (running) { running = false; isLeader = false; contender.revokeLeadership(); contender = null; } } } // ------------------------------------------------------------------------ private class EmbeddedLeaderRetrievalService implements LeaderRetrievalService { volatile LeaderRetrievalListener listener; volatile boolean running; @Override public void start(LeaderRetrievalListener listener) throws Exception { checkNotNull(listener); addListener(this, listener); } @Override public void stop() throws Exception { removeListener(this); } public void shutdown(Exception cause) { if (running) { running = false; listener = null; } } } // ------------------------------------------------------------------------ // asynchronous notifications // ------------------------------------------------------------------------ private static class NotifyOfLeaderCall implements Runnable { @Nullable private final String address; // null if leader revoked without new leader @Nullable private final UUID leaderSessionId; // null if leader revoked without new leader private final LeaderRetrievalListener listener; private final Logger logger; NotifyOfLeaderCall( @Nullable String address, @Nullable UUID leaderSessionId, LeaderRetrievalListener listener, Logger logger) { this.address = address; this.leaderSessionId = leaderSessionId; this.listener = checkNotNull(listener); this.logger = checkNotNull(logger); } @Override public void run() { try { listener.notifyLeaderAddress(address, leaderSessionId); } catch (Throwable t) { logger.warn("Error notifying leader listener about new leader", t); listener.handleError(t instanceof Exception ? (Exception) t : new Exception(t)); } } } // ------------------------------------------------------------------------ private static class GrantLeadershipCall implements Runnable { private final EmbeddedLeaderElectionService leaderElectionService; private final UUID leaderSessionId; private final Logger logger; GrantLeadershipCall( EmbeddedLeaderElectionService leaderElectionService, UUID leaderSessionId, Logger logger) { this.leaderElectionService = checkNotNull(leaderElectionService); this.leaderSessionId = checkNotNull(leaderSessionId); this.logger = checkNotNull(logger); } @Override public void run() { leaderElectionService.isLeader = true; final LeaderContender contender = leaderElectionService.contender; try { contender.grantLeadership(leaderSessionId); } catch (Throwable t) { logger.warn("Error granting leadership to contender", t); contender.handleError(t instanceof Exception ? (Exception) t : new Exception(t)); leaderElectionService.isLeader = false; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy