Prior to this commit, the current CaffeineCacheManager
supports setting the cache builder through setCaffeine
, but only supports the synchronous cacheLoader
(which cannot be set by Caffeine builder).
There is a useful feature in Caffeine: refreshAfterWrite
which allows us to get the latest value of the cache as early as possible in the background (note that this itself is an asynchronous operation), instead of synchronously getting data from the data source when it expires.
The current cacheLoader
can also be used to refresh the cache, but due to its synchronous blocking call characteristics, this will reduce the cache refresh rate, resulting in a lot of delay in the actual refresh time of many cache items. By using asyncCacheLoader
, the blocking of the refresh thread can be avoided, thereby speeding up the cache refresh rate (how to implement efficient asynchronous calls is guaranteed by the asyncCacheLoader
provider).
Because of the use of com.github.benmanes.caffeine.cache.AsyncLoadingCache#synchronous
, it will create a new view for the cache, which will change the action of fetching the cache from asynchronous to synchronous, so this will not break the existing APIs
Comment From: snicoll
@jizhuozhi can you please share your use case? Are you using @Cacheable
or are you using the cache manager programmatically?
The reason for asking is that having a cache loader isn't really suited for the cache abstraction that is mostly synchronous. And the loader is meant to be the annotated method. I am worried that introducing such change would make the API more confusing for the mainstream use case.
Comment From: jizhuozhi
Hello, @snicoll . We use it programmatically.
In fact, what we need is the ability to refresh. The use case here is that we want the service discovery to be based on refresh rather than sending requests after expiration, which may be due to various A failure of causes no instances are available. It's provided by Caffeine.
The need for asyncCacheLoader is because the service discovery client we used supports asynchronous requests based on the netty event loop, which means that the event source of CompletableFuture is not the thread pool of Caffeine, but netty. That is, a fixed small number of threads can drive a large number of Refresh request. And if I don't use this kind of event-driven asynchronous request, I need to allocate a thread for each refresh task, and the thread pool will be exhausted soon, and the request will be queued, resulting in reduced throughput
Comment From: ben-manes
fwiw, if all you need is async refresh then no changes are required. All refreshes are asynchronous and you can override CacheLoader.asyncReload
. However, if you need the load to be asynchronous then you would need an AsyncCache
type. Note that CacheLoader
extends AsyncCacheLoader
to allow for a convenient synchronous style that is wrapped as async when invoked by AsyncLoadingCache
, so the Caffeine.buildAsync
accepts both loader types.
Comment From: jizhuozhi
Hello, @ben-manes . Thanks for your reply.
I understand what you mean, that is to say, in fact, Caffeine will wrap load
by default method asyncLoad
, and finally construct it in the way of AsyncCacheLoader
, and then load it through AsyncCache
when loading, so-called synchronous methods Just a wrapper for asynchronous methods?
If so, indeed, I can accomplish my purpose without changing the CacheManager.
Thank you very much for your answer.
Comment From: snicoll
We use it programmatically.
Thanks for the feedback. Are you supporting another cache library or is it using only Caffeine? For such a specific use case of the underlying cache library, I would strongly advice to use their API directly. I am afraid I am not keen to introduce a setting in the cache abstraction that would lead to unexpected result with the annotation model.
Comment From: simonbasle
Yeah it looks like trying to modify the cache abstraction here doesn't really make sense after all @jizhuozhi. Fine-tuned use cases like this one which build upon the specific features of cache providers like Caffeine are best addressed programmatically indeed.
So Caffeine should be used directly here, keeping in mind what @ben-manes said (thanks for chiming in, Ben).
You could still possibly simply use Spring to inject an AsyncCache
(or its #synchronous()
view) in your components, of course.
Comment From: ben-manes
When using a LoadingCache
it will load the entry by calling CacheLoader.load
, CacheLoader.loadAll
, and AsyncCacheLoader.asyncReload
.
If using an AsyncLoadingCache
, it will load the entry by calling AsyncCacheLoader.asyncLoad
, AsyncCacheLoader.asyncLoadAll
, and AsyncCacheLoader.asyncReload
.
CacheLoader
extends AsyncCacheLoader
, with default methods to delegate and wrap. This allows for constructing an AsyncLoadingCache
with loading code written in a synchronous style. It offers reload
which delegates to load
, and the default asyncReload
wraps reload
with a future.
The layering makes it flexible but it is a little confusing about what is available and when, but the abstract
methods help guide the user when implementing. In your case of wanting a LoadingCache
to use a blocking load and supply an async reload, then that can be done without code changes. If you need an async load in all cases, then an AsyncLoadingCache
is required like in your changes.
A lot of these advanced caching features are difficult to adapt and use from using an annotation-based abstraction, so Spring's tendency is to use the vendor's APIs directly in those cases instead of complicating their abstraction. In your case this does sound complicated enough that using the Caffeine's apis will be less confusing when maintaining your code.
Hope that helps. If you have any questions or run into any problems please feel welcome to ask.