From f312bf382a96cce0f9d097c1b556b76a5c00f624 Mon Sep 17 00:00:00 2001 From: PengJingzhao <1670916827@qq.com> Date: Sat, 22 Mar 2025 21:12:23 +0800 Subject: [PATCH 1/8] feat(TTLCache): feat a feature that extend the DefaultCache with ttl support --- .../java/redis/clients/jedis/TTLCache.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/main/java/redis/clients/jedis/TTLCache.java diff --git a/src/main/java/redis/clients/jedis/TTLCache.java b/src/main/java/redis/clients/jedis/TTLCache.java new file mode 100644 index 0000000000..bf05ee8109 --- /dev/null +++ b/src/main/java/redis/clients/jedis/TTLCache.java @@ -0,0 +1,88 @@ +package redis.clients.jedis; + +import redis.clients.jedis.csc.*; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; + +public class TTLCache extends DefaultCache { + + private final Map expirationTimes; + private final Long defaultMilis; + private final ScheduledExecutorService cleanupExecutor; + private final AtomicBoolean isShutdown = new AtomicBoolean(false); + + /** + * create a ttlCache object with thd ttl support + * + * @param maximumSize the maximum size of the cache + * @param ttl time to live + * @param timeUnit the unit of the time + */ + public TTLCache(int maximumSize, Long ttl, TimeUnit timeUnit) { + super(maximumSize); + this.defaultMilis = timeUnit.toMillis(ttl); + + this.expirationTimes = new ConcurrentHashMap<>(); + this.cleanupExecutor = new ScheduledThreadPoolExecutor(1); + initCleanupTask(); + } + + + protected TTLCache(int maximumSize, Map expirationTimes, Long defaultMilis, ScheduledExecutorService cleanupExecutor) { + super(maximumSize); + this.expirationTimes = expirationTimes; + this.defaultMilis = defaultMilis; + this.cleanupExecutor = cleanupExecutor; + + initCleanupTask(); + } + + private void initCleanupTask() { + + // decide the time interval of the cleaning up + long timeInterval = Math.max(this.defaultMilis / 10, 1000); + + if (defaultMilis > 0) { + cleanupExecutor.scheduleAtFixedRate(this::cleanupExpiredEntries, timeInterval, timeInterval, TimeUnit.MILLISECONDS); + } + } + + public boolean removeFromStore(CacheKey key) { + expirationTimes.remove(key); + return super.removeFromStore(key); + } + + /** + * use the scheduled thread to clean up all the expired entries at the fixed rate + */ + public void cleanupExpiredEntries() { + // if the thread is shut down + if (isShutdown.get()) { + return; + } + + // get the time millis of now + long now = System.currentTimeMillis(); + // remove all the expired key + expirationTimes.entrySet().removeIf(new Predicate>() { + @Override + public boolean test(Map.Entry entry) { + Long expiredTime = entry.getValue(); + CacheKey cacheKey = entry.getKey(); + + // if the expired time of the key less than now show the key is expired + if (expiredTime < now) { + removeFromStore(cacheKey); + return true; + } + return false; + } + }); + } +} From eb56edf6f19376511231f64affca091b66e043c4 Mon Sep 17 00:00:00 2001 From: PengJingzhao <1670916827@qq.com> Date: Sun, 23 Mar 2025 17:32:06 +0800 Subject: [PATCH 2/8] feat(TTLCache): feat a method that can set up a ttl for the key before put into store --- src/main/java/redis/clients/jedis/TTLCache.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/redis/clients/jedis/TTLCache.java b/src/main/java/redis/clients/jedis/TTLCache.java index bf05ee8109..c97b1ac968 100644 --- a/src/main/java/redis/clients/jedis/TTLCache.java +++ b/src/main/java/redis/clients/jedis/TTLCache.java @@ -85,4 +85,20 @@ public boolean test(Map.Entry entry) { } }); } + + /** + * before put the cache into store,we should put the key into the expirationTimes Map + * + * @param key cache key + * @param entry cache value + * @return cache entry + */ + @Override + public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) { + if (defaultMilis > 0) { + // set up the ttl from now + expirationTimes.put(key, System.currentTimeMillis() + defaultMilis); + } + return super.putIntoStore(key, entry); + } } From 746a642909d0f6478f96fdd9d09f76921eb2e2b8 Mon Sep 17 00:00:00 2001 From: PengJingzhao <1670916827@qq.com> Date: Sun, 23 Mar 2025 17:47:07 +0800 Subject: [PATCH 3/8] feat(TTLCache): feat a method that can clear the expirationTimes map before clear the cache --- src/main/java/redis/clients/jedis/TTLCache.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/redis/clients/jedis/TTLCache.java b/src/main/java/redis/clients/jedis/TTLCache.java index c97b1ac968..5d19d1e810 100644 --- a/src/main/java/redis/clients/jedis/TTLCache.java +++ b/src/main/java/redis/clients/jedis/TTLCache.java @@ -53,6 +53,12 @@ private void initCleanupTask() { } } + /** + * remove the key from the expirationTimes map before remove from cache + * + * @param key cache key + * @return + */ public boolean removeFromStore(CacheKey key) { expirationTimes.remove(key); return super.removeFromStore(key); @@ -101,4 +107,14 @@ public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) { } return super.putIntoStore(key, entry); } + + /** + * clear the expirationTimes map before clear the cache + * @param key cache key + */ + protected void clearStore(CacheKey key) { + expirationTimes.clear(); + super.clearStore(); + } + } From 4df87bf7c898dfb93ae47fc80be574999de45397 Mon Sep 17 00:00:00 2001 From: PengJingzhao <1670916827@qq.com> Date: Sun, 23 Mar 2025 17:59:14 +0800 Subject: [PATCH 4/8] feat(TTLCache): feat a method that can decide the cache key expired or not --- src/main/java/redis/clients/jedis/TTLCache.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/redis/clients/jedis/TTLCache.java b/src/main/java/redis/clients/jedis/TTLCache.java index 5d19d1e810..cc03718e7c 100644 --- a/src/main/java/redis/clients/jedis/TTLCache.java +++ b/src/main/java/redis/clients/jedis/TTLCache.java @@ -110,6 +110,7 @@ public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) { /** * clear the expirationTimes map before clear the cache + * * @param key cache key */ protected void clearStore(CacheKey key) { @@ -117,4 +118,19 @@ protected void clearStore(CacheKey key) { super.clearStore(); } + /** + * decide the key is expired or not + * + * @param key cache key + * @return if the key is expired return true + */ + private boolean isExpired(CacheKey key) { + Long expirationTime = expirationTimes.get(key); + if (expirationTime == null) { + // the key has not yet been set up a expirationTime + return false; + } + return System.currentTimeMillis() > expirationTime; + } + } From b46642368d5cce93035b1e5c8a427a12d8b06fd4 Mon Sep 17 00:00:00 2001 From: PengJingzhao <1670916827@qq.com> Date: Sun, 23 Mar 2025 18:03:00 +0800 Subject: [PATCH 5/8] feat(TTLCache): feat a method that can decide the store contains the key or not --- src/main/java/redis/clients/jedis/TTLCache.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/redis/clients/jedis/TTLCache.java b/src/main/java/redis/clients/jedis/TTLCache.java index cc03718e7c..445539e6c5 100644 --- a/src/main/java/redis/clients/jedis/TTLCache.java +++ b/src/main/java/redis/clients/jedis/TTLCache.java @@ -133,4 +133,19 @@ private boolean isExpired(CacheKey key) { return System.currentTimeMillis() > expirationTime; } + /** + * decide the key is contained in the store or not + * + * @param key cache key + * @return if the key belong to the cache return true + */ + protected boolean containsKeyInStore(CacheKey key) { + if (isExpired(key)) { + // if the key is expired , we should remove it from the store + removeFromStore(key); + return false; + } + return super.containsKeyInStore(key); + } + } From e8dc29bad17f4fe189e7f4abaac3c0be3caecaf3 Mon Sep 17 00:00:00 2001 From: PengJingzhao <1670916827@qq.com> Date: Sun, 23 Mar 2025 18:06:38 +0800 Subject: [PATCH 6/8] feat(TTLCache): feat a method that can set up a ttl for any other key --- src/main/java/redis/clients/jedis/TTLCache.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/redis/clients/jedis/TTLCache.java b/src/main/java/redis/clients/jedis/TTLCache.java index 445539e6c5..062e8842f9 100644 --- a/src/main/java/redis/clients/jedis/TTLCache.java +++ b/src/main/java/redis/clients/jedis/TTLCache.java @@ -148,4 +148,20 @@ protected boolean containsKeyInStore(CacheKey key) { return super.containsKeyInStore(key); } + /** + * set up a ttl for any other key + * + * @param key the cache key + * @param ttl time to live + * @param timeUnit time unit + */ + public void setTTL(CacheKey key, Long ttl, TimeUnit timeUnit) { + // if the ttl less than zero , remove the key from the expirationTimes + if (ttl <= 0) { + expirationTimes.remove(key); + } else { + expirationTimes.put(key, System.currentTimeMillis() + ttl); + } + } + } From e660beb2c481f347b54373ed1a0d5f393d677876 Mon Sep 17 00:00:00 2001 From: PengJingzhao <1670916827@qq.com> Date: Sun, 23 Mar 2025 18:11:29 +0800 Subject: [PATCH 7/8] feat(TTLCache): feat a method that can get the remaining ttl of the key --- .../java/redis/clients/jedis/TTLCache.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/redis/clients/jedis/TTLCache.java b/src/main/java/redis/clients/jedis/TTLCache.java index 062e8842f9..ba98d40262 100644 --- a/src/main/java/redis/clients/jedis/TTLCache.java +++ b/src/main/java/redis/clients/jedis/TTLCache.java @@ -10,6 +10,11 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; +/** + * @author PengJingzhao + * @date 2025-03-23 + * add a ttl support for the default cache + */ public class TTLCache extends DefaultCache { private final Map expirationTimes; @@ -164,4 +169,20 @@ public void setTTL(CacheKey key, Long ttl, TimeUnit timeUnit) { } } + /** + * get the remaining ttl of the key + * + * @param key cache key + * @return the remaining ttl of the key + */ + public long getTTL(CacheKey key) { + Long expirationTime = expirationTimes.get(key); + if (expirationTime == null) { + return -1; + } + + long remaining = expirationTime - System.currentTimeMillis(); + return remaining > 0 ? remaining : 0; + } + } From 4817eb7411de63d0898f36548d5cc6bffca15665 Mon Sep 17 00:00:00 2001 From: PengJingzhao <1670916827@qq.com> Date: Sun, 23 Mar 2025 18:18:58 +0800 Subject: [PATCH 8/8] feat(TTLCache): feat a method that can shut down the cleanup executor --- .../java/redis/clients/jedis/TTLCache.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/redis/clients/jedis/TTLCache.java b/src/main/java/redis/clients/jedis/TTLCache.java index ba98d40262..7b0dd24967 100644 --- a/src/main/java/redis/clients/jedis/TTLCache.java +++ b/src/main/java/redis/clients/jedis/TTLCache.java @@ -50,10 +50,9 @@ protected TTLCache(int maximumSize, Map expirationTimes, Long de private void initCleanupTask() { - // decide the time interval of the cleaning up - long timeInterval = Math.max(this.defaultMilis / 10, 1000); - if (defaultMilis > 0) { + // decide the time interval of the cleaning up + long timeInterval = Math.max(this.defaultMilis / 10, 1000); cleanupExecutor.scheduleAtFixedRate(this::cleanupExpiredEntries, timeInterval, timeInterval, TimeUnit.MILLISECONDS); } } @@ -115,10 +114,8 @@ public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) { /** * clear the expirationTimes map before clear the cache - * - * @param key cache key */ - protected void clearStore(CacheKey key) { + protected void clearStoreWithTTL() { expirationTimes.clear(); super.clearStore(); } @@ -185,4 +182,13 @@ public long getTTL(CacheKey key) { return remaining > 0 ? remaining : 0; } + /** + * shut down the cleanup executor + */ + public void shutdown() { + if (isShutdown.compareAndSet(false, true)) { + cleanupExecutor.shutdown(); + } + } + }