Redis实践-存储Java对象

Redis作为目前主流的缓存数据库,提供了丰富的数据结构,这次做的项目里我们大量使用了Redis作为数据缓存,甚至在某些场景下直接作为了数据库使用(当然这是建立在公司内部基础组件足够可靠的前提下),由于项目是从零开始,在使用Redis的过程中,遇到了一些设计上的挑战和问题,因此特别在博客里记录下来。

最基础的肯定就是Java对象的存储,使用SQL型数据库的时候,可以借助ORM框架直接进行把Java对象映射到表中的一行,但是Redis作为K-V型数据库,显然是无法直接这样做的。

1. 序列化对象存储二进制

我们知道Java对象可以序列化成二进制数据,基于此点,我们可以直接把Java对象存储到RedisString结构中。

1.1 序列化对象

这里选择使用protostuff作为序列化工具,maven项目中添加如下依赖:

        <dependency>
            <groupId>io.protostuffgroupId>
            <artifactId>protostuff-coreartifactId>
            <version>1.6.2version>
        dependency>

        <dependency>
            <groupId>io.protostuffgroupId>
            <artifactId>protostuff-runtimeartifactId>
            <version>1.6.2version>
        dependency>

编写代码,SerializationUtil 工具类使用protostuff序列化/反序列化对象。

@Slf4j
public class SerializationUtil {

    // 避免protostuff每次序列化都重新申请Buffer空间
    private static LinkedBuffer buffer = LinkedBuffer.allocate();

    /**
     * 序列化对象到字节数组
     *
     * @param obj   待序列化对象
     * @param clazz 待序列化对象类型
     * @return 序列化得到字节数组
     */
    public static <T> byte[] serialize(T obj, Class<T> clazz) {
        Schema<T> schema = RuntimeSchema.getSchema(clazz);
        byte[] data;
        try {
            data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } finally {
            buffer.clear();
        }
        return data;
    }

    /**
     * 但序列化字节数组到指定类型对象
     *
     * @param data  字节数组
     * @param clazz 指定类型
     * @return 反序列化得到对象
     */
    public static <T> T deserialize(byte[] data, Class<T> clazz) {
        Schema<T> schema = RuntimeSchema.getSchema(clazz);
        T obj = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(data, obj, schema);
        return obj;
    }
}

这里我们定义一个Java类TestBean ,作为测试类(这里注解使用Lombok

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestBean {
    private String aString;
    private Integer aInteger;
    private Double aDouble;
    private List<String> stringList;
}

这里我使用了Jedis作为Redis连接驱动,当然你也可以使用Spring-Data-Redis或者Lettuce,这里测试一下代码:

@Slf4j
public class JedisTest {

    public static void main(String[] args) throws UnsupportedEncodingException {
        // 建立redis连接到本地
        Jedis jedis = new Jedis("localhost");
        log.debug("jedis测试连接:{}", jedis.ping());
        // 新建一个测试对象
        TestBean testBean = new TestBean(null, 1, 0.1, Arrays.asList("1", "2", "3"));
        byte[] redisKey = "test:string:1".getBytes("UTF8");
        byte[] redisValue = SerializationUtil.serialize(testBean, TestBean.class);
        log.debug("jedis返回状态:{}", jedis.set(redisKey, redisValue));
    }

}

运行以后,可以看到已经成功写入Redis了。
在这里插入图片描述
我们在Redis-Cli里看一下我们写入的键,然而这里看到的全是十六进制,这是为什么呢?
Redis实践-存储Java对象_第1张图片
答案很简单,因为Protostuff是按照自己定义的规则序列化了对象,得到的是二进制数组,这里甚至可以打印一下序列化得到的二进制数组:

        log.debug("序列化得到的二进制数据:{}", redisValue);

		/**
		* 2019-12-16 20:40:59 DEBUG com.cringkong.mytest.service.JedisTest:21 - 
		* 序列化得到的二进制数据:
		* [16, 1, 25, -102, -103, -103,
		* -103, -103, -103, -71, 63, 34, 1, 
		* 49, 34, 1, 50, 34, 1, 51]
		*/

1.2 反序列化

也就是说,序列化以后的对象,我们是不可读的,只有Protostuff按照一定的规则反序列化后,才能得到一个对象,这里我们反序列化:

    public static void main(String[] args) throws UnsupportedEncodingException {
        // 建立redis连接到本地
        Jedis jedis = new Jedis("localhost");
        log.debug("jedis测试连接:{}", jedis.ping());
        // 新建一个测试对象
        TestBean testBean = new TestBean(null, 1, 0.1, Arrays.asList("1", "2", "3"));
        byte[] redisKey = "test:string:1".getBytes("UTF8");
        byte[] redisValue = SerializationUtil.serialize(testBean, TestBean.class);
        log.debug("序列化得到的二进制数据:{}", redisValue);
        log.debug("jedis返回状态:{}", jedis.set(redisKey, redisValue));
        // 从redis中取数据
        byte[] getValue = jedis.get(redisKey);
        // 反序列化数据
        log.debug(SerializationUtil.deserialize(getValue, TestBean.class).toString());
        }

可以看到反序列化得到我们存储到Redis中的对象:
在这里插入图片描述

2. 序列化对象存储Json字符串

Java对象可以序列化成二进制数组,当然也可以序列化成Json字符串,这里我们可以直接将Json字符串存储进Redis中。

这里选择jackson作为Json序列化工具,当然使用FastJson或者Gson也是完全没有问题的。

public class SerializationUtil {

    private static ObjectMapper jacksonMapper = new ObjectMapper();

    /**
     * 序列化对象成Json字符串
     *
     * @param o 对象
     * @return Json字符串
     */
    public static String ObjectToJson(Object o) {
        try {
            return jacksonMapper.writeValueAsString(o);
        } catch (JsonProcessingException e) {
            log.error("序列化生成Json出现", e);
        }
        return null;
    }

    /**
     * 反序列化Json成置顶类型对象
     *
     * @param json  Json字符串
     * @param clazz 泛型类型
     * @return 指定类型对象
     */
    public static <T> T jsonToObject(String json, Class<T> clazz) {
        try {
            return jacksonMapper.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            log.error("Json反序列化出现问题", e);
        }
        return null;
    }
}

测试代码如下:


    public static void main(String[] args) throws UnsupportedEncodingException {
        // 建立redis连接到本地
        Jedis jedis = new Jedis("localhost");
        log.debug("jedis测试连接:{}", jedis.ping());
        // 新建一个测试对象
        TestBean testBean = new TestBean("亦可赛艇", 123, 0.1123, Arrays.asList("13", "42", "53"));
        log.debug("jedis返回状态:{}", jedis.set("test:string:2", SerializationUtil.ObjectToJson(testBean)));
        log.debug("redis直接取值:{}",jedis.get("test:string:2"));
    }

Redis实践-存储Java对象_第2张图片
这里可以看到,redis中存储的值就是一个Json字符串,同理我们直接在Redis-cli中查看:
Redis实践-存储Java对象_第3张图片
这里可以看到数据是可读的,当然这里面的astring是十六进制,这是因为汉字在UTF8编码下占用24位二进制的原因。

这样反序列化我们就很熟悉了,得到一个对象:
在这里插入图片描述

3. 两种方式对比

优劣对比 序列化成二进制 序列化成JSON
序列化速度 相对慢
序列后大小 相对大
序列化后可读性 完全不可读 可读
Redis中数据类型 String String

其实序列化成Json最大的优势就在于可读性,便于我们在项目开发中更快的发现、定位代码中可能存在的问题,这里序列化成二进制特指使用Protostuff,如果使用Java自带的序列化方式,可能速度会比序列化成Json还要慢。

另外需要说明一点的是JedisJson字符串的时候,还会进行一次编码:
Redis实践-存储Java对象_第4张图片
Redis实践-存储Java对象_第5张图片
也就是说如果直接存储二进制数组的话,Jedis代码层面上的性能开销也会更小,总之序列化成二进制的有点就是性能要比序列化成Json好很多。

因此大家在开发环境下选择哪种方式,还是要自行判断场景进行定夺。

你可能感兴趣的:(数据库,中间件使用)