spring session + redis to realize distributed session

introduction

In general, in order to solve a single point of failure, web applications in the production environment will be deployed on multiple platforms, so there will be a problem. When we log in, we will store the login information in the session. If we rely on the session in the native web container, when the load is balanced to different servers, there will be different login States, as shown in the following figure:

                                             

When the user accesses the service for the first time, it is assumed that the user is proxied to Server A by Nginx, and fills in the account information to log in. At this time, a session will be created on Server A to save the login information. When the user continues to access the service, it may be proxied to Server B by Nginx, but the server This session is not found on B. at this time, the user will be guided to log in. Obviously, this processing cannot meet the needs. In general, we will not directly use the session of the web container, but store the login information in the third-party storage (such as redis). At this time, the architecture will evolve as follows:

                         

This architecture is also commonly used in actual development. Today, we will introduce another commonly used implementation, spring session + redis.

1, Environment construction

1. Based on the spring boot web environment, add the following configuration

pom.xml

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

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

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

application.yml

server:
  port: 8080
  servlet:
    session:
#       Session timeout. The default unit is seconds. It doesn't work. Use @ enablereredishttpsession (maxinactive intervalinseconds = 300000000) to configure it
#      timeout: 86400 # 24 * 60 * 60
      cookie:
        name: sts # Configure cookie name
        # cookie timeout,seconds
        max-age: 3600
        

spring:
  ######## redis configuration #######
  redis:
    database: 5
    host: 192.168.0.121
#    host: 127.0.0.1
    port: 6379
    timeout: 5000
    password:
    lettuce:
      pool:
        max-idle: 9
        min-idle: 1
        max-active: 9
        max-wait: 5000
        
   ######## spring session configuration Used here@EnableRedisHttpSession Annotation configuration#######
#  session:
    # Session store type
#    store-type: redis
#    redis:
      # Session flush model
#      flush-mode: ON_SAVE
      # Namespace for keys used to store sessions.
#      namespace: spring:session

Application.java

@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 30, redisNamespace = "CAS_SSO_SESSION")
@SpringBootApplication
public class SessionApplication {

	public static void main(String[] args) {
		SpringApplication.run(SessionApplication.class, args);
	}
}

LocalHttpSessionListener.java

/**
 * session Life cycle monitoring
 * 
 */
@Configuration
public class LocalHttpSessionListener implements HttpSessionListener {
	
	@Override
	public void sessionCreated(HttpSessionEvent se) {
		HttpSession session = se.getSession();
		System.out.println(String.format("session[id=%s]Created", session.getId()));
	}
	
	@Override
	public void sessionDestroyed(HttpSessionEvent se) {
		HttpSession session = se.getSession();
		System.out.println(String.format("session[id=%s]Expired ", session.getId()));
	}
	
}

 SessionController.java

@RestController
public class SessionController {
	
	@GetMapping("/sessionId")
	public String getSessionId(HttpServletRequest request, HttpSession session) throws ServletException {
		String uid = null;
		if (request.getSession().getAttribute("uid") != null) {
			uid = request.getSession().getAttribute("uid").toString();
		}else {
			uid = UUID.randomUUID().toString();
		}
		request.getSession().setAttribute("uid", uid);
		return request.getSession().getId();
	}
	
}

2. Test

a. start service

b. visit http://localhost:8080/sessionId to see if there is data storage in redis

c. restart the service and visit again http://localhost:8080/sessionId to see if uid still exists

2, Main configuration class loading

1. Integration portal

RedisHttpSessionConfiguration: introduced by the enablereredishttpsession annotation, it mainly initializes the following important classes:

a. SessionRepositoryFilter: filter, proxy HttpServletRequest and HttpServletResponse of web container as SessionRepositoryRequestWrapper and SessionRepositoryResponseWrapper respectively

b. RedisIndexedSessionRepository: redis session key operation class

c. RedisMessageListenerContainer: redis key lifecycle listener

SessionAutoConfiguration: by spring.factories In addition, the following important classes are initialized:

a. Defaultcookie serializer: cookie related operations, such as writing cookies to response

2. The sequence diagram is as follows:

 

3. Related session key

As shown in the figure above, a session will be stored as three keys. In the figure, there is a session with id b6c9ccd7-5670-4cdf-b509-ece45f3ca943, and the three keys are respectively:

a. CAS_SSO_SESSION:sessions:b6c9ccd7-5670-4cdf-b509-ece45f3ca943

The key type is Hashes, and the expiration time is maxinactivalintervalseconds + 300s (5min). The main data of the session is stored, as shown in the figure below. The creation time, the maximum effective time (the value of maxinactivalintervalseconds attribute in the annotation of enablereredishttpsession), the latest access time, and the business attribute value are saved( request.getSession().setAttribute("uid", uid)).

b. CAS_SSO_SESSION:sessions:expires:b6c9ccd7-5670-4cdf-b509-ece45f3ca943

The key type is Strings, the expiration time is maxinactive intervalseconds (the expiration time is subject to the key), the value of the key is empty, and it is used in combination with the key in c below. We can monitor the key expiration event to realize our own business (for example, the authentication center in single sign on, after the session expires, we need to clear all the local client sessions through this Function implementation).

c. CAS_SSO_SESSION:expirations:1891606440000

The key type is Sets, the expiration time is maxinactivalintervalseconds + 300s (5min), and the saved value is expires:[sessionId]. 1891606440000 is converted from the next full minute of lastAccessedTimeInMillis and maxinactiviinseconds. The source code is as follows:

static long expiresInMillis(Session session) {
	int maxInactiveInSeconds = (int) session.getMaxInactiveInterval().getSeconds();
	long lastAccessedTimeInMillis = session.getLastAccessedTime().toEpochMilli();
	return lastAccessedTimeInMillis + TimeUnit.SECONDS.toMillis(maxInactiveInSeconds);
}

// The timeInMs parameter is obtained by the expiresInMillis method above
static long roundUpToNextMinute(long timeInMs) {

	Calendar date = Calendar.getInstance();
	date.setTimeInMillis(timeInMs);
	date.add(Calendar.MINUTE, 1);
	date.clear(Calendar.SECOND);
	date.clear(Calendar.MILLISECOND);
	return date.getTimeInMillis();
}

This key is mainly used to make up for the fact that Redis key space notifications cannot guarantee the timeliness of expired key notifications. It is executed by a scheduled task to trigger the key expiration event. The source code is as follows:

// Scheduled task execution, in order to make up for the fact that Redis key space notifications cannot guarantee the timeliness of expired key notifications.
void cleanExpiredSessions() {
	long now = System.currentTimeMillis();
	long prevMin = roundDownMinute(now);

	if (logger.isDebugEnabled()) {
		logger.debug("Cleaning up sessions expiring at " + new Date(prevMin));
	}

	String expirationKey = getExpirationKey(prevMin);
	Set<Object> sessionsToExpire = this.redis.boundSetOps(expirationKey).members();
	this.redis.delete(expirationKey);
	for (Object session : sessionsToExpire) {
		String sessionKey = getSessionKey((String) session);
		touch(sessionKey);
	}
}

/**
 * By trying to access the session we only trigger a deletion if it the TTL is
 * expired. This is done to handle
 * https://github.com/spring-projects/spring-session/issues/93
 * @param key the key
 */
private void touch(String key) {
	this.redis.hasKey(key);
}

The scheduling meeting obtains CAS regularly_ SSO_ SESSION:expirations : value in 1891606440000( expires:b6c9ccd7-5670-4cdf-b509-ece45f3ca943 )And query whether the key exists. If the key has expired (not cleaned up in time for some reasons), the key expiration event will be triggered

Source address

Keywords: Session Redis Spring Java

Added by mouloud2001 on Tue, 09 Jun 2020 05:20:25 +0300