Wednesday, March 1, 2023

Spring Session with Redis Example

In this article, we will talk about Spring session with Redis. We will demonstrate the difference between maintaining a session with HttpSession versus a session with Redis. Why would we want our session to be backed with Redis? Read on and we'll find out why. By the way, Spring session can also persist data using JDBC, Gemfire, or MongoDB.

Overview

Offloading session management from the servlet container (e.g. Tomcat's HTTP session) because of its limitations and eating up of server memory are some reasons we want to persist session data somewhere else. The most likely reason you'ld want to use Spring session with Redis is when you are scaling up. For example, you have multiple instances of a microservice running behind a load balancer and you'ld want to keep track of session state. Why have multiple instances? Because you want zero downtime of your service. With Redis, our session data can be shared between multiple microservices. By persisting sessions, multiple application instances can serve the same session and individual instances can crash without impacting other application instances.

Redis

I will not be providing instructions on how to set up Redis on your local machine. For that, I'll point you to Redis and make your way to the Get Started section. I'm using Windows 10 so I followed the Install on Windows instructions. I also had to set up my Windows Subsystem for Linux and got Ubuntu running on my local machine. In the end and by default, your Redis server should be running on port 6379 and you should be able to ping it. You should have something like below:

Spring Session with Redis
Redis Running

Spring Session with Redis Project

I will not provide instructions on how to create this project as well. My focus will be demostrating HttpSession versus Spring session with Redis. Anyway, if you want to create the project, you can follow the steps from my previous blogs (e.g. Spring Boot MockMvc Example). Here is what your Spring Initialzr would look like:

Spring Session with Redis
Spring Initializr

Here is the finished project, clone it from github.com/jpllosa/spring-session-redis.

@RestController


package com.blogspot.jpllosa.controller;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestBody;

import javax.servlet.http.HttpSession;

import com.blogspot.jpllosa.model.UserData;

@RestController
public class HttpSessionController {

	@PostMapping("/save-to-http-session")
	public HttpEntity saveToHttpSession(@RequestBody UserData userData, HttpSession httpSession) {
		httpSession.setAttribute("userData", userData);
		return new ResponseEntity<>("", HttpStatus.ACCEPTED);
	}
	
	@GetMapping("/get-from-http-session")
	public HttpEntity getFromHttpSession(HttpSession httpSession) {
		return new ResponseEntity<>((UserData) httpSession.getAttribute("userData"), HttpStatus.OK);
	}
}

This is where all the action happens. Our microservice is pretty simple really. It accepts user data via an HTTP POST then we can send it back via an HTTP GET if it was saved. The response to a successful POST is a 202, meaning it was accepted.

Background

In our demonstration, we will run two instances of our microservice. MS 80, microservice on port 8080 and MS 81, microservice on port 8081. We'll pretend both are behind a load balancer. A network load balancer will decide where in coming requests go to which microservice. But we won't actually have a load balancer.

Tomcat HTTP Session

We'll need to comment out a few bits in the configuration. And just to be sure, we will stop the Redis server. To stop the server execute sudo service redis-server stop then you should see this message "Stopping redis-server: redis-server.". Comment out spring.session.store-type=redis in your application.properties and comment out the two Redis dependencies in you POM, like so:


<!--
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.session</groupId>
	<artifactId>spring-session-data-redis</artifactId>
</dependency>
-->

Open two command prompts/terminals and run MS 80 on one and MS 81 on the other. So that's mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8080 for MS 80 and mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8081 for MS 81. You should see something like 2023-02-23 16:27:13.547 INFO 25148 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http). Of course 8080 will show for MS 80. Now, let's POST some user data. Let's send a POST via Postman to MS 80 like so:

Spring Session with Redis
HTTP POST to MS 80

Now, let's check if our user data has persisted on both microservices. Let's do a GET on MS 80 and 81, like so:

Spring Session with Redis
HTTP GET from MS 80
Spring Session with Redis
HTTP GET from MS 81

As you can see, only MS 80 returned the user data. Now, do you see the problem if we had to maintain session state between multiple instances? Only one MS maintained the state. Imagine if we were going to shutdown or restart MS 80 to update it or something, the session data will be lost. What if the session data was banking transaction? Oh dear!

Spring Session with Redis

Now the fun part. Start the Redis server, sudo service redis-server start. Then undo the comments you made on your POM and application.properties. This should make your app Redis ready. With all that done, restart MS 80 and MS 81. So that's Ctrl+C then execute the maven command again as mentioned above. Let's do the POST again on MS 80 and do a GET on MS 80 and 81. Now the GET response will show the user data like so:

Spring Session with Redis
HTTP POST to MS 80 with Redis
Spring Session with Redis
HTTP GET from MS 80 with Redis
Spring Session with Redis
HTTP GET from MS 81 with Redis

There you have it. MS 80 and MS 81 are sending back the user data information. The magic that caused it all is spring.session.store-type=redis in your application.properties. Thanks to Spring Boot, a lot of steps have been done for us with just a single line in the application properties file. What did Spring Boot do for us? It applied a configuration that is equivalent to adding an @EnableRedisHttpSession to a class. This created a Spring bean with the name of springSessionRepositoryFilter that implements Filter. The filter is in charge of replacing the HttpSession implementation to be backed by Spring Session. Then Spring Boot automatically created a RedisConnectionFactory that connects Spring Session to a Redis Server on localhost on port 6379 (default port).

In a production environment, I would recommend to add more configuration and/or create a configuration class annotated with @EnableRedisHttpSession for more fine grained control over a session. Here are some of the configurations you can add:

  1. server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds is used.
  2. spring.session.redis.flush-mode=on_save # Sessions flush mode.
  3. spring.session.redis.namespace=spring:session # Namespace for keys used to store sessions.
  4. spring.redis.host=localhost # Redis server host.
  5. spring.redis.password= # Login password of the redis server.
  6. spring.redis.port=6379 # Redis server port.

There you have it. Hope you had much fun playing with Spring Session with Redis, I know I did.

No comments:

Post a Comment