Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.redisson.command.CommandAsyncService Maven / Gradle / Ivy
Go to download
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-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 org.redisson.command;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.util.ReferenceCountUtil;
import org.redisson.RedissonReference;
import org.redisson.SlotCallback;
import org.redisson.api.RFuture;
import org.redisson.cache.LRUCacheMap;
import org.redisson.client.RedisClient;
import org.redisson.client.RedisException;
import org.redisson.client.RedisRedirectException;
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.connection.ConnectionManager;
import org.redisson.connection.MasterSlaveEntry;
import org.redisson.connection.NodeSource;
import org.redisson.liveobject.core.RedissonObjectBuilder;
import org.redisson.misc.CompletableFutureWrapper;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
*
* @author Nikita Koksharov
*
*/
public class CommandAsyncService implements CommandAsyncExecutor {
static final Logger log = LoggerFactory.getLogger(CommandAsyncService.class);
final ConnectionManager connectionManager;
final RedissonObjectBuilder objectBuilder;
final RedissonObjectBuilder.ReferenceType referenceType;
public CommandAsyncService(ConnectionManager connectionManager, RedissonObjectBuilder objectBuilder, RedissonObjectBuilder.ReferenceType referenceType) {
this.connectionManager = connectionManager;
this.objectBuilder = objectBuilder;
this.referenceType = referenceType;
}
@Override
public ConnectionManager getConnectionManager() {
return connectionManager;
}
private boolean isRedissonReferenceSupportEnabled() {
return objectBuilder != null;
}
@Override
public void syncSubscription(CompletableFuture> future) {
get(future);
}
@Override
public void syncSubscriptionInterrupted(CompletableFuture> future) throws InterruptedException {
getInterrupted(future);
}
@Override
public V getNow(CompletableFuture future) {
try {
return future.getNow(null);
} catch (Exception e) {
return null;
}
}
@Override
public void transfer(CompletableFuture 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> readAllAsync(RedisCommand command, Object... params) {
return readAllAsync(connectionManager.getCodec(), command, params);
}
@Override
public RFuture> readAllAsync(Codec codec, RedisCommand command, Object... params) {
Collection nodes = connectionManager.getEntrySet();
List> futures = new ArrayList<>();
for (MasterSlaveEntry entry : nodes) {
RFuture f = async(true, new NodeSource(entry), codec, command, params, true, false);
futures.add(f.toCompletableFuture());
}
CompletableFuture future = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
CompletableFuture> resFuture = future.thenApply(r -> {
List results = new ArrayList<>();
for (CompletableFuture> f : futures) {
Object res = f.getNow(null);
if (res instanceof Collection) {
results.addAll((Collection) res);
} else {
results.add((R) res);
}
}
return results;
});
return new CompletableFutureWrapper<>(resFuture);
}
@Override
public RFuture readRandomAsync(Codec codec, RedisCommand command, Object... params) {
CompletableFuture mainPromise = createPromise();
List nodes = new ArrayList(connectionManager.getEntrySet());
Collections.shuffle(nodes);
retryReadRandomAsync(codec, command, mainPromise, nodes, params);
return new CompletableFutureWrapper<>(mainPromise);
}
@Override
public RFuture readRandomAsync(MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
CompletableFuture mainPromise = createPromise();
retryReadRandomAsync(codec, command, mainPromise, Collections.singletonList(entry), params);
return new CompletableFutureWrapper<>(mainPromise);
}
private void retryReadRandomAsync(Codec codec, RedisCommand command, CompletableFuture mainPromise,
List nodes, Object... params) {
MasterSlaveEntry entry = nodes.remove(0);
RFuture attemptPromise = async(true, new NodeSource(entry), 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 writeAllAsync(RedisCommand command, Object... params) {
return writeAllAsync(command, null, params);
}
@Override
public RFuture writeAllAsync(RedisCommand command, SlotCallback callback, Object... params) {
return allAsync(false, connectionManager.getCodec(), command, callback, params);
}
@Override
public RFuture writeAllAsync(Codec codec, RedisCommand command, SlotCallback callback, Object... params) {
return allAsync(false, codec, command, callback, params);
}
@Override
public RFuture readAllAsync(RedisCommand command, SlotCallback callback, Object... params) {
return allAsync(true, connectionManager.getCodec(), command, callback, params);
}
private RFuture allAsync(boolean readOnlyMode, Codec codec, RedisCommand command, SlotCallback callback, Object... params) {
RPromise mainPromise = new RedissonPromise();
Collection nodes = connectionManager.getEntrySet();
AtomicInteger counter = new AtomicInteger(nodes.size());
BiConsumer listener = new BiConsumer() {
@Override
public void accept(T result, Throwable u) {
if (u != null && !(u instanceof RedisRedirectException)) {
mainPromise.tryFailure(u);
return;
}
if (u instanceof RedisRedirectException) {
result = command.getConvertor().convert(result);
}
if (callback != null) {
callback.onSlotResult(result);
}
if (counter.decrementAndGet() == 0) {
if (callback != null) {
mainPromise.trySuccess(callback.onFinish());
} else {
mainPromise.trySuccess(null);
}
}
}
};
for (MasterSlaveEntry entry : nodes) {
RFuture promise = async(readOnlyMode, new NodeSource(entry), codec, command, params, true, false);
promise.whenComplete(listener);
}
return mainPromise;
}
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);
}
@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);
}
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, connectionManager.getCodec(), command, params);
}
@Override
public RFuture evalReadAsync(String key, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
NodeSource source = getNodeSource(key);
return evalAsync(source, true, codec, evalCommandType, script, keys, false, params);
}
@Override
public RFuture evalReadAsync(MasterSlaveEntry entry, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
return evalAsync(new NodeSource(entry), true, codec, evalCommandType, script, keys, false, params);
}
@Override
public RFuture evalReadAsync(RedisClient client, String name, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
int slot = connectionManager.calcSlot(name);
return evalAsync(new NodeSource(slot, client), true, codec, evalCommandType, script, keys, false, params);
}
@Override
public RFuture evalWriteAsync(String key, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
NodeSource source = getNodeSource(key);
return evalAsync(source, false, codec, evalCommandType, script, keys, false, params);
}
@Override
public RFuture evalWriteNoRetryAsync(String key, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
NodeSource source = getNodeSource(key);
return evalAsync(source, false, codec, evalCommandType, script, keys, true, params);
}
public RFuture evalWriteAsync(MasterSlaveEntry entry, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
return evalAsync(new NodeSource(entry), false, codec, evalCommandType, script, keys, false, params);
}
@Override
public RFuture evalWriteAllAsync(RedisCommand command, SlotCallback callback, String script, List keys, Object... params) {
return evalAllAsync(false, command, callback, script, keys, params);
}
public RFuture evalAllAsync(boolean readOnlyMode, RedisCommand command, SlotCallback callback, String script, List keys, Object... params) {
RPromise mainPromise = new RedissonPromise();
Collection entries = connectionManager.getEntrySet();
AtomicInteger counter = new AtomicInteger(entries.size());
BiConsumer listener = new BiConsumer() {
@Override
public void accept(T t, Throwable u) {
if (u != null && !(u instanceof RedisRedirectException)) {
mainPromise.tryFailure(u);
return;
}
callback.onSlotResult(t);
if (counter.decrementAndGet() == 0
&& !mainPromise.isDone()) {
mainPromise.trySuccess(callback.onFinish());
}
}
};
List args = new ArrayList(2 + keys.size() + params.length);
args.add(script);
args.add(keys.size());
args.addAll(keys);
args.addAll(Arrays.asList(params));
for (MasterSlaveEntry entry : entries) {
RFuture promise = async(readOnlyMode, new NodeSource(entry), connectionManager.getCodec(), command, args.toArray(), true, false);
promise.whenComplete(listener);
}
return mainPromise;
}
private RFuture loadScript(RedisClient client, String script) {
MasterSlaveEntry entry = getConnectionManager().getEntry(client);
if (entry.getClient().equals(client)) {
return writeAsync(entry, StringCodec.INSTANCE, RedisCommands.SCRIPT_LOAD, script);
}
return readAsync(client, StringCodec.INSTANCE, RedisCommands.SCRIPT_LOAD, script);
}
protected boolean isEvalCacheActive() {
return getConnectionManager().getCfg().isUseScriptCache();
}
private static final Map SHA_CACHE = new LRUCacheMap(500, 0, 0);
private String calcSHA(String script) {
String digest = SHA_CACHE.get(script);
if (digest == null) {
try {
MessageDigest mdigest = MessageDigest.getInstance("SHA-1");
byte[] s = mdigest.digest(script.getBytes());
digest = ByteBufUtil.hexDump(s);
SHA_CACHE.put(script, digest);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
return digest;
}
private Object[] copy(Object[] params) {
List result = new ArrayList();
for (Object object : params) {
if (object instanceof ByteBuf) {
ByteBuf b = (ByteBuf) object;
ByteBuf nb = ByteBufAllocator.DEFAULT.buffer(b.readableBytes());
int ri = b.readerIndex();
nb.writeBytes(b);
b.readerIndex(ri);
result.add(nb);
} else {
result.add(object);
}
}
return result.toArray();
}
private RFuture evalAsync(NodeSource nodeSource, boolean readOnlyMode, Codec codec, RedisCommand evalCommandType,
String script, List keys, boolean noRetry, Object... params) {
if (isEvalCacheActive() && evalCommandType.getName().equals("EVAL")) {
CompletableFuture mainPromise = new CompletableFuture<>();
Object[] pps = copy(params);
CompletableFuture promise = new CompletableFuture<>();
String sha1 = calcSHA(script);
RedisCommand cmd = new RedisCommand(evalCommandType, "EVALSHA");
List args = new ArrayList(2 + keys.size() + params.length);
args.add(sha1);
args.add(keys.size());
args.addAll(keys);
args.addAll(Arrays.asList(params));
RedisExecutor executor = new RedisExecutor<>(readOnlyMode, nodeSource, codec, cmd,
args.toArray(), promise, false,
connectionManager, objectBuilder, referenceType, noRetry);
executor.execute();
promise.whenComplete((res, e) -> {
if (e != null) {
if (e.getMessage().startsWith("NOSCRIPT")) {
RFuture loadFuture = loadScript(executor.getRedisClient(), script);
loadFuture.whenComplete((r, ex) -> {
if (ex != null) {
free(pps);
mainPromise.completeExceptionally(ex);
return;
}
RedisCommand command = new RedisCommand(evalCommandType, "EVALSHA");
List newargs = new ArrayList(2 + keys.size() + params.length);
newargs.add(sha1);
newargs.add(keys.size());
newargs.addAll(keys);
newargs.addAll(Arrays.asList(pps));
NodeSource ns = nodeSource;
if (ns.getRedisClient() == null) {
ns = new NodeSource(nodeSource, executor.getRedisClient());
}
RFuture future = async(readOnlyMode, ns, codec, command, newargs.toArray(), false, noRetry);
future.whenComplete((re, exс) -> {
if (exс != null) {
mainPromise.completeExceptionally(exс);
} else {
mainPromise.complete(re);
}
});
});
} else {
free(pps);
mainPromise.completeExceptionally(e);
}
return;
}
free(pps);
mainPromise.complete(res);
});
return new CompletableFutureWrapper<>(mainPromise);
}
List args = new ArrayList(2 + keys.size() + params.length);
args.add(script);
args.add(keys.size());
args.addAll(keys);
args.addAll(Arrays.asList(params));
return async(readOnlyMode, nodeSource, codec, evalCommandType, args.toArray(), false, noRetry);
}
@Override
public RFuture writeAsync(String key, RedisCommand command, Object... params) {
return writeAsync(key, connectionManager.getCodec(), command, params);
}
@Override
public RFuture writeAsync(String key, Codec codec, RedisCommand command, Object... params) {
NodeSource source = getNodeSource(key);
return async(false, source, codec, command, params, false, false);
}
public RFuture writeAsync(byte[] key, Codec codec, RedisCommand command, Object... params) {
NodeSource source = getNodeSource(key);
return async(false, source, codec, command, params, false, false);
}
public RFuture async(boolean readOnlyMode, NodeSource source, Codec codec,
RedisCommand command, Object[] params, boolean ignoreRedirect, boolean noRetry) {
CompletableFuture mainPromise = createPromise();
RedisExecutor executor = new RedisExecutor<>(readOnlyMode, source, codec, command, params, mainPromise,
ignoreRedirect, connectionManager, objectBuilder, referenceType, noRetry);
executor.execute();
return new CompletableFutureWrapper<>(mainPromise);
}
private void free(Object[] params) {
for (Object obj : params) {
ReferenceCountUtil.safeRelease(obj);
}
}
@Override
public RFuture readBatchedAsync(Codec codec, RedisCommand command, SlotCallback callback, String... keys) {
return executeBatchedAsync(true, codec, command, callback, keys);
}
@Override
public RFuture writeBatchedAsync(Codec codec, RedisCommand command, SlotCallback callback, String... keys) {
return executeBatchedAsync(false, codec, command, callback, keys);
}
private RFuture executeBatchedAsync(boolean readOnly, Codec codec, RedisCommand command, SlotCallback callback, String[] keys) {
if (!connectionManager.isClusterMode()) {
Object[] params = callback.createParams(Arrays.asList(keys));
if (readOnly) {
return readAsync((String) null, codec, command, params);
}
return writeAsync((String) null, codec, command, params);
}
Map>> entry2keys = Arrays.stream(keys).collect(
Collectors.groupingBy(k -> {
int slot = connectionManager.calcSlot(k);
return connectionManager.getEntry(slot);
}, Collectors.groupingBy(k -> {
return connectionManager.calcSlot(k);
}, Collectors.toList())));
List> futures = new ArrayList<>();
for (Entry>> entry : entry2keys.entrySet()) {
// executes in batch due to CROSSLOT error
CommandBatchService executorService;
if (this instanceof CommandBatchService) {
executorService = (CommandBatchService) this;
} else {
executorService = new CommandBatchService(this);
}
for (List groupedKeys : entry.getValue().values()) {
RedisCommand c = command;
RedisCommand newCommand = callback.createCommand(groupedKeys);
if (newCommand != null) {
c = newCommand;
}
if (readOnly) {
RFuture f = executorService.readAsync(entry.getKey(), codec, c, callback.createParams(groupedKeys));
futures.add(f.toCompletableFuture());
} else {
RFuture f = executorService.writeAsync(entry.getKey(), codec, c, callback.createParams(groupedKeys));
futures.add(f.toCompletableFuture());
}
}
if (!(this instanceof CommandBatchService)) {
executorService.executeAsync();
}
}
CompletableFuture future = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
CompletableFuture result = future.whenComplete((res, e) -> {
futures.forEach(f -> {
if (!f.isCompletedExceptionally() && f.getNow(null) != null) {
callback.onSlotResult((T) f.getNow(null));
}
});
}).thenApply(r -> callback.onFinish());
return new CompletableFutureWrapper<>(result);
}
@Override
public RedissonObjectBuilder getObjectBuilder() {
return objectBuilder;
}
@Override
public ByteBuf encode(Codec codec, Object value) {
if (isRedissonReferenceSupportEnabled()) {
RedissonReference reference = getObjectBuilder().toReference(value);
if (reference != null) {
value = reference;
}
}
try {
return codec.getValueEncoder().encode(value);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public ByteBuf encodeMapKey(Codec codec, Object value) {
if (isRedissonReferenceSupportEnabled()) {
RedissonReference reference = getObjectBuilder().toReference(value);
if (reference != null) {
value = reference;
}
}
try {
return codec.getMapKeyEncoder().encode(value);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public ByteBuf encodeMapValue(Codec codec, Object value) {
if (isRedissonReferenceSupportEnabled()) {
RedissonReference reference = getObjectBuilder().toReference(value);
if (reference != null) {
value = reference;
}
}
try {
return codec.getMapValueEncoder().encode(value);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public RFuture pollFromAnyAsync(String name, Codec codec, RedisCommand command, long secondsTimeout, String... queueNames) {
if (connectionManager.isClusterMode() && queueNames.length > 0) {
RPromise result = new RedissonPromise();
AtomicReference> ref = new AtomicReference>();
List names = new ArrayList();
names.add(name);
names.addAll(Arrays.asList(queueNames));
ref.set(names.iterator());
AtomicLong counter = new AtomicLong(secondsTimeout);
poll(name, codec, result, ref, names, counter, command);
return result;
} else {
List params = new ArrayList(queueNames.length + 1);
params.add(name);
params.addAll(Arrays.asList(queueNames));
params.add(secondsTimeout);
return writeAsync(name, codec, command, params.toArray());
}
}
private void poll(String name, Codec codec, RPromise result, AtomicReference> ref,
List names, AtomicLong counter, RedisCommand command) {
if (ref.get().hasNext()) {
String currentName = ref.get().next();
RFuture future = writeAsync(currentName, codec, command, currentName, 1);
future.whenComplete((res, e) -> {
if (e != null) {
result.tryFailure(e);
return;
}
if (res != null) {
result.trySuccess(res);
} else {
if (counter.decrementAndGet() == 0) {
result.trySuccess(null);
return;
}
poll(name, codec, result, ref, names, counter, command);
}
});
} else {
ref.set(names.iterator());
poll(name, codec, result, ref, names, counter, command);
}
}
}