req-shield has not yet been published to the maven repository, please be patient.
Visit the github wiki for more information.
A lib that regulates the cache-based requests an application receives in terms of request-collapsing.
- Java 8 or later if you are a user.
- Kotlin 1.8 or later if you are a user.
- (If link this lib with Spring) Spring Boot 2.7 (Spring Framework 5.3) or later if you are a user.
- Choose from the following based on your platform (There is a detailed explanation in the wiki.)
implementation("com.linecorp.cse.reqshield:core:{version}")
implementation("com.linecorp.cse.reqshield:core-reactor:{version}")
implementation("com.linecorp.cse.reqshield:core-kotlin-coroutine:{version}")
implementation("com.linecorp.cse.reqshield:core-spring:{version}")
implementation("com.linecorp.cse.reqshield:core-spring-webflux:{version}")
implementation("com.linecorp.cse.reqshield:core-spring-webflux-kotlin-coroutine:{version}")
- Redis-backed integration tests using Testcontainers always run as part of module test tasks.
- Requirements:
- A working local Docker daemon with network access to pull
redis:6.2.7-alpineon first run. - Sufficient permissions to start containers from tests.
- A working local Docker daemon with network access to pull
- If you need to temporarily bypass Redis ITs locally (e.g., no Docker), run specific unit-test-only tasks or exclude the example modules when invoking Gradle.
@ReqShieldCacheable(nullHandling = ...)controls hownullvalues are emitted in WebFlux:EMIT_EMPTY(default): mapnulltoMono.empty().ERROR: throw anIllegalStateExceptionif anullvalue is produced.
- The Spring adapters store every entry under
"{cacheName}::{key}"(the same convention as Spring'sRedisCacheManager), wherekeyis the SpEL result or theKeyGeneratoroutput.@ReqShieldCacheEvictapplies the same rule, so an evict with the samecacheNameandkeyalways targets the entry written by@ReqShieldCacheable. - Lock keys are derived from that namespaced key and prefixed with
reqshield:lock:, so two caches that happen to use the same raw key never share a lock or an entry. - If you call
ReqShielddirectly (core / core-reactor / core-kotlin-coroutine), the key you pass is used as is.
- When
isLocalLock = false, your cache bean must also implement the module'sGlobalLockSupportinterface (com.linecorp.cse.reqshield.spring.cache.GlobalLockSupport,...spring.webflux.cache.GlobalLockSupport,...spring.webflux.kotlin.coroutine.cache.GlobalLockSupport). If it does not, the first call to the annotated method fails with anIllegalArgumentExceptioninstead of silently running without request collapsing. - Every lock acquisition carries an ownership token. Only the holder that acquired the lock can release it, so a slow holder whose lock already expired can no longer release the lock of the next holder.
- Recommended Redis implementation:
- Lock:
SET {lockKey} {token} NX PX {ttlMillis}(atomic; neverSETNXfollowed by a separatePEXPIRE) - Unlock: compare-and-delete in a Lua script, e.g.
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
- Lock:
- The example modules contain working implementations for
RedisTemplate,ReactiveRedisTemplateand the coroutine extensions.
@ReqShieldCacheEvictevicts after the annotated method completes successfully (Spring's@CacheEvictdefault). If the method throws, nothing is evicted; if the eviction itself fails, that failure propagates to the caller.- In WebFlux the eviction also runs when the method completes empty (for example a
Mono<Void>handler).
- A request that loses the lock polls the cache every 50 ms, up to
maxAttemptGetCachetimes (default 60). - A cache read failure while polling is logged and counted as a failed attempt. Three consecutive failures are treated as a cache outage and the request falls back to calling the supplier itself right away.
- When the attempts are exhausted the request calls the supplier once. A supplier failure is propagated as a
ClientException(SUPPLIER_ERROR)with the original exception ascause; it is never turned into a cachednull.
coreuses aScheduledExecutorService. The default is a shared daemon pool; the Spring adapter exposes it as thereqShieldExecutorbean, which you can override.core-reactoraccepts aScheduler(defaultboundedElastic). The Spring WebFlux adapter exposes it as thereqShieldSchedulerbean.core-kotlin-coroutineaccepts aCoroutineScopefor background cache writes (default: a shared supervisor scope onDispatchers.IO). The coroutine Spring adapter exposes it as thereqShieldCoroutineScopebean and cancels it on context shutdown.
@ReqShieldCacheable/@ReqShieldCacheEvictfromcore-spring-webflux-kotlin-coroutinerequiresuspendfunctions. Annotating a regular function fails fast with anIllegalArgumentException; usecore-springorcore-spring-webfluxfor blocking orMono-returning methods.
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Please see CONTRIBUTING.md for contributing to Req-Shield.
Apache License 2.0
- File an issue in the issue tracker to report a bug or suggest an idea.