Implementation of Redis in a Microservice/Spring Boot Application
Learn to implement Redis in your Spring Boot microservice application for cache management, to reduce client workload and speed up your application.
Join the DZone community and get the full member experience.
Join For FreeWhat Is Redis?
Redis is an open-source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker. It supports data structures such as string, hashes, lists, sets, sorted sets with range queries, bitmaps, hyper logs, and geospatial indexes with radius queries.
Redis is written in ANSI C and works in most POSIX systems like Linux, BSD, and OS X without external dependencies. Linux and OS X are the two operating systems where Redis is developed and more tested. Redis may work in Solaris-derived systems like SmartOS. There is no official support for Windows builds, but Microsoft develops and maintains a Win-64 port of Redis.
Why Redis?
Redis is basically used for cache management. It reduces the client workload and speeds up the application.
Let's discuss a scenario where Redis could be helpful.
1) Calling an External Application API
Your microservice is calling other application’s API in order to get some data. The call is made often, the data in response is huge, and you know the response data is not going to change often in the other application. In our case, we are calling one API and we get the response in 10000 to 15000 ms. It is not good for any application to wait for such a long time to get the response. In that case, Redis is helpful.
We can cache the response in Redis with an expiration time so instead of the actual call, you will get the response from the Redis cache. In our case, we had set the expiration time of the data to 1 day. When the data expires, the call goes to the actual web service and refreshes the data in the cache. The next time, the data will be returned from the cache.
Here, we reduce the load time of the data by 90% and we were getting the same data in between 7000 to 1000 milliseconds.
2) Frequently Querying the Reference Table or Master Table in the Database
Another scenario is when you have a table in your database, and that table contains reference data that is not going to change frequently. You query that table often to get the data and populate the UI. In this scenario, you can also use Redis. Instead of querying the database each time from the disk, you can cache the table in Redis with an expiration time so the load time of your UI will be faster.
There are other caching mechanisms available, but we are using Redis because it provides more data structure and algorithms for storing and retrieving data. It also provides a clustering mechanism. You can set in-memory up to 2GB in Redis.
Installation Guide for Redis
You can go through this manual for installation on Windows or Linux.
There is also a user interface available for Redis called Redis Desktop Manager. You can download RDM to manage the in-memory data.
In Windows, you just have to download the zip and extract it into any location. Open redis-server.exe from the extracted folder and the Redis server will be started.
After that, you can open redis-cli.exe from the same folder. redis-cli is the Redis command line interface, a simple program that allows you to send commands to Redis and read the replies sent by the server directly from the terminal.
Now, we will look into the actual implementation of Redis in microservices.
We have used:
Spring Data Redis, which provides the abstractions of the Spring Data platform to Redis
Spring Boot version 2.0.2
You can get the required dependencies for Spring Data Redis and Jedis Client here.
Redis Configuration
Implement the Redis Configuration class in your microservice that is going to establish the connection with Redis.
RedisStandaloneConfiguration: This class used for setting up the connection for single node Redis installation.
JedisClientConfiguration: This class provides an optional feature for SSLSocketFactory and JedisPoolConfig specific to Jedis client features.
JedisConnectionFactory: This is the connection factory for the Jedis base connection. It uses the JedisClientConfiguration and RedisStandaloneConfiguration.
RedisTemplate<K,V>: This helper simplifies data access for Redis. It uses automatic serialization and deserialization for a given object and underlying binary data in the Redis store.
This class is thread-safe:
@Configuration
public class RedisConfiguration {
@Value("${spring.redis.host}")
private String REDIS_HOSTNAME;
@Value("${spring.redis.port}")
private int REDIS_PORT;
@Bean
protected JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(REDIS_HOSTNAME, REDIS_PORT);
JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder().usePooling().build();
JedisConnectionFactory factory = new JedisConnectionFactory(configuration,jedisClientConfiguration);
factory.afterPropertiesSet();
return factory;
}
@Bean
public RedisTemplate<String,Object> redisTemplate() {
final RedisTemplate<String,Object> redisTemplate = new RedisTemplate<String,Object>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new GenericToStringSerializer<Object>(Object.class));
redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
}
Connection details in the property file:
spring.redis.port=6379
spring.redis.password=password
spring.redis.host=localhost
Now that the connection is established, we have to perform an operation with the Redis database, so we have created one generic util class that is going to perform the operation on the Redis database for the different data structure.
RedisUtil
This class is generic because you might have a requirement to cache the table or API response. As it is a generic class, you have to just create an instance of this class with the required type you want to cache. It is good practice to have only a single class performing the operation on DB, as we are following in the microservice.
In that class, we have implemented multiple data structures, as per the requirement required method can be used for storing and retrieving data in the cache.
RedisTemplate provides many methods in order to perform an operation for multiple data structures.
We have used some in the below classes:
opsForHash(): Return the HashOperations<K,HK,HV> class. This class use for hash operations on Redis.
opsForList(): Return the ListOperations<K,V> used for list operations.
opsForValue(): Return the ValueOperations<K,V> Perform simple key value operations; each key will have an entry in Redis for its associated value
@Configuration
public class RedisUtil<T> {
private RedisTemplate<String,T> redisTemplate;
private HashOperations<String,Object,T> hashOperation;
private ListOperations<String,T> listOperation;
private ValueOperations<String,T> valueOperations;
@Autowired
RedisUtil(RedisTemplate<String,T> redisTemplate){
this.redisTemplate = redisTemplate;
this.hashOperation = redisTemplate.opsForHash();
this.listOperation = redisTemplate.opsForList();
this.valueOperations = redisTemplate.opsForValue();
}
public void putMap(String redisKey,Object key,T data) {
hashOperation.put(redisKey, key, data);
}
public T getMapAsSingleEntry(String redisKey,Object key) {
return hashOperation.get(redisKey,key);
}
public Map<Object, T> getMapAsAll(String redisKey) {
return hashOperation.entries(redisKey);
}
public void putValue(String key,T value) {
valueOperations.set(key, value);
}
public void putValueWithExpireTime(String key,T value,long timeout,TimeUnit unit) {
valueOperations.set(key, value, timeout, unit);
}
public T getValue(String key) {
return valueOperations.get(key);
}
public void setExpire(String key,long timeout,TimeUnit unit) {
redisTemplate.expire(key, timeout, unit);
}
}
Cache Manager
We have implemented this in such way that our code should be loosely coupled from the service layer of microservice. For that, we have created one Manager interface and its implementation class so all the Redis-related code will be independent of the service layer of your microservice.
We will use the implementation of the Manager interface in the service layer to cache the data. The Manager will act as a bridge between your service layer and Redis code. If something is going to change in Redis, it should not impact our service layer or any requirements or make upgrades so it can be easily adaptable.
public interface StudentCacheManager {
void cacheStudnetDetails(StudentsBean students);
}
@Configuration
public class StudentCacheManagerImpl implements StudentCacheManager {
public static final String TABLE_STUDENT = "TABLE_STUDENT";
public static final String STUDENT = "STUDENT_";
private RedisUtil<StudentsBean> redisUtilStudent;
@Autowired
public StudentCacheManagerImpl(RedisUtil<StudentsBean> redisUtilStudent) {
this.redisUtilStudent = redisUtilStudent;
}
@Override
public void cacheStudnetDetails(StudentsBean students){
redisUtilStudent.putMap(TABLE_STUDENT,STUDENT_+students.getId(),students);
redisUtilStudent.setExpire(TABLE_STUDENT,1,TimeUnit.DAYS);
}
}
Service Layer
You can use the cache manager class in any service layer of the microservice.
@Configuration
class StudentServiceImpl imlements StudentService{
private StudentCacheManager studentCacheManager;
@Autowired
public StudentServiceImpl(StudentCacheManager redisCacheManager) {
this.studentCacheManager = studentCacheManager;
}
@Override
public void cacheStudentsDetails(boolean checkFlag) throws Exception {
if(studentCacheManager.checkEmpty()) {// If cache is empty the put the data
List<StudentsBean> students= studentDAO.getStudentList();
students.forEach(stud->studentCacheManager.cacheStudentDetails(stud));
}
}
}
There are guidelines we have used for Redis Key, as it provides multiple data structures, but all data structures are associated with the key in the Redis database. If you want to cache 50 tables and retrieve the data from the cache, you should know the key for retrieving the cached data.
Suppose you want to store a record of the database table in a key-value(Map) pair in Redis. The key will be like this:
Redis Key = Table+"_"Table Name
Map key = Table Name + "_"+ value you want to store
You can refer to the implementation of the cache manager class for a guideline.
In summary, this post will help you to implement the cache in microservice with abstraction layer of cache. This will help your microservice to avoid tight coupling with cache implementation.
Happy learning. Any suggestions are appreciated!
Published at DZone with permission of Belal Khan. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments