org.redisson.command.CommandAsyncService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redisson-all Show documentation
Show all versions of redisson-all Show documentation
Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC
/**
* Copyright (c) 2013-2024 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 io.netty.buffer.ByteBuf;
import io.netty.util.ReferenceCountUtil;
import org.redisson.RedissonReference;
import org.redisson.SlotCallback;
import org.redisson.api.BatchOptions;
import org.redisson.api.BatchResult;
import org.redisson.api.NodeType;
import org.redisson.api.RFuture;
import org.redisson.api.options.ObjectParams;
import org.redisson.client.RedisClient;
import org.redisson.client.RedisException;
import org.redisson.client.RedisNodeNotFoundException;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.config.DefaultCommandMapper;
import org.redisson.connection.ConnectionManager;
import org.redisson.connection.MasterSlaveEntry;
import org.redisson.connection.NodeSource;
import org.redisson.connection.ServiceManager;
import org.redisson.liveobject.core.RedissonObjectBuilder;
import org.redisson.misc.CompletableFutureWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
*
* @author Nikita Koksharov
*
*/
public class CommandAsyncService implements CommandAsyncExecutor {
static final Logger log = LoggerFactory.getLogger(CommandAsyncService.class);
final Codec codec;
final ConnectionManager connectionManager;
final RedissonObjectBuilder objectBuilder;
final RedissonObjectBuilder.ReferenceType referenceType;
private final int retryAttempts;
private final int retryInterval;
private final int responseTimeout;
private final boolean trackChanges;
@Override
public CommandAsyncExecutor copy(boolean trackChanges) {
return new CommandAsyncService(this, trackChanges);
}
protected CommandAsyncService(CommandAsyncExecutor executor, boolean trackChanges) {
CommandAsyncService service = (CommandAsyncService) executor;
this.codec = service.codec;
this.connectionManager = service.connectionManager;
this.objectBuilder = service.objectBuilder;
this.referenceType = service.referenceType;
this.retryAttempts = service.retryAttempts;
this.retryInterval = service.retryInterval;
this.responseTimeout = service.responseTimeout;
this.trackChanges = trackChanges;
}
@Override
public CommandAsyncExecutor copy(ObjectParams objectParams) {
return new CommandAsyncService(this, objectParams);
}
protected CommandAsyncService(CommandAsyncExecutor executor,
ObjectParams objectParams) {
CommandAsyncService service = (CommandAsyncService) executor;
this.codec = service.codec;
this.connectionManager = service.connectionManager;
this.objectBuilder = service.objectBuilder;
this.referenceType = service.referenceType;
if (objectParams.getRetryAttempts() >= 0) {
this.retryAttempts = objectParams.getRetryAttempts();
} else {
this.retryAttempts = connectionManager.getServiceManager().getConfig().getRetryAttempts();
}
if (objectParams.getRetryInterval() > 0) {
this.retryInterval = objectParams.getRetryInterval();
} else {
this.retryInterval = connectionManager.getServiceManager().getConfig().getRetryInterval();
}
if (objectParams.getTimeout() > 0) {
this.responseTimeout = objectParams.getTimeout();
} else {
this.responseTimeout = connectionManager.getServiceManager().getConfig().getTimeout();
}
this.trackChanges = false;
}
public CommandAsyncService(ConnectionManager connectionManager, RedissonObjectBuilder objectBuilder,
RedissonObjectBuilder.ReferenceType referenceType) {
this.connectionManager = connectionManager;
this.objectBuilder = objectBuilder;
this.referenceType = referenceType;
this.codec = connectionManager.getServiceManager().getCfg().getCodec();
this.retryAttempts = connectionManager.getServiceManager().getConfig().getRetryAttempts();
this.retryInterval = connectionManager.getServiceManager().getConfig().getRetryInterval();
this.responseTimeout = connectionManager.getServiceManager().getConfig().getTimeout();
this.trackChanges = false;
}
@Override
public ConnectionManager getConnectionManager() {
return connectionManager;
}
private boolean isRedissonReferenceSupportEnabled() {
return objectBuilder != null;
}
@Override
public V getNow(CompletableFuture future) {
try {
return future.getNow(null);
} catch (Exception e) {
return null;
}
}
@Override
public void transfer(CompletionStage future1, CompletableFuture future2) {
future1.whenComplete((res, e) -> {
if (e != null) {
future2.completeExceptionally(e);
return;
}
future2.complete(res);
});
}
@Override
public V get(RFuture future) {
if (Thread.currentThread().getName().startsWith("redisson-netty")) {
throw new IllegalStateException("Sync methods can't be invoked from async/rx/reactive listeners");
}
try {
return future.toCompletableFuture().get();
} catch (InterruptedException e) {
future.cancel(true);
Thread.currentThread().interrupt();
throw new RedisException(e);
} catch (ExecutionException e) {
throw convertException(e);
}
}
@Override
public V get(CompletableFuture future) {
if (Thread.currentThread().getName().startsWith("redisson-netty")) {
throw new IllegalStateException("Sync methods can't be invoked from async/rx/reactive listeners");
}
try {
return future.get();
} catch (InterruptedException e) {
future.cancel(true);
Thread.currentThread().interrupt();
throw new RedisException(e);
} catch (ExecutionException e) {
throw convertException(e);
}
}
@Override
public V getInterrupted(RFuture future) throws InterruptedException {
try {
return future.toCompletableFuture().get();
} catch (InterruptedException e) {
future.toCompletableFuture().completeExceptionally(e);
throw e;
} catch (ExecutionException e) {
throw convertException(e);
}
}
@Override
public V getInterrupted(CompletableFuture future) throws InterruptedException {
try {
return future.get();
} catch (InterruptedException e) {
future.completeExceptionally(e);
throw e;
} catch (ExecutionException e) {
throw convertException(e);
}
}
protected CompletableFuture createPromise() {
return new CompletableFuture();
}
@Override
public RFuture readAsync(RedisClient client, MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
return async(true, new NodeSource(entry, client), codec, command, params, false, false);
}
@Override
public RFuture readAsync(RedisClient client, String name, Codec codec, RedisCommand command, Object... params) {
int slot = connectionManager.calcSlot(name);
return async(true, new NodeSource(slot, client), codec, command, params, false, false);
}
public RFuture readAsync(RedisClient client, byte[] key, Codec codec, RedisCommand command, Object... params) {
int slot = connectionManager.calcSlot(key);
return async(true, new NodeSource(slot, client), codec, command, params, false, false);
}
@Override
public RFuture readAsync(RedisClient client, Codec codec, RedisCommand command, Object... params) {
return async(true, new NodeSource(client), codec, command, params, false, false);
}
@Override
public RFuture readRandomAsync(Codec codec, RedisCommand command, Object... params) {
CompletableFuture mainPromise = createPromise();
List nodes = connectionManager.getEntrySet().stream().map(e -> e.getClient()).collect(Collectors.toList());
Collections.shuffle(nodes);
retryReadRandomAsync(codec, command, mainPromise, nodes, params);
return new CompletableFutureWrapper<>(mainPromise);
}
@Override
public RFuture readRandomAsync(RedisClient client, Codec codec, RedisCommand command, Object... params) {
CompletableFuture mainPromise = createPromise();
List list = new ArrayList<>(1);
list.add(client);
retryReadRandomAsync(codec, command, mainPromise, list, params);
return new CompletableFutureWrapper<>(mainPromise);
}
private void retryReadRandomAsync(Codec codec, RedisCommand command, CompletableFuture mainPromise,
List nodes, Object... params) {
RedisClient client = nodes.remove(0);
MasterSlaveEntry masterSlaveEntry = connectionManager.getEntry(client);
RFuture attemptPromise = async(true, new NodeSource(masterSlaveEntry, client), codec, command, params, false, false);
attemptPromise.whenComplete((res, e) -> {
if (e == null) {
if (res == null) {
if (nodes.isEmpty()) {
mainPromise.complete(null);
} else {
retryReadRandomAsync(codec, command, mainPromise, nodes, params);
}
} else {
mainPromise.complete(res);
}
} else {
mainPromise.completeExceptionally(e);
}
});
}
@Override
public RFuture writeAllVoidAsync(RedisCommand command, Object... params) {
List> futures = writeAllAsync(StringCodec.INSTANCE, command, params);
CompletableFuture f = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
return new CompletableFutureWrapper<>(f);
}
@Override
public List> writeAllAsync(RedisCommand> command, Object... params) {
return writeAllAsync(codec, command, params);
}
@Override
public List> writeAllAsync(Codec codec, RedisCommand> command, Object... params) {
List> futures = connectionManager.getEntrySet().stream().map(e -> {
RFuture f = async(false, new NodeSource(e),
codec, command, params, true, false);
return f.toCompletableFuture();
}).collect(Collectors.toList());
return futures;
}
@Override
public List> readAllAsync(Codec codec, RedisCommand> command, Object... params) {
List> futures = connectionManager.getEntrySet().stream().map(e -> {
RFuture f = async(true, new NodeSource(e), codec, command, params, true, false);
return f.toCompletableFuture();
}).collect(Collectors.toList());
return futures;
}
@Override
public List> readAllAsync(RedisCommand> command, Object... params) {
return readAllAsync(codec, command, params);
}
@Override
public List> executeAllAsync(MasterSlaveEntry entry, RedisCommand> command, Object... params) {
List> futures = new ArrayList<>();
RFuture promise = async(false, new NodeSource(entry),
codec, command, params, true, false);
futures.add(promise.toCompletableFuture());
entry.getAllEntries().stream()
.filter(c -> c.getNodeType() == NodeType.SLAVE
&& !c.isFreezed())
.forEach(c -> {
RFuture slavePromise = async(true, new NodeSource(entry, c.getClient()),
codec, command, params, true, false);
futures.add(slavePromise.toCompletableFuture());
});
return futures;
}
@Override
public List> executeAllAsync(RedisCommand> command, Object... params) {
Collection nodes = connectionManager.getEntrySet();
List> futures = new ArrayList<>();
nodes.forEach(e -> {
RFuture promise = async(false, new NodeSource(e),
codec, command, params, true, false);
futures.add(promise.toCompletableFuture());
e.getAllEntries().stream().filter(c -> c.getNodeType() == NodeType.SLAVE && !c.isFreezed()).forEach(c -> {
RFuture slavePromise = async(true, new NodeSource(e, c.getClient()),
codec, command, params, true, false);
futures.add(slavePromise.toCompletableFuture());
});
});
return futures;
}
public RedisException convertException(ExecutionException e) {
if (e.getCause() instanceof RedisException) {
return (RedisException) e.getCause();
}
return new RedisException("Unexpected exception while processing command", e.getCause());
}
private NodeSource getNodeSource(String key) {
int slot = connectionManager.calcSlot(key);
return new NodeSource(slot);
}
private NodeSource getNodeSource(byte[] key) {
int slot = connectionManager.calcSlot(key);
return new NodeSource(slot);
}
private NodeSource getNodeSource(ByteBuf key) {
int slot = connectionManager.calcSlot(key);
return new NodeSource(slot);
}
@Override
public RFuture readAsync(String key, Codec codec, RedisCommand command, Object... params) {
NodeSource source = getNodeSource(key);
return async(true, source, codec, command, params, false, false);
}
@Override
public RFuture readAsync(byte[] key, Codec codec, RedisCommand command, Object... params) {
NodeSource source = getNodeSource(key);
return async(true, source, codec, command, params, false, false);
}
@Override
public RFuture readAsync(ByteBuf key, Codec codec, RedisCommand command, Object... params) {
NodeSource source = getNodeSource(key);
return async(true, source, codec, command, params, false, false);
}
public RFuture readAsync(MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
return async(true, new NodeSource(entry), codec, command, params, false, false);
}
@Override
public RFuture writeAsync(RedisClient client, Codec codec, RedisCommand command, Object... params) {
MasterSlaveEntry entry = getConnectionManager().getEntry(client);
return writeAsync(entry, codec, command, params);
}
@Override
public RFuture writeAsync(MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
return async(false, new NodeSource(entry), codec, command, params, false, false);
}
@Override
public RFuture readAsync(String key, RedisCommand command, Object... params) {
return readAsync(key, codec, command, params);
}
@Override
public RFuture evalReadAsync(String key, Codec codec, RedisCommand evalCommandType, String script, List