SpringBoot implements distributed session

Keywords: Programming Session Redis Spring JDK

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

Posted by frosero on Sun, 15 Sep 2019 19:15:18 -0700