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

com.github.lontime.shaded.org.redisson.connection.MasterSlaveEntry Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2013-2021 Nikita Koksharov
 *
 * 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.github.lontime.shaded.org.redisson.connection;

import io.netty.channel.ChannelFuture;
import com.github.lontime.shaded.org.redisson.api.NodeType;
import com.github.lontime.shaded.org.redisson.client.RedisClient;
import com.github.lontime.shaded.org.redisson.client.RedisConnection;
import com.github.lontime.shaded.org.redisson.client.RedisPubSubConnection;
import com.github.lontime.shaded.org.redisson.client.protocol.CommandData;
import com.github.lontime.shaded.org.redisson.client.protocol.RedisCommand;
import com.github.lontime.shaded.org.redisson.config.MasterSlaveServersConfig;
import com.github.lontime.shaded.org.redisson.config.ReadMode;
import com.github.lontime.shaded.org.redisson.config.SubscriptionMode;
import com.github.lontime.shaded.org.redisson.connection.ClientConnectionsEntry.FreezeReason;
import com.github.lontime.shaded.org.redisson.connection.balancer.LoadBalancerManager;
import com.github.lontime.shaded.org.redisson.connection.pool.MasterConnectionPool;
import com.github.lontime.shaded.org.redisson.connection.pool.MasterPubSubConnectionPool;
import com.github.lontime.shaded.org.redisson.misc.RedisURI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 *
 * @author Nikita Koksharov
 *
 */
public class MasterSlaveEntry {

    final Logger log = LoggerFactory.getLogger(getClass());

    LoadBalancerManager slaveBalancer;
    ClientConnectionsEntry masterEntry;

    int references;
    
    final MasterSlaveServersConfig config;
    final ConnectionManager connectionManager;

    final MasterConnectionPool writeConnectionPool;
    
    final MasterPubSubConnectionPool pubSubConnectionPool;

    final AtomicBoolean active = new AtomicBoolean(true);
    
    public MasterSlaveEntry(ConnectionManager connectionManager, MasterSlaveServersConfig config) {
        this.connectionManager = connectionManager;
        this.config = config;

        slaveBalancer = new LoadBalancerManager(config, connectionManager, this);
        writeConnectionPool = new MasterConnectionPool(config, connectionManager, this);
        pubSubConnectionPool = new MasterPubSubConnectionPool(config, connectionManager, this);
    }

    public MasterSlaveServersConfig getConfig() {
        return config;
    }

    public CompletableFuture initSlaveBalancer(Collection disconnectedNodes) {
        return initSlaveBalancer(disconnectedNodes, null);
    }

    public CompletableFuture initSlaveBalancer(Collection disconnectedNodes, String slaveSSLHostname) {
        List> result = new ArrayList<>(config.getSlaveAddresses().size());
        for (String address : config.getSlaveAddresses()) {
            RedisURI uri = new RedisURI(address);
            CompletableFuture f = addSlave(uri, disconnectedNodes.contains(uri), NodeType.SLAVE, slaveSSLHostname);
            result.add(f);
        }

        CompletableFuture future = CompletableFuture.allOf(result.toArray(new CompletableFuture[0]));
        return future.thenAccept(v -> {
            useMasterAsSlave();
        });
    }

    private void useMasterAsSlave() {
        if (slaveBalancer.getAvailableClients() == 0) {
            slaveUp(masterEntry.getClient().getAddr(), FreezeReason.SYSTEM);
        } else {
            slaveDown(masterEntry.getClient().getAddr(), FreezeReason.SYSTEM);
        }
    }

    public CompletableFuture setupMasterEntry(InetSocketAddress address, RedisURI uri) {
        RedisClient client = connectionManager.createClient(NodeType.MASTER, address, uri, null);
        return setupMasterEntry(client);
    }

    public CompletableFuture setupMasterEntry(RedisURI address) {
        return setupMasterEntry(address, null);
    }

    public CompletableFuture setupMasterEntry(RedisURI address, String sslHostname) {
        RedisClient client = connectionManager.createClient(NodeType.MASTER, address, sslHostname);
        return setupMasterEntry(client);
    }

    private CompletableFuture setupMasterEntry(RedisClient client) {
        CompletableFuture addrFuture = client.resolveAddr();
        return addrFuture.thenCompose(res -> {
            masterEntry = new ClientConnectionsEntry(
                    client,
                    config.getMasterConnectionMinimumIdleSize(),
                    config.getMasterConnectionPoolSize(),
                    config.getSubscriptionConnectionMinimumIdleSize(),
                    config.getSubscriptionConnectionPoolSize(),
                    connectionManager,
                    NodeType.MASTER);

            List> futures = new ArrayList<>();
            if (!config.checkSkipSlavesInit() && !slaveBalancer.contains(client.getAddr())) {
                CompletableFuture masterAsSlaveFuture = addSlave(client.getAddr(), client.getConfig().getAddress(),
                        true, NodeType.MASTER, client.getConfig().getSslHostname());
                futures.add(masterAsSlaveFuture);
            }

            CompletableFuture writeFuture = writeConnectionPool.add(masterEntry);
            futures.add(writeFuture);

            if (config.getSubscriptionMode() == SubscriptionMode.MASTER) {
                CompletableFuture pubSubFuture = pubSubConnectionPool.add(masterEntry);
                futures.add(pubSubFuture);
            }
            return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
        }).whenComplete((r, e) -> {
            if (e != null) {
                client.shutdownAsync();
            }
        }).thenApply(r -> client);
    }

    public boolean slaveDown(ClientConnectionsEntry entry, FreezeReason freezeReason) {
        ClientConnectionsEntry e = slaveBalancer.freeze(entry, freezeReason);
        if (e == null) {
            return false;
        }
        
        return slaveDown(entry);
    }
    
    public boolean slaveDown(InetSocketAddress address, FreezeReason freezeReason) {
        ClientConnectionsEntry entry = slaveBalancer.freeze(address, freezeReason);
        if (entry == null) {
            return false;
        }
        
        return slaveDown(entry);
    }

    public CompletableFuture slaveDownAsync(InetSocketAddress address, FreezeReason freezeReason) {
        ClientConnectionsEntry entry = slaveBalancer.freeze(address, freezeReason);
        if (entry == null) {
            return CompletableFuture.completedFuture(false);
        }

        return slaveDownAsync(entry);
    }

    public boolean slaveDown(RedisURI address, FreezeReason freezeReason) {
        ClientConnectionsEntry entry = slaveBalancer.freeze(address, freezeReason);
        if (entry == null) {
            return false;
        }
        
        return slaveDown(entry);
    }

    private boolean slaveDown(ClientConnectionsEntry entry) {
        if (entry.isMasterForRead()) {
            return false;
        }

        // add master as slave if no more slaves available
        if (!config.checkSkipSlavesInit() && slaveBalancer.getAvailableClients() == 0) {
            if (slaveBalancer.unfreeze(masterEntry.getClient().getAddr(), FreezeReason.SYSTEM)) {
                log.info("master {} used as slave", masterEntry.getClient().getAddr());
            }
        }

        return nodeDown(entry);
    }

    private CompletableFuture slaveDownAsync(ClientConnectionsEntry entry) {
        if (entry.isMasterForRead()) {
            return CompletableFuture.completedFuture(false);
        }

        // add master as slave if no more slaves available
        if (!config.checkSkipSlavesInit() && slaveBalancer.getAvailableClients() == 0) {
            CompletableFuture f = slaveBalancer.unfreezeAsync(masterEntry.getClient().getAddr(), FreezeReason.SYSTEM);
            f.thenApply(value -> {
                if (value) {
                    log.info("master {} used as slave", masterEntry.getClient().getAddr());
                }

                return nodeDown(entry);
            });
        }

        return CompletableFuture.completedFuture(nodeDown(entry));
    }

    public void masterDown() {
        nodeDown(masterEntry);
    }

    public boolean nodeDown(ClientConnectionsEntry entry) {
        entry.reset();
        
        for (RedisConnection connection : entry.getAllConnections()) {
            connection.closeAsync();
            reattachBlockingQueue(connection.getCurrentCommand());
        }
        while (true) {
            RedisConnection connection = entry.pollConnection(null);
            if (connection == null) {
                break;
            }
        }
        entry.getAllConnections().clear();

        for (RedisPubSubConnection connection : entry.getAllSubscribeConnections()) {
            connection.closeAsync();
            connectionManager.getSubscribeService().reattachPubSub(connection);
        }
        while (true) {
            RedisConnection connection = entry.pollSubscribeConnection();
            if (connection == null) {
                break;
            }
        }
        entry.getAllSubscribeConnections().clear();
        
        return true;
    }

    private void reattachBlockingQueue(CommandData commandData) {
        if (commandData == null
                || !commandData.isBlockingCommand()
                    || commandData.getPromise().isDone()) {
            return;
        }

        String key = null;
        for (int i = 0; i < commandData.getParams().length; i++) {
            Object param = commandData.getParams()[i];
            if ("STREAMS".equals(param)) {
                key = (String) commandData.getParams()[i+1];
                break;
            }
        }
        if (key == null) {
            key = (String) commandData.getParams()[0];
        }

        MasterSlaveEntry entry = connectionManager.getEntry(key);
        if (entry == null) {
            connectionManager.newTimeout(timeout ->
                    reattachBlockingQueue(commandData), 1, TimeUnit.SECONDS);
            return;
        }

        CompletableFuture newConnectionFuture = entry.connectionWriteOp(commandData.getCommand());
        newConnectionFuture.whenComplete((newConnection, e) -> {
            if (e != null) {
                connectionManager.newTimeout(timeout ->
                        reattachBlockingQueue(commandData), 1, TimeUnit.SECONDS);
                return;
            }

            if (commandData.getPromise().isDone()) {
                entry.releaseWrite(newConnection);
                return;
            }

            ChannelFuture channelFuture = newConnection.send(commandData);
            channelFuture.addListener(future -> {
                if (!future.isSuccess()) {
                    connectionManager.newTimeout(timeout ->
                            reattachBlockingQueue(commandData), 1, TimeUnit.SECONDS);
                    return;
                }
                log.info("command '{}' has been resent to '{}'", commandData, newConnection.getRedisClient());
            });
            commandData.getPromise().whenComplete((r, ex) -> {
                entry.releaseWrite(newConnection);
            });
        });
    }
    
    public boolean hasSlave(RedisClient redisClient) {
        return slaveBalancer.contains(redisClient);
    }

    public boolean hasSlave(InetSocketAddress addr) {
        return slaveBalancer.contains(addr);
    }
    
    public boolean hasSlave(RedisURI addr) {
        return slaveBalancer.contains(addr);
    }

    public int getAvailableSlaves() {
        return slaveBalancer.getAvailableSlaves();
    }

    public int getAvailableClients() {
        return slaveBalancer.getAvailableClients();
    }

    public CompletableFuture addSlave(RedisURI address) {
        return addSlave(address, false, NodeType.SLAVE, null);
    }
    
    public CompletableFuture addSlave(InetSocketAddress address, RedisURI uri) {
        return addSlave(address, uri, false, NodeType.SLAVE, null);
    }

    public CompletableFuture addSlave(InetSocketAddress address, RedisURI uri, String sslHostname) {
        return addSlave(address, uri, false, NodeType.SLAVE, sslHostname);
    }

    public CompletableFuture addSlave(RedisClient client) {
        return addSlave(client, false, NodeType.SLAVE);
    }

    private CompletableFuture addSlave(RedisClient client, boolean freezed, NodeType nodeType) {
        CompletableFuture addrFuture = client.resolveAddr();
        return addrFuture.thenCompose(res -> {
            ClientConnectionsEntry entry = new ClientConnectionsEntry(client,
                    config.getSlaveConnectionMinimumIdleSize(),
                    config.getSlaveConnectionPoolSize(),
                    config.getSubscriptionConnectionMinimumIdleSize(),
                    config.getSubscriptionConnectionPoolSize(), connectionManager, nodeType);
            if (freezed) {
                synchronized (entry) {
                    entry.setFreezeReason(FreezeReason.SYSTEM);
                }
            }
            return slaveBalancer.add(entry);
        }).whenComplete((r, ex) -> {
            if (ex != null) {
                client.shutdownAsync();
            }
        });
    }

    private CompletableFuture addSlave(InetSocketAddress address, RedisURI uri, boolean freezed, NodeType nodeType, String sslHostname) {
        RedisClient client = connectionManager.createClient(NodeType.SLAVE, address, uri, sslHostname);
        return addSlave(client, freezed, nodeType);
    }
    
    public CompletableFuture addSlave(RedisURI address, boolean freezed, NodeType nodeType, String sslHostname) {
        RedisClient client = connectionManager.createClient(nodeType, address, sslHostname);
        return addSlave(client, freezed, nodeType);
    }

    public Collection getAllEntries() {
        return slaveBalancer.getEntries();
    }
    
    public ClientConnectionsEntry getEntry(RedisClient redisClient) {
        return slaveBalancer.getEntry(redisClient);
    }

    public ClientConnectionsEntry getEntry(RedisURI addr) {
        return slaveBalancer.getEntry(addr);
    }

    public RedisClient getClient() {
        return masterEntry.getClient();
    }

    public boolean slaveUp(ClientConnectionsEntry entry, FreezeReason freezeReason) {
        if (!slaveBalancer.unfreeze(entry, freezeReason)) {
            return false;
        }

        InetSocketAddress addr = masterEntry.getClient().getAddr();
        // exclude master from slaves
        if (!config.checkSkipSlavesInit()
                && !addr.equals(entry.getClient().getAddr())) {
            if (slaveDown(addr, FreezeReason.SYSTEM)) {
                log.info("master {} excluded from slaves", addr);
            }
        }
        return true;
    }
    
    public boolean slaveUp(RedisURI address, FreezeReason freezeReason) {
        if (!slaveBalancer.unfreeze(address, freezeReason)) {
            return false;
        }

        InetSocketAddress addr = masterEntry.getClient().getAddr();
        // exclude master from slaves
        if (!config.checkSkipSlavesInit()
                && !RedisURI.compare(addr, address)) {
            if (slaveDown(addr, FreezeReason.SYSTEM)) {
                log.info("master {} excluded from slaves", addr);
            }
        }
        return true;
    }

    public CompletableFuture slaveUpAsync(RedisURI address, FreezeReason freezeReason) {
        CompletableFuture f = slaveBalancer.unfreezeAsync(address, freezeReason);
        return f.thenCompose(v -> {
            if (!v) {
                return CompletableFuture.completedFuture(false);
            }

            InetSocketAddress addr = masterEntry.getClient().getAddr();
            // exclude master from slaves
            if (!config.checkSkipSlavesInit()
                    && !RedisURI.compare(addr, address)) {
                CompletableFuture downFuture = slaveDownAsync(addr, FreezeReason.SYSTEM);
                return downFuture.thenApply(r -> {
                    if (r) {
                        log.info("master {} excluded from slaves", addr);
                    }
                    return r;
                });
            }

            return CompletableFuture.completedFuture(true);
        });
    }

    public boolean slaveUp(InetSocketAddress address, FreezeReason freezeReason) {
        if (!slaveBalancer.unfreeze(address, freezeReason)) {
            return false;
        }

        InetSocketAddress addr = masterEntry.getClient().getAddr();
        // exclude master from slaves
        if (!config.checkSkipSlavesInit()
                && !addr.equals(address)) {
            if (slaveDown(addr, FreezeReason.SYSTEM)) {
                log.info("master {} excluded from slaves", addr);
            }
        }
        return true;
    }


    /**
     * Freeze slave with redis(s)://host:port from slaves list.
     * Re-attach pub/sub listeners from it to other slave.
     * Shutdown old master client.
     * 
     * @param address of Redis
     * @return client 
     */
    public CompletableFuture changeMaster(RedisURI address) {
        ClientConnectionsEntry oldMaster = masterEntry;
        CompletableFuture future = setupMasterEntry(address);
        return changeMaster(address, oldMaster, future);
    }
    
    public CompletableFuture changeMaster(InetSocketAddress address, RedisURI uri) {
        ClientConnectionsEntry oldMaster = masterEntry;
        CompletableFuture future = setupMasterEntry(address, uri);
        return changeMaster(uri, oldMaster, future);
    }


    private CompletableFuture changeMaster(RedisURI address, ClientConnectionsEntry oldMaster,
                              CompletableFuture future) {
        return future.whenComplete((newMasterClient, e) -> {
            if (e != null) {
                if (oldMaster != masterEntry) {
                    writeConnectionPool.remove(masterEntry);
                    pubSubConnectionPool.remove(masterEntry);
                    masterEntry.shutdownAsync();
                    masterEntry = oldMaster;
                }
                log.error("Unable to change master from: " + oldMaster.getClient().getAddr() + " to: " + address, e);
                return;
            }
            
            writeConnectionPool.remove(oldMaster);
            pubSubConnectionPool.remove(oldMaster);

            synchronized (oldMaster) {
                oldMaster.setFreezeReason(FreezeReason.MANAGER);
            }
            nodeDown(oldMaster);

            slaveBalancer.changeType(oldMaster.getClient().getAddr(), NodeType.SLAVE);
            slaveBalancer.changeType(newMasterClient.getAddr(), NodeType.MASTER);
            // freeze in slaveBalancer
            slaveDown(oldMaster.getClient().getAddr(), FreezeReason.MANAGER);

            // check if at least one slave is available, use master as slave if false
            if (!config.checkSkipSlavesInit()) {
                useMasterAsSlave();
            }
            oldMaster.shutdownAsync();
            log.info("master {} has changed to {}", oldMaster.getClient().getAddr(), masterEntry.getClient().getAddr());
        });
    }

    public CompletableFuture shutdownAsync() {
        if (!active.compareAndSet(true, false)) {
            return CompletableFuture.completedFuture(null);
        }

        List> futures = new ArrayList<>();
        if (masterEntry != null) {
            futures.add(masterEntry.shutdownAsync());
        }
        futures.add(slaveBalancer.shutdownAsync());

        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
    }

    public CompletableFuture connectionWriteOp(RedisCommand command) {
        return writeConnectionPool.get(command);
    }

    public CompletableFuture redirectedConnectionWriteOp(RedisCommand command, RedisURI addr) {
        return slaveBalancer.getConnection(command, addr);
    }

    public CompletableFuture connectionReadOp(RedisCommand command) {
        if (config.getReadMode() == ReadMode.MASTER) {
            return connectionWriteOp(command);
        }
        return slaveBalancer.nextConnection(command);
    }

    public CompletableFuture connectionReadOp(RedisCommand command, RedisURI addr) {
        return slaveBalancer.getConnection(command, addr);
    }
    
    public CompletableFuture connectionReadOp(RedisCommand command, RedisClient client) {
        if (config.getReadMode() == ReadMode.MASTER) {
            return connectionWriteOp(command);
        }
        return slaveBalancer.getConnection(command, client);
    }

    public CompletableFuture nextPubSubConnection() {
        if (config.getSubscriptionMode() == SubscriptionMode.MASTER) {
            return pubSubConnectionPool.get();
        }
        
        return slaveBalancer.nextPubSubConnection();
    }

    public void returnPubSubConnection(RedisPubSubConnection connection) {
        if (config.getSubscriptionMode() == SubscriptionMode.MASTER) {
            pubSubConnectionPool.returnConnection(masterEntry, connection);
            return;
        }
        slaveBalancer.returnPubSubConnection(connection);
    }

    public void releaseWrite(RedisConnection connection) {
        writeConnectionPool.returnConnection(masterEntry, connection);
    }

    public void releaseRead(RedisConnection connection) {
        if (config.getReadMode() == ReadMode.MASTER) {
            releaseWrite(connection);
            return;
        }
        slaveBalancer.returnConnection(connection);
    }

    public void incReference() {
        references++;
    }

    public int decReference() {
        return --references;
    }

    public int getReferences() {
        return references;
    }

    @Override
    public String toString() {
        return "MasterSlaveEntry [masterEntry=" + masterEntry + "]";
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy