I have a Spring Boot/MVC app that should store some Simple POJOs sent from users for 15 minutes of time. When this time expires, this object should be removed from ConcurrentHashMap. At first ConcurrentHashMap was something I wanted to implement this feature with, but then I thought maybe leveraging Guava's cache would be a better option, since it has a time-based eviction out of the box.
My service implementation
#CachePut(cacheNames = "teamConfigs", key = "#authAccessDto.accessToken")
#Override
public OAuthAccessDto saveConfig(OAuthAccessDto authAccessDto) {
return authAccessDto;
}
#Override
#Cacheable(cacheNames = "teamConfigs")
public OAuthAccessDto getConfig(String accessToken) {
// we assume that the cache is already populated
return null;
}
As you can see, we save data with saveConfig and then when we need to retrieve it, we call getConfig.
Cache configuration in Spring boot is the following (yml file):
spring:
cache:
cache-names: teamConfigs
guava:
spec: expireAfterWrites=900s
However, after reading Guava's cache doc https://github.com/google/guava/wiki/CachesExplained I found that Guava can clean up caches even before the time defined in expireAfterWrites elapses (and even before it runs out of memory).
How can I configure Guava Cache to keep objects until the time expires (considering it did not run out of memory). Maybe I should opt for some other solution?
I don't know about Guava but you could use any JSR-107 compliant provider and a simple configuration that would look like this:
#Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
return cm -> {
cm.createCache("teamConfigs", new MutableConfiguration<>()
.setExpiryPolicyFactory(CreatedExpiryPolicy
.factoryOf(new Duration(MINUTES, 15)));
};
}
Caffeine (a rewrite of Guava with Java8) has a JSR-107 provider so you could use that. Maybe that version does not exhibit what you experience with Guava? If so, support is expected in Spring Boot 1.4 but you can give the JSR-107 support a try right now.
If you don't want to use that, you could give expiringmap a try. It does not have any Cache implementation in the Spring abstraction but since it's a Map you can easily wrap it, something like on the top of my head:
#Bean
public Cache teamConfigsCache() {
Map<Object, Object> map = ExpiringMap.builder()
.expiration(15, TimeUnit.MINUTES)
.build();
return new ConcurrentMapCache("teamConfigs", map , true);
}
If Spring Boot discovers at least a Cache bean in your configuration, it auto-creates a CacheManager implementation that wraps them. You can force that behaviour via the spring.cache.type property.
Related
I'm using Spring boot with Ehcache for caching some data in the application.
The application is a rest service that caches some data that has high usage.
The code in our controllers looks like:
#Cacheable("CategoryModels")
#GetMapping("/category/{companyId}")
public List<CategoryViewModel> getAllCategories(#PathVariable(value = "companyId", required = true) long companyId,
#RequestHeader("user") String user) {
//custom code here
}
Now in some situations the users are getting different data sets back from the server. Can someone explain this in the above situation?
If data is changed in the database I refresh the cache and the program will auto update the updated data to the
For refreshing the cache I use a custom written method:
Cache categoryCache = (Cache) manager.getCache("CategoryModels").getNativeCache();
categoryCache.removeAll();
categoryController.getAllCategories(company.getCompanyId(), null);
I have the same behavior on other caches that are used and refreshed on the same way the above cache is used.
You should try to parametrize your cache definition with :
#Cacheable(value="CategoryModels", key="{ #root.methodName, #companyId, #user.id }")
It may be a couple of things. First off the default key resolver that spring provides does not consider anything but the names of the parameters. The cleanest way to fix this kid to write your own key revolver that considers both class and method, without this it could be possible to get back data from a completely different method that happens to share the same parameter list.
I’m using the spring-boot spring-data-redis 1.8.9.RELEASE RedisCacheManager implementation of CacheManager for caching. One metric that I want visibility into is the cache hit/miss ratio. To get that, I’m extracting the keyspace_hits and keyspace_misses exposed via the redis server which can also be viewed via the redis_cli with INFO STATS. The problem is that RedisCacheManager never registers cache misses, i.e. keyspace_misses never increments even if there is a cache "miss".
Debugging the code, I see that spring-data-redis actually checks to see if the key EXISTS in redis before retrieving it. I see the sense with this approach however when EXISTS is executed against the redis server, it does not register a cache miss.
Is there any way to use RedisCacheManager and register cache misses? I know I can use other redis objects to accomplish this but I was wondering if it could be done with the standard CacheManager implementation?
Edit
The ideal solution won't add a great deal of overhead and I am unable to edit the configuration of the redis server.
Code that RedisCacheManager uses when retrieving an element from cache. Notice Boolean exists:
public RedisCacheElement get(final RedisCacheKey cacheKey) {
Assert.notNull(cacheKey, "CacheKey must not be null!");
Boolean exists = (Boolean)this.redisOperations.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.exists(cacheKey.getKeyBytes());
}
});
return !exists ? null : new RedisCacheElement(cacheKey, this.fromStoreValue(this.lookup(cacheKey)));
}
The above code will execute these commands on redis viewable via MONITOR on a cache miss. Notice again that EXISTS is executed as per the code:
After the above commands are executed, keyspace_misses is not incremented even though there was a cache miss:
The code mentioned in the question is part of RedisCache provided by Spring.
Extend and Create a custom implementation of RedisCache class to override the behavior of "get" method to suit your need.
Extend RedisCacheManager to override the method "createRedisCache" to use your custom RedisCache that you created in first step instead of default cache.
What's the recommended way to store pieces of data and let it expire automatically from within a Java EE/WebApp container? I could use the session persistence mechanism, but my http sessions are usually A LOT longer than I want those pieces of information to be retained.
Is there something provided by Java EE 7 or by CDI? Some preliminary variant of the JCache spec JSR 107? Any other good solution?
I'm not sure it is the "best" way but I've been using the Google Guava cache in Wildfly (8 through 10 but hopefully still applicable). For me I'm caching Oauth tokens because of a very slow auth server. My code looks something like:
private static LoadingCache<String, MyPrincipal> tokenCacheMap;
#PostConstruct
private void postConstruct() {
tokenCacheMap = CacheBuilder.newBuilder()
.expireAfterAccess(15, TimeUnit.MINUTES)
.build(
new CacheLoader<String, MyUserPrincipal>() {
#Override
public MyUserPrincipal load(String token) {
MyUserPrincipal myUserPrincipal = getUserFromToken(token);
if( myUserPrincipal != null ) {
myUserPrincipal.setToken(token);
return myUserPrincipal;
}
throw new SecurityException("token is not valid");
}
}
);
}
//
// later in the code...
//
MyUserPrincipal myUserPrincipal = tokenCacheMap.get(token);
Basically what this does is set up a cache where the tokens live for 15 minutes. If needed, the load() method is called to, in this case, get an auth token and user. The cache is lazily filled as needed - the first call will have the overhead of getting the token but after that it's all in memory.
There are other options to, for example, evict old information based on the number of items in the cache. The documentation is very good and should get you going.
The downside is that this is not a JEE standard but it has worked for me in the past.
You can use infinispan-jcache if you want to use the JCache API. Infinispan is a scalable, highly available key/value data store included in WildFly.
To use it, add infinispan-jcache to pom.xml:
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-jcache</artifactId>
<version>...</version> <!-- e.g. 9.1.4.Final -->
</dependency>
and access the cache as follows:
import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.configuration.CompleteConfiguration;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.Duration;
import javax.cache.spi.CachingProvider;
import java.util.concurrent.TimeUnit;
...
// Construct a simple local cache manager with default configuration
// and default expiry time of 5 minutes.
CacheManager cacheManager = Caching.getCachingProvider().getCacheManager();
CompleteConfiguration<String, String> configuration = new
MutableConfiguration<String, String>()
.setTypes(String.class, String.class)
.setExpiryPolicyFactory(factoryOf(new CreatedExpiryPolicy(
new Duration(TimeUnit.MINUTES, 5))));
// Create a cache using the supplied configuration.
Cache<String, String> cache = cacheManager.createCache("myCache", configuration);
// Store a value, the entry will expire in 2 seconds.
cache.put("key", "value", 2, TimeUnit.SECONDS);
// Retrieve the value and print it out.
System.out.printf("key = %s\n", cache.get("key"));
// Stop the cache manager and release all resources,
// use try-with-resources in real code.
cacheManager.close();
Note that Infinispan has great docs.
I have a Java web service that uses Redis for caching. Initially I created a CacheService that directly accessed the Redisson client in order to handle caching. I recently refactored the cache handling to use the Spring Cache abstraction, which made the code a lot cleaner and encouraged modular design. Unfortunately Spring uses Jackson to serialize/deserialize the cached objects, resulting in the cached values being much larger than before due to type info being stored in the JSON. This caused an unacceptable increase in response time in reads from the cache. Is there any way to customize the way that Spring serializes and deserializes the cached content? I'd like to replace it with my own logic, but don't see anything in the docs. I'd rather not have to roll my own AspectJ cache implementation if possible.
The RedisCacheManager takes a RedisOperations and you can configure there how serialization works. You can tune serialization for keys and values though I suspect key should use StringRedisSerializer.
Redisson also provides Spring Cache integration. It supports many popular codecs: Jackson JSON, Avro, Smile, CBOR, MsgPack, Kryo, FST, LZ4, Snappy and JDK Serialization.
Here is an example:
#Bean
CacheManager cacheManager(RedissonClient redissonClient) {
Codec codec = new JsonJacksonCodec();
Map<String, CacheConfig> config = new HashMap<String, CacheConfig>();
config.put("testMap", new CacheConfig(24*60*1000, 12*60*1000));
return new RedissonSpringCacheManager(redissonClient, config, codec);
}
I'm working to develop a multi-tenant Play Framework 2.1 application. I intend to override the onRequest method of the GlobalSettings class to load and set a custom configuration based on the subdomain of the request. Problem is, I don't see how this would be possible in Play 2.x.
I can override system properties at the command line when starting the server, but how can I do this programmatically in Java code for each request?
The code would look something like this (I assume):
#Override
public play.mvc.Action onRequest(Request request, Method actionMethod) {
//Look up configuration settings in Cache based on request subdomain
//(i.e. Cache.get("subdomain.conf"))
//if not in cache:
//load appropriate configuration file for this subdomain (java.io.File)
//set new configuration from file for this request
//cache the configuration for future use in a new thread
//else
//set configuration from cache for this request
return super.onRequest(request, actionMethod);
}
}
Looking up the URL and getting/setting the cache is easy, but I cannot figure out how to SET a new configuration programmatically for Play Framework 2.1 and the documentation is a little light on things like this.
Any thoughts? Anyone know a better, more efficient way to do this?
So, in a sort of roundabout way, I created the basis for a multi-tenant Play application using a Scala Global. There may be a more efficient way to implement this using a filter, but I'm finding this seems to work so far. This does not appear to be as easily implemented in Java.
Instead of using the configuration file, I'm using the database. I assume it would be far more efficient to use a key-value cache, but this seems to work for now.
In Global.scala:
object Global extends GlobalSettings {
override def onRouteRequest(request: RequestHeader): Option[Handler] = {
if (request.session.get("site").isEmpty){
val id = models.Site.getSiteIDFromURL(request.host)
request.session.+("site" -> id)
}
super.onRouteRequest(request)
}
}
And then, obviously, you have to create a database model to query the site based on the request domain and/or the session value set in the request. If anyone knows a better way I'd love to hear it.