最近把公司的公共配置服务工程做了重构,并且在新的工程中加入了二级缓存,默认使用Guava和Redis实现。Guava作为本地一级缓存,Redis作为二级分布式缓存,并支持一二级缓存技术的替换。待工程完善之后,会再写一篇博客分享我在重构过程中的一些想法。

在使用Redis作为二级缓存的过程中,冒出了这么一个想法,我是不是可以将Hash的结构也通过String进行存储。因为Hash其实也可以转换成为String,如下图所示。

shell获取redis数据类型 redis读取hash_redis

这样也可以通过String的数据类型达到Hash的效果,但是还是决定先从存储大小消耗和获取对应key耗时两个方面做一个简单的测试,看看哪种数据结构更优。

下面是我的测试步骤:

1、获取到当前Redis存储大小M0;

2、插入10WString类型数据,记录此时存储大小为M1;

3、插入10WHash类型数据,记录此时存储大小为M2;

4、分别获取500次和1000次String和Hash数据,计算平均获取耗时;

下面是简单的测试代码:

@Test
    public void testInsertString() {
        // 插入10W条数据
        Map<String, String> map = new HashMap<>();
        for (int i = 0; i < 100000; i++) {
            map.put("value : test " + UUID.randomUUID().toString(), i + UUID.randomUUID().toString() + UUID.randomUUID().toString());
        }
        long l = System.currentTimeMillis();
        redisTemplate.opsForValue().multiSet(map);
        System.out.println("========> timeCost = " + (System.currentTimeMillis() - l));
    }

    @Test
    public void testInsertHash() {
        // 插入10W条数据
        Map<String, String> map = new HashMap<>();
        for (int i = 0; i < 100000; i++) {
            map.put(" test " + UUID.randomUUID().toString(), i + UUID.randomUUID().toString() + UUID.randomUUID().toString());
        }
        long l = System.currentTimeMillis();
        redisTemplate.opsForHash().putAll("value :", map);
        System.out.println("========> timeCost = " + (System.currentTimeMillis() - l));
    }

    @Test
    public void testGetTime() {
        redisTemplate.opsForHash().get("value :", " test aa183d07-dbf8-4a66-af1b-351ff8c07ba0");
        long time1 = System.currentTimeMillis();
        for (int i = 0; i < 200; i++) {
            Object o1 = redisTemplate.opsForHash().get("value :", " test 31eab493-f227-4a68-bf92-2fe822a1a212");
            Object o2 = redisTemplate.opsForHash().get("value :", " test d081190d-b4d9-4e27-9b4b-cc2a3cbd7c60");
            Object o3 = redisTemplate.opsForHash().get("value :", " test 651c5b2d-48e5-45ca-a21f-0d74a4588765");
            Object o4 = redisTemplate.opsForHash().get("value :", " test 9761a7a2-b341-47aa-89f1-1c1a9c4f03ed");
            Object o5 = redisTemplate.opsForHash().get("value :", " test 1736e334-3017-430d-8cd1-37c9e287fdb7");
        }
        long time2 = System.currentTimeMillis();
        System.out.println("========> hash get timeCost = " + ( time2 - time1));

        for (int i = 0; i < 200; i++) {
            Object o1 = redisTemplate.opsForValue().get("value : test 0006713f-51bc-47ae-9272-852a2dbc17e5");
            Object o2 = redisTemplate.opsForValue().get("value : test 00075fef-3310-4669-9c26-c8c416269cf9");
            Object o3 = redisTemplate.opsForValue().get("value : test 0009e0b4-3ac4-44fb-adf5-8cd042c6dbce");
            Object o4 = redisTemplate.opsForValue().get("value : test 91d927fa-a929-4453-8e42-5e20b58586dd");
            Object o5 = redisTemplate.opsForValue().get("value : test d60c0d5f-904f-425f-85be-58c999b1d2f2");
        }
        long time3 = System.currentTimeMillis();
        System.out.println("========> key value get timeCost = " + ( time3 - time2));
    }

初始大小M0 = 853.30K

shell获取redis数据类型 redis读取hash_System_02

下面是插入10W String数据之后,M1 = 20.13M

shell获取redis数据类型 redis读取hash_hash_03

插入10W Hash之后,M2 = 37.15:

shell获取redis数据类型 redis读取hash_System_04

即:10W String所占用内存为M1 - M2 = 19.30M,10W Hash所占用内存为 M3 - M2 = 17.02M。从数据上来说,Hash所耗内存更小,因为hKey复用了。

下面看看获取值耗时:

shell获取redis数据类型 redis读取hash_数据结构_05

这是分别获取1000次String和1000次Hash的结果,单次耗时相差0.174毫秒,几乎可以忽略不计。

所以我们在写代码的时候,还是需要根据实际场景来决定使用Hash结构或者String结构,不用过分关注性能和占用内存大小,差距不大。

对于需要存储对象,并且会对该对象当中的某些属性进行修改的时候,我们适合使用Hash结构。但是目前Redis只能对第一层的key设置超时时间,所以想要精准的控制超时时间,还是得需要使用String,或者自己使用其他补偿方法实现。

 

在写代码的过程中,如果有什么疑问,其实大可以写个简单的demo来验证下自己的想法,还是很有帮助的。