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

com.lambdaworks.redis.cluster.StatefulRedisClusterConnectionImpl Maven / Gradle / Ivy

Go to download

Advanced and thread-safe Java Redis client for synchronous, asynchronous, and reactive usage. Supports Cluster, Sentinel, Pipelining, Auto-Reconnect, Codecs and much more.

The newest version!
/*
 * Copyright 2011-2016 the original author or authors.
 *
 * 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.lambdaworks.redis.cluster;

import static com.lambdaworks.redis.protocol.CommandType.AUTH;
import static com.lambdaworks.redis.protocol.CommandType.READONLY;
import static com.lambdaworks.redis.protocol.CommandType.READWRITE;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;

import com.lambdaworks.redis.*;
import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.api.sync.RedisCommands;
import com.lambdaworks.redis.cluster.api.NodeSelectionSupport;
import com.lambdaworks.redis.cluster.api.StatefulRedisClusterConnection;
import com.lambdaworks.redis.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import com.lambdaworks.redis.cluster.api.reactive.RedisAdvancedClusterReactiveCommands;
import com.lambdaworks.redis.cluster.api.sync.NodeSelection;
import com.lambdaworks.redis.cluster.api.sync.NodeSelectionCommands;
import com.lambdaworks.redis.cluster.api.sync.RedisAdvancedClusterCommands;
import com.lambdaworks.redis.cluster.api.sync.RedisClusterCommands;
import com.lambdaworks.redis.cluster.models.partitions.Partitions;
import com.lambdaworks.redis.cluster.models.partitions.RedisClusterNode;
import com.lambdaworks.redis.codec.RedisCodec;
import com.lambdaworks.redis.internal.AbstractInvocationHandler;
import com.lambdaworks.redis.internal.LettuceAssert;
import com.lambdaworks.redis.protocol.CompleteableCommand;
import com.lambdaworks.redis.protocol.ConnectionWatchdog;
import com.lambdaworks.redis.protocol.RedisCommand;

import io.netty.channel.ChannelHandler;

/**
 * A thread-safe connection to a Redis Cluster. Multiple threads may share one {@link StatefulRedisClusterConnectionImpl}
 *
 * A {@link ConnectionWatchdog} monitors each connection and reconnects automatically until {@link #close} is called. All
 * pending commands will be (re)sent after successful reconnection.
 * 
 * @author Mark Paluch
 * @since 4.0
 */
public class StatefulRedisClusterConnectionImpl extends RedisChannelHandler
        implements StatefulRedisClusterConnection {

    private Partitions partitions;

    private char[] password;
    private boolean readOnly;

    protected final RedisCodec codec;
    protected final RedisAdvancedClusterCommands sync;
    protected final RedisAdvancedClusterAsyncCommandsImpl async;
    protected final RedisAdvancedClusterReactiveCommandsImpl reactive;

    /**
     * Initialize a new connection.
     *
     * @param writer the channel writer
     * @param codec Codec used to encode/decode keys and values.
     * @param timeout Maximum time to wait for a response.
     * @param unit Unit of time for the timeout.
     */
    public StatefulRedisClusterConnectionImpl(RedisChannelWriter writer, RedisCodec codec, long timeout,
            TimeUnit unit) {
        super(writer, timeout, unit);
        this.codec = codec;

        this.async = new RedisAdvancedClusterAsyncCommandsImpl<>(this, codec);
        this.sync = (RedisAdvancedClusterCommands) Proxy.newProxyInstance(AbstractRedisClient.class.getClassLoader(),
                new Class[] { RedisAdvancedClusterCommands.class }, syncInvocationHandler());
        this.reactive = new RedisAdvancedClusterReactiveCommandsImpl<>(this, codec);
    }

    @Override
    public RedisAdvancedClusterCommands sync() {
        return sync;
    }

    public InvocationHandler syncInvocationHandler() {
        return new ClusterFutureSyncInvocationHandler<>(this, async());
    }

    @Override
    public RedisAdvancedClusterAsyncCommands async() {
        return async;
    }

    @Override
    public RedisAdvancedClusterReactiveCommands reactive() {
        return reactive;
    }

    private RedisURI lookup(String nodeId) {

        for (RedisClusterNode partition : partitions) {
            if (partition.getNodeId().equals(nodeId)) {
                return partition.getUri();
            }
        }
        return null;
    }

    @Override
    public StatefulRedisConnection getConnection(String nodeId) {

        RedisURI redisURI = lookup(nodeId);

        if (redisURI == null) {
            throw new RedisException("NodeId " + nodeId + " does not belong to the cluster");
        }

        return getClusterDistributionChannelWriter().getClusterConnectionProvider()
                .getConnection(ClusterConnectionProvider.Intent.WRITE, nodeId);
    }

    @Override
    public StatefulRedisConnection getConnection(String host, int port) {

        return getClusterDistributionChannelWriter().getClusterConnectionProvider()
                .getConnection(ClusterConnectionProvider.Intent.WRITE, host, port);
    }

    public ClusterDistributionChannelWriter getClusterDistributionChannelWriter() {
        return (ClusterDistributionChannelWriter) super.getChannelWriter();
    }

    @Override
    public void activated() {

        super.activated();
        // do not block in here, since the channel flow will be interrupted.
        if (password != null) {
            async.authAsync(new String(password));
        }

        if (readOnly) {
            async.readOnly();
        }
    }

    @Override
    public > C dispatch(C cmd) {

        RedisCommand local = cmd;

        if (local.getType().name().equals(AUTH.name())) {
            local = attachOnComplete(local, status -> {
                if (status.equals("OK") && cmd.getArgs().getFirstString() != null) {
                    this.password = cmd.getArgs().getFirstString().toCharArray();
                }
            });
        }

        if (local.getType().name().equals(READONLY.name())) {
            local = attachOnComplete(local, status -> {
                if (status.equals("OK")) {
                    this.readOnly = true;
                }
            });
        }

        if (local.getType().name().equals(READWRITE.name())) {
            local = attachOnComplete(local, status -> {
                if (status.equals("OK")) {
                    this.readOnly = false;
                }
            });
        }

        return super.dispatch((C) local);
    }

    private  RedisCommand attachOnComplete(RedisCommand command, Consumer consumer) {

        if (command instanceof CompleteableCommand) {
            CompleteableCommand completeable = (CompleteableCommand) command;
            completeable.onComplete(consumer);
        }
        return command;
    }

    public void setPartitions(Partitions partitions) {
        this.partitions = partitions;
        getClusterDistributionChannelWriter().setPartitions(partitions);
    }

    public Partitions getPartitions() {
        return partitions;
    }

    @Override
    public void setReadFrom(ReadFrom readFrom) {
        LettuceAssert.notNull(readFrom, "ReadFrom must not be null");
        getClusterDistributionChannelWriter().setReadFrom(readFrom);
    }

    @Override
    public ReadFrom getReadFrom() {
        return getClusterDistributionChannelWriter().getReadFrom();
    }

    /**
     * Invocation-handler to synchronize API calls which use Futures as backend. This class leverages the need to implement a
     * full sync class which just delegates every request.
     *
     * @param  Key type.
     * @param  Value type.
     * @author Mark Paluch
     * @since 3.0
     */
    @SuppressWarnings("unchecked")
    private static class ClusterFutureSyncInvocationHandler extends AbstractInvocationHandler {

        private final StatefulRedisClusterConnection connection;
        private final Object asyncApi;
        private final Map apiMethodCache = new ConcurrentHashMap<>(RedisClusterCommands.class.getMethods().length, 1);
        private final Map connectionMethodCache = new ConcurrentHashMap<>(5, 1);

        private static final Constructor LOOKUP_CONSTRUCTOR;

        static {
            try {
                LOOKUP_CONSTRUCTOR = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
                if (!LOOKUP_CONSTRUCTOR.isAccessible()) {
                    LOOKUP_CONSTRUCTOR.setAccessible(true);
                }
            } catch (NoSuchMethodException exp) {
                // should be impossible, but...
                throw new IllegalStateException(exp);
            }
        }

        ClusterFutureSyncInvocationHandler(StatefulRedisClusterConnection connection, Object asyncApi) {
            this.connection = connection;
            this.asyncApi = asyncApi;
        }

        static MethodHandles.Lookup privateMethodHandleLookup(Class declaringClass) {
            try {
                return LOOKUP_CONSTRUCTOR.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE);
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                throw new IllegalStateException(e);
            }
        }

        static MethodHandle getDefaultMethodHandle(Method method) {
            Class declaringClass = method.getDeclaringClass();
            try {
                return privateMethodHandleLookup(declaringClass).unreflectSpecial(method, declaringClass);
            } catch (IllegalAccessException e) {
                throw new IllegalArgumentException("Did not pass in an interface method: " + method);
            }
        }

        /**
         *
         * @see AbstractInvocationHandler#handleInvocation(Object, Method, Object[])
         */
        @Override
        protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable {

            try {

                if (method.isDefault()) {
                    return getDefaultMethodHandle(method).bindTo(proxy).invokeWithArguments(args);
                }

                if (method.getName().equals("getConnection") && args.length > 0) {
                    Method targetMethod = connectionMethodCache.computeIfAbsent(method, key -> {
                        try {
                            return connection.getClass().getMethod(key.getName(), key.getParameterTypes());
                        } catch (NoSuchMethodException e) {
                            throw new IllegalStateException(e);
                        }
                    });

                    Object result = targetMethod.invoke(connection, args);
                    if (result instanceof StatefulRedisClusterConnection) {
                        StatefulRedisClusterConnection connection = (StatefulRedisClusterConnection) result;
                        return connection.sync();
                    }

                    if (result instanceof StatefulRedisConnection) {
                        StatefulRedisConnection connection = (StatefulRedisConnection) result;
                        return connection.sync();
                    }
                }

                if (method.getName().equals("readonly") && args.length == 1) {
                    return nodes((Predicate) args[0], ClusterConnectionProvider.Intent.READ, false);
                }

                if (method.getName().equals("nodes") && args.length == 1) {
                    return nodes((Predicate) args[0], ClusterConnectionProvider.Intent.WRITE, false);
                }

                if (method.getName().equals("nodes") && args.length == 2) {
                    return nodes((Predicate) args[0], ClusterConnectionProvider.Intent.WRITE,
                            (Boolean) args[1]);
                }

                Method targetMethod = apiMethodCache.computeIfAbsent(method, key -> {

                    try {
                        return asyncApi.getClass().getMethod(key.getName(), key.getParameterTypes());
                    } catch (NoSuchMethodException e) {
                        throw new IllegalStateException(e);
                    }
                });

                Object result = targetMethod.invoke(asyncApi, args);

                if (result instanceof RedisFuture) {
                    RedisFuture command = (RedisFuture) result;
                    if (!method.getName().equals("exec") && !method.getName().equals("multi")) {
                        if (connection instanceof StatefulRedisConnection && ((StatefulRedisConnection) connection).isMulti()) {
                            return null;
                        }
                    }
                    return LettuceFutures.awaitOrCancel(command, connection.getTimeout(), connection.getTimeoutUnit());
                }

                return result;

            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            }
        }

        protected NodeSelection nodes(Predicate predicate, ClusterConnectionProvider.Intent intent,
                boolean dynamic) {

            NodeSelectionSupport, ?> selection;

            if (dynamic) {
                selection = new DynamicSyncNodeSelection<>(connection, predicate, intent);
            } else {
                selection = new StaticSyncNodeSelection<>(connection, predicate, intent);
            }

            NodeSelectionInvocationHandler h = new NodeSelectionInvocationHandler((AbstractNodeSelection) selection,
                    true, connection.getTimeout(), connection.getTimeoutUnit());
            return (NodeSelection) Proxy.newProxyInstance(NodeSelectionSupport.class.getClassLoader(),
                    new Class[] { NodeSelectionCommands.class, NodeSelection.class }, h);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy