推荐阅读:我总结了72份面试题,累计3170页,斩获了30+互联网公司offer(含BATJM)
一、关于 redis key:
1、是二进制安全的,也就是说,你可以使用任何形式的二进制序列来作为key,比如一个string,或者一个jpg图片的数据,需要说明的是,空字符串也是一个有效的key。
2、不建议使用过长的key,影响内存占用及数据查性能,对于过长的key,可以通过hash(例如SHA1)处理转换。
3、建议使用有意义及统一格式的key。
4、最大允许key大小为512M。
二、String 类型应用:
1、作为原子计数器:incr、decr、incrby
2、结合append命令,作为基于时间的增量序列。
3、随机访问及获取值区域,getrange、setrange。
附:需要注意的是append及range操作容易引起内存浪费和碎片化问题。
三、hash 类型:ziplist or hashtable
1、单个hash最多支持232 - 1个键值对。
2、关于hash类型的内部编码:ziplist(压缩列表) & hashtable(哈希表)
配置:hash-max-ziplist-entries(hash类型最大kv数据,默认512)、hash-max-ziplist-value(单个v值最大值, 默认64)
redis 采用何种结构取决于hash中元素数及元素值得大小,当同时满足小于配置时,redis使用ziplist编码存储,否则会转化为hashtable。
ziplist编码使用更加紧凑的结构实现多个元素的连续存储,因此占用的内存更小。
当数据类型无法满足配置条件,此时使用ziplist编码存储读写效率会下降,所以转换使用hashtable编码存储(O(1)时间复杂度)。
示例:添加 testuser hash类型key,先后设置元素name、desc不同长度元素值,分别查看内部编码类型
四、关于redis存储:redisObject
redis 对象内部存储形态。
1、**type:**数据类型、例如sting、hash、list、set、zset等,值类型查看命令【type】。
2、encoding:值存储内部实现的数据结构,具体可以参考第七部分。
3、lru:最后一次被访问时间,辅助回收,可以通过 object idletime {key} 在不更新lru属性情况下查看key的空闲时间。
4、refcount:当前对象被引用次数,辅助回收,可以通过 object refcount {key} 查看引用数,当对象为整数且值在范围在[0-9999]时,redis可以通过共享对象的方式来节省内存。
目前共享对象池只对整数设置了0~9999个共享对象,一方面整数对象池复用率最大,同时等值判断上时间复杂度为O(1)。
5、*ptr:数据存储或指向,数据本身或者指向数据的指针,redis3.0之后,长度在39以内的字符串数据,内部编码为embstr,内存创建时,字符串和redisObject一起分配,减少一次内存分配。
五、关于SDS
simple dynamic string:redis内部自定义简单动态字符串结构。
1、字符串属性的O(1)时间复杂度获取。
2、空间与分配、减少内存再分配。
3、惰性删除机制,字符串缩减后空间不释放,作为预分配空间保留。
六、关于对象属性存储:json or hash
对象属性存储可以通过整体json存储或者hash kv存储。具体应用选择,可以结合整体对象大小及属性操作需求来决定。
对于频繁整体操作,且对象数据量较小的一般采用json字符串类型存储。
对于多对象属性层级操作情景,可能hash会比较合适。
七、关于存储编码
如上图,同一种数据类型,可以有多种不同内部编码存储形式。具体redis采用那种编码形式与实际应用的数据值类型相关,如上述第三部分论述hash类型的编码转换。
数据的编码类型在数据写入的时候确定,不可变换,且只能向大内存编码行使转换。
如下,重新设置 testuser desc值,testuser对象的编码形式保持不变:
编码转换时机:
八、关于ziplist
通过第七节,我们可以看到hash、list、zset底层都有应用这种存储结构。
基本特点:
1、连续性内存存储。
2、可以模拟双向链表,O(1)时间复杂度内出入队操作。
3、读写性能跟数据的元素个数及值长度相关,适合存储小对象和长度有限的数据。
4、数据增删涉及复杂的内存操作。
ziplist基本结构:
1、zlbytes:int32类型、4字节,ziplist整体字节长度。
2、zltail:int32类型、4字节,记录距离尾节点偏移量,用于尾节点弹出。
3、zllen:int16类型,2字节,ziplist节点数量。
4、entry:数据节点,长度不定。
entry 即链表node,内部结构包含:
prev_entry_bytes_length:前一个节点所占用的空间,用户快速定位。
encoding:当前数据节点编码类型及长度,前两位标识编码类型,其余标识数据长度。
contents:节点数据值。
5、zlend:1字节,记录列表结尾。
九、关于redis内存消耗
redis内存消耗包括自身内存,键值对象占用、缓冲区内存占用及内存碎片占用。
1、缓冲区内存:包括客户端缓存、复制及压缓冲区及AOF缓冲区。
2、内存碎片:固定范围内存块儿分配。
redis默认使用jemalloc内存分配器,其它包括glibc、tcmalloc。
内存分配器会首先将可管理的内存分配为规定不同大小的内存块以备不同的数据存储需求,但是,我们知道实际应用中需要存储的数据大小不一,规范不一,内存分配器只能选择最接近数据需求大小的内存块儿进行分配,这样就伴随着“占不满”空间的碎片浪费。
jemalloc针对内存碎片有相应的优化策略,正常碎片率为mem_fragmentation_ratio在1.03左右。
第二部分我们说过,对string值得频繁append及range操作会会导致内存碎片问题,另外,第七部分,SDS惰性内存回收也会导致内存碎片,同时过期键内存回收也伴随着所释放空间的无法充分利用,导致内存碎片率上升的问题。
碎片处理:
应用层面:尽量避免差异化的键值使用,做好数据对齐。
redis服务层面:可以通过重启服务,进行碎片整理。
3、maxmemory及maxmemory-policy控制redis最大可用内存及内存回收。需要注意的是内存回收执行影响redis的性能,避免频繁的内存回收开销。