Java tutorial
/** * Copyright 2016 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; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.redisson.api.RBatch; import org.redisson.api.RBlockingQueue; import org.redisson.api.RBlockingQueueAsync; import org.redisson.api.RFuture; import org.redisson.api.RRemoteService; import org.redisson.api.RedissonClient; import org.redisson.client.codec.Codec; import org.redisson.client.codec.LongCodec; import org.redisson.client.protocol.RedisCommands; import org.redisson.command.CommandExecutor; import org.redisson.remote.RRemoteServiceResponse; import org.redisson.remote.RemoteParams; import org.redisson.remote.RemoteServiceAck; import org.redisson.remote.RemoteServiceCancelRequest; import org.redisson.remote.RemoteServiceCancelResponse; import org.redisson.remote.RemoteServiceKey; import org.redisson.remote.RemoteServiceMethod; import org.redisson.remote.RemoteServiceRequest; import org.redisson.remote.RemoteServiceResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.netty.util.internal.PlatformDependent; /** * * @author Nikita Koksharov * */ public class RedissonRemoteService extends BaseRemoteService implements RRemoteService { private static final Logger log = LoggerFactory.getLogger(RedissonRemoteService.class); private final Map<RemoteServiceKey, RemoteServiceMethod> beans = PlatformDependent.newConcurrentHashMap(); public RedissonRemoteService(RedissonClient redisson, CommandExecutor commandExecutor) { super(redisson, commandExecutor); } public RedissonRemoteService(RedissonClient redisson, String name, CommandExecutor commandExecutor) { super(redisson, name, commandExecutor); } public RedissonRemoteService(Codec codec, RedissonClient redisson, CommandExecutor commandExecutor) { super(codec, redisson, commandExecutor); } public RedissonRemoteService(Codec codec, RedissonClient redisson, String name, CommandExecutor commandExecutor) { super(codec, redisson, name, commandExecutor); } @Override public <T> void register(Class<T> remoteInterface, T object) { register(remoteInterface, object, 1); } @Override public <T> void register(Class<T> remoteInterface, T object, int workers) { register(remoteInterface, object, workers, commandExecutor.getConnectionManager().getExecutor()); } @Override public <T> void register(Class<T> remoteInterface, T object, int workers, ExecutorService executor) { if (workers < 1) { throw new IllegalArgumentException("executorsAmount can't be lower than 1"); } for (Method method : remoteInterface.getMethods()) { RemoteServiceMethod value = new RemoteServiceMethod(method, object); RemoteServiceKey key = new RemoteServiceKey(remoteInterface, method.getName(), getMethodSignatures(method)); if (beans.put(key, value) != null) { return; } } for (int i = 0; i < workers; i++) { String requestQueueName = getRequestQueueName(remoteInterface); RBlockingQueue<RemoteServiceRequest> requestQueue = redisson.getBlockingQueue(requestQueueName, getCodec()); subscribe(remoteInterface, requestQueue, executor); } } private <T> void subscribe(final Class<T> remoteInterface, final RBlockingQueue<RemoteServiceRequest> requestQueue, final ExecutorService executor) { RFuture<RemoteServiceRequest> take = requestQueue.takeAsync(); take.addListener(new FutureListener<RemoteServiceRequest>() { @Override public void operationComplete(Future<RemoteServiceRequest> future) throws Exception { if (!future.isSuccess()) { log.error("Can't process the remote service request.", future.cause()); if (future.cause() instanceof RedissonShutdownException) { return; } // re-subscribe after a failed takeAsync subscribe(remoteInterface, requestQueue, executor); return; } // do not subscribe now, see // https://github.com/mrniko/redisson/issues/493 // subscribe(remoteInterface, requestQueue); final RemoteServiceRequest request = future.getNow(); // check the ack only if expected if (request.getOptions().isAckExpected() && System.currentTimeMillis() - request.getDate() > request .getOptions().getAckTimeoutInMillis()) { log.debug("request: {} has been skipped due to ackTimeout"); // re-subscribe after a skipped ackTimeout subscribe(remoteInterface, requestQueue, executor); return; } final String responseName = getResponseQueueName(remoteInterface, request.getRequestId()); // send the ack only if expected if (request.getOptions().isAckExpected()) { String ackName = getAckName(remoteInterface, request.getRequestId()); RFuture<Boolean> ackClientsFuture = commandExecutor.evalWriteAsync(responseName, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if redis.call('setnx', KEYS[1], 1) == 1 then " + "redis.call('pexpire', KEYS[1], ARGV[2]);" + "redis.call('rpush', KEYS[2], ARGV[1]);" + "redis.call('pexpire', KEYS[2], ARGV[2]);" + "return 1;" + "end;" + "return 0;", Arrays.<Object>asList(ackName, responseName), encode(new RemoteServiceAck()), request.getOptions().getAckTimeoutInMillis()); ackClientsFuture.addListener(new FutureListener<Boolean>() { @Override public void operationComplete(Future<Boolean> future) throws Exception { if (!future.isSuccess()) { log.error("Can't send ack for request: " + request, future.cause()); if (future.cause() instanceof RedissonShutdownException) { return; } // re-subscribe after a failed send (ack) subscribe(remoteInterface, requestQueue, executor); return; } if (!future.getNow()) { subscribe(remoteInterface, requestQueue, executor); return; } executeMethod(remoteInterface, requestQueue, executor, request); } }); } else { executeMethod(remoteInterface, requestQueue, executor, request); } } }); } private <T> void executeMethod(final Class<T> remoteInterface, final RBlockingQueue<RemoteServiceRequest> requestQueue, final ExecutorService executor, final RemoteServiceRequest request) { final RemoteServiceMethod method = beans .get(new RemoteServiceKey(remoteInterface, request.getMethodName(), request.getSignatures())); final String responseName = getResponseQueueName(remoteInterface, request.getRequestId()); RBlockingQueue<RemoteServiceCancelRequest> cancelRequestQueue = redisson .getBlockingQueue(getCancelRequestQueueName(remoteInterface, request.getRequestId()), getCodec()); final RFuture<RemoteServiceCancelRequest> cancelRequestFuture = cancelRequestQueue.takeAsync(); final AtomicReference<RRemoteServiceResponse> responseHolder = new AtomicReference<RRemoteServiceResponse>(); final java.util.concurrent.Future<?> submitFuture = executor.submit(new Runnable() { @Override public void run() { invokeMethod(remoteInterface, requestQueue, request, method, responseName, executor, cancelRequestFuture, responseHolder); } }); cancelRequestFuture.addListener(new FutureListener<RemoteServiceCancelRequest>() { @Override public void operationComplete(Future<RemoteServiceCancelRequest> future) throws Exception { if (!future.isSuccess()) { return; } boolean res = submitFuture.cancel(future.getNow().isMayInterruptIfRunning()); if (res) { RemoteServiceCancelResponse response = new RemoteServiceCancelResponse(); if (!responseHolder.compareAndSet(null, response)) { response = new RemoteServiceCancelResponse(false); } // could be removed not from future object if (future.getNow().getResponseId() != null) { String cancelResponseName = getResponseQueueName(remoteInterface, future.getNow().getResponseId()); send(60 * 1000, cancelResponseName, response); } } } }); } private <T> void invokeMethod(final Class<T> remoteInterface, final RBlockingQueue<RemoteServiceRequest> requestQueue, final RemoteServiceRequest request, RemoteServiceMethod method, String responseName, final ExecutorService executor, RFuture<RemoteServiceCancelRequest> cancelRequestFuture, final AtomicReference<RRemoteServiceResponse> responseHolder) { try { if (method.getBean() instanceof RemoteParams) { ((RemoteParams) method.getBean()).setRequestId(request.getRequestId()); } Object result = method.getMethod().invoke(method.getBean(), request.getArgs()); RemoteServiceResponse response = new RemoteServiceResponse(result); responseHolder.compareAndSet(null, response); } catch (Exception e) { RemoteServiceResponse response = new RemoteServiceResponse(e.getCause()); responseHolder.compareAndSet(null, response); log.error("Can't execute: " + request, e); } if (cancelRequestFuture != null) { cancelRequestFuture.cancel(false); } // send the response only if expected or task was canceled if (request.getOptions().isResultExpected() || responseHolder.get() instanceof RemoteServiceCancelResponse) { long timeout = 60 * 1000; if (request.getOptions().getExecutionTimeoutInMillis() != null) { timeout = request.getOptions().getExecutionTimeoutInMillis(); } RFuture<List<?>> clientsFuture = send(timeout, responseName, responseHolder.get()); clientsFuture.addListener(new FutureListener<List<?>>() { @Override public void operationComplete(Future<List<?>> future) throws Exception { if (!future.isSuccess()) { log.error("Can't send response: " + responseHolder.get() + " for request: " + request, future.cause()); if (future.cause() instanceof RedissonShutdownException) { return; } } // re-subscribe anyways (fail or success) after the send // (response) subscribe(remoteInterface, requestQueue, executor); } }); } else { // re-subscribe anyways after the method invocation subscribe(remoteInterface, requestQueue, executor); } } private <T extends RRemoteServiceResponse> RFuture<List<?>> send(long timeout, String responseName, T response) { RBatch batch = redisson.createBatch(); RBlockingQueueAsync<T> queue = batch.getBlockingQueue(responseName, getCodec()); queue.putAsync(response); queue.expireAsync(timeout, TimeUnit.MILLISECONDS); return batch.executeAsync(); } }