RedisTemplate 中序列化方式辨析

在Spring Data Redis中,RedisTemplate 是操作Redis的核心类,它提供了丰富的API来与Redis进行交互。由于Redis是一个键值存储系统,它存储的是字节序列,因此在使用RedisTemplate时,需要指定键(Key)和值(Value)的序列化方式。不同的序列化方式适用于不同的场景。下面将详细介绍几种序列化方法。

序列化如下对象

User 类

java 复制代码
public class User implements Serializable {
    String name;
    String ID;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", ID='" + ID + '\'' +
                '}';
    }

    public User(String name, String ID) {
        this.name = name;
        this.ID = ID;
    }

    public User() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getID() {
        return ID;
    }

    public void setID(String ID) {
        this.ID = ID;
    }
}

JdkSerializationRedisSerializer

JdkSerializationRedisSerializer 是使用JDK自带的序列化机制(ObjectOutputStream 和 ObjectInputStream)来序列化和反序列化POJO对象。这种序列化方式会将对象转换成字节序列,并存储在Redis中。这是RedisTemplate中默认的序列化策略之一(但通常不是推荐用于生产环境的默认策略,因为JDK序列化通常效率较低且生成的字节序列较大)。最大的缺点就是:要求序列化的对象要求继承Serializable类,这是DTO无法容忍的一个要求。

优点:
  • 与其他两个比几乎没有优点,超级不推荐!
缺点:
  • 二进制形式存储,不利于查看!94 bytes
  • 序列化生成的字节序列较大,导致网络传输和存储成本较高。
  • 序列化速度慢。
  • 序列化的字节序列是私有的,不便于跨语言或跨平台共享。
代码示例
java 复制代码
    @Test
    public void test4(){
        User user = new User("李白","123456");

//        GenericToStringSerializer<Object> genericToStringSerializer = new GenericToStringSerializer<>(Object.class);
//        redisTemplate.setKeySerializer(genericToStringSerializer);
//        JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
//        redisTemplate.setValueSerializer(jdkSerializationRedisSerializer);

        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(RedisSerializer.java());

        // 记录开始时间
        Instant start = Instant.now();
        redisTemplate.opsForValue().set("test4",user);
        User user2 = (User)redisTemplate.opsForValue().get("test4");
        logger.info(user2.toString());
        // 记录结束时间
        Instant end = Instant.now();

        // 计算运行时间
        long duration = Duration.between(start, end).toMillis();
        logger.info(duration+"ms");
    }

StringRedisSerializer

StringRedisSerializer 是最简单的序列化器,它直接将字符串(或任何可以转换为字符串的数据)作为字节序列存储,不需要进行任何特殊的序列化操作。它适用于键或值为字符串的场景。

优点:
  • 效率高,不需要额外的序列化/反序列化开销。
  • 易于理解和维护。
缺点:
  • 只能用于字符串数据。

Jackson2JsonRedisSerializer

Jackson2JsonRedisSerializer 是基于Jackson库实现的JSON序列化器,它可以将Java对象序列化成JSON格式的字符串,并存储在Redis中。Jackson是一个流行的JSON处理库,提供了丰富的API来序列化和反序列化Java对象。

优点:
  • User 对象不需要实现 Serializable接口。
  • 生成的JSON格式易于阅读和调试。
  • 序列化后的数据相对较小,传输和存储效率较高,64 bytes。
  • 支持复杂的Java对象,包括嵌套对象和集合。
java 复制代码
@Test
public void test3(){
    User user = new User("李白","123456");

    redisTemplate.setKeySerializer(RedisSerializer.string());
//        Jackson2JsonRedisSerializer<User> userJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(User.class);
//        redisTemplate.setValueSerializer(userJackson2JsonRedisSerializer);
    redisTemplate.setValueSerializer(RedisSerializer.json());

    // 记录开始时间
    Instant start = Instant.now();
    redisTemplate.opsForValue().set("test3",user);
    User user2 = (User)redisTemplate.opsForValue().get("test3");
    logger.info(user2.toString());
    // 记录结束时间
    Instant end = Instant.now();

    // 计算运行时间
    long duration = Duration.between(start, end).toMillis();
    logger.info(duration+"ms");
}

GenericFastJsonRedisSerializer

GenericFastJsonRedisSerializer 是基于Fastjson库实现的JSON序列化器,与JacksonJsonRedisSerializer类似,但它使用的是Fastjson库。Fastjson是另一个流行的JSON处理库,以其高性能著称。

优点:
  • 序列化性能高。
  • 支持复杂的Java对象。
  • 生成的JSON格式易于阅读和调试。
缺点:
  • 可能存在安全漏洞(历史版本中曾发现过安全问题,使用时需注意版本),据测试,FastJson性能不能完全超过Json库,建议生产中别用!。
java 复制代码
    @Test
    public void test5(){
        User user = new User("杜甫","123456");

        redisTemplate.setKeySerializer(RedisSerializer.string());
        GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
        redisTemplate.setValueSerializer(genericFastJsonRedisSerializer);

        // 记录开始时间
        Instant start = Instant.now();
        redisTemplate.opsForValue().set("test5",user);
        User user2 = (User)redisTemplate.opsForValue().get("test5");
        logger.info(user2.toString());
        // 记录结束时间
        Instant end = Instant.now();

        // 计算运行时间
        long duration = Duration.between(start, end).toMillis();
        logger.info(duration+"ms");
    }

总结

  • 可读性:JacksonJsonRedisSerializer 和 GenericFastJsonRedisSerializer 皆可
  • 内存占用:GenericFastJsonRedisSerializer 59 bytes 小于 JacksonJsonRedisSerializer 60 bytes 小于 JdkSerializationRedisSerializer 94 bytes。
  • 耗时:JacksonJsonRedisSerializer 和 GenericFastJsonRedisSerializer 差不多 且 都比 JdkSerializationRedisSerializer 。

生产环境中,无脑选择JacksonJsonRedisSerializer即可!

总的来说,在选择序列化器时,应根据具体的应用场景和需求来决定使用哪种序列化方式。对于大多数基于Spring Boot和Spring Data Redis的项目,推荐使用JacksonJsonRedisSerializer 来序列化和反序列化Java对象,因为它们提供了灵活性和高性能。

嵌套对象测试

java 复制代码
    public Map<String, List<User>> getNestedObj(){
        ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            users.add(new User(UUID.randomUUID().toString().substring(0,8),UUID.randomUUID().toString()));
        }
        HashMap<String, List<User>> nestedObj = new HashMap<>();
        nestedObj.put("one",users);

        return nestedObj;
    }

JdkSerializationRedisSerializer

java 复制代码
    @Test
    public void test11(){

        Map<String, List<User>> nestedObj = getNestedObj();
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(RedisSerializer.java());
        // 记录开始时间
        Instant start = Instant.now();
        redisTemplate.opsForValue().set("test11",nestedObj);
        // 记录结束时间
        Instant end = Instant.now();

        // 计算运行时间
        long duration = Duration.between(start, end).toMillis();
        logger.info(duration+"ms");

        // 从redis获取nestedObj并反序列化
        Map<String, List<User>> nestedObj2 = (Map<String, List<User>>)redisTemplate.opsForValue().get("test11");
        logger.info(nestedObj2.toString());

    }

JacksonJsonRedisSerializer

java 复制代码
    @Test
    public void test12(){

        Map<String, List<User>> nestedObj = getNestedObj();
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(RedisSerializer.json());
        // 记录开始时间
        Instant start = Instant.now();
        redisTemplate.opsForValue().set("test12",nestedObj);
        // 记录结束时间
        Instant end = Instant.now();

        // 计算运行时间
        long duration = Duration.between(start, end).toMillis();
        logger.info(duration+"ms");

        // 从redis获取nestedObj并反序列化
        Map<String, List<User>> nestedObj2 = (Map<String, List<User>>)redisTemplate.opsForValue().get("test12");
        logger.info(nestedObj2.toString());

    }

GenericFastJsonRedisSerializer

java 复制代码
 @Test
    public void test13(){

        Map<String, List<User>> nestedObj = getNestedObj();
        redisTemplate.setKeySerializer(RedisSerializer.string());
        GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
        redisTemplate.setValueSerializer(genericFastJsonRedisSerializer);

        // 记录开始时间
        Instant start = Instant.now();
        redisTemplate.opsForValue().set("test13",nestedObj);
        // 记录结束时间
        Instant end = Instant.now();

        // 计算运行时间
        long duration = Duration.between(start, end).toMillis();
        logger.info(duration+"ms");

        // 从redis获取nestedObj并反序列化
        Map<String, List<User>> nestedObj2 = (Map<String, List<User>>)redisTemplate.opsForValue().get("test13");
        logger.info(nestedObj2.toString());

    }
相关推荐
IT大白鼠5 天前
Redis 系列 · 第 01 篇——认知入门:Redis 是什么
redis·nosql
彧azz5 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
lbb 小魔仙5 天前
OpenClaw + cpolar 实战:远程 NAS、分享小游戏、RDP,再配置公网 AI 入口
数据库·人工智能·redis·oracle·prometheus
夕除5 天前
redis--018
redis
两锭子6 天前
Redis基础
redis
烟沙九洲6 天前
Redis 缓存穿透、缓存击穿、缓存雪崩
数据库·redis
字节探索6 天前
Redis 只会当缓存用?这 10 大实战场景,让你的系统快到飞起
redis·后端
于樱花森上飞舞6 天前
【Redis】哨兵详解
java·开发语言·数据库·redis
Rain的Java大神之路6 天前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
Wang's Blog6 天前
Java 接入Redis: 通用命令与键管理
java·服务器·redis