正如标题说的,redis提供简单的缓存读取,读写性能可以在10ms级别,有利有弊,内存限制也成了redis稳定与否的关键,特别是海量集key的场景,内存优化是跳不过去的坎。
假设一个1亿个key的场景,key是32位的guid,value是个人信息,假设100个字符,redis key占用的空间就要5g,小公司单机16g内存来说还是比较可观的。
第一种优化方式牺牲时间换空间。
简单能想到到的一个key存多个value,reids的hashset正好可以做到这一点,
假设我们有5个key:
key1_afdfdfdffffffadeeecfyyiiid: key1value
key2_cdfadfddrgfgfdgggadda: key2value
key3_aeedfddeedgggaddabd: key3value
key4_yyrrrtttgggaddahjkpppiy: key4value
key5_rdfaerergggaddabbgbgb: key5value
我们用一个简单的函数对key按10000取模,newkey = hash10000(key),所有key的结果都会落在1到10000之间,所以有很多key的取模会一样,这个就是key压缩的关键。
假设key1和key2的取模结果都是5,key3,4,5的结果是8,那么key1和key2都可以放入
hashset_5中,3,4,5放入hashset_8中,filed是真正的key,这样只用2个hash就替代了原来的5个key,付出的代价是取值的时候需要先把key用newkey函数计算属于哪个hash而已。
第二种是利用reids的 bitmap位图数据结构。
这种情况对key和value有特殊的场景限制,比如我们有很多用户,然后想统计是否上线,或者是一个网站有很多不同的产品,想统计某个产品是否被访问,也即是value必须是0或1能表示。
传统我们会用
13535366:1
31135355:0
这样1亿个用户就需要1亿的key,实在太浪费了。
而bitmap是一个byte数组,可以把不同的用户id对应到这个数字的不同位置,也就是说我们可以只用一个bitmap对象来存放所有用户的信息!!!
0000 0000 0000 0000 0000 0000 0000
从左往右数,id位1的用户打卡了,就把第一个位设置位1
如下图表示id位1和10的用户都打卡了。
0000 0000 0000 0000 0010 0000 0001
bitmap命令,设置和获取某一个位置的值:
SETBIT key useid 1
GETBIT key userid
bitmap对reidis来说就是一个字符串,所以最大长度是redis字符串最大长度512mb,换算成bit是4294967296,所以小于42亿的数字都可以表示。
第三种方式我们可以对结果进行压缩。
以前用PHP开发,我们会把一个对象用json_encode(obj)的方式转正字符给redis,这个就是用到了序列化,不同序列方式的结果大小一样。
一般常用的序列化有JdkSerializationRedisSerializer和GenericFastJsonRedisSerializer,网上有的文章说jdk序列化省内存,我觉得还是测试看下好。
我建了一个对象,包含两个简单的属性。
public class RedisObject implements Serializable {
private String fied1 = "";
private String fied2 = "";;
public RedisObject(String value){
this.fied1 = value;
this.fied2 = value;
}
}
String value1 = "Licensed to the Apache Software Foundation (ASF) under one or more" ;
String value2 = "Unless required by applicable law or agreed to in writing";
fastjson序列化后的结果
"{"@type":"com.example.demo.myservice.emtity.RedisObject","fied1":"Licensed to the Apache Software Foundation (ASF) under one or more","fied2":"Unless required by applicable law or agreed to in writing"}"
jdk序列化的结果要大一些,主要是描述字段类型耗费的额外字符比fastjson多。
"\xac\xed\x00\x05sr\x00-com.example.demo.myservice.emtity.RedisObjectQ5i\x01\xf1\xf3)6\x02\x00\x02L\x00\x05fied1t\x00\x12Ljava/lang/String;L\x00\x05fied2q\x00~\x00\x01xpt\x00BLicensed to the Apache Software Foundation (ASF) under one or moret\x009Unless required by applicable law or agreed to in writing"
假设我们把两个value设置成一样值,再看看。
String value1 = "Licensed to the Apache Software Foundation (ASF) under one or more" ;
String value2 = "Licensed to the Apache Software Foundation (ASF) under one or more";
json序列化还是不会压缩,而jdk序列化结果小了一些,相同的原文只出现了一次。
"{"@type":"com.example.demo.myservice.emtity.RedisObject","fied1":"Licensed to the Apache Software Foundation (ASF) under one or more","fied2":"Licensed to the Apache Software Foundation (ASF) under one or more"}"
"\xac\xed\x00\x05sr\x00-com.example.demo.myservice.emtity.RedisObjectQ5i\x01\xf1\xf3)6\x02\x00\x02L\x00\x05fied1t\x00\x12Ljava/lang/String;L\x00\x05fied2q\x00~\x00\x01xpt\x00BLicensed to the Apache Software Foundation (ASF) under one or moreq\x00~\x00\x03"
一般我们对象里比如分类等信息也是会有一些重复,所以不太好说用哪种方式耗内存就一定少,最好的方法还是跑一下线上的数据,看下最终耗的内存数和执行时间来采取合适自己程序的序列化方式。