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 + "]";
}
}