SpringBoot实现分布式session

实现分布式session

实现基于redis的分布式session


原理

基于HttpRequestWapper,对request获取的Session实现类进行替换,即提供一个从redis获取数据的Session实现类

依赖

引入spring-boot-starter-data-redisspring-session-data-redis
引入kryo作为序列化方案

1 <dependency> 2 <groupId>org.springframework.session</groupId> 3 <artifactId>spring-session-data-redis</artifactId> 4 </dependency> 5 6 <dependency> 7 <groupId>org.springframework.boot</groupId> 8 <artifactId>spring-boot-starter-data-redis</artifactId> 9 </dependency> 10 11 <dependency> 12 <groupId>com.esotericsoftware</groupId> 13 <artifactId>kryo</artifactId> 14 <version>4.0.0</version> 15 </dependency> 16 17 <dependency> 18 <groupId>de.javakaffee</groupId> 19 <artifactId>kryo-serializers</artifactId> 20 <version>0.41</version> 21 </dependency> 22

修改配置

1spring.redis.host=127.0.0.1 2spring.redis.database=2 3

ps: 如果有需要还可以修改对应的连接池配置,或者更换默认的lettuce框架

编写序列化类

该类用于数据的序列化和反序列化,本样例基于kryo实现,该框架具有体积小、速度快等优势。

此步骤可以省略,不提供具体实现Spring默认使用Jdk序列化方案


序列化需要实现org.springframework.data.redis.serializer.RedisSerializer

该类可以用于不同的存储方案

demo如下

1public class KryoRedisSerializer<T> implements RedisSerializer<T> { 2 private static final Logger logger = LoggerFactory.getLogger(KryoRedisSerializer.class); 3 4 public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 5 6 private static final ThreadLocal<Kryo> kryos = ThreadLocal.withInitial(Kryo::new); 7 8 private Class<T> clazz; 9 10 public KryoRedisSerializer(Class<T> clazz) { 11 super(); 12 this.clazz = clazz; 13 } 14 15 @Override 16 public byte[] serialize(T t) throws SerializationException { 17 if (t == null) { 18 return EMPTY_BYTE_ARRAY; 19 } 20 21 logger.debug("序列化{},{}", t.getClass(), t); 22 23 Kryo kryo = kryos.get(); 24 kryo.setRegistrationRequired(false);//关闭注册行为,避免相同类无法强转 25 kryo.setReferences(false); 26// kryo.register(clazz); 27 28 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 29 Output output = new Output(baos)) { 30 kryo.writeClassAndObject(output, t); 31 output.flush(); 32 return baos.toByteArray(); 33 } catch (Exception e) { 34 logger.error(e.getMessage(), e); 35 } 36 37 return EMPTY_BYTE_ARRAY; 38 } 39 40 @Override 41 public T deserialize(byte[] bytes) throws SerializationException { 42 if (bytes == null || bytes.length <= 0) { 43 return null; 44 } 45 46 Kryo kryo = kryos.get(); 47 kryo.setReferences(false); 48 kryo.setRegistrationRequired(false); 49// kryo.register(clazz); 50 51 logger.debug("反序列化"); 52 try (Input input = new Input(bytes)) { 53 return (T) kryo.readClassAndObject(input); 54 } catch (Exception e) { 55 logger.error(e.getMessage(), e); 56 } 57 58 return null; 59 } 60} 61

ps: 需要注意的是,虽然该类提供了一个泛型对象,但是实际运用中并没有什么用;在反序列化时并不能知道应该返回一个什么类型的对象;因此多数序列化框架都是采取的在序列化结果中存储该对象的实际类型;

再ps:由于kryo序列化后为二进制,因此对于List、Map等可能带有泛型,且存储对象并非同一个子类的情况还需要进行测试

注入Spring容器

Spring Session通过org.springframework.session.data.redis.RedisOperationsSessionRepository 实现Session的替换,以及数据的序列化

仔细查看该类可知,该类有两个较为重要的属性

  • RedisSerializer<Object> defaultSerializer
  • RedisOperations<Object, Object> sessionRedisOperations

defaultSerializer初始化为JdkSerializationRedisSerializer

用于onMessage方法,该方法可能为Redis值过期事件响应,负责对传递过来的数据做session删除和过期操作

不太明白为什么不从sessionRedisOperations中获取序列化实例,这点有待研究


sessionRedisOperations在构造方法中传入

由于Spring Bean注入顺序的原因,该值为Spring redis starter自动创建的RedisTemplate实例,该实例中使用了Jdk序列化方案,需要修改,但是暂时没找到办法注入自己创建的实例

故采取折中方案,即获取RedisOperationsSessionRepository实例,手动修改里面的RedisTemplate里的·RedisSerializer


最终配置类如下

1@Configuration 2@EnableRedisHttpSession 3public class RedisSessionConfig { 4 5 @Bean 6 public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory, RedisOperationsSessionRepository repository) { 7 RedisTemplate<Object, Object> template = new RedisTemplate<>(); 8 template.setConnectionFactory(redisConnectionFactory); 9 10 RedisSerializer<Object> serializer = new KryoRedisSerializer<>(Object.class); 11 // redis value使用的序列化器 12 template.setValueSerializer(serializer); 13 // redis key使用的序列化器 14 template.setKeySerializer(new StringRedisSerializer()); 15 16 repository.setDefaultSerializer(serializer); 17 //由于RedisOperationsSessionRepository 要先构造,且不提供方法修改属性,只能采取这种这种的方法 18 RedisOperations<Object, Object> sessionRedisOperations = repository.getSessionRedisOperations(); 19 if (sessionRedisOperations instanceof RedisTemplate){ 20 RedisTemplate<Object,Object> redisTemplate = ((RedisTemplate<Object, Object>) sessionRedisOperations); 21 redisTemplate.setValueSerializer(serializer); 22 redisTemplate.setHashValueSerializer(serializer); 23 } 24 25 template.afterPropertiesSet(); 26 return template; 27 } 28}

ps: 更多配置信息亦可通过RedisOperationsSessionRepository修改,如session有效实现,cookie name值等等

todo

  • 寻找更合适的注入Serializer方式

代码

具体实现可查阅github

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )