I am trying to add a data model to a redis cache. When I take the model out I am getting the exception
org.springframework.core.convert.ConversionFailedException: Failed to convert from type [byte[]] to type [java.util.concurrent.atomic.AtomicInteger] for value '{53}'; nested exception is java.lang.IllegalArgumentException: Cannot convert String [5] to target class [java.util.concurrent.atomic.AtomicInteger]] with root cause
The model is
@RedisHash(value = "ThottleRate", timeToLive = 5)
public class ThottleRate implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private String url;
private AtomicInteger rate;
public ThottleRate(String url, int rate) {
super();
this.url = url;
this.rate = new AtomicInteger(rate);
}
public boolean isAllowed(){
if(this.rate.decrementAndGet() <= 0) {
return false;
}
return true;
}
The calling code is
try {
option = throttleRateRepository.findById(url);
ThottleRate rate = option.get();
allow = rate.isAllowed();
} catch(NoSuchElementException e) {
throttleRateRepository.save(new ThottleRate(url, 5));
allow = true;
}
What I am trying to do is throttling functionality, using redis cache. It has a time to live, and within that a number of times a url can be accessed.
But when the call is
option = throttleRateRepository.findById(url);
This throws the IllegalArgumentException
This seems the most simple way to use redis cache with a time to live and a number of rates
CodePudding user response:
The exception is thrown because Spring Data Redis does not know how to deserialize array of bytes which is stored in Redis to AtomicInteger
type when loading the model. You need to register a data type converter for this.
Here's sample data type converter:
@Component
@ReadingConverter
public class BytesToAtomicIntegerConverter implements Converter<byte[], AtomicInteger> {
@Override
public AtomicInteger convert(byte[] source) {
if (ObjectUtils.isEmpty(source)) {
return null;
}
int n = NumberUtils.parseNumber(
new String(source, StandardCharsets.UTF_8), Integer.class);
return new AtomicInteger(n);
}
}
It can be registered with with Spring Data Redis by adding this bean to SpringBoot configuration:
@Bean
public RedisCustomConversions redisCustomConversions(
BytesToAtomicIntegerConverter converter) {
return new RedisCustomConversions(List.of(converter));
}
Once the data converter is registered the error will go away.
When implementing such solution you have to take few things in mind though. The AtomicInteger
field is used here to keep count of requests for specific person. If there are more than one server that serve requests then different servers will end up having different value stored in this field in local memory. Since communication with Redis takes time (even if only in milliseconds range) there is far from zero chance the value will be updated by another server. This might not be an issue if such deviations are ok for the use case.
I also recommend to look at some existing libraries that could help to implement request throttling. Here're some examples: