A Redis production accident cost the company millions

1, Antecedents

There is a core project of the company. The client of redis has always used jedis. Later, the technical director asked to replace the jedis client with a more efficient lettuce client, and use the RedisTemplate class of spring framework to operate redis.

However, the world is unpredictable. It is such a simple demand that makes the teacher overturn the boat...

2, Accident rehearsal

According to the preset results, this development task should be very easy:

  1. Translate and replace the configuration item of jedis connection pool in the configuration file with lettuce;
  2. Delete the jedis configuration related codes in the project;
  3. Replace the place where jedis is used with redisTemplate.

Pseudo code

Other configuration items are not displayed one by one

spring.redis.jedis.pool.max-idle = 200
spring.redis.jedis.pool.min-idle = 10
spring.redis.jedis.pool.max-active = 200
spring.redis.jedis.pool.max-wait = 2000
 Copy code

replace with

spring.redis.lettuce.pool.max-idle = 200
spring.redis.lettuce.pool.min-idle = 10
spring.redis.lettuce.pool.max-wait = 2000
spring.redis.lettuce.pool.max-active = 200
 Copy code

The business code is also changed from jedis to redisTemplate

Pseudo code of jedis:

/**
 * Set commodity inventory to redis - jedis
 * @param goodId Commodity id
 * @param count Inventory
 * @return
 */    
@PatchMapping("/storage/jedis")
public String setStorageByJedis(
    @RequestParam("goodId") String goodId,
    @RequestParam("count") String count) {
    Jedis jedis = getJedis();
    jedis.set("good:" + goodId, count);
    jedis.close();
    return "success";
}
Copy code

Pseudo code of redisTemplate:

/**
 * Set commodity inventory to redis - redisTemplate
 * @param goodId Commodity id
 * @param count Inventory
 * @return
 */
@PatchMapping("/storage")
public String setStorage(
    @RequestParam("goodId") String goodId,
    @RequestParam("count") String count) {
    redisTemplate.opsForValue().set("good:" + goodId, count);
    return "success";
}
Copy code

However, after all the work was done and released online with full confidence, online bug s broke out in a large area. It is a serious production accident.

From the error log, we can clearly see that because the data of String type cannot be converted into int type, a big question mark appears in my heart: clearly, what I saved in redis is a String that can be converted into digital type?

Cause analysis

View data through redis Desktop Manager visualization tool

It is found that the key value of string type has an extra pair of double quotation marks

what! Is there a time to change to disje?

After checking the code, it seems that one step is missing in the process of using redisTemplate: configuring serialization. Generally, if there is no special configuration or redis connection pool is to be used, it can only be added in the configuration center or configuration file

spring.redis.host = 172.0.0.1
spring.redis.port = 6379
spring.redis.password = 123456Copy to clipboardErrorCopied
 Copy code

Then inject redisTemplate and you can use it. It's very simple.

However, the default serializer used by RedisTemplate is JDK's own serializer. See the source code:

RedisTemplate class diagram

Because RedisTemplate inherits RedisAccessor and RedisAccessor implements InitializingBean, after the initialization of RedisTemplate class, you can override afterpropertieset() method to set serializer.

Solution

Write a redis configuration class and reset the serializer.

@Configuration
@ConditionalOnClass(RedisOperations.class)
public class RedisTemplateAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(name="redisTemplate")
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate template=new RedisTemplate();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
Copy code

Here, the StringRedisSerializer serializer is only configured for the string type of redis. You can add the configuration of Hash object type according to the actual needs of the project.

Spring comes with a variety of serializers, as follows

You can also customize the serializer. You need to implement the RedisSerializer interface and override the serialize() and deserialize() methods.

For the convenience of demonstration, the global redis configuration class is not written, and the serializer is reset directly in the interface. The pseudo code is as follows:

@PatchMapping("/storage")
public String setStorage(
    @RequestParam("goodId") String goodId,
    @RequestParam("count") String count) {
    redisTemplate.setKeySerializer(new StringRedisSerializer()); // Reset the serializer of redis string type key
    redisTemplate.setValueSerializer(new StringRedisSerializer()); // Reset the serializer of redis string type value
    redisTemplate.opsForValue().set("good:" + goodId, count);
    return "success";
}

Keywords: Java Redis Programmer Cache

Added by HAVOCWIZARD on Thu, 10 Mar 2022 14:48:00 +0200