Scaling out with Spring Session

Stateless architecture has become increasingly popular during resent years and for good reasons. However, stateful session based applications continue to play an important role, for example when issuing CSRF tokens for improved security. When deployed on a single server with little load, session management is pretty straight forward as long as you use a reasonable expiration timeout and do not store to much data in the session. Things become trickier when scaling out because each request needs to be associated with its corresponding session that may reside on another server. To overcome this, server vendors have implemented various kinds of session replication between their servers. Alternatively, load balancers can be configured to use sticky sessions. Both these solutions work, but with Spring Session Spring has created another option. This blog will show how you can use Redis together with Spring Session to scale out sessions. The suggested solution can be used with any servlet (not just Spring based) which also makes it suitable if you need to scale out legacy web apps.

Example Application

First, we take a look at a simple, session based, HelloServlet:

When called with a HTTP GET, the servlet will respond with either Hello Mattias! if there is a session that has an attribute called name with the value Mattias. If not, the servlet will respond with a default Hello World!.

When called with a HTTP POST, the servlet will read the name parameter from the request, create a new session (or re-use an existing) and store the value of the name in the corresponding session attribute.

First request, no session, i.e. GET the default answer:

Second request, create a session by POSTing a request with a name attribute:

Side note, the -i (or —-include) is a cURL flag for including HTTP headers in the response and the -d (or —data) flag is used to submit data as request parameters. In the response, we take note of the value of the Set-Cookie header.

Third request, GET and validate the session state:

The problem we are facing is that when we perform the same request on another instance of the application, the default response is returned despite that the session is provided:

Enter Spring Session!

Spring Session architecture

The idea behind Spring Session is pretty straight forward:

  • Create a new Servlet filter
  • Add the filter to the filter chain of your servlet
  • Connect the filter to the Redis connection (or an other MapSessionRepository backed by Hazelcast, GemFire, Coherence or any other data grid that can give you a Map reference, but that is outside the scope of this blog)

Adding dependencies

First we need to add a couple of dependencies. If you use Maven, you can add the following lines to your pom:

The first dependency is required for the Redis connection, the second is required by Spring to create the servlet filter.

Spring Session Config

Spring Session comes with support for Redis connection (based on the Jedis client internally). You will find an example of a Spring XML based configuration below, but you can substitute that for a Java based configuration instead.

The last two lines instruct Spring to look for an application.properties file with a spring.redis.port value. In this example, it is just a one-liner:

Registering the Spring Session Config

You need to add a few lines to the web.xml in order for the Spring Session configuration to be loaded (unless your application uses Spring already):

Adding the Spring Session servlet filter

The last change that is required in your application is that you add a new servlet filter. Open the web.xml again, and add these lines:

You can see all required changes in a single commit.

Verification

Now that we have updated the application, it is time to verify that it works as expected.

  • Rebuild your application
  • Make sure that your Redis cluster is up and running at port 6379 (which is the default port). If it is running on different port, you need to update the spring.redis.port setting accordingly
  • Start two instances of the application, lets say one on port 8080 and one on port 8081, that are connected to the same Redis cluster
  • Issue the request as before

First, POST the session state to one of the server instance:

Verify the session state by making a GET request to the same instance:

And if we repeat the same request on the other server, we see that we have the same session data:

Considerations

  • You can start and stop server instances as you please without worrying about loosing sessions, i.e. failover and autoscaling is handled automatically from a session point of view.
  • You do not need sticky sessions or any advanced load balancer configuration. A simple round-robin strategy to distribute the load will suffice.
  • From an operations point of view there is still work to do. Someone needs to setup and manage the Redis cluster (Amazon ElasticCache is a good fit if you are running on AWS).
  • If you are using Spring Boot you probably do not want to add neither the spring-session.xml configuration nor the web.xml. Take a look at the Spring Boot Guide to see what a Java based configuration may look like.
  • What if you are developing a RESTful API and do not like cookies? Take a look at Spring Session Rest.

References

16 Comments

  1. Great tutorial Mattias, but how can I make the session expire? And also, would you please post the Java Config Example? There is some folks around here who prefer Java Based Config.

  2. very nice post.
    i have an question ?. is it correct that we can using spring session by redis for sso
    i have 3 app that is not equal (not scale) .
    thnaks .

    • @azizkhani: A HTTP session is scoped to a specific web application, i.e. it cannot be shared to another applications. One exception to this rule is that some application servers provide proprietary support for session sharing if you deploy the applications to the same server. Another exception is if you deploy the applications behind an API gateway that, but that would effectively transform your three apps into one app from a client perspective.

      My advice for implementing a SSO solution is to use either a third party authentication provider such as Twitter or Facebook (Spring Social provides abstractions for many different services). If you are required to implement your own authentication provider, I would take a closer look at Spring Security that have sub-projects for SAML and OAuth as well as OAuth2.

  3. venkat

    i am looking for some sample code to implement MapSessionRepository in Spring 4

  4. Kissen

    How does this compare with jwt, do they achieve the same thing or can they complement each other?

    • @Kissen: I guess that you are referring to JSON Web Tokens. I have no experience from working with JWT, so I cannot give a good answer. However, on JWT’s introduction there is some information that you may find useful. Specifically, this blog post describes how Spring Session can be used to address the challenges that are described in the Apps are distributed across many servers paragraph. Another observation is that “JWTs are used primarily for user authentication”, but “we can use JWTs to send information […] securely”. Great, if that is the problem you are attempting to solve, but how does it behave if you need to store other interim “session-like” data? My concern is that there may be a lot of network overhead if the data is added to each request / response. Perhaps this is a no issue (as I said, I have not used JWT), but this is one thing that I would read up on before choosing JWT in such a scenario.

  5. ketan

    I am not sure how to run this project?

    • @ketan: Thank you for your question. The project is a sample project that is used to demonstrate the capabilities of Spring Session. Typically, you would only re-use the servlet filter (defined in web.xml) and the Spring Session configuration (defined in spring-session.xml) and add deploy them to your existing servlet container.
      Having said that, you will find brief usage instructions of the sample project in the README.md file in the GitHub repo. If you glance through the rest of the source code you will find that it consists of the HelloServlet mentioned above and a simple Main class that bootstraps an embedded Tomcat. Moreover, if you take a look at the pom.xml you will find that it uses the appassembler-maven-plugin that targets the Main class to create an executable main script.

  6. Ricardo Mano

    Hello,

    I’ve seen a few examples on how to use Redis and other tools/frameworks but I’m having some troubles with this kind of code:

    session.setAttribute(“myKey”, new ArrayList());
    List list1 = session.getAttribute(“myKey”);
    list1.add(“myValue”);

    If I don’t re-set myKey in session (session.setAttribute(“myKey”, list1)) , the value won’t be set in Redis, is there some configuration to allow this?
    I’m trying to use Redis in a old and complex application and it’s hard to garantee that everytime I change an object in session, I re-set it.

    Have you have a problem like this?

    • @Ricardo: I have not experienced this problem.

      My guess is that the call to HttpSession.setAttribute(name, value) makes a deep copy of the value when its called. I base this assumption after reading in the JavaDoc) that the attribute will broadcasted the to any HttpSessionAttributeListener in the web app. This mechanism does not work if you later change the content of the list by just adding myValue to the list, since the listeners will not get notified about the changed state.

      If my assumption is correct, then I suggest that you try and finalize the state of the list before passing it to the session, e.g.

  7. Mayank

    I am using spring redis session with spring security

    springSessionRepositoryFilter
    org.springframework.web.filter.DelegatingFilterProxy

    springSessionRepositoryFilter
    /*
    REQUEST
    FORWARD

    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy

    springSecurityFilterChain
    *.do

    When I am trying to set anything in session in spring security , i am not getting it saved in Redis as redis session value is null.

Trackbacks for this post

  1. Non-sticky sessions with Spring-Session (Part 1) | my blog
  2. OOP | danghuynhhai

Leave a Reply