-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
201 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
cache/src/main/java/me/qwqdev/library/cache/service/redis/RedisCacheService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package me.qwqdev.library.cache.service.redis; | ||
|
||
import io.fairyproject.log.Log; | ||
import me.qwqdev.library.cache.model.ExpirationSettings; | ||
import org.redisson.Redisson; | ||
import org.redisson.api.RBucket; | ||
import org.redisson.api.RLock; | ||
import org.redisson.api.RedissonClient; | ||
import org.redisson.config.Config; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
import java.util.function.Function; | ||
import java.util.function.Supplier; | ||
|
||
/** | ||
* Redis Cache Service that supports flexible functional programming to operate the cache. | ||
* | ||
* @param <K> the type of the key | ||
* @param <V> the type of the value | ||
* @author qwq-dev | ||
* @since 2024-12-21 12:00 | ||
*/ | ||
public class RedisCacheService<K, V> { | ||
private final RedissonClient redissonClient; | ||
|
||
/** | ||
* Initializes the Redis Cache Service with the provided Redisson configuration. | ||
* | ||
* @param config the Redisson configuration | ||
*/ | ||
public RedisCacheService(Config config) { | ||
this.redissonClient = Redisson.create(config); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
* | ||
* @param key {@inheritDoc} | ||
* @param function {@inheritDoc} | ||
* @param query {@inheritDoc} | ||
* @param cacheAfterQuery {@inheritDoc} | ||
* @param expirationSettings {@inheritDoc} | ||
* @return | ||
*/ | ||
public V get(K key, Function<RedissonClient, RBucket<V>> function, Supplier<V> query, boolean cacheAfterQuery, ExpirationSettings expirationSettings) { | ||
return executeCacheOperation(function, key, query, cacheAfterQuery, expirationSettings); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
* | ||
* @param key {@inheritDoc} | ||
* @param function {@inheritDoc} | ||
* @param query {@inheritDoc} | ||
* @param cacheAfterQuery {@inheritDoc} | ||
* @param expirationSettings {@inheritDoc} | ||
* @return {@inheritDoc} | ||
*/ | ||
public V getWithLock(K key, Function<RedissonClient, RBucket<V>> function, Supplier<V> query, boolean cacheAfterQuery, ExpirationSettings expirationSettings) { | ||
return executeCacheOperationWithLock(function, key, query, cacheAfterQuery, expirationSettings); | ||
} | ||
|
||
private V executeCacheOperation(Function<RedissonClient, RBucket<V>> function, K key, Supplier<V> query, boolean cacheAfterQuery, ExpirationSettings expirationSettings) { | ||
RBucket<V> rBucket = function.apply(redissonClient); | ||
return retrieveOrStoreInCache(rBucket, key, query, cacheAfterQuery, expirationSettings); | ||
} | ||
|
||
private V executeCacheOperationWithLock(Function<RedissonClient, RBucket<V>> function, K key, Supplier<V> query, boolean cacheAfterQuery, ExpirationSettings expirationSettings) { | ||
RLock lock = redissonClient.getLock("lock:" + key); | ||
|
||
try { | ||
if (lock.tryLock(30, 10, TimeUnit.SECONDS)) { | ||
return executeCacheOperation(function, key, query, cacheAfterQuery, expirationSettings); | ||
} | ||
String msg = "Could not acquire lock for key: " + key; | ||
Log.error(msg); | ||
throw new RuntimeException(msg); | ||
} catch (InterruptedException exception) { | ||
Thread.currentThread().interrupt(); | ||
String msg = "Thread interrupted while trying to acquire lock for key: " + key; | ||
Log.error(msg, exception); | ||
throw new RuntimeException(msg, exception); | ||
} finally { | ||
try { | ||
lock.unlock(); | ||
} catch (Exception exception) { | ||
Log.error("Error unlocking lock for key: " + key, exception); | ||
} | ||
} | ||
} | ||
|
||
private V retrieveOrStoreInCache(RBucket<V> rBucket, K key, Supplier<V> query, boolean cacheAfterQuery, ExpirationSettings expirationSettings) { | ||
V value = rBucket.get(); | ||
|
||
if (value != null) { | ||
return value; | ||
} | ||
|
||
value = query.get(); | ||
|
||
if (cacheAfterQuery) { | ||
storeInCache(rBucket, value, expirationSettings); | ||
} | ||
|
||
return value; | ||
} | ||
|
||
private void storeInCache(RBucket<V> rBucket, V value, ExpirationSettings expirationSettings) { | ||
if (expirationSettings != null) { | ||
rBucket.set(value, expirationSettings.toDuration()); | ||
} else { | ||
rBucket.set(value); | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public void shutdown() { | ||
if (redissonClient != null) { | ||
try { | ||
redissonClient.shutdown(); | ||
} catch (Exception exception) { | ||
Log.error("Error shutting down Redisson client", exception); | ||
} | ||
} | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
cache/src/main/java/me/qwqdev/library/cache/service/redis/RedisCacheServiceInterface.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package me.qwqdev.library.cache.service.redis; | ||
|
||
import me.qwqdev.library.cache.model.ExpirationSettings; | ||
import org.redisson.api.RBucket; | ||
import org.redisson.api.RedissonClient; | ||
|
||
import java.util.function.Function; | ||
import java.util.function.Supplier; | ||
|
||
/** | ||
* Interface for redis cache service. | ||
* | ||
* @param <K> the type of the key | ||
* @param <V> the type of the value | ||
* @author qwq-dev | ||
* @since 2024-12-21 12:16 | ||
*/ | ||
public interface RedisCacheServiceInterface<K, V> { | ||
/** | ||
* Get the {@link RedissonClient} that implements the cache | ||
* | ||
* @return the {@link RedissonClient} | ||
*/ | ||
RedissonClient getRedissonClient(); | ||
|
||
/** | ||
* Retrieves a value from the Redis cache. If the value is not found, it fetches the value | ||
* using the provided query function and optionally stores it in the cache. | ||
* | ||
* @param key the cache key | ||
* @param function function to access the Redis cache (e.g., using {@link RedissonClient#getBucket(String)}) | ||
* @param query the query function to fetch the value when not present in the cache | ||
* @param cacheAfterQuery whether to cache the value after querying | ||
* @param expirationSettings the expiration settings for the cache item | ||
* @return the value from the cache, or the queried value if not found | ||
*/ | ||
V get(K key, Function<RedissonClient, RBucket<V>> function, Supplier<V> query, boolean cacheAfterQuery, ExpirationSettings expirationSettings); | ||
|
||
/** | ||
* Retrieves a value from the Redis cache with a distributed lock to ensure exclusive access | ||
* in high-concurrency environments. If the value is not found, it fetches the value using | ||
* the provided query function and optionally stores it in the cache. | ||
* | ||
* @param key the cache key | ||
* @param function function to access the Redis cache (e.g., using {@link RedissonClient#getBucket(String)}) | ||
* @param query the query function to fetch the value when not present in the cache | ||
* @param cacheAfterQuery whether to cache the value after querying | ||
* @param expirationSettings the expiration settings for the cache item | ||
* @return the value from the cache, or the queried value if not found | ||
* @throws RuntimeException if the lock cannot be acquired | ||
*/ | ||
V getWithLock(K key, Function<RedissonClient, RBucket<V>> function, Supplier<V> query, boolean cacheAfterQuery, ExpirationSettings expirationSettings); | ||
|
||
/** | ||
* Shuts down the Redisson client and releases resources. | ||
*/ | ||
void shutdown(); | ||
} |