最近把公司的公共配置服务工程做了重构,并且在新的工程中加入了二级缓存,默认使用Guava和Redis实现。Guava作为本地一级缓存,Redis作为二级分布式缓存,并支持一二级缓存技术的替换。待工程完善之后,会再写一篇博客分享我在重构过程中的一些想法。
在使用Redis作为二级缓存的过程中,冒出了这么一个想法,我是不是可以将Hash的结构也通过String进行存储。因为Hash其实也可以转换成为String,如下图所示。
这样也可以通过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
下面是插入10W String数据之后,M1 = 20.13M
插入10W Hash之后,M2 = 37.15:
即:10W String所占用内存为M1 - M2 = 19.30M,10W Hash所占用内存为 M3 - M2 = 17.02M。从数据上来说,Hash所耗内存更小,因为hKey复用了。
下面看看获取值耗时:
这是分别获取1000次String和1000次Hash的结果,单次耗时相差0.174毫秒,几乎可以忽略不计。
所以我们在写代码的时候,还是需要根据实际场景来决定使用Hash结构或者String结构,不用过分关注性能和占用内存大小,差距不大。
对于需要存储对象,并且会对该对象当中的某些属性进行修改的时候,我们适合使用Hash结构。但是目前Redis只能对第一层的key设置超时时间,所以想要精准的控制超时时间,还是得需要使用String,或者自己使用其他补偿方法实现。
可以参见这篇来选择合适的数据结构(https://juejin.im/post/6844904023120691213)。
在写代码的过程中,如果有什么疑问,其实大可以写个简单的demo来验证下自己的想法,还是很有帮助的。