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

com.hazelcast.client.connection.nio.ClusterConnectorServiceImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
 *
 * Licensed 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 com.hazelcast.client.connection.nio;

import com.hazelcast.client.ClientNotAllowedInClusterException;
import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.client.config.ClientConnectionStrategyConfig;
import com.hazelcast.client.config.ClientNetworkConfig;
import com.hazelcast.client.config.ConnectionRetryConfig;
import com.hazelcast.client.connection.AddressProvider;
import com.hazelcast.client.connection.Addresses;
import com.hazelcast.client.connection.ClientConnectionStrategy;
import com.hazelcast.client.impl.clientside.CandidateClusterContext;
import com.hazelcast.client.impl.clientside.ClientDiscoveryService;
import com.hazelcast.client.impl.clientside.ClientLoggingService;
import com.hazelcast.client.impl.clientside.HazelcastClientInstanceImpl;
import com.hazelcast.client.impl.clientside.LifecycleServiceImpl;
import com.hazelcast.client.spi.impl.ClientExecutionServiceImpl;
import com.hazelcast.config.ConfigurationException;
import com.hazelcast.core.LifecycleEvent;
import com.hazelcast.core.Member;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.Address;
import com.hazelcast.nio.Connection;
import com.hazelcast.nio.ConnectionListener;
import com.hazelcast.util.executor.SingleExecutorThreadFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static com.hazelcast.client.spi.properties.ClientProperty.SHUFFLE_MEMBER_LIST;
import static com.hazelcast.util.ExceptionUtil.rethrow;

/**
 * Helper to ClientConnectionManager.
 * Implemented in this class: selecting owner connection, connecting and disconnecting from the cluster.
 */
public class ClusterConnectorServiceImpl implements ClusterConnectorService, ConnectionListener {

    private static final int DEFAULT_CONNECTION_ATTEMPT_LIMIT_SYNC = 2;
    private static final int DEFAULT_CONNECTION_ATTEMPT_LIMIT_ASYNC = 20;

    private final ILogger logger;
    private final HazelcastClientInstanceImpl client;
    private final ClientConnectionManagerImpl connectionManager;
    private final ClientConnectionStrategy connectionStrategy;
    private final ExecutorService clusterConnectionExecutor;
    private final boolean shuffleMemberList;
    private final WaitStrategy waitStrategy;
    private final ClientDiscoveryService discoveryService;
    private volatile Address ownerConnectionAddress;
    private volatile Address previousOwnerConnectionAddress;

    public ClusterConnectorServiceImpl(HazelcastClientInstanceImpl client,
                                       ClientConnectionManagerImpl connectionManager,
                                       ClientConnectionStrategy connectionStrategy,
                                       ClientDiscoveryService discoveryService) {
        this.client = client;
        this.connectionManager = connectionManager;
        this.logger = client.getLoggingService().getLogger(ClusterConnectorService.class);
        this.connectionStrategy = connectionStrategy;
        this.clusterConnectionExecutor = createSingleThreadExecutorService(client);
        this.shuffleMemberList = client.getProperties().getBoolean(SHUFFLE_MEMBER_LIST);
        this.discoveryService = discoveryService;
        this.waitStrategy = initializeWaitStrategy(client.getClientConfig());
    }

    private WaitStrategy initializeWaitStrategy(ClientConfig clientConfig) {
        ClientConnectionStrategyConfig connectionStrategyConfig = clientConfig.getConnectionStrategyConfig();
        ConnectionRetryConfig expoRetryConfig = connectionStrategyConfig.getConnectionRetryConfig();
        if (expoRetryConfig.isEnabled()) {
            return new ExponentialWaitStrategy(expoRetryConfig.getInitialBackoffMillis(),
                    expoRetryConfig.getMaxBackoffMillis(),
                    expoRetryConfig.getMultiplier(),
                    expoRetryConfig.isFailOnMaxBackoff(),
                    expoRetryConfig.getJitter());
        }
        ClientNetworkConfig networkConfig = clientConfig.getNetworkConfig();

        int connectionAttemptPeriod = networkConfig.getConnectionAttemptPeriod();

        boolean isAsync = connectionStrategyConfig.isAsyncStart();

        int connectionAttemptLimit = networkConfig.getConnectionAttemptLimit();
        if (connectionAttemptLimit < 0) {
            connectionAttemptLimit = isAsync ? DEFAULT_CONNECTION_ATTEMPT_LIMIT_ASYNC
                    : DEFAULT_CONNECTION_ATTEMPT_LIMIT_SYNC;
        } else {
            connectionAttemptLimit = connectionAttemptLimit == 0 ? Integer.MAX_VALUE : connectionAttemptLimit;
        }

        return new DefaultWaitStrategy(connectionAttemptPeriod, connectionAttemptLimit);
    }

    @Override
    public void connectToCluster() {
        try {
            connectToClusterAsync().get();
        } catch (Exception e) {
            throw rethrow(e);
        }
    }

    @Override
    public boolean isClusterAvailable() {
        return getOwnerConnectionAddress() != null;
    }

    public Address getOwnerConnectionAddress() {
        return ownerConnectionAddress;
    }

    public void setOwnerConnectionAddress(Address ownerConnectionAddress) {
        if (this.ownerConnectionAddress != null) {
            this.previousOwnerConnectionAddress = this.ownerConnectionAddress;
        }
        this.ownerConnectionAddress = ownerConnectionAddress;
    }

    private Connection connectAsOwner(Address address) {
        Connection connection = null;
        try {
            logger.info("Trying to connect to " + address + " as owner member");
            connection = connectionManager.getOrConnect(address, true);
            setOwnerConnectionAddress(connection.getEndPoint());
            client.onClusterConnect(connection);
            fireLifecycleEvent(LifecycleEvent.LifecycleState.CLIENT_CONNECTED);
            connectionStrategy.onClusterConnect();
        } catch (ConfigurationException e) {
            setOwnerConnectionAddress(null);
            logger.warning("Exception during initial connection to " + address + ": " + e);
            if (null != connection) {
                connection.close("Could not connect to " + address + " as owner", e);
            }
            throw rethrow(e);
        } catch (ClientNotAllowedInClusterException e) {
            setOwnerConnectionAddress(null);
            logger.warning("Exception during initial connection to " + address + ": " + e);
            if (null != connection) {
                connection.close("Could not connect to " + address + " as owner", e);
            }
            throw e;
        } catch (Exception e) {
            setOwnerConnectionAddress(null);
            logger.warning("Exception during initial connection to " + address + ": " + e);
            if (null != connection) {
                connection.close("Could not connect to " + address + " as owner", e);
            }
            return null;
        }
        return connection;
    }

    private void fireLifecycleEvent(LifecycleEvent.LifecycleState state) {
        LifecycleServiceImpl lifecycleService = (LifecycleServiceImpl) client.getLifecycleService();
        lifecycleService.fireLifecycleEvent(state);
    }

    private ExecutorService createSingleThreadExecutorService(HazelcastClientInstanceImpl client) {
        ClassLoader classLoader = client.getClientConfig().getClassLoader();
        SingleExecutorThreadFactory threadFactory = new SingleExecutorThreadFactory(classLoader, client.getName() + ".cluster-");
        return Executors.newSingleThreadExecutor(threadFactory);
    }

    private void connectToClusterInternal() {
        CandidateClusterContext currentClusterContext = discoveryService.current();
        logger.info("Trying to connect to cluster with name: " + currentClusterContext.getName());
        if (connectToCandidate(currentClusterContext)) {
            return;
        }

        // we reset the search so that we will iterate the list try-count times, each time we start searching for a new cluster
        discoveryService.resetSearch();

        while (discoveryService.hasNext() && client.getLifecycleService().isRunning()) {
            CandidateClusterContext candidateClusterContext = discoveryService.next();
            beforeClusterSwitch(candidateClusterContext);
            logger.info("Trying to connect to next cluster with name: " + candidateClusterContext.getName());

            if (connectToCandidate(candidateClusterContext)) {
                //reset queryCache context, publishes eventLostEvent to all caches
                client.getQueryCacheContext().recreateAllCaches();
                fireLifecycleEvent(LifecycleEvent.LifecycleState.CLIENT_CHANGED_CLUSTER);
                return;
            }
        }
        if (!client.getLifecycleService().isRunning()) {
            throw new IllegalStateException("Client is being shutdown.");
        } else {
            throw new IllegalStateException("Unable to connect to any cluster.");
        }
    }

    private void beforeClusterSwitch(CandidateClusterContext context) {
        //reset near caches, clears all near cache data
        try {
            client.getNearCacheManager().clearAllNearCaches();
        } catch (Throwable e) {
            logger.warning("Error when clearing near caches before cluster switch ", e);
        }
        //clear the member list
        client.getClientClusterService().reset();
        //clear the partition table
        client.getClientPartitionService().reset();
        //close all the connections, consequently waiting invocations get TargetDisconnectedException
        //non retryable client messages will fail immediately
        //retryable client messages will be retried but they will wait for new partition table
        client.getConnectionManager().beforeClusterSwitch(context);
        //update logger with new group name
        ((ClientLoggingService) client.getLoggingService()).updateGroupName(context.getName());
    }

    private boolean connectToCandidate(CandidateClusterContext context) {
        Set
triedAddresses = new HashSet
(); client.getConnectionManager().setCandidateClusterContext(context); waitStrategy.reset(); label: do { Collection
addresses = getPossibleMemberAddresses(context.getAddressProvider()); for (Address address : addresses) { if (!client.getLifecycleService().isRunning()) { throw new IllegalStateException("Giving up retrying to connect to cluster since client is shutdown."); } triedAddresses.add(address); try { Connection connection = connectAsOwner(address); if (connection != null) { return true; } } catch (ClientNotAllowedInClusterException e) { break label; } } // If the address providers load no addresses (which seems to be possible), then the above loop is not entered // and the lifecycle check is missing, hence we need to repeat the same check at this point. if (!client.getLifecycleService().isRunning()) { throw new IllegalStateException("Client is being shutdown."); } } while (waitStrategy.sleep()); logger.warning("Unable to connect to any address for cluster: " + context.getName() + ". The following addresses were tried: " + triedAddresses); return false; } @Override public Future connectToClusterAsync() { return clusterConnectionExecutor.submit(new Callable() { @Override public Void call() { try { connectToClusterInternal(); } catch (Throwable e) { logger.warning("Could not connect to any cluster, shutting down the client: " + e.getMessage()); new Thread(new Runnable() { @Override public void run() { try { client.getLifecycleService().shutdown(); } catch (Exception exception) { logger.severe("Exception during client shutdown", exception); } } }, client.getName() + ".clientShutdown-").start(); throw rethrow(e); } return null; } }); } Collection
getPossibleMemberAddresses(AddressProvider addressProvider) { LinkedHashSet
addresses = new LinkedHashSet
(); Collection memberList = client.getClientClusterService().getMemberList(); for (Member member : memberList) { addresses.add(member.getAddress()); } if (shuffleMemberList) { addresses = (LinkedHashSet
) shuffle(addresses); } LinkedHashSet
providedAddresses = new LinkedHashSet
(); try { Addresses result = addressProvider.loadAddresses(); if (shuffleMemberList) { // The relative order between primary and secondary addresses should not be changed. // so we shuffle the lists separately and then add them to the final list so that // secondary addresses are not tried before all primary addresses have been tried. // Otherwise we can get startup delays. Collections.shuffle(result.primary()); Collections.shuffle(result.secondary()); } providedAddresses.addAll(result.primary()); providedAddresses.addAll(result.secondary()); } catch (NullPointerException e) { throw e; } catch (Exception e) { logger.warning("Exception from AddressProvider: " + discoveryService, e); } addresses.addAll(providedAddresses); if (previousOwnerConnectionAddress != null) { /* * Previous owner address is moved to last item in set so that client will not try to connect to same one immediately. * It could be the case that address is removed because it is healthy(it not responding to heartbeat/pings) * In that case, trying other addresses first to upgrade make more sense. */ addresses.remove(previousOwnerConnectionAddress); addresses.add(previousOwnerConnectionAddress); } return addresses; } private static Set shuffle(Set set) { List shuffleMe = new ArrayList(set); Collections.shuffle(shuffleMe); return new LinkedHashSet(shuffleMe); } public void shutdown() { ClientExecutionServiceImpl.shutdownExecutor("cluster", clusterConnectionExecutor, logger); } @Override public void connectionAdded(Connection connection) { } @Override public void connectionRemoved(Connection connection) { final ClientConnection clientConnection = (ClientConnection) connection; if (clientConnection.isAuthenticatedAsOwner()) { clusterConnectionExecutor.execute(new Runnable() { @Override public void run() { Address endpoint = clientConnection.getEndPoint(); // it may be possible that while waiting on executor queue, the client got connected (another connection), // then we do not need to do anything for cluster disconnect. if (endpoint == null || !endpoint.equals(ownerConnectionAddress)) { return; } setOwnerConnectionAddress(null); connectionStrategy.onDisconnectFromCluster(); client.onClusterDisconnect(); if (client.getLifecycleService().isRunning()) { fireLifecycleEvent(LifecycleEvent.LifecycleState.CLIENT_DISCONNECTED); } } }); } } interface WaitStrategy { void reset(); boolean sleep(); } class DefaultWaitStrategy implements WaitStrategy { private final int connectionAttemptPeriod; private final int connectionAttemptLimit; private int attempt; DefaultWaitStrategy(int connectionAttemptPeriod, int connectionAttemptLimit) { this.connectionAttemptPeriod = connectionAttemptPeriod; this.connectionAttemptLimit = connectionAttemptLimit; } @Override public void reset() { attempt = 0; } @Override public boolean sleep() { attempt++; if (attempt >= connectionAttemptLimit) { logger.warning(String.format("Unable to get live cluster connection, attempt %d of %d.", attempt, connectionAttemptLimit)); return false; } logger.warning(String.format("Unable to get live cluster connection, retry in %d ms, attempt %d of %d.", connectionAttemptPeriod, attempt, connectionAttemptLimit)); try { Thread.sleep(connectionAttemptPeriod); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } return true; } } class ExponentialWaitStrategy implements WaitStrategy { private final int initialBackoffMillis; private final int maxBackoffMillis; private final double multiplier; private final boolean failOnMaxBackoff; private final double jitter; private final Random random = new Random(); private int attempt; private int currentBackoffMillis; ExponentialWaitStrategy(int initialBackoffMillis, int maxBackoffMillis, double multiplier, boolean failOnMaxBackoff, double jitter) { this.initialBackoffMillis = initialBackoffMillis; this.maxBackoffMillis = maxBackoffMillis; this.multiplier = multiplier; this.failOnMaxBackoff = failOnMaxBackoff; this.jitter = jitter; } @Override public void reset() { attempt = 0; currentBackoffMillis = Math.min(maxBackoffMillis, initialBackoffMillis); } @Override public boolean sleep() { attempt++; if (failOnMaxBackoff && currentBackoffMillis >= maxBackoffMillis) { logger.warning(String.format("Unable to get live cluster connection, attempt %d.", attempt)); return false; } //random_between // Random(-jitter * current_backoff, jitter * current_backoff) long actualSleepTime = (long) (currentBackoffMillis + currentBackoffMillis * jitter * (2.0 * random.nextDouble() - 1.0)); logger.warning(String.format("Unable to get live cluster connection, retry in %d ms, attempt %d" + ", retry timeout millis %d cap", actualSleepTime, attempt, maxBackoffMillis)); try { Thread.sleep(actualSleepTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } currentBackoffMillis = (int) Math.min(currentBackoffMillis * multiplier, maxBackoffMillis); return true; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy