Чтение одного и того же кеша из разных микросервисов

Я использую клиент spring-data-redis(2.1.5.RELEASE) и jedis(2.10.2) для подключения к моему экземпляру azure redis из разных служб, работающих как приложение spring-boot.

Две службы имеют одинаковые методы кэширования и указывают на один и тот же кеш, реализуя следующую конфигурацию. Проблема, с которой я сталкиваюсь, заключается в том, что когда одна служба пытается прочитать кэшированное значение, созданное другой службой, возникает исключение де-серализации.

Исключение:

org.springframework.data.redis.serializer.SerializationException: Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is org.springframework.core.NestedIOException: Failed to deserialize object type; nested exception is java.lang.ClassNotFoundException

Примечание: Я использую Redis только для кэширования данных, считанных из моей базы данных.

Redis Cache Конфигурация микросервиса 1

public RedisCacheWriter redisCacheWriter(RedisConnectionFactory connectionFactory) {
    return RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
}

@Bean
public RedisCacheManager cacheManager() {
    Map<String, RedisCacheConfiguration> cacheNamesConfigurationMap = new HashMap<>();
    cacheNamesConfigurationMap.put("employers", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(90000)));
    cacheNamesConfigurationMap.put("employees", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(90000)));

    RedisCacheManager manager = new RedisCacheManager(redisCacheWriter(), RedisCacheConfiguration.defaultCacheConfig(), cacheNamesConfigurationMap);
    manager.setTransactionAware(true);
    manager.afterPropertiesSet();

    return manager;
}

Redis Cache Конфигурация микросервиса 2

public RedisCacheWriter redisCacheWriter(RedisConnectionFactory connectionFactory) {
    return RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
}

@Bean
public RedisCacheManager cacheManager() {
    Map<String, RedisCacheConfiguration> cacheNamesConfigurationMap = new HashMap<>();
    cacheNamesConfigurationMap.put("employees", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(90000)));

    RedisCacheManager manager = new RedisCacheManager(redisCacheWriter(), RedisCacheConfiguration.defaultCacheConfig(), cacheNamesConfigurationMap);
    manager.setTransactionAware(true);
    manager.afterPropertiesSet();

    return manager;
}

Методы кэширования в обоих сервисах

@Cacheable(value = "employees", key = "#employeesId") public Employee getEmployee(String employeesId) { //methods }

Класс сотрудников в обеих службах

public class Employee implements Serializable { private String id; private String name; }

Можете ли вы опубликовать точное исключение, которое вы получаете?

Muhammad Inshal 21.03.2019 09:35

@MuhammadInshal Я добавил исключение, с которым столкнулся.

Venkat 21.03.2019 10:50

@Venkat — это два отдельных приложения или одно и то же приложение развернуто дважды?

Jerome 22.03.2019 08:32

@Jerome эти два являются отдельными приложениями, и если одно и то же приложение будет развернуто дважды, служба сможет читать кэшированные данные (которые создаются этой службой)

Venkat 26.03.2019 07:30

Нашел проблему. Имел Employee.java в разных пакетах

Venkat 28.03.2019 10:41

@Venkat, ты нашел какое-нибудь решение для этого?

Roul 07.08.2021 17:19

@Roul Как уже упоминалось. Классы сотрудников сериализуются с именем пакета. Таким образом, в разных микросервисах класс Employee должен добавляться по одному и тому же пути к пакету. Например. : src/java/com/company/domain/Employee

Venkat 09.08.2021 15:46
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
2
7
1 434
2

Ответы 2

Я бы посоветовал вам убедиться, что классы Employee идентичны, а затем добавить в классы поле serialVersionUID. Очистите кэш Redis и повторите попытку.

Это из Javadocs в интерфейсе java.io.Serializable:

If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification. However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization. Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class--serialVersionUID fields are not useful as inherited members.

Я добавил serialVersionUID, как вы предложили, но все равно сталкиваюсь с тем же ClassNotFoundException

Venkat 26.03.2019 06:47

Либо убедитесь, что возвращенный (де(сериализованный)) объект находится в том же пакете, либо запретите регистрацию класса в данных. Это происходит потому, что имя класса с полным именем пакета устанавливается в данные (в моем случае JSON). Вот конфигурация, которую я использовал

  • Не ограничивайтесь использованием того же имени класса, что и dto!
  • Используйте типизированную де-(сериализацию), а не общие типы для преобразования из/в JSON
  • Удалить отметку класса, поставленную в данных
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

@Configuration
@RequiredArgsConstructor
public class RedisConfig {

    @Bean
    RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
        return builder -> {
            Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>();
            configurationMap.put("whatever-cache-name",
                    RedisCacheConfiguration.defaultCacheConfig()
                            .entryTtl(Duration.ofSeconds(3600))
                            .serializeValuesWith(
                                    SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Employee.class))
                            )
            );
            builder.withInitialCacheConfigurations(configurationMap);
        };
    }

}

Только

  • Удалить отметку класса, поставленную в данных

Просто используйте вместо этого другой сериализатор

@AutoWired
ObjectMapper objectMapper;

@Bean
    RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
        return builder -> {
            Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>();
            configurationMap.put("whatever-cache-name",
                    RedisCacheConfiguration.defaultCacheConfig()
                            .entryTtl(Duration.ofSeconds(3600))
                            .serializeValuesWith(
                                    SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper))
                            )
            );
            builder.withInitialCacheConfigurations(configurationMap);
        };
    }

Обратите внимание, что по умолчанию GenericJackson2JsonRedisSerializer создает новый ObjectMapper, если вы не укажете его в конструкторе, и реализация по умолчанию добавит полное имя класса в данные, и поэтому я использовал тот, который предоставлен Spring, поскольку он был правильно настроен достаточно в тот случай.

Другие вопросы по теме