ASCENDING Technical Blog

How to integrate Redis with SpringBoot

Written by Liang | Jan 15, 2020 10:50:06 AM

 

Introduction

What is Redis?

Redis stands for REmote DIctionary Server. It is an in-memory key-value database server. In-memory means that redis using memory to store data instead of disks, which makes redis super fast for reading and writing data. Since redis is a key-value database server, it is a no-SQL server. It supports data structures such as strings, lists, sets, sorted sets, hashes, HyperLogLogs, maps, bitmaps, streams, and spatial indexes. For more information about redis, please visit official website.

Why Redis?

Two most important reasons:

  1. Fast (as it is in-memory)
  2. Not CPU intensive (so it can handle super huge data set fast)

Besides the above two reasons, there are some other reasons why we want to use redis, such as well documented, simple to use and scalable. Redis is most commonly used as a cache server. So the next question is what cache is?

What is cache?

A cache server is a dedicated network server or service acting as a server that saves Web pages or other Internet request/content locally. By placing previously requested information in temporary storage, or cache, a cache server both speeds up access to data and reduces demand on bandwidth and repeatable computing cost.

Why cache?

In a word, cache can increase system performance, such as:

  1. Speeding up access to data
  2. Saving computing power
  3. Reducing system hotspot (reducing response pressure) 
  4. Durability

Cache strategy

Based on how the data is read and write in the server, there are different cache strategies to choose:

  1. Cache-Aside: the cache server sits on the aside. Application will read data from cache first. If it misses, then application will go to database to fetch the data. In this strategy, if the cache server is down for some reason, it is not a big deal.
  2. Read-Through Cache: the cache server sits between the database and the application.  Application reads data in the cache first. If it misses, it reads data from database. In this strategy, if the cache server is down, there will be an error. See my example below.
  3. Write-Through Cache: the cache sits in-line with the database. Data is always written to the cache first and then to the database. See my example below.
  4. Write-Around: application writes data directly into the database and only saves the data that is read into the cache.
  5. Write-Back: application writes data to the cache first and after a delay it writes data to the database. It is good for write-heavy workloads.

This article gives more details about each strategy and when to choose it. I am not going to repeat here.

How to use Redis as a cache in SpringBoot applications?

1. Run a redis server from docker (what is docker? and how to install?)

For demo purpose, we run a redis server locally in docker with the following two commands:

 

 docker pull redis
 docker run -p 6379:6379 --name some-redis redis

When the redis server is build successfully, you can access the server by opening another terminal and typing the following command:

 

 docker exec -it some-redis /bin/bash

2. Some commonly used redis commands

Once we access the server sucessfull, we can use the following commands:

redis-cli keys * (check all current keys in redis database)
redis-cli FLUSHDB (clean redis database)
redis-cli info stats | grep 'keyspace_*' (check miss and hit, which will be explained later)

3. Setup redis integration in SpringBoot

Once the redis server is ready, we can integrate it with SpringBoot application. PostgreSQL database is used in this demo. Before you follow the following steps, you should make sure your SpringBoot can access your database through controllers.

3.1 Add dependency in pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.2 To make sure SpringBoot can find the redis server, in file “application.properties” (if you don’t have this file, create one under src/main/resources/) add the following content:
#redis config
spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379
3.3 In file “AppInitializer” (SpringBoot driver class) add @EnableCaching in front of the class name.
3.4 Add the following in front of the controller methods you want to use cache:

For a read-through cache, add:

@Cacheable(value = "departments")

For a write-through cache, add:

@CachePut(value = "departments", key = "#department.id", unless = “#department.name==null”)
3.5 Model/Mapping classes need to implement Serializable (object needs to be serializable first so the object can be transferred to cache), for example:
@Entity  
@Table(name="departments")  
public class Department implements Serializable {
//...
}

Or other wise you may get the following error:


Request processing failed;
nested exception is org.springframework.data.redis.serializer.SerializationException:Cannot deserialize;
nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?;
nested exception is java.io.InvalidClassException: com.ascending.training.model.Department;
class invalid for deserialization

 

A Read-Through Cache example

Step 1: To integrate a Read-Through Cache, we add @Cacheable(value = “departments”) in front of GET methods in the DepartmentController. As we metioned above, redis is a key-value server. Here “departments” in the bracket is a string for the value part.


@Cacheable(value = "departments")  
@GetMapping(value = "/{deptName}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})  
public Department getDepartmentByName(@PathVariable(name = "deptName") String deptName1){  
    return departmentService.getDepartmentByName(deptName1);  
}

Step 2: start a redis server in docker container, and run docker exec -it some-redis /bin/bash to get into the redis server. Run redis-cli keys * and find that there is no key in it. Run redis-cli info stats | grep ‘keyspace_*’ and get

keyspace_hits:0

keyspace_misses:0

Step 3: start SpringBoot and then in postman submit a GET request to get department by department name “AOES” as usual.

Step 4: run redis-cli info stats | grep ‘keyspace_*’ and get

keyspace_hits:0

keyspace_misses:1

because redis server is empty so it misses the first request from postman. Run redis-cli keys * and this time we find that “value::key” pair shown as

1) "departments::AOES"

where “departments” is the value we defined in the controller and “AOES” is the department name we requested. If you send the same request again by postman and run redis-cli info stats | grep ‘keyspace_*’, you will get

keyspace_hits:1

keyspace_misses:1

because after the first request redis has stored the data in the database and for the second request, postman gets the data from the redis server successfully (1 hit). If you check the server end output from the IntelliJ IDE, you will find no Hibernate output, which proves that the second request gets data from cache instead of database.

Also note that the second time when we read data from redis is way faster than the first time we read data from database. Using an App’s health diagnose tool Spring Boot Actuator, I compared the time difference. It takes about 457 ms reading data from database, while only around 100 ms from redis server.

A Write-Through Cache example

Step 1: Add @CachePut(value = “departments”, key = “#department.id”, unless = “#department.name==null”) in front of POST methods for a Write-Through Cache. For the key parameter, we use “#department.id”, where “department” is our Java object name, “#” sign means “department” is not a string but a variable.  “unless” will exclude some conditions. In this case, cache will not store the record when the department name is null.


@CachePut(value = "departments", key = "#department.id", unless = "#department.name == null")
@PostMapping(value = "", consumes = {MediaType.APPLICATION_JSON_VALUE})
public String creatDepartment(@RequestBody Department department){
  boolean isSuccess = departmentService.save(department);
  return isSuccess?"Success":"Fail";
}

Step 2: Similar to the read-through cache example, at the beginning, there is no keys stored in the server and 0 misses/0 hits.

Step 3: start SpringBoot and then in postman submit a POST request to create a new department { “id”:5, “name”: “MS”,”location”:”Exploring Hall”,”description”:”Mathematical Sciences”}.

Step 4: run redis-cli info stats | grep ‘keyspace_*’ and still get

keyspace_hits:0

keyspace_misses:0

However if we run redis-cli keys * and this time we find that “value::key” pair shown as

1) "departments::5"

where “departments” is the value we defined in the controller and “5” is the id of the department we just created. The write-through cache worked!

 

Acknowledgments:

The author would like to thank Ryo for his awesome redis lecture, and also thank him for reviewing and editing an early version of this article and for many constrictive suggestions.

His github: https://github.com/ascending-llc/spring-redis

 

Have fun with redis!

 

Github: https://github.com/lnsdlszsqxxx/runtime-ii