io.vertx.redis.client.impl.RedisReplicationConnection Maven / Gradle / Ivy
package io.vertx.redis.client.impl;
import io.vertx.codegen.annotations.Nullable;
import io.vertx.core.*;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.redis.client.*;
import java.util.*;
public class RedisReplicationConnection implements RedisConnection {
private static final Logger LOG = LoggerFactory.getLogger(RedisReplicationConnection.class);
// we need some randomness, it doesn't need
// to be secure or unpredictable
private static final SplittableRandom RANDOM = new SplittableRandom();
// List of commands they should run every time only against master nodes
private static final List MASTER_ONLY_COMMANDS = new ArrayList<>();
public static void addMasterOnlyCommand(Command command) {
MASTER_ONLY_COMMANDS.add(command);
}
private final RedisOptions options;
private final RedisConnection master;
private final List replicas;
RedisReplicationConnection(Vertx vertx, RedisOptions options, RedisConnection master, List replicas) {
this.options = options;
this.master = master;
this.replicas = replicas;
}
@Override
public RedisConnection exceptionHandler(Handler handler) {
master.exceptionHandler(handler);
for (RedisConnection conn : replicas) {
if (conn != null) {
conn.exceptionHandler(handler);
}
}
return this;
}
@Override
public RedisConnection handler(Handler handler) {
master.handler(handler);
for (RedisConnection conn : replicas) {
if (conn != null) {
conn.handler(handler);
}
}
return this;
}
@Override
public RedisConnection pause() {
master.pause();
for (RedisConnection conn : replicas) {
if (conn != null) {
conn.pause();
}
}
return this;
}
@Override
public RedisConnection resume() {
master.resume();
for (RedisConnection conn : replicas) {
if (conn != null) {
conn.resume();
}
}
return this;
}
@Override
public RedisConnection fetch(long amount) {
master.fetch(amount);
for (RedisConnection conn : replicas) {
if (conn != null) {
conn.fetch(amount);
}
}
return this;
}
@Override
public RedisConnection endHandler(@Nullable Handler handler) {
master.endHandler(handler);
for (RedisConnection conn : replicas) {
if (conn != null) {
conn.endHandler(handler);
}
}
return this;
}
@Override
public Future send(Request request) {
// process commands for cluster mode
final RequestImpl req = (RequestImpl) request;
final Command cmd = req.command();
final boolean forceMasterEndpoint = MASTER_ONLY_COMMANDS.contains(cmd);
return selectMasterOrReplicaEndpoint(!cmd.isWrite(), forceMasterEndpoint)
.send(request);
}
@Override
public Future> batch(List requests) {
if (requests.isEmpty()) {
LOG.debug("Empty batch");
return Future.succeededFuture(Collections.emptyList());
} else {
boolean readOnly = false;
boolean forceMasterEndpoint = false;
// look up the base slot for the batch
for (Request request : requests) {
// process commands for cluster mode
final RequestImpl req = (RequestImpl) request;
final Command cmd = req.command();
readOnly |= !cmd.isWrite();
forceMasterEndpoint |= MASTER_ONLY_COMMANDS.contains(cmd);
}
return selectMasterOrReplicaEndpoint(readOnly, forceMasterEndpoint)
.batch(requests);
}
}
@Override
public Future close() {
List futures = new ArrayList<>();
futures.add(master.close());
for (RedisConnection conn : replicas) {
if (conn != null) {
futures.add(conn.close());
}
}
final Promise promise = Promise.promise();
CompositeFuture.all(futures)
.onSuccess(ignore -> promise.complete())
.onFailure(promise::fail);
return promise.future();
}
@Override
public boolean pendingQueueFull() {
boolean result = master.pendingQueueFull();
for (RedisConnection conn : replicas) {
if (conn != null) {
result |= conn.pendingQueueFull();
}
}
return result;
}
private RedisConnection selectMasterOrReplicaEndpoint(boolean read, boolean forceMasterEndpoint) {
if (forceMasterEndpoint) {
return master;
}
// always, never, share
RedisReplicas useReplicas = options.getUseReplicas();
if (read && useReplicas != RedisReplicas.NEVER && replicas.size() > 0) {
switch (useReplicas) {
// always use a replica for read commands
case ALWAYS:
return replicas.get(RANDOM.nextInt(replicas.size()));
// share read commands across master + replicas
case SHARE:
int r = RANDOM.nextInt(replicas.size() + 1);
if (r == 0) {
return master;
} else {
return replicas.get(r - 1);
}
}
}
// fallback to master
return master;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy