SpringBoot интегрирует дневник Redis Pit

Redis

В дополнение к превосходному автоматизированному тестированию для часто используемых реляционных баз данных, SpringBoot также обеспечивает автоматическую поддержку конфигурации для многих баз данных NoSQL, включая: Redis, MongoDB, Elasticsearch, Solr и Cassandra.

Интегрировать Redis

Redis — это очень быстрая нереляционная база данных (нереляционная база данных), она может хранить отображение (mapping) между ключом (key) и 5 ​​различными типами значения (value). Данные пары значений сохраняются на диск. Функцию репликации можно использовать для масштабирования производительности чтения, а сегментирование на стороне клиента — для масштабирования производительности записи.

  • официальный сайт редис
  • Redis китайское сообщество

импортировать зависимости

Spring Boot предоставляет пакет компонентов для интеграции с Redis: spring-boot-starter-data-redis, spring-boot-starter-data-redis зависит от spring-data-redis и салата.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

конфигурация параметров

Добавьте соответствующую конфигурацию сервера Redis в application.properties:

#redis配置
#Redis服务器地址
spring.redis.host=127.0.0.1
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database=0  
#连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=50
#连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=3000ms
#连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=20
#连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=2
#连接超时时间(毫秒)
spring.redis.timeout=5000ms

Конфигурация spring.redis.database обычно использует 0. Redis может указать количество баз данных при настройке.По умолчанию 16, что можно понимать как схему базы данных.

тестовый доступ

Проиллюстрируйте, как получить доступ к Redis, написав тестовые примеры.

@RunWith(SpringRunner.class)
@SpringBootTest
public class FirstSampleApplicationTests {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Test
    public void test() throws Exception {
        // 保存字符串
        stringRedisTemplate.opsForValue().set("name", "chen");
        Assert.assertEquals("chen", stringRedisTemplate.opsForValue().get("name"));
    }
}

Приведенный выше случай автоматически настраиваетсяStringRedisTemplateОбъект выполняет операцию записи redis. Из имени объекта видно, что поддерживается строковый тип. Если вы использовали spring-data-redis, вы должны быть знакомы с ним.RedisTemplate<K,V>интерфейс, StringRedisTemplate эквивалентенRedisTemplate<String, String>реализация.

Помимо типа String, объекты на практике часто хранятся в Redis, и нам нужно сериализовать объекты при их сохранении. Ниже приведен пример завершения операции записи объекта.

Создать сущность пользователя

@Data
public class User implements Serializable {

    private String userName;

    private Integer age;
}

Настройте экземпляр RedisTemplate для объекта

@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {
 
    /**
     * 采用RedisCacheManager作为缓存管理器
     * @param connectionFactory
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheManager redisCacheManager = RedisCacheManager.create(connectionFactory);
        return  redisCacheManager;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        //解决键、值序列化问题
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

}

После завершения работы по настройке напишите эффект эксперимента тестового примера.

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class FirstSampleApplicationTests {
    @Autowired
    RedisTemplate redisTemplate;
    @Test
    public void test() throws Exception {
        //保存对象
        User user = new User();
        user.setUserName("chen");
        user.setAge(22);
        redisTemplate.opsForValue().set(user.getUserName(), user);
        log.info("result:{}",redisTemplate.opsForValue().get("chen"));
    }
}

Таким образом, мы можем кэшировать объект. Но в более глубоком понимании redis я случайно наступил в яму, и ниже приводится запись ямы, на которую наступил redis.

Побить пит-рекорд

Наступление на яму 1: искаженная проблема, вызванная кешируемой аннотацией

@RestController
@RequestMapping("/chen/user")
@Slf4j
public class UserController {
    @Autowired
    IUserService userService;

    @GetMapping("/hello")
    @Cacheable(value = "redis_key",key = "#name",unless = "#result == null")
    public User hello(@RequestParam("name")String name){
        User user = new User();
        user.setName(name);
        user.setAge(22);
        user.setEmail("chen_ti@outlook.com");
        return user;
    }
}

При использовании SpringBoot1.x вы можете просто настроить RedisTemplete, обновить до SpringBoot2.0, spring-boot-starter-data-redis также поднимается, а @Cacheable появляется искаженным, вы можете пройти. Вышеупомянутый файл конфигурации RedisConfiguration решается следующим изменения:

@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {
    @Bean(name="redisTemplate")
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, String> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        // 配置序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))             .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(redisCacheConfiguration)
                .build();
        return cacheManager;
    }
}

Наступая на яму 2: Redis получает исключение кеша

Сообщение об ошибке:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.tuhu.twosample.chen.entity.User

Redis получает исключение кеша: java.lang.ClassCastException: java.util.LinkedHashMap нельзя привести к XXX.

Когда возникает это исключение, нам нужно настроитьObjectMapper, задайте некоторые параметры вместо прямого использования распознанного ObjectMapper в классе Jackson2JsonRedisSerializer. Из исходного кода видно, что ObjectMapper в Jackson2JsonRedisSerializer создается напрямую с использованием new ObjectMapper(), поэтому ObjectMapper десериализует строку в redis в класс java. Тип util.LinkedHashMap приводит к тому, что последующий Spring преобразует его в ошибку. На самом деле он нужен нам только для возврата типа Object.

Используя следующий метод, создайтеJackson2JsonRedisSerializerобъект и внедрить его в RedisCacheManager.

	/**
     * 通过自定义配置构建Redis的Json序列化器
     * @return Jackson2JsonRedisSerializer对象
     */
    private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer(){
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
                new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        // 此项必须配置,否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        return jackson2JsonRedisSerializer;
    }

Шагающая яма 3: путь смены класса

Печать исключений:

19:32:47 INFO  - Started Application in 10.932 seconds (JVM running for 12.296)
19:32:50 INFO  - get data from redis, key = 10d044f9-0e94-420b-9631-b83f5ca2ed30
19:32:50 WARN  - /market/renewal/homePage/index
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Could not resolve type id 'com.pa.market.common.util.UserInfoExt' into a subtype of [simple type, class java.lang.Object]: no such class found
 at [Source: [B@641a684c; line: 1, column: 11]; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'com.pa.market.common.util.UserInfoExt' into a subtype of [simple type, class java.lang.Object]: no such class found at [Source: [B@641a684c; line: 1, column: 11]

причины проблемы:

В проекте используется перехватчик для перехвата каждого http-запроса. Через токен, переданный из внешнего интерфейса, перейдите в кеш Redis, чтобы получить информацию о пользователе UserInfoExt.Информация о пользователе сохраняется в кеше Redis, когда пользователь входит в систему. По полученной информации о пользователе определяется, есть ли статус входа. Поэтому, за исключением URL-адресов в белом списке, другие запросы должны выполнять эту операцию. При печати журнала становится очевидным, что этапы операции сериализации и десериализации в объекте UserInfoExt хранятся в Redis.

Решение:

@Bean
public RedisTemplate<K, V> redisTemplate() {
    RedisTemplate<K, V> redisTemplate = new RedisTemplate<K, V>();
    redisTemplate.setConnectionFactory(jedisConnectionFactory());
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    return redisTemplate;
 }

Глядя на определение bean-компонента redis, обнаруживается, что сериализация ключа использует сериализацию StringRedisSerializer, а сериализация значения значенияGenericJackson2JsonRedisSerializerметод сериализации.

вGenericJackson2JsonRedisSerializerМетод сериализации записывает информацию @class класса в Redis следующим образом:

{
    "@class": "com.pa.market.common.util.UserInfoExt",
    "url": "www.baidu.com",
    "name": "baidu"
}

"@class": "com.pa.market.common.util.UserInfoExt", каждый объект будет иметь этот id (вы можете увидеть, почему существует этот @class через исходный код), если пользователь вошел в систему, да Сериализация с путем com.pa.market.common.util.UserInfoExt. Но после перемещения пути к классам UserInfoExt полное имя пакета изменилось. Таким образом, будет выдано исключение, что такой класс не найден. Таким образом, возникает исключение, когда оценивается, существует ли пользователь, поэтому все запросы терпят неудачу, и вошедший в систему пользователь не может выполнять какие-либо операции.

Хорошо, я записал все ямы, на которые я наступил, и, наконец, выдохнул свой последний вздох, я могу легко избежать таких ям в будущем, но в redis все еще много ям, и я могу легко запрыгнуть в будущем.