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

com.github.lontime.extredisson.container.WatchService Maven / Gradle / Ivy

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

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.github.lontime.base.commonj.components.DelayedProcessor;
import com.github.lontime.base.commonj.utils.CollectionHelper;
import com.github.lontime.base.logging.GLogger;
import com.github.lontime.extredisson.RedissonInstance;
import com.github.lontime.extredisson.common.Constants;
import com.github.lontime.extredisson.common.RedisKeys;
import com.github.lontime.extredisson.common.WatchKind;
import com.github.lontime.extredisson.configuration.NodeOption;
import com.github.lontime.extredisson.configuration.OptionResolver;
import com.github.lontime.extredisson.configuration.WatchOption;
import com.github.lontime.shaded.org.redisson.api.RLock;
import com.github.lontime.shaded.org.redisson.api.RScript;


/**
 * WatchService.
 *
 * @author lontime
 * @since 1.0
 */
public class WatchService extends DelayedProcessor {

    /**
     * 2022-01-01 00:00:00.
     * 1640966400000L
     */
    private static final long START_SCORE_BASED_TIME = 0L;

    private WatchOption option;

    private List specs = new ArrayList<>();

    public WatchService() {
        this.option = OptionResolver.getInstance().getWatchOption();
    }

    @Override
    public void startUp() {
        if (option != null && option.getEnabled()) {
            initSpecs();
        }
    }

    private void initSpecs() {
        addNodeWatch();
        this.specs.addAll(option.getSpecs().stream()
                .filter(s -> s.getKind() != WatchKind.REPLY).collect(Collectors.toList()));
        if (option.getEnableStream()) {
            final List specStreamNames = option.getSpecs().stream()
                    .filter(s -> s.getKind() == WatchKind.STREAM)
                    .map(WatchOption.Spec::getName).collect(Collectors.toList());
            this.specs.addAll(OptionResolver.getInstance().getStreamNames()
                    .stream().filter(s -> !specStreamNames.contains(s)).map(s -> {
                        final WatchOption.Spec specOption = new WatchOption.Spec();
                        specOption.setName(s);
                        specOption.setKind(WatchKind.STREAM);
                        return specOption;
                    }).collect(Collectors.toList()));
        }
        if (option.getEnableServiceReply() && option.getServiceReply() != null) {
            this.specs.add(option.getServiceReply());
        }
    }

    private void addNodeWatch() {
        final NodeOption nodeOption = OptionResolver.getInstance().getNodeOption();
        final WatchOption.Spec nodeSpec = new WatchOption.Spec();
        nodeSpec.setKind(WatchKind.ZSET);
        nodeSpec.setName(nodeOption.getNamespace());
        nodeSpec.setTimeToLive(nodeOption.getTtl());
        this.specs.add(nodeSpec);
    }

    @Override
    protected Duration sleepTimeout() {
        return option.getInterval();
    }

    @Override
    protected Duration initialDelay() {
        return option.getWarmup();
    }

    @Override
    public boolean doLoop() {
        if (CollectionHelper.isEmpty(specs)) {
            return false;
        }
        specs.forEach(this::doLoopInternal);
        return true;
    }

    private void doLoopInternal(WatchOption.Spec specOption) {
        try {
            runActual(specOption);
        } catch (InterruptedException e) {
            GLogger.defaults().warn(getClass(), e, "doLoopInternal is error");
        }
    }

    private void runActual(WatchOption.Spec specOption) throws InterruptedException {
        final String name = specOption.getName();
        final String lockName = buildLockName(specOption);
        final RLock lock = RedissonInstance.get().getLock(lockName);
        if (lock.tryLock(specOption.getLockWaitTime().toMillis(),
                specOption.getLockLeaseTime().toMillis(), TimeUnit.MILLISECONDS)) {
            try {
                runUnsafe(specOption);
            } finally {
                lock.unlock();
            }
        } else {
            GLogger.defaults().info(getClass(), "runActual <{0}> is locking, lockName:{1}!", name, lockName);
        }
    }

    private String buildLockName(WatchOption.Spec specOption) {
        return RedisKeys.create(RedisKeys.LOCK_WATCH,
                specOption.getKind().name().toLowerCase(), specOption.getName())
                .getFullName();
    }

    /**
     * runUnsafe.
     *
     * @param specOption option
     */
    private void runUnsafe(WatchOption.Spec specOption) {
        Long result = 0L;
        final RScript script = RedissonInstance.get().getScript(specOption.getName());
        if (specOption.getKind() == WatchKind.STREAM) {
            final String limitSize = String.valueOf(specOption.getMaxLen() * specOption.getMaxLenRate());
            result = script.eval(RScript.Mode.READ_ONLY,
                    "local len1 = redis.call('XLEN', KEYS[1]); "
                            + "if len1 > tonumber(KEYS[3]) then "
                            + "  redis.call('XTRIM', KEYS[1], 'MAXLEN', '~', KEYS[3]); "
                            + "end; "
                            + "local len2 = redis.call('XLEN', KEYS[2]); "
                            + "if len2 > tonumber(KEYS[3]) then "
                            + "  redis.call('XTRIM', KEYS[2], 'MAXLEN', '~', KEYS[3]); "
                            + "end; "
                            + "return 1;"
                    , RScript.ReturnType.VALUE, Arrays.asList(specOption.getName(),
                            specOption.getName() + Constants.STREAM_RECHECK_SUFFIX, limitSize));
        } else if (specOption.getKind() == WatchKind.SERVICE) {
            final String name = "service_" + specOption.getName();
            final String limitSize = String.valueOf(specOption.getMaxLen() * specOption.getMaxLenRate());
            result = script.eval(RScript.Mode.READ_ONLY,
                    "local len1 = redis.call('XLEN', KEYS[1]); "
                            + "if len1 > tonumber(KEYS[3]) then "
                            + "  redis.call('XTRIM', KEYS[1], 'MAXLEN', '~', KEYS[3]); "
                            + "end; "
                            + "return 1;"
                    , RScript.ReturnType.VALUE, Arrays.asList(name, limitSize));
        } else if (specOption.getKind() == WatchKind.ZSET) {
            result = script.eval(RScript.Mode.READ_ONLY,
                    "local ss = redis.call('exists', KEYS[1]); "
                            + "if ss == 0 then "
                            + "  return -1; "
                            + "end; "
                            + "return redis.call('ZREMRANGEBYSCORE', KEYS[1], tonumber(KEYS[2]), tonumber(KEYS[3]));"
                    , RScript.ReturnType.VALUE, Arrays.asList(specOption.getName(), String.valueOf(START_SCORE_BASED_TIME),
                            String.valueOf(createEndScore(specOption))));
        } else if (specOption.getKind() == WatchKind.HASH) {
            result = script.eval(RScript.Mode.READ_ONLY,
                    "local expiredValues = redis.call('zrangebyscore', KEYS[2], ARGV[1], ARGV[2], 'limit', 0, ARGV[3]); "
                            + "if #expiredValues > 0 then "
                            + "  for i, v in ipairs(expiredValues) do "
                            + "     redis.call('hdel', KEYS[1], v); "
                            + "     redis.call('zrem', KEYS[2], v); "
                            + "   end; "
                            + "   return 1;"
                            + "end; "
                            + "return 0;"
                    , RScript.ReturnType.VALUE, Arrays.asList(specOption.getName(), specOption.getName() + ":alive"),
                    START_SCORE_BASED_TIME, createEndScore(specOption), specOption.getBatchSize());
        } else if (specOption.getKind() == WatchKind.REPLY) {
            result = script.eval(RScript.Mode.READ_ONLY,
                    "local expiredValues = redis.call('zrangebyscore', KEYS[1], ARGV[1], ARGV[2], 'limit', 0, ARGV[3]); "
                            + "if #expiredValues > 0 then "
                            + "  for i, v in ipairs(expiredValues) do "
                            + "     redis.call('lpop', v); "
                            + "     redis.call('zrem', KEYS[1], v); "
                            + "   end; "
                            + "   return 1;"
                            + "end; "
                            + "return 0;"
                    , RScript.ReturnType.VALUE, Arrays.asList(specOption.getName()),
                    START_SCORE_BASED_TIME, createEndScore(specOption), specOption.getBatchSize());
        }
        GLogger.defaults().debug(getClass(), "runUnsafe result {0}!", result);
    }

    private long createEndScore(WatchOption.Spec specOption) {
        long score = System.currentTimeMillis() - specOption.getTimeToLive().toMillis();
        return score;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy