【问题标题】:Reading same cache from different microservices从不同的微服务读取相同的缓存
【发布时间】:2019-03-21 06:17:02
【问题描述】:

我正在使用 spring-data-redis(2.1.5.RELEASE) 和 jedis(2.10.2) 客户端从作为 spring-boot 应用程序运行的不同服务连接到我的 azure redis 实例。

两个服务具有相同的缓存方法,并通过实现以下配置指向相同的缓存。我面临的问题是当一个服务试图读取另一个服务创建的缓存值时,会发生反序列化异常。

例外:

org.springframework.data.redis.serializer.SerializationException:无法反序列化;嵌套异常是 org.springframework.core.serializer.support.SerializationFailedException: 无法反序列化有效负载。字节数组是 DefaultDeserializer 对应序列化的结果吗?嵌套异常是 org.springframework.core.NestedIOException: 无法反序列化对象类型;嵌套异常是 java.lang.ClassNotFoundException

注意:我只使用redis来缓存从我的数据库中读取的数据。

微服务1的Redis缓存配置

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;
}

微服务2的Redis缓存配置

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; }

【问题讨论】:

  • 你能发布你得到的确切异常吗?
  • @MuhammadInshal 我已经添加了我面临的异常。
  • @Venkat 是这两个独立的应用程序,还是同一个应用程序部署了两次?
  • @Jerome 这两个是独立的应用程序,如果两次部署同一个应用程序,服务能够读取缓存数据(由该服务创建)
  • 发现问题。 Employee.java 在不同的包中

标签: spring-boot spring-data-redis azure-redis-cache


【解决方案1】:

要么确保返回的 (de(serialized)) 对象在完全相同的包中,要么阻止在数据中注册类。发生这种情况是因为具有完整包限定名称的类名被设置到数据中(在我的例子中是 JSON)。这是我以前的配置

  • 不要限制使用与 dto 相同的类名!
  • 使用类型化的反(序列化)而不是泛型类型来转换 JSON/转换为 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 提供的类名的原因因为在这种情况下它已经被正确配置了。

【讨论】:

    【解决方案2】:

    我建议您确保 Employee 类是相同的,然后在这些类中添加一个 serialVersionUID 字段。清除 Redis 缓存,然后重试。

    这是来自 java.io.Serializable 接口中的 Javadocs:

    如果一个可序列化的类没有显式声明一个 serialVersionUID,那么 序列化运行时将计算默认的 serialVersionUID 值 为基于类的各个方面的类,如在 Java(TM) 对象序列化规范。但是,强烈 建议所有可序列化的类显式声明 serialVersionUID 值,因为默认的 serialVersionUID 计算是 对可能因编译器而异的类细节高度敏感 实现,因此可能会导致意外 InvalidClassExceptions 在反序列化期间。因此,对 保证跨不同 java 编译器的 serialVersionUID 值一致 实现,可序列化的类必须声明一个显式 serialVersionUID 值。还强烈建议明确 serialVersionUID 声明使用 private 修饰符,其中 可能的,因为此类声明仅适用于立即声明 class--serialVersionUID 字段不能用作继承的成员。

    【讨论】:

    • 我已经按照你的建议添加了serialVersionUID,仍然面临着同样的ClassNotFoundException
    猜你喜欢
    • 2022-01-20
    • 1970-01-01
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    • 2020-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多