org.redisson.command.CommandBatchService Maven / Gradle / Ivy
/**
* Copyright 2018 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 org.redisson.command;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.redisson.RedissonShutdownException;
import org.redisson.api.BatchOptions;
import org.redisson.api.BatchOptions.ExecutionMode;
import org.redisson.api.BatchResult;
import org.redisson.api.RFuture;
import org.redisson.client.RedisAskException;
import org.redisson.client.RedisConnection;
import org.redisson.client.RedisLoadingException;
import org.redisson.client.RedisMovedException;
import org.redisson.client.RedisResponseTimeoutException;
import org.redisson.client.RedisTimeoutException;
import org.redisson.client.RedisTryAgainException;
import org.redisson.client.WriteRedisConnectionException;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.BatchCommandData;
import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.CommandsData;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.connection.ConnectionManager;
import org.redisson.connection.MasterSlaveEntry;
import org.redisson.connection.NodeSource;
import org.redisson.connection.NodeSource.Redirect;
import org.redisson.misc.CountableListener;
import org.redisson.misc.LogHelper;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.pubsub.AsyncSemaphore;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.PlatformDependent;
/**
*
* @author Nikita Koksharov
*
*/
public class CommandBatchService extends CommandAsyncService {
public static class ConnectionEntry {
boolean firstCommand = true;
RFuture connectionFuture;
public RFuture getConnectionFuture() {
return connectionFuture;
}
public void setConnectionFuture(RFuture connectionFuture) {
this.connectionFuture = connectionFuture;
}
public boolean isFirstCommand() {
return firstCommand;
}
public void setFirstCommand(boolean firstCommand) {
this.firstCommand = firstCommand;
}
}
public static class Entry {
Deque> commands = new LinkedBlockingDeque>();
volatile boolean readOnlyMode = true;
public Deque> getCommands() {
return commands;
}
public void setReadOnlyMode(boolean readOnlyMode) {
this.readOnlyMode = readOnlyMode;
}
public boolean isReadOnlyMode() {
return readOnlyMode;
}
public void clearErrors() {
for (BatchCommandData, ?> commandEntry : commands) {
commandEntry.clearError();
}
}
}
private final AtomicInteger index = new AtomicInteger();
private ConcurrentMap commands = PlatformDependent.newConcurrentHashMap();
private ConcurrentMap connections = PlatformDependent.newConcurrentHashMap();
private BatchOptions options;
private Map, List> nestedServices = PlatformDependent.newConcurrentHashMap();
private AtomicBoolean executed = new AtomicBoolean();
public CommandBatchService(ConnectionManager connectionManager) {
super(connectionManager);
}
public CommandBatchService(ConnectionManager connectionManager, BatchOptions options) {
super(connectionManager);
this.options = options;
}
public BatchOptions getOptions() {
return options;
}
public void add(RFuture> future, List services) {
nestedServices.put(future, services);
}
@Override
public void async(boolean readOnlyMode, NodeSource nodeSource,
Codec codec, RedisCommand command, Object[] params, RPromise mainPromise, int attempt, boolean ignoreRedirect) {
if (nodeSource.getEntry() != null) {
Entry entry = commands.get(nodeSource.getEntry());
if (entry == null) {
entry = new Entry();
Entry oldEntry = commands.putIfAbsent(nodeSource.getEntry(), entry);
if (oldEntry != null) {
entry = oldEntry;
}
}
if (!readOnlyMode) {
entry.setReadOnlyMode(false);
}
Object[] batchParams = null;
if (!isRedisBasedQueue()) {
batchParams = params;
}
BatchCommandData commandData = new BatchCommandData(mainPromise, codec, command, batchParams, index.incrementAndGet());
entry.getCommands().add(commandData);
}
if (!isRedisBasedQueue()) {
return;
}
if (!readOnlyMode && this.options.getExecutionMode() == ExecutionMode.REDIS_READ_ATOMIC) {
throw new IllegalStateException("Data modification commands can't be used with queueStore=REDIS_READ_ATOMIC");
}
super.async(readOnlyMode, nodeSource, codec, command, params, mainPromise, attempt, true);
}
AsyncSemaphore semaphore = new AsyncSemaphore(0);
@Override
protected RPromise createPromise() {
if (isRedisBasedQueue()) {
return new BatchPromise(executed);
}
return super.createPromise();
}
@Override
protected void releaseConnection(NodeSource source, RFuture connectionFuture,
boolean isReadOnly, RPromise attemptPromise, AsyncDetails details) {
if (!isRedisBasedQueue() || RedisCommands.EXEC.getName().equals(details.getCommand().getName())) {
super.releaseConnection(source, connectionFuture, isReadOnly, attemptPromise, details);
}
}
@Override
protected void handleSuccess(final AsyncDetails details, RPromise promise, RedisCommand> command, R res) {
if (RedisCommands.EXEC.getName().equals(command.getName())) {
super.handleSuccess(details, promise, command, res);
return;
}
if (RedisCommands.DISCARD.getName().equals(command.getName())) {
super.handleSuccess(details, promise, command, null);
if (executed.compareAndSet(false, true)) {
details.getConnectionFuture().getNow().forceFastReconnectAsync().addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
CommandBatchService.super.releaseConnection(details.getSource(), details.getConnectionFuture(), details.isReadOnlyMode(), details.getAttemptPromise(), details);
}
});
}
return;
}
if (isRedisBasedQueue()) {
BatchPromise batchPromise = (BatchPromise) promise;
RPromise sentPromise = (RPromise) batchPromise.getSentPromise();
super.handleSuccess(details, sentPromise, command, null);
semaphore.release();
}
}
@Override
protected void handleError(final AsyncDetails details, RPromise promise, Throwable cause) {
if (isRedisBasedQueue() && promise instanceof BatchPromise) {
BatchPromise batchPromise = (BatchPromise) promise;
RPromise sentPromise = (RPromise) batchPromise.getSentPromise();
sentPromise.tryFailure(cause);
promise.tryFailure(cause);
if (executed.compareAndSet(false, true)) {
details.getConnectionFuture().getNow().forceFastReconnectAsync().addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
CommandBatchService.super.releaseConnection(details.getSource(), details.getConnectionFuture(), details.isReadOnlyMode(), details.getAttemptPromise(), details);
}
});
}
semaphore.release();
return;
}
super.handleError(details, promise, cause);
}
@Override
protected void sendCommand(AsyncDetails details, RedisConnection connection) {
if (!isRedisBasedQueue()) {
super.sendCommand(details, connection);
return;
}
ConnectionEntry connectionEntry = connections.get(details.getSource().getEntry());
if (details.getSource().getRedirect() == Redirect.ASK) {
List> list = new ArrayList>(2);
RPromise promise = new RedissonPromise();
list.add(new CommandData(promise, details.getCodec(), RedisCommands.ASKING, new Object[]{}));
if (connectionEntry.isFirstCommand()) {
list.add(new CommandData(promise, details.getCodec(), RedisCommands.MULTI, new Object[]{}));
connectionEntry.setFirstCommand(false);
}
list.add(new CommandData(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));
RPromise main = new RedissonPromise();
ChannelFuture future = connection.send(new CommandsData(main, list, true));
details.setWriteFuture(future);
} else {
if (log.isDebugEnabled()) {
log.debug("acquired connection for command {} and params {} from slot {} using node {}... {}",
details.getCommand(), LogHelper.toString(details.getParams()), details.getSource(), connection.getRedisClient().getAddr(), connection);
}
if (connectionEntry.isFirstCommand()) {
List> list = new ArrayList>(2);
list.add(new CommandData(new RedissonPromise(), details.getCodec(), RedisCommands.MULTI, new Object[]{}));
list.add(new CommandData(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));
RPromise main = new RedissonPromise();
ChannelFuture future = connection.send(new CommandsData(main, list, true));
connectionEntry.setFirstCommand(false);
details.setWriteFuture(future);
} else {
if (RedisCommands.EXEC.getName().equals(details.getCommand().getName())) {
Entry entry = commands.get(details.getSource().getEntry());
List> list = new LinkedList>();
if (options.isSkipResult()) {
list.add(new CommandData(new RedissonPromise(), details.getCodec(), RedisCommands.CLIENT_REPLY, new Object[]{ "OFF" }));
}
list.add(new CommandData(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));
if (options.isSkipResult()) {
list.add(new CommandData(new RedissonPromise(), details.getCodec(), RedisCommands.CLIENT_REPLY, new Object[]{ "ON" }));
}
if (options.getSyncSlaves() > 0) {
BatchCommandData, ?> waitCommand = new BatchCommandData(RedisCommands.WAIT,
new Object[] { this.options.getSyncSlaves(), this.options.getSyncTimeout() }, index.incrementAndGet());
list.add(waitCommand);
entry.getCommands().add(waitCommand);
}
RPromise main = new RedissonPromise();
ChannelFuture future = connection.send(new CommandsData(main, list, new ArrayList(entry.getCommands())));
details.setWriteFuture(future);
} else {
RPromise main = new RedissonPromise();
List> list = new LinkedList>();
list.add(new CommandData(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));
ChannelFuture future = connection.send(new CommandsData(main, list, true));
details.setWriteFuture(future);
}
}
}
}
@Override
protected RFuture getConnection(boolean readOnlyMode, NodeSource source,
RedisCommand command) {
if (!isRedisBasedQueue()) {
return super.getConnection(readOnlyMode, source, command);
}
ConnectionEntry entry = connections.get(source.getEntry());
if (entry == null) {
entry = new ConnectionEntry();
ConnectionEntry oldEntry = connections.putIfAbsent(source.getEntry(), entry);
if (oldEntry != null) {
entry = oldEntry;
}
}
if (entry.getConnectionFuture() != null) {
return entry.getConnectionFuture();
}
synchronized (this) {
if (entry.getConnectionFuture() != null) {
return entry.getConnectionFuture();
}
RFuture connectionFuture;
if (this.options.getExecutionMode() == ExecutionMode.REDIS_WRITE_ATOMIC) {
connectionFuture = connectionManager.connectionWriteOp(source, null);
} else {
connectionFuture = connectionManager.connectionReadOp(source, null);
}
connectionFuture.syncUninterruptibly();
entry.setConnectionFuture(connectionFuture);
return connectionFuture;
}
}
public BatchResult> execute() {
RFuture> f = executeAsync(BatchOptions.defaults());
return get(f);
}
public BatchResult> execute(BatchOptions options) {
RFuture> f = executeAsync(options);
return get(f);
}
public RFuture executeAsyncVoid() {
final RedissonPromise promise = new RedissonPromise();
RFuture> res = executeAsync(BatchOptions.defaults());
res.addListener(new FutureListener>() {
@Override
public void operationComplete(Future> future) throws Exception {
if (future.isSuccess()) {
promise.trySuccess(null);
} else {
promise.tryFailure(future.cause());
}
}
});
return promise;
}
public RFuture> executeAsync() {
return executeAsync(BatchOptions.defaults());
}
public RFuture executeAsync(BatchOptions options) {
if (executed.get()) {
throw new IllegalStateException("Batch already executed!");
}
if (commands.isEmpty()) {
executed.set(true);
BatchResult
© 2015 - 2025 Weber Informatics LLC | Privacy Policy