Package com.lambdaworks.redis.cluster

Source Code of com.lambdaworks.redis.cluster.RedisClusterClient

package com.lambdaworks.redis.cluster;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import java.lang.reflect.Proxy;
import java.net.SocketAddress;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import com.google.common.base.Supplier;
import com.google.common.collect.Lists;
import com.lambdaworks.redis.AbstractRedisClient;
import com.lambdaworks.redis.CloseEvents;
import com.lambdaworks.redis.FutureSyncInvocationHandler;
import com.lambdaworks.redis.RedisAsyncConnectionImpl;
import com.lambdaworks.redis.RedisClusterAsyncConnection;
import com.lambdaworks.redis.RedisClusterConnection;
import com.lambdaworks.redis.RedisException;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.codec.RedisCodec;
import com.lambdaworks.redis.codec.Utf8StringCodec;
import com.lambdaworks.redis.protocol.CommandHandler;
import com.lambdaworks.redis.protocol.RedisCommand;

import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

/**
* A scalable thread-safe <a href="http://redis.io/">Redis</a> cluster client. Multiple threads may share one connection
* provided they avoid blocking and transactional operations such as BLPOP and MULTI/EXEC.
*
* @author <a href="mailto:mpaluch@paluch.biz">Mark Paluch</a>
* @since 26.05.14 17:08
*/
public class RedisClusterClient extends AbstractRedisClient {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(RedisClusterClient.class);
    private final RedisCodec<String, String> codec = new Utf8StringCodec();
    private Partitions partitions;

    private List<RedisURI> initialUris = Lists.newArrayList();

    /**
     * Initialize the client with an initial cluster URI.
     *
     * @param initialUri
     */
    public RedisClusterClient(RedisURI initialUri) {
        this(Collections.singletonList(checkNotNull(initialUri, "initialUri must not be null")));
    }

    /**
     * Initialize the client with a list of cluster URI's. All uris are tried in sequence for connecting initially to the
     * cluster. If any uri is sucessful for connection, the others are not tried anymore. The initial uri is needed to discover
     * the cluster structure for distributing the requests.
     *
     * @param initialUris
     */
    public RedisClusterClient(List<RedisURI> initialUris) {
        this.initialUris = initialUris;
        checkNotNull(initialUris, "initialUris must not be null");
        checkArgument(!initialUris.isEmpty(), "initialUris must not be empty");

        setDefaultTimeout(getFirstUri().getTimeout(), getFirstUri().getUnit());
    }

    /**
     * Open a new synchronous connection to the redis cluster that treats keys and values as UTF-8 strings.
     *
     * @return A new connection.
     */
    public RedisClusterConnection<String, String> connectCluster() {

        return connectCluster(codec);
    }

    /**
     * Open a new synchronous connection to the redis server. Use the supplied {@link RedisCodec codec} to encode/decode keys
     * and values.
     *
     * @param codec Use this codec to encode/decode keys and values.
     * @return A new connection.
     */
    @SuppressWarnings("unchecked")
    public <K, V> RedisClusterConnection<K, V> connectCluster(RedisCodec<K, V> codec) {

        FutureSyncInvocationHandler<K, V> h = new FutureSyncInvocationHandler<K, V>(connectClusterAsyncImpl(codec));
        return (RedisClusterConnection<K, V>) Proxy.newProxyInstance(getClass().getClassLoader(),
                new Class<?>[] { RedisClusterConnection.class }, h);
    }

    /**
     * Creates a connection to the redis cluster.
     *
     * @return A new connection.
     */
    public RedisClusterAsyncConnection<String, String> connectClusterAsync() {
        return connectClusterAsyncImpl(codec, getSocketAddressSupplier());
    }

    /**
     * Creates a connection to the redis cluster.
     *
     * @param codec Use this codec to encode/decode keys and values.
     * @return A new connection.
     */
    public <K, V> RedisClusterAsyncConnection<K, V> connectClusterAsync(RedisCodec<K, V> codec) {
        return connectClusterAsyncImpl(codec, getSocketAddressSupplier());
    }

    RedisAsyncConnectionImpl<String, String> connectAsyncImpl(SocketAddress socketAddress) {
        return connectAsyncImpl(codec, socketAddress);
    }

    /**
     * Create a connection to a redis socket address.
     *
     * @param socketAddress
     * @return RedisAsyncConnectionImpl<String, String>
     */
    <K, V> RedisAsyncConnectionImpl<K, V> connectAsyncImpl(RedisCodec<K, V> codec, final SocketAddress socketAddress) {

        logger.debug("connectAsyncImpl(" + socketAddress + ")");
        BlockingQueue<RedisCommand<K, V, ?>> queue = new LinkedBlockingQueue<RedisCommand<K, V, ?>>();

        CommandHandler<K, V> handler = new CommandHandler<K, V>(queue);
        RedisAsyncConnectionImpl<K, V> connection = new RedisAsyncConnectionImpl<K, V>(handler, codec, timeout, unit);

        connectAsyncImpl(handler, connection, new Supplier<SocketAddress>() {
            @Override
            public SocketAddress get() {
                return socketAddress;
            }
        }, true);

        connection.addListener(new CloseEvents.CloseListener() {
            @Override
            public void resourceClosed(Object resource) {
                closeableResources.remove(resource);
            }
        });
        closeableResources.add(connection);

        return connection;
    }

    <K, V> RedisAsyncConnectionImpl<K, V> connectClusterAsyncImpl(RedisCodec<K, V> codec) {
        return connectClusterAsyncImpl(codec, getSocketAddressSupplier());
    }

    /**
     * Create a clustered connection with command distributor.
     *
     * @param codec
     * @param socketAddressSupplier
     * @param <K>
     * @param <V>
     * @return
     */
    <K, V> RedisAsyncConnectionImpl<K, V> connectClusterAsyncImpl(RedisCodec<K, V> codec,
            final Supplier<SocketAddress> socketAddressSupplier) {

        if (partitions == null) {
            initializePartitions();
        }

        logger.debug("connectCluster(" + socketAddressSupplier.get() + ")");
        BlockingQueue<RedisCommand<K, V, ?>> queue = new LinkedBlockingQueue<RedisCommand<K, V, ?>>();

        CommandHandler<K, V> handler = new CommandHandler<K, V>(queue);

        final PooledClusterConnectionProvider<K, V> pooledClusterConnectionProvider = new PooledClusterConnectionProvider<K, V>(
                this, partitions, codec);

        final ClusterDistributionChannelWriter<K, V> clusterWriter = new ClusterDistributionChannelWriter<K, V>(handler,
                pooledClusterConnectionProvider);
        RedisAsyncConnectionImpl<K, V> connection = new RedisAsyncConnectionImpl<K, V>(clusterWriter, codec, timeout, unit);

        connectAsyncImpl(handler, connection, socketAddressSupplier, true);

        connection.addListener(new CloseEvents.CloseListener() {
            @Override
            public void resourceClosed(Object resource) {
                closeableResources.remove(resource);
                closeableResources.remove(clusterWriter);
                closeableResources.remove(pooledClusterConnectionProvider);
            }
        });

        closeableResources.add(connection);
        closeableResources.add(clusterWriter);
        closeableResources.add(pooledClusterConnectionProvider);

        if (getFirstUri().getPassword() != null) {
            connection.auth(new String(getFirstUri().getPassword()));
        }

        return connection;

    }

    /**
     * Reload partitions and re-initialize the distribution table.
     */
    public void reloadPartitions() {
        if (partitions == null) {
            initializePartitions();
        } else {
            Partitions partitions = getPartitions();
            this.partitions.getPartitions().clear();
            this.partitions.getPartitions().addAll(partitions.getPartitions());
        }
    }

    private void initializePartitions() {

        Partitions partitions = loadPartitions();
        this.partitions = partitions;
    }

    protected Partitions getPartitions() {
        return partitions;
    }

    /**
     * Retrieve partitions.
     *
     * @return Partitions
     */
    protected Partitions loadPartitions() {
        String clusterNodes = null;
        RedisURI nodeUri = null;
        Exception lastException = null;
        for (RedisURI initialUri : initialUris) {

            try {
                RedisAsyncConnectionImpl<String, String> connection = connectAsyncImpl(initialUri.getResolvedAddress());
                nodeUri = initialUri;
                clusterNodes = connection.clusterNodes().get();
                connection.close();
                break;
            } catch (Exception e) {
                lastException = e;
            }

        }

        if (clusterNodes == null) {
            if (lastException == null) {
                throw new RedisException("Cannot retrieve initial cluster partitions from initial URIs " + initialUris);
            }

            throw new RedisException("Cannot retrieve initial cluster partitions from initial URIs " + initialUris,
                    lastException);
        }

        Partitions partitions = ClusterPartitionParser.parse(clusterNodes);

        for (RedisClusterNode partition : partitions) {
            if (partition.getFlags().contains(RedisClusterNode.NodeFlag.MYSELF)) {
                partition.setUri(nodeUri);
            }

            if (nodeUri != null && nodeUri.getPassword() != null) {
                partition.getUri().setPassword(new String(nodeUri.getPassword()));
            }
        }
        return partitions;
    }

    protected RedisURI getFirstUri() {
        checkState(!initialUris.isEmpty(), "initialUris must not be empty");
        return initialUris.get(0);
    }

    private Supplier<SocketAddress> getSocketAddressSupplier() {
        return new Supplier<SocketAddress>() {
            @Override
            public SocketAddress get() {
                return getFirstUri().getResolvedAddress();
            }
        };
    }
}
TOP

Related Classes of com.lambdaworks.redis.cluster.RedisClusterClient

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.