How CacheEvictedEventBus Enables Distributed Cache Invalidation in Cocache
The CacheEvictedEventBus is a lightweight publish/subscribe mechanism that broadcasts cache eviction events across JVMs, ensuring coherent caches stay synchronized by invalidating stale entries locally when remote modifications occur.
In the ahoo-wang/cocache repository, the CacheEvictedEventBus serves as the backbone for maintaining consistency across distributed cache instances. When one node modifies a cache entry, this event bus propagates CacheEvictedEvent messages to all registered subscribers, triggering local invalidation without requiring direct remote calls per operation.
Core Architecture
The distributed invalidation system revolves around three primary components defined in the cocache-core module.
CacheEvictedEventBus Interface
The contract is defined in cocache-core/src/main/kotlin/me/ahoo/cache/consistency/CacheEvictedEventBus.kt and specifies three primitive operations:
publish(event: CacheEvictedEvent)– Broadcasts an eviction event to all subscribersregister(subscriber: CacheEvictedSubscriber)– Adds a subscriber to receive future eventsunregister(subscriber: CacheEvictedSubscriber)– Removes a subscriber from the bus
CacheEvictedEvent Payload
The event structure in CacheEvictedEvent.kt carries the minimal data required for invalidation:
cacheName– Identifies which cache instance should evictkey– The specific cache key to invalidatepublisherId– Unique identifier of the originating node (prevents self-eviction loops)
CacheEvictedSubscriber Interface
Implementations of CacheEvictedSubscriber (such as DefaultCoherentCache) provide the onEvicted(event: CacheEvictedEvent) method to handle incoming invalidation requests.
Distributed Invalidation Flow
When a cache entry is modified, the CacheEvictedEventBus orchestrates consistency through the following sequence:
-
Local Mutation –
DefaultCoherentCache.setCache()orevict()updates the client-side and distributed cache layers, then callscacheEvictedEventBus.publish(). -
Event Propagation –
- Guava implementation:
eventBus.post(event)notifies in-process subscribers instantly - Redis implementation:
redisTemplate.convertAndSend(channel, message)publishes to a Redis Pub/Sub channel named after the cache
- Guava implementation:
-
Remote Reception – Each
CacheEvictedSubscriberreceives the event via:- Guava's
@Subscribeannotation adapter - Redis
MessageListenerAdapterthat deserializes the message back toCacheEvictedEvent
- Guava's
-
Local Eviction –
DefaultCoherentCache.onEvicted()validates the event by checking:- The
cacheNamematches the local cache - The
publisherIddiffers from the local node ID (avoiding self-eviction) - If valid, calls
clientSideCache.evict(key)to remove the stale entry
- The
Implementation Strategies
The repository provides two concrete implementations of the CacheEvictedEventBus interface to support different deployment topologies.
GuavaCacheEvictedEventBus
Located in cocache-core/src/main/kotlin/me/ahoo/cache/consistency/GuavaCacheEvictedEventBus.kt, this implementation wraps Google's EventBus for single-process or testing environments. It provides synchronous, in-memory event dispatch without external dependencies.
RedisCacheEvictedEventBus
Found in cocache-spring-redis/src/main/kotlin/me/ahoo/cache/spring/redis/RedisCacheEvictedEventBus.kt, this production-ready implementation leverages Redis Pub/Sub for cross-process communication. Each subscriber creates a MessageListenerAdapter that converts Redis messages back into CacheEvictedEvent objects, enabling cache coherence across distributed microservices.
Wiring Components Together
The DefaultCoherentCacheFactory in cocache-core/src/main/kotlin/me/ahoo/cache/consistency/DefaultCoherentCacheFactory.kt injects the chosen CacheEvictedEventBus implementation into DefaultCoherentCache instances during creation. This factory pattern ensures that every coherent cache automatically participates in the distributed invalidation protocol without manual wiring.
Practical Examples
Guava-Based Event Bus for Unit Testing
import me.ahoo.cache.consistency.*
// Create the in-process bus
val evictedBus = GuavaCacheEvictedEventBus()
// Register a subscriber
evictedBus.register(object : CacheEvictedSubscriber {
override fun onEvicted(event: CacheEvictedEvent) {
println("Received eviction: ${event.cacheName}:${event.key}")
}
})
// Publish an event from producer node "app-01"
evictedBus.publish(CacheEvictedEvent("userCache", "user:123", "app-01"))
Redis-Backed Production Configuration
import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.data.redis.listener.RedisMessageListenerContainer
import me.ahoo.cache.spring.redis.RedisCacheEvictedEventBus
@Bean
fun cacheEvictedEventBus(
redisTemplate: StringRedisTemplate,
listenerContainer: RedisMessageListenerContainer
): CacheEvictedEventBus {
return RedisCacheEvictedEventBus(redisTemplate, listenerContainer)
}
// Automatic invalidation occurs when:
// myCoherentCache.setCache("product:456", cacheValue)
// This triggers publish() behind the scenes
Factory-Based Creation
val bus: CacheEvictedEventBus = RedisCacheEvictedEventBus(redisTemplate, listenerContainer)
val factory = DefaultCoherentCacheFactory(bus)
val coherentCache = factory.create(
cacheName = "orderCache",
clientSideCache = caffeineCache,
distributedCache = redisCache,
cacheSource = orderCacheSource
)
Summary
- CacheEvictedEventBus provides the publish/subscribe contract for distributed invalidation through
publish(),register(), andunregister()methods - CacheEvictedEvent carries
cacheName,key, andpublisherIdto target specific entries while avoiding self-eviction loops - DefaultCoherentCache automatically publishes events on write operations and handles remote invalidations via
onEvicted() - GuavaCacheEvictedEventBus supports single-process deployments using Google's EventBus
- RedisCacheEvictedEventBus enables cross-JVM coherence using Redis Pub/Sub channels
- The DefaultCoherentCacheFactory wires the event bus into cache instances automatically
Frequently Asked Questions
How does CacheEvictedEventBus prevent a node from evicting its own cache entries?
Each CacheEvictedEvent includes a publisherId field containing the unique identifier of the originating node. When DefaultCoherentCache.onEvicted() receives an event, it compares the event's publisherId against the local node ID. If they match, the event is ignored, preventing self-eviction while still processing events from other nodes.
What is the difference between GuavaCacheEvictedEventBus and RedisCacheEvictedEventBus?
GuavaCacheEvictedEventBus uses Google's in-memory EventBus and only works within a single JVM process, making it suitable for testing or monolithic deployments. RedisCacheEvictedEventBus publishes events to Redis Pub/Sub channels, allowing multiple JVM processes or microservices to subscribe to invalidation events and maintain cache coherence across distributed systems.
Which source files define the core CacheEvictedEventBus contract?
The interface is defined in cocache-core/src/main/kotlin/me/ahoo/cache/consistency/CacheEvictedEventBus.kt. Supporting classes include CacheEvictedEvent.kt (payload), CacheEvictedSubscriber.kt (consumer interface), and DefaultCoherentCache.kt (publisher/subscriber implementation). Redis-specific implementation resides in cocache-spring-redis/src/main/kotlin/me/ahoo/cache/spring/redis/RedisCacheEvictedEventBus.kt.
How is CacheEvictedEventBus integrated with Spring Boot applications?
The CoCacheAutoConfiguration class in the Spring Boot starter automatically configures a RedisCacheEvictedEventBus when Spring Data Redis is detected on the classpath. The DefaultCoherentCacheFactory then injects this bean into all coherent cache instances, enabling distributed invalidation without explicit configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →