Implement distributed session
Implement redis-based distributed session
principle
Replace the Session implementation class obtained from request based on HttpRequestWapper by providing a Session implementation class that gets data from redis
rely on
Introduce spring-boot-starter-data-redis, spring-session-data-redis;
Introducing kryo as a serialization scheme
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>de.javakaffee</groupId> <artifactId>kryo-serializers</artifactId> <version>0.41</version> </dependency>
Modify Configuration
spring.redis.host=127.0.0.1 spring.redis.database=2
ps: You can also modify the corresponding connection pool configuration or change the default lettuce framework if needed
Writing serialized classes
This class is used for data serialization and deserialization. This sample is based on kryo implementation, which has advantages such as small size and fast speed.
This step can be omitted without providing a specific implementation for Spring using the Jdk serialization scheme by default
Serialization requires the implementation of the org.springframework.data.redis.serializer.RedisSerializer class
This class can be used for different storage schemes
demo is as follows
public class KryoRedisSerializer<T> implements RedisSerializer<T> { private static final Logger logger = LoggerFactory.getLogger(KryoRedisSerializer.class); public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private static final ThreadLocal<Kryo> kryos = ThreadLocal.withInitial(Kryo::new); private Class<T> clazz; public KryoRedisSerializer(Class<T> clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return EMPTY_BYTE_ARRAY; } logger.debug("serialize{},{}", t.getClass(), t); Kryo kryo = kryos.get(); kryo.setRegistrationRequired(false);//Turn off Registration behavior to avoid impossible enforcement of the same class kryo.setReferences(false); // kryo.register(clazz); try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); Output output = new Output(baos)) { kryo.writeClassAndObject(output, t); output.flush(); return baos.toByteArray(); } catch (Exception e) { logger.error(e.getMessage(), e); } return EMPTY_BYTE_ARRAY; } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { return null; } Kryo kryo = kryos.get(); kryo.setReferences(false); kryo.setRegistrationRequired(false); // kryo.register(clazz); logger.debug("Deserialize"); try (Input input = new Input(bytes)) { return (T) kryo.readClassAndObject(input); } catch (Exception e) { logger.error(e.getMessage(), e); } return null; } }
ps: It is important to note that although this class provides a generic object, it is not useful in practice; when deserializing, you do not know what type of object should be returned; therefore, most serialization frameworks take the actual type of object stored in the serialization results;
Again ps: Since kryo is serialized as binary, testing is required for cases where lists, maps, and so on may be generic and the storage objects are not of the same subclass
Injecting Spring Container
Spring Session replaces Session and serializes data through org.springframework.session.data.redis.RedisOperations Session Repository
Looking closely at this class, you can see that it has two more important properties
- RedisSerializer<Object> defaultSerializer
- RedisOperations<Object, Object> sessionRedisOperations
defaultSerializer initialized to JdkSerializationRedisSerializer
Used with the onMessage method, which may respond to Redis value expiration events and is responsible for session deletion and expiration of passed data
It is not clear why serialization instances are not available from session RedisOperations, which needs to be studied
sessionRedisOperations passed in the construction method
Due to the Spring Bean injection order, this value is the EdsTemplate instance automatically created by Spring redis starter, which uses the Jdk serialization scheme and needs to be modified, but there is no way to inject the instance created by itself for the time being
So a compromise is taken to get a RedisOperations SessionRepository instance and manually modify the RedisSerializer inside the RedisTemplate
The final configuration class is as follows
@Configuration @EnableRedisHttpSession public class RedisSessionConfig { @Bean public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory, RedisOperationsSessionRepository repository) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); RedisSerializer<Object> serializer = new KryoRedisSerializer<>(Object.class); // Serializer used by redis value template.setValueSerializer(serializer); // Serializer used by redis key template.setKeySerializer(new StringRedisSerializer()); repository.setDefaultSerializer(serializer); //Since RedisOperationsSessionRepository is constructed first and does not provide a way to modify properties, this is the only way to do it RedisOperations<Object, Object> sessionRedisOperations = repository.getSessionRedisOperations(); if (sessionRedisOperations instanceof RedisTemplate){ RedisTemplate<Object,Object> redisTemplate = ((RedisTemplate<Object, Object>) sessionRedisOperations); redisTemplate.setValueSerializer(serializer); redisTemplate.setHashValueSerializer(serializer); } template.afterPropertiesSet(); return template; } }
ps: More configuration information can also be modified through RedisOperations SessionRepository, such as session valid implementation, cookie name value, etc.
todo
- Find a more appropriate injection Serializer method
Code
Specific implementations are available for reference github