Achieve multiple solutions for session sharing

Session sharing

Why is there session sharing

  1. At present, most of the projects of Internet companies are built in a micro-service Huahe distributed environment. This will lead to a project being likely to be distributed and deployed in several or even many server clusters. At this time, a problem will arise.
  2. When a user has a session, for example, when a user logs in to a project, most projects of large companies have Nginx as a reverse proxy. Here is a brief example of several reverse proxy strategies commonly used by Nginx: 1. Round Query strategy, 2. Weight proportion strategy, 3. IP_hash strategy, 4. Customizable strategies. Under Nginx’s reverse proxy, user requests are generally distributed to different servers, but if the user’s request is stored On server A, the user’s session ID is stored in a ConcurrentHashmap of the JVM on the server, with session ID as the key.
  3. But if a service module requested by the user at this time may need to be called to server B, when the user initiates the request, the user’s session ID is not stored on server B at this time, so the user will be asked to log in again, and It may cause the user to simply complete an order operation, but log in several times to clear it.
  4. All session sharing solutions are particularly important in distributed environments and microservice systems.

Solution 1 Ip_hash load balancing based on Nginx

  • It means to model the requested IP address on your multiple available servers, and then distribute your request to the corresponding server through Nginx’s reverse proxy.

Here, the available servers will be put into an array. If the result obtained by taking the modulo is 0, the request will be assigned to the server with the subscript of 0 in the server array.

Concrete implementation

  • You need to make corresponding modifications in the Nginx.conf file and add your own available servers.
upstream zx.com {
ip_hash;
        server localhost:8081 weight=1;
        server localhost:8082 weight=2;
        server localhost:8083 weight=3;
        server localhost:8084 weight=4;
        server localhost:8085 backup;
        server localhost:8086 backup;
    }

    server {
        listen 8088;
        server_name localhost;
}

Advantages:

  • Simple configuration, non-invasive to the application, no need to modify the code
  • As long as the hash attributes are uniform, the loads of multiple web-servers are balanced.
  • Facilitates server horizontal expansion
  • Higher security

Disadvantages

  • Restarting the server will cause some sessions to be lost.
  • Horizontal expansion will also cause some sessions to be lost.
  • There is a risk of high single point load

Solution 2: Session replication based on Tomcat

  • The solution is to copy the generated sessionID to all servers in the system when the user makes a request. This ensures that when the current user requests a call from server A to server B, server B can also make a call. The user’s session ID, this solves the problem

Concrete implementation

  1. Modify the Cluster node in server.xml;
  2. Modify application web.xml and add node:

Advantages:

  • Simple configuration, non-invasive to the application, no need to modify the code
  • Can adapt to various load balancing strategies
  • Server restart or downtime will not cause session loss
  • Higher security

Disadvantages

  • There will be a certain delay in session synchronization
  • Occupying intranet broadband resources
  • Limited by memory resources, poor horizontal expansion capabilities
  • The more servers there are
  • Serialization and deserialization consume CPU performance

Solution 3: Use Redis as a unified cache for cached sessions

  • This method is to store the session ID generated for each user request on the Redis server. Set an expiration time mechanism based on the characteristics of Redis, so as to ensure that the user is within the session expiration time in Redis we set. , no need to log in again

Concrete implementation

1. SpringSession based on Cookie under SpringBoot
  1. Import dependencies
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>No10_Session</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.7.17</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

</project>
  1. Write configuration file
server:
  port: 8081
  servlet:
    context-path: /

spring:
  redis:
    host: 192.168.230.190
    port: 6379
    password: 123456
  1. Add annotations to the project startup class to enable Spring-session
@SpringBootApplication
@EnableRedisHttpSession
public class GlobalSessionApplication {<!-- -->
public static void main(String[] args) {<!-- -->
SpringApplication.run(GlobalSessionApplication.class, args);
}
}
  1. Add control class to project
@RestController
public class SessionController {<!-- -->

    @Value("${server.port}")
    private Integer pore;

    @GetMapping("/set")
    public String set (HttpSession session){<!-- -->
        session.setAttribute("user","Zhang San");
        return String.valueOf(pore);
    }

    @GetMapping("/get")
    public String get (HttpSession session){<!-- -->
        return "user: " + session.getAttribute("user") + " \\
 Port number: " + pore;
    }
}
  1. Start the project and view the values in Redis
    Configure the port number when starting the project
    Please add a picture description
    View stored data
    Please add a picture description

  2. In the above operation, session sharing has been completed, but you will find that the data in Redis is garbled. This is because the default JDK serialization method is used to store it, and the readability is relatively poor. To hide it, we can add Spring serialization. method

public class SessionConfig {<!-- -->

    @Bean
    public RedisSerializer springSessionDefaultRedisSerializer(){<!-- -->

        return new GenericJackson2JsonRedisSerializer();
    }

    @Bean
    public CookieSerializer cookieSerializer(){<!-- -->
        DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
        defaultCookieSerializer.setCookiePath("/"); //Solve the problem of different contextPath
//Set the first-level domain name of the cookie. If not set, it will default to the domain name in the URL.
// cookieSerializer.setSameSite(null); //Solve the problem of invalid cookies carried across axios across domains
        return defaultCookieSerializer;
    }
}

This way the whole problem is solved!!!

Storage instructions in Redis:

  1. spring:session is the default Redis HttpSession prefix (in redis, we often use ‘:‘ as the delimiter).
  2. Each session will create 3 sets of data
  • Hash structure, the main content stored in spring-session
    spring:session:sessions:709261a8-8c40-4097-8df2-92f88447063f
    The hash structure has key and field. As in the above example: the hash key is “spring:session:sessions” prefixed with 709261a8-8c40-4097-8df2-92f88447063f. The fields under this key are:

    • key=sessionAttr:user, value=“Zhang San” //session stores data content

    • key=creationTime, value=1644302828502 //Creation time (saved in milliseconds)

    • key=maxInactiveInterval, value=1800 //Maximum inactive interval (default 1800 seconds, i.e. 30 minutes)

    • key=lastAccessedTime, value=1644302833137 //Last access time (saved in milliseconds)

      When we revisit localhost:8082/get, we can see that lastAccessedTime will change.

  • String structure, used for ttl expiration time record
    spring:session:sessions:expires:70924e71-c540-4097-8df2-92f88447063f key is “spring:session:sessions:expires:” prefix + 70924e71-c540-4097-8df2-92f88447063fvalue is empty

  • set structure, expiration time record
    spring:session:expirations:16443046000000
    The key of set is fixed to “spring:session:expirations:16443046000000” and the set values of set are:

    • expires:c7fc28d7-5ae2-4077-bff2-5b2df6de11d8 //(one per session)

A brief mention: Redis’s behavior of clearing expired keys is an asynchronous behavior and a low-priority behavior. To use the original words in the document, it may cause the session to not be cleared. Therefore, a special expiresKey was introduced to be responsible for session clearing, which we also need to pay attention to when using redis. At the development level, we only need to pay attention to the first set of hash structure data.

2. SpringSession based on Token under SpringBoot
  1. Modify configuration class
@Bean
public HeaderHttpSessionIdResolver headerHttpSessionIdResolver(){<!-- -->
return new HeaderHttpSessionIdResolver("token");
}
  1. Write configuration file
spring:
  session:
    store-type: redis
  1. Configure same-origin policy under cross-domain conditions
@Configuration
public class CorsConfig implements WebMvcConfigurer {<!-- -->
@Override
public void addCorsMappings(CorsRegistry registry) {<!-- -->
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("*")
.allowedOrigins("*")
.exposedHeaders("token");
}
}

Solution 4: Store Session in Cookie

Put the session in the cookie, because every time the user requests, his cookie will be put in the request, so this can ensure that every time the user requests, the user will not be in a distributed environment. Performing second login.