归档
Unit-Test
说明
@Override
public V put(K key, V value, long ttl, TimeUnit unit) {
return get(putAsync(key, value, ttl, unit));
}
@Override
public RFuture<V> putAsync(K key, V value, long ttl, TimeUnit ttlUnit) {
return putAsync(key, value, ttl, ttlUnit, 0, null);
}
@Override
public RFuture<V> putAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
...
long ttlTimeout = 0;
long ttlTimeoutDelta = 0;
if (ttl > 0) {
ttlTimeoutDelta = ttlUnit.toMillis(ttl);
ttlTimeout = System.currentTimeMillis() + ttlTimeoutDelta;
}
...
RFuture<V> future = putOperationAsync(key, value, ttlTimeout, maxIdleTimeout, maxIdleDelta, ttlTimeoutDelta);
if (hasNoWriter()) {
return future;
}
...
}
protected RFuture<V> putOperationAsync(K key, V value, long ttlTimeout, long maxIdleTimeout,
long maxIdleDelta, long ttlTimeoutDelta) {
String name = getRawName(key);
RFuture<V> future = commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
"local insertable = false; "
...
+ "if tonumber(ARGV[2]) > 0 then "
+ "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); "
+ "else "
+ "redis.call('zrem', KEYS[2], ARGV[5]); "
+ "end; "
...
+ "local value = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]); "
+ "redis.call('hset', KEYS[1], ARGV[5], value); "
...
+ "return val",
Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name),
getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)),
System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value));
return future;
}
过期处理
public RedissonMapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor,
String name, RedissonClient redisson, MapOptions<K, V> options, WriteBehindService writeBehindService) {
super(commandExecutor, name, redisson, options, writeBehindService);
if (evictionScheduler != null) {
evictionScheduler.schedule(getRawName(), getTimeoutSetName(), getIdleSetName(), getExpiredChannelName(), getLastAccessTimeSetName());
}
this.evictionScheduler = evictionScheduler;
}
public void schedule(String name, String timeoutSetName, String maxIdleSetName, String expiredChannelName, String lastAccessTimeSetName) {
EvictionTask task = new MapCacheEvictionTask(name, timeoutSetName, maxIdleSetName, expiredChannelName, lastAccessTimeSetName, executor);
EvictionTask prevTask = tasks.putIfAbsent(name, task);
if (prevTask == null) {
task.schedule();
}
}
public void schedule() {
scheduledFuture = executor.getConnectionManager().getGroup().schedule(this, delay, TimeUnit.SECONDS);
}
@Override
public void run() {
...
RFuture<Integer> future = execute();
future.whenComplete((size, e) -> {
if (e != null) {
log.error("Unable to evict elements for '{}'", getName(), e);
schedule();
return;
}
...
sizeHistory.add(size);
schedule();
});
}
@Override
RFuture<Integer> execute() {
int latchExpireTime = Math.min(delay, 30);
return executor.evalWriteNoRetryAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_INTEGER,
"if redis.call('setnx', KEYS[6], ARGV[4]) == 0 then "
+ "return -1;"
+ "end;"
+ "redis.call('expire', KEYS[6], ARGV[3]); "
+ "local expiredKeys1 = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); "
+ "for i, key in ipairs(expiredKeys1) do "
+ "local v = redis.call('hget', KEYS[1], key); "
+ "if v ~= false then "
+ "local t, val = struct.unpack('dLc0', v); "
+ "local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(val), val); "
+ "local listeners = redis.call('publish', KEYS[4], msg); "
+ "if (listeners == 0) then "
+ "break;"
+ "end; "
+ "end;"
+ "end;"
+ "for i=1, #expiredKeys1, 5000 do "
+ "redis.call('zrem', KEYS[5], unpack(expiredKeys1, i, math.min(i+4999, table.getn(expiredKeys1)))); "
+ "redis.call('zrem', KEYS[3], unpack(expiredKeys1, i, math.min(i+4999, table.getn(expiredKeys1)))); "
+ "redis.call('zrem', KEYS[2], unpack(expiredKeys1, i, math.min(i+4999, table.getn(expiredKeys1)))); "
+ "redis.call('hdel', KEYS[1], unpack(expiredKeys1, i, math.min(i+4999, table.getn(expiredKeys1)))); "
+ "end; "
...
+ "return #expiredKeys1 + #expiredKeys2;",
Arrays.<Object>asList(name, timeoutSetName, maxIdleSetName, expiredChannelName, lastAccessTimeSetName, executeTaskOnceLatchName),
System.currentTimeMillis(), keysLimit, latchExpireTime, 1);
}
问题
- 单元测试中,任务是 5s 执行一次,但 TTL 设置为 3s 时,key 也能删除
Ref
- Lua 解释:https://blog.youkuaiyun.com/Michelle_Zhong/article/details/126391915
- 大量 new 会创建大量任务,引起 OOM: https://juejin.cn/post/6844903842728034317