All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.lontime.extredisson.service.RealProducerHandler Maven / Gradle / Ivy

The newest version!
package com.github.lontime.extredisson.service;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.github.lontime.base.commonj.result.ProcessResult;
import com.github.lontime.base.commonj.utils.CollectionHelper;
import com.github.lontime.base.commonj.utils.StringHelper;
import com.github.lontime.base.commonj.utils.UuidHelper;
import com.github.lontime.base.errors.ErrorCodeEnum;
import com.github.lontime.base.errors.ErrorException;
import com.github.lontime.base.logging.GLogger;
import com.github.lontime.base.serial.model.Header;
import com.github.lontime.base.serial.model.Message;
import com.github.lontime.extredisson.RedissonInstance;
import com.github.lontime.extredisson.codec.StringBytesMapCodec;
import com.github.lontime.extredisson.common.ListenerKind;
import com.github.lontime.extredisson.common.MessageWithId;
import com.github.lontime.extredisson.common.ProjectRedisKeyGenerator;
import com.github.lontime.extredisson.common.RedisKeys;
import com.github.lontime.extredisson.common.Topic;
import com.github.lontime.extredisson.configuration.OptionResolver;
import com.github.lontime.extredisson.configuration.ProducerOption;
import com.github.lontime.extredisson.listener.HandlerInterface;
import com.github.lontime.shaded.com.google.common.collect.Lists;
import com.github.lontime.shaded.org.redisson.api.RBatch;
import com.github.lontime.shaded.org.redisson.api.RScript;
import com.github.lontime.shaded.org.redisson.api.RedissonClient;
import com.github.lontime.shaded.org.redisson.api.stream.StreamAddArgs;
import com.github.lontime.shaded.org.redisson.client.codec.ByteArrayCodec;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;

import static com.github.lontime.extredisson.common.Constants.SERVICE_DATA_KEY;

/**
 * RealProducerHandler.
 * @author lontime
 * @since 1.0
 */
public class RealProducerHandler {

    private final ProducerOption option;
    private final Topic topic;
    private final List tags;
    private final boolean ref;
    private final long keepAlive;

    public RealProducerHandler(ProducerOption producerOption) {
        this.option = producerOption;
        this.topic = Topic.create(ListenerKind.SERVICE, producerOption);
        this.tags = producerOption.getTags();
        this.ref = producerOption.getRef();
        this.keepAlive = OptionResolver.getInstance().registry().getKeepAlive().toMillis();
    }

    public List handle(final List messagesIn, final HandlerInterface handler) {
        if (messagesIn.isEmpty()) {
            return Collections.emptyList();
        }
        final List messages = enhanceMessages(messagesIn);
        final List messageList = publishMessages(option, messages);
        if (messageList.size() <= option.getPage()) {
            return execute(messageList, handler);
        }
        final List> subList = Lists.partition(messageList, option.getPage());
        final List outputs = Flux.fromIterable(subList)
                .subscribeOn(Schedulers.boundedElastic())
                .flatMapIterable(s -> execute(s, handler)).collectList().toFuture().join();
        return outputs;
    }

    private List enhanceMessages(List messages) {
        final List newMessages = new ArrayList<>(messages.size());
        final List
headers = option.getHeaders(); for (Message message : messages) { if (StringHelper.isEmpty(message.getServiceName())) { continue; } final Message.Builder builder = Message.builder().copy(message); if (CollectionHelper.isNotEmpty(option.getHeaders())) { builder.addHeadersIfAbsent(headers); } builder.topic(option.getTopic()); if (option.getEnableReply() && StringHelper.isEmpty(message.getReplyTo())) { builder.replyTo(ProjectRedisKeyGenerator.reply(UuidHelper.fastUUID()).getFullName()); } newMessages.add(builder.build()); } return newMessages; } private List execute(List messages, HandlerInterface handler) { //批量执行内部业务逻辑 final ProcessResult result = executeInternal(messages, handler); return messages.stream().map(s -> buildReplyOutput(s, result)).collect(Collectors.toList()); } private ProcessResult executeInternal(List messages, HandlerInterface handler) { final List newMessages = messages.stream().filter(s -> StringHelper.hasText(s.getId())). collect(Collectors.toList()); if (newMessages.isEmpty()) { return ProcessResult.createFail("message is empty"); } final String msgIds = newMessages.stream().map(MessageWithId::getId).collect(Collectors.joining(",")); CallbackContext callbackContext = CallbackContext.create(newMessages); ProcessResult result = handler.run(callbackContext); for (int i = 0; i < option.getMaxRecheck(); i++) { if (result.successful()) { if (option.shouldRef()) { publishRefMessages(messages); } return ProcessResult.createSuccess(); } else if (result.failed()) { handler.fail(callbackContext); GLogger.defaults().infov("executeInternal<{0}> is fail! message-id: {1}", i, msgIds); return result; } callbackContext = CallbackContext.create(i, newMessages); result = handler.recheck(callbackContext); } throw ErrorException.from(ErrorCodeEnum.ERROR_SYSTEM, "executeInternal is error"); } private List publishMessages(ProducerOption option, List messages) { if (option.shouldRef()) { return publishMessagesBatch(messages); } return publishMessagesScript(messages); } private List publishMessagesBatch(List messages) { final RBatch batch = client().createBatch(); final List> futures = new ArrayList<>(messages.size()); for (final Message message : messages) { futures.add(batch.getStream(topic.redisKeyReal(), StringBytesMapCodec.INSTANCE) .addAsync(StreamAddArgs.entry(SERVICE_DATA_KEY, message.serializeToBytes())) .thenApply(s -> MessageWithId.create(s.toString(), message)).toCompletableFuture()); } batch.execute(); return futures.stream().map(CompletableFuture::join).collect(Collectors.toList()); } private List publishMessagesScript(List messages) { final List keys = new ArrayList<>(); final List values = new ArrayList<>(); for (int i = 0; i < messages.size(); i++) { final Message message = messages.get(i); keys.add(String.valueOf(i)); keys.add(topic.getName()); keys.add(topic.redisKeyReal()); keys.add(Long.toString(System.currentTimeMillis() - keepAlive)); keys.add(StringHelper.isNotEmpty(message.getReplyTo()) ? message.getReplyTo() : "??"); values.add(message.serializeToBytes()); } final List byteIds = client().getScript(ByteArrayCodec.INSTANCE).eval(RScript.Mode.READ_ONLY, "local result = {}; " + "local j = 1; " + "for i = 1, #ARGV, 1 do " + " local lastDate = 0; " + " local index = KEYS[j]; " + " local topic = KEYS[j+1]; " + " local key = KEYS[j+2]; " + " local expireDate = tonumber(KEYS[j+3]); " + " local replyTo = KEYS[j+4]; " + " j=j+5; " + " local lastIdle = redis.call('zscore', '" + ProjectRedisKeyGenerator.watchAllTopic().getFullName() + "', topic); " + " if lastIdle ~= false then " + " lastDate = tonumber(lastIdle); " + " end; " + " table.insert(result, index); " + " if lastDate >= expireDate then " + " local value = redis.call('xadd', key, '*', '" + SERVICE_DATA_KEY + "', ARGV[i]); " + " table.insert(result, value); " + " else " + " table.insert(result, ''); " + " if replyTo ~= '??' then" + " redis.call('lpush', replyTo, ''); " + " end; " + " end; " + "end; " + "return result; " , RScript.ReturnType.MULTI, new ArrayList<>(keys), values.toArray()); final List list = byteIds.stream().map(s -> new String(s, StandardCharsets.UTF_8)).collect(Collectors.toList()); final List messageWithIds = new ArrayList<>(messages.size()); for (int i = 0; i < list.size(); i += 2) { final int index = Integer.parseInt(list.get(i)); final String msgId = list.get(i + 1); final Message message = messages.get(index); messageWithIds.add(new MessageWithId(msgId, message)); } return messageWithIds; } private List publishRefMessages(List messages) { final List refMessages = new ArrayList<>(); for (final MessageWithId message : messages) { if (ref) { refMessages.add(createRefServiceMessage(message, null)); } if (tags != null) { for (String tag : tags) { refMessages.add(createRefServiceMessage(message, tag)); } } } return publishRefMessagesInternal(refMessages); } private RefServiceMessage createRefServiceMessage(MessageWithId message, String tag) { final String to = Optional.ofNullable(message.getMessage().getReplyTo()) .map(s -> RedisKeys.create(null, s, tag).getFullName()) .orElse(null); final RefServiceMessage refMessage = new RefServiceMessage(); refMessage.setMessage(message.getMessage()); refMessage.setTopic(Topic.service(topic.getOrig(), tag, true)); refMessage.setReplyTo(to); refMessage.setMsgId(message.getId()); return refMessage; } private List publishRefMessagesInternal(List messages) { final List keys = new ArrayList<>(); final List values = new ArrayList<>(); for (final RefServiceMessage refMessage : messages) { keys.add(refMessage.getTopic().getName()); keys.add(refMessage.getTopic().redisKeyRef()); keys.add(Long.toString(System.currentTimeMillis() - keepAlive)); keys.add(StringHelper.isNotEmpty(refMessage.getReplyTo()) ? refMessage.getReplyTo() : "??"); values.add(refMessage.serialize()); } final List byteIds = client().getScript(ByteArrayCodec.INSTANCE).eval(RScript.Mode.READ_ONLY, "local result = {}; " + "local j = 1; " + "for i = 1, #ARGV, 1 do " + " local lastDate = 0; " + " local topic = KEYS[j]; " + " local key = KEYS[j+1]; " + " local expireDate = tonumber(KEYS[j+2]); " + " local replyTo = KEYS[j+3]; " + " j=j+4; " + " local lastIdle = redis.call('zscore', '" + ProjectRedisKeyGenerator.watchAllTopic().getFullName() + "', topic); " + " if lastIdle ~= false then " + " lastDate = tonumber(lastIdle); " + " end; " + " if lastDate >= expireDate then " + " local value = redis.call('xadd', key, '*', '" + SERVICE_DATA_KEY + "', ARGV[i]); " + " table.insert(result, value); " + " else " + " table.insert(result, ''); " + " if replyTo ~= '??' then" + " redis.call('lpush', replyTo, ''); " + " end; " + " end; " + "end; " + "return result; " , RScript.ReturnType.MULTI, new ArrayList<>(keys), values.toArray()); final List ids = byteIds.stream().map(s -> new String(s, StandardCharsets.UTF_8)).collect(Collectors.toList()); final String refMsgIds = ids.stream().collect(Collectors.joining(",", "[", "]")); GLogger.defaults().infov("publishRefMessages success, ref-message-id:{2}", refMsgIds); return ids; } private Map> buildResponses(Message message) { final List replyTos = option.getTags().stream() .map(tag -> RedisKeys.create(null, message.getReplyTo(), tag).getFullName()) .collect(Collectors.toList()); return waitAsync(replyTos); } private CompletableFuture buildResponse(Message message) { return waitAsync(message.getReplyTo()); } private ReplyOutput buildReplyOutput(MessageWithId messageWithId, ProcessResult result) { final String msgId = messageWithId.getId(); final Message message = messageWithId.getMessage(); final ReplyOutput output = new ReplyOutput(msgId, message, option.getTimeout()); output.setResult(result); if (StringHelper.isEmpty(message.getReplyTo())) { return output; } if (CollectionHelper.isEmpty(tags)) { output.setResponse(buildResponse(message)); return output; } final Map> futureMap = buildResponses(message); final Map> responses = new HashMap<>(futureMap.size()); for (Map.Entry> entry : futureMap.entrySet()) { final String key = entry.getKey(); final int index = key.lastIndexOf(RedisKeys.SPLITTER); responses.put(key.substring(index + 1), entry.getValue()); } output.setResponses(responses); return output; } private CompletableFuture waitAsync(String replyTo) { final RedissonClient client = client(); return client.getBlockingQueue(replyTo, ByteArrayCodec.INSTANCE) .pollAsync(OptionResolver.getInstance().getWaitPollTimeout().getSeconds(), TimeUnit.SECONDS) .toCompletableFuture(); } private Map> waitAsync(List replyTos) { if (CollectionHelper.isEmpty(replyTos)) { return Collections.emptyMap(); } final RedissonClient client = client(); final Map> futures = new HashMap<>(replyTos.size()); for (String replyTo : replyTos) { GLogger.defaults().infov("waitAsync replyTo:{0}", replyTo); final CompletableFuture future = client.getBlockingQueue(replyTo, ByteArrayCodec.INSTANCE) .pollAsync(OptionResolver.getInstance().getWaitPollTimeout().getSeconds(), TimeUnit.SECONDS) .toCompletableFuture(); futures.put(replyTo, future); } return futures; } private RedissonClient client() { return RedissonInstance.get().client(topic.getOrig()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy