spring-session-data-redis 1.3.x 实现原理

本片文章适合对spring-session的工作原理有所理解的同学,如果还没有理解spring-session的核心原理,可以参考spring-session 原理及源码解析

1 spring-session-data-redis工作原理

在spring-session过滤session的基础上,spring-session-data-redis做了redis的实现,使我们可以通过redis来集中管理session。由于redis的高性能、易管理、持久化等特性,spring-session-data-redis经常被作为spring-session仓库的主要选择。sessionRepository是spring-session框架中管理session的接口,spring-session-data-redis的RedisOperationsSessionRepository实现了这个接口,自定义了session管理细节。我们先来看RedisOperationsSessionRepository的UML图:

它实现了session检索仓库FindByIndexNameSessionRepository,因此具备检索功能,同时实现了MessageListener,可以监听redis发布/订阅的消息。在其内部自定义了RedisSession,RedisSession实现了ExpiringSession,因此具备超时功能。

session的管理是通过三个key来管理session,便于区分,下问以简称代替

key 简称 类型 value 超时时间
spring:session:sessions:sessionId sessionKey Hash session的属性 超时+5分钟
spring:session:sessions:expires:sessionId 超时sessionKey String   超时
 spring:session:expirations:时间戳(最后访问时间+超时时间) 时间戳key Set expires:sessionId 超时+5分钟

1 session的管理

1.1 创建session

	public RedisSession createSession() {
//新建RedisSession,并设置超时时间
		RedisSession redisSession = new RedisSession();
		if (this.defaultMaxInactiveInterval != null) {
			redisSession.setMaxInactiveIntervalInSeconds(this.defaultMaxInactiveInterval);
		}
		return redisSession;
	}

1.2 保存session

public void save(RedisSession session) {
//		保存sesion
		session.saveDelta();
//如果是新创建的session,发布一个session创建消息
		if (session.isNew()) {
			String sessionCreatedKey = getSessionCreatedChannel(session.getId());
			this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
			session.setNew(false);
		}
	}

saveDelta的流程图如下:

1.3 清除session

public void delete(String sessionId) {
		RedisSession session = getSession(sessionId, true);
		if (session == null) {
			return;
		}

		cleanupPrincipalIndex(session);
//触发onDelete,移除时间戳Key中的sessionId
		this.expirationPolicy.onDelete(session);
//移除超时sessionkey
		String expireKey = getExpiredKey(session.getId());
		this.sessionRedisOperations.delete(expireKey);
//清零超时时间,重新保存session
		session.setMaxInactiveIntervalInSeconds(0);
		save(session);
	}

1.4 定时清除过期session

这里使用spring定时器,每分钟调用一次清理过期session操作,这个定时策略可通过自定义属性spring.session.cleanup.cron.expression来设置。

@Scheduled(cron = "${spring.session.cleanup.cron.expression:0 * * * * *}")
	public void cleanupExpiredSessions() {
		this.expirationPolicy.cleanExpiredSessions();
	}

/**
	 * 清除过期的session
	 */
	public 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();
		//删除当前分钟的时间戳Key
		this.redis.delete(expirationKey);
		for (Object session : sessionsToExpire) {
			//移除过期的sessionKey
			String sessionKey = getSessionKey((String) session);
			touch(sessionKey);
		}
	}

1.5  获取session

public RedisSession getSession(String id) {
		return getSession(id, false);
	}
	private RedisSession getSession(String id, boolean allowExpired) {
		//从sessionKey中获取所有的Hash属性
		Map<Object, Object> entries = getSessionBoundHashOperations(id).entries();
		//不存在则返回null
		if (entries.isEmpty()) {
			return null;
		}
		//重新封装进一个MapSession
		MapSession loaded = loadSession(id, entries);
		//确保获取未过期的session
		if (!allowExpired && loaded.isExpired()) {
			return null;
		}
		//从新封装一个RedisSession
		RedisSession result = new RedisSession(loaded);
		result.originalLastAccessTime = loaded.getLastAccessedTime();
		return result;
	}

1.6 获取某个索引绑定的所有session

public Map<String, RedisSession> findByIndexNameAndIndexValue(String indexName,
			String indexValue) {
//		如果索引名称不是指定名称,则返回空map
		if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
			return Collections.emptyMap();
		}
//		用indexValue组装principalKey
		String principalKey = getPrincipalKey(indexValue);
//		获取principalKey的所有sessionId集合
		Set<Object> sessionIds = this.sessionRedisOperations.boundSetOps(principalKey)
				.members();
//		封装进一个Map<String, RedisSession>并返回
		Map<String, RedisSession> sessions = new HashMap<String, RedisSession>(
				sessionIds.size());
		for (Object id : sessionIds) {
			RedisSession session = getSession((String) id);
			if (session != null) {
				sessions.put(session.getId(), session);
			}
		}
		return sessions;
	}

1.7 移除索引绑定的session

private void cleanupPrincipalIndex(RedisSession session) {
		String sessionId = session.getId();
		String principal = PRINCIPAL_NAME_RESOLVER.resolvePrincipal(session);
		if (principal != null) {
			this.sessionRedisOperations.boundSetOps(getPrincipalKey(principal))
					.remove(sessionId);
		}
	}

2 事件的支持

spring-session定义了AbstractSessionEvent来实现事件的支持,这个抽象类继承了ApplicationEvent,这里使用了spring的事件支持,spring-session又定义了SessionCreatedEvent,SessionDeletedEvent,SessionDestroyedEvent,SessionExpiredEvent四个事件来支持session的创建、删除、销毁、过期。RedisOperationsSessionRepository实现了MessageListener,在监听到消息时做了如下操作:

public void onMessage(Message message, byte[] pattern) {
		byte[] messageChannel = message.getChannel();
		byte[] messageBody = message.getBody();
		if (messageChannel == null || messageBody == null) {
			return;
		}

		String channel = new String(messageChannel);
//如果通道以spring:session:event:created:开始,则反序列化message里的body,发布session创建事件
		if (channel.startsWith(getSessionCreatedChannelPrefix())) {
			// TODO: is this thread safe?
			Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer
					.deserialize(message.getBody());
			handleCreated(loaded, channel);
			return;
		}

		String body = new String(messageBody);
//		如果body不以spring:session:sessions:expires:开始,则返回。
		if (!body.startsWith(getExpiredKeyPrefix())) {
			return;
		}
//如果通道以:del结尾,则是删除通道
		boolean isDeleted = channel.endsWith(":del");
		//如果是删除通道或者以:expired结尾
		if (isDeleted || channel.endsWith(":expired")) {
			int beginIndex = body.lastIndexOf(":") + 1;
			int endIndex = body.length();
			String sessionId = body.substring(beginIndex, endIndex);

			RedisSession session = getSession(sessionId, true);

			if (session == null) {
				logger.warn("Unable to publish SessionDestroyedEvent for session "
						+ sessionId);
				return;
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Publishing SessionDestroyedEvent for session " + sessionId);
			}
//移除索引绑定session
			cleanupPrincipalIndex(session);

			if (isDeleted) {
				//发布删除事件
				handleDeleted(session);
			}
			else {
				//发布过期事件
				handleExpired(session);
			}
		}
	}

 

因此,spring-session-data-redis是通过redis的发布订阅机制,触发spring的事件发布机制,从而实现支持session事件的功能。如果我们想使用这个功能,只需要实现spring的事件监听接口ApplicationListener,重写onApplicationEvent即可。

 

spring-session-data-redis 1.3.x的整合从注解@EnableRedisHttpSession入手,EnableRedisHttpSession引入了RedisHttpSessionConfiguration类,主要配置在RedisHttpSessionConfiguration中。我们来看看RedisHttpSessionConfiguration都做了什么操作。

2 可自定义的部分

1 RedisHttpSessionConfiguration

1.1 自定义sessionRedisTemplate

通过@Bean注解定义注入仅供spring-session操作的RedisTemplate(sessionRedisTemplate),这里可以通过自定义bean(springSessionDefaultRedisSerializer)来指定默认序列化方式,使得我们在引入spring-session框架时,不会影响之前的RedisTemplate属性。如果我们需要额外的修改,可以通过bean标签在xml中自定义sessionRedisTemplate来覆盖这个sessionRedisTemplate。

@Autowired(required = false)
@Qualifier("springSessionDefaultRedisSerializer")
public void setDefaultRedisSerializer(
			RedisSerializer<Object> defaultRedisSerializer) {
		this.defaultRedisSerializer = defaultRedisSerializer;
}
@Bean
public RedisTemplate<Object, Object> sessionRedisTemplate(
			RedisConnectionFactory connectionFactory) {
//创建一个新的RedisTemplate,定义序列化为StringRedisSerializer,
		RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
		template.setKeySerializer(new StringRedisSerializer());
		template.setHashKeySerializer(new StringRedisSerializer());
//这里我们可以指定默认序列化
		if (this.defaultRedisSerializer != null) {
			template.setDefaultSerializer(this.defaultRedisSerializer);
		}
		template.setConnectionFactory(connectionFactory);
		return template;
}

1.2 创建session仓库RedisOperationsSessionRepository

@Bean
public RedisOperationsSessionRepository sessionRepository(
			@Qualifier("sessionRedisTemplate") RedisOperations<Object, Object> sessionRedisTemplate,
			ApplicationEventPublisher applicationEventPublisher) {
//创建一个RedisOperationsSessionRepository实例
		RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(
				sessionRedisTemplate);
//添加指定的ApplicationEventPublisher
		sessionRepository.setApplicationEventPublisher(applicationEventPublisher);
//指定session超时时间
		sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
//指定默认序列化方式
		if (this.defaultRedisSerializer != null) {
			sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
		}
//指定redis命名空间
		String redisNamespace = getRedisNamespace();
		if (StringUtils.hasText(redisNamespace)) {
			sessionRepository.setRedisKeyNamespace(redisNamespace);
		}
//指定flush模式
		sessionRepository.setRedisFlushMode(this.redisFlushMode);
		return sessionRepository;
	}

1.3 指定ConfigureRedisAction

private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();

@Autowired(required = false)
public void setConfigureRedisAction(ConfigureRedisAction configureRedisAction) {
		this.configureRedisAction = configureRedisAction;
}
@Bean
public InitializingBean enableRedisKeyspaceNotificationsInitializer(
			RedisConnectionFactory connectionFactory) {
//通过RedisConnectionFactory和ConfigureRedisAction创建EnableRedisKeyspaceNotificationsInitializer,我们可以指定ConfigureRedisAction
		return new EnableRedisKeyspaceNotificationsInitializer(connectionFactory,
				this.configureRedisAction);
}

1.4 自定义springSessionRedisTaskExecutor(bean),指定redisTaskExecutor线程池

@Autowired(required = false)
@Qualifier("springSessionRedisTaskExecutor")
public void setRedisTaskExecutor(Executor redisTaskExecutor) {
		this.redisTaskExecutor = redisTaskExecutor;
}

1.5 自定义springSessionRedisSubscriptionExecutor(bean),指定redisSubscriptionExecutor线程池

@Autowired(required = false)
@Qualifier("springSessionRedisSubscriptionExecutor")
public void setRedisSubscriptionExecutor(Executor redisSubscriptionExecutor) {
		this.redisSubscriptionExecutor = redisSubscriptionExecutor;
}

2 EnableRedisHttpSession

EnableRedisHttpSession可以自定义三个属性:

属性名称 类型 描述 默认
maxInactiveIntervalInSeconds int 单位(秒) session超时时间 1800
redisNamespace String redis命名空间 ""
redisFlushMode RedisFlushMode redis刷新模式 RedisFlushMode.ON_SAVE

 

 

发布了175 篇原创文章 · 获赞 39 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/top_explore/article/details/105248316
今日推荐