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;
}
}