在web项目开发过程中,会话通常用于存储一些业务中间数据、状态数据,是十分重要的一部分,在单机环境下这十分容易,但是在分布式的环境下,单机无法实现,就需要有一个完整的方案,目前业界常见的方案有两种:(1)Spring集成redis实现分布式会话(2)使用JWT(JSON Web Token)实现。
本文介绍在springboot环境下如何利用spring集成redis来实现分布式会话,并提供了相关代码和各项配置。
一、实现原理
将session id和session value以key-value的形式存于redis中,设置key的过期失效时间,当key值失效时就是session失效时。
二、引入依赖
如果你的应用是springboot应用并且使用了spring data,那么集成redis是十分简单的事情,因为redis已经提供了相关的依赖,而开发者只需要进行简单的配置即可。
org.springframework.boot spring-boot-starter-data-redis org.springframework.session spring-session-data-redis
三、添加配置
#session超时时间设置为1000秒server.session.timeout=1000#设置session的存储方式,none为存在本地内存中,redis为存在redis中spring.session.store-type=redis#namespace用于区分不同应用的分布式sessionspring.session.redis.namespace=demo#session更新到redis的模式,分为on_save和immediate,on_save是当执行执行完响应以后才将数据同步到redis,immediate是在使用session的set等操作时就同步将数据更新到redis,建议使用on_savespring.session.redis.flush-mode=on_save#redis配置,针对springboot2.X以下版本#redis的数据库,默认为0spring.redis.database=0#redis主机spring.redis.host=127.0.0.1#redis端口spring.redis.port=6380#redis密码spring.redis.password=#连接池最大连接数,使用负值表示没有限制spring.redis.pool.max-active=40#连接池最大阻塞等待时间,使用负值表示没有限制spring.redis.pool.max-wait=1000#连接池中的最大空闲连接spring.redis.pool.max-idle=5#连接超时时间,单位毫秒spring.redis.timeout=1000
四、启用spring session
在spring入口应用上使用注解@EnableRedisHttpSession开启spring redis session。
//session失效时间设置为30分钟=1800秒@Configuration@EnableRedisHttpSession(maxInactiveIntervalInSeconds=1800,redisNamespace="test-service",redisFlushMode=RedisFlushMode.ON_SAVE)
4.1 配置事件通知
在有些情况下,应用程序需要监听到session实效,并且进行一些操作,例如记录用户退出时间,给用户发送一些通知等,在这种情况下,必须要能监听到redis中key值的实效。
当使用@EnableRedisHttpSession时,sessionMessageListener和redis KESPACE events将会被自动配置,这将在session失效时能够通知到应用,以便应用可以进行相关的操作。
值得注意的是,这个功能需要redis 2.8以上版本,并且开启事件通知 ,而事件通知在一般情况下都是关闭的,所以需要特别进行开启,方法有两种:
1.配置文件配置
notify-keyspace-events Egx
2.使用config命令
config set notify-keyspace-events Egx
若是不需要使用到session失效通知,可以加上以下配置:
@Beanpublic static ConfigureRedisAction configureRedisAction(){ return ConfigureRedisAction.NO_OP;}
4.2 配置监听线程池
Spring session监听默认采用的线程池是SimpleAsyncTaskExecutor,虽然名义上也是线程池,但是实际上每次请求都会创建新的线程,这在流量较大的情况下,如果响应较慢,大量请求将会导致出现大量的线程,容易导致OOM。一般情况下可以自行定义线程池,如下例子:
@Beanpublic ThreadPoolTaskExecutor threadPoolTaskExecutor(){ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(30); executor.setKeepAliveSeconds(30) executor.setThreadNamePrefix("spring-redis-") return executor;}
4.3 设置session Id传递方式
默认情况下session id通过cookie进行传递,但是有些浏览器不支持cookie,或者用户主动关闭了cookie,所以cookie的方式不是最稳妥的,spring redis提供了将session id放到请求头的方式,设置x-auth-token header头,对应的值就是session id,在首次请求以后spring会将x-auth-token放到header中,后续前端请求的时候需要继续将对应的session id放到header头中,这是需要前端主动进行的操作,需要特别注意。
@Beanpublic HttpSessionStrategy httpSessionStrategy(){ return new HeaderHttpSessionStrategy();}
综上整体的配置如下:
@SpringBootConfiguration@ConfigurationProperties(prefix="spring.session.custom.executor")public class SpringRedisSessionConfig(){ private Integer corePoolSize; private Integer maxPoolSize; private Integer keepAliveSeconds; private String threadNamePrefix; /*与HttpSessionListener两者选其一*/ @Bean public static ConfigureRedisAction configureRedisAction(){ return ConfigureRedisAction.NO_OP; } @Bean public HttpSessionListener httpSessionListener(){ return new HttpSessionListener(); } @Bean public ThreadPoolTaskExecutor threadPoolTaskExecutor(){ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(this.corePoolSize); executor.setMaxPoolSize(this.maxPoolSize); executor.setKeepAliveSeconds(this.keepAliveSeconds) executor.setThreadNamePrefix(this.threadNamePrefix) return executor; } @Bean public HttpSessionStrategy httpSessionStrategy(){ return new HeaderHttpSessionStrategy(); } public Integer getCorePoolSize(){ return corePoolSize; } public Integer getMaxPoolSize(){ return maxPoolSize; } public Integer getKeepAliveSeconds(){ return keepAliveSeconds; } public String getThreadNamePrefix(){ return threadNamePrefix; }}