作者:京东零售吴佳前言redis,对于一个java开发工程师来说,其实并不是什么复杂新颖的技术,只是可能很少有人深入了解它底层的东西。接下来,我们将通过内存统计、内存划分、存储明细、对象类型&内部编码、手写笔记、潜心练习四个模块来学习redis的内存模型。1、Redismemorystatisticsinfomemory命令查看内存使用情况:服务器基本信息、CPU、内存、持久化、客户端连接信息等,如下图:(1)used_memory和used_memory_rssused_memory:由Redis分配器分配的Total内存+虚拟内存(磁盘)used_memory_rss:Redis进程占用操作系统的内存+进程自身需要的内存+内存碎片等(*:注意used_memory_rss不包括虚拟内存)两者的区别:①Orientationangle:used_memory:RedisAngleused_memory_rss:operatingsystemangle②后者的大小不一定比前者大:内存碎片和Redis进程运行需要占用内存,所以前者可能比后者小,另一方面,虚拟内存的存在使得前者可能比后者大(2)mem_fragmentation_ratio内存碎片率,等于used_memory_rss/used_memorymem_fragmentation_ratio>1:该值越大,内存碎片率越大mem_fragmentation_ratio<1:意思是Redis使用的是虚拟内存*:由于虚拟内存的介质是磁盘,所以比内存速度要慢很多。出现这种情况要及时排查,内存不足要及时处理,比如增加Redis节点,增加Redis服务器内存,优化应用等。正常情况下情况:mem_fragmentation_ratio=1.03左右(healthy:forjemalloc)以上情况:Redis没有存储数据,Redis进程本身的内存使得used_memory_rss比used_memory大很多(3)mem_allocator:Redis使用的内存分配器,指定在编译时,可以是libc、jemalloc或tcmalloc,默认是jemalloc。(4)used_memory_peak:Redis的内存消耗峰值(5)used_memory_human和used_memory_peak_human:字面意思,以人读的方式返回。2、redis的内存划分数据:最重要的部分会算在used_memory中。实际上,在Redis内部,每种类型可能有2种或更多的内部编码实现。另外,Redis在存储对象时,并不会直接将数据扔到内存中,而是通过各种方式对对象进行包装:如RedisObject、SDS等。进程本身的内存:Redis主进程本身必须占用内存,比如代码,常量池等等。这部分内存大约有几兆,与大多数生产环境中Redis数据占用的内存相比,可以忽略不计。这部分内存不是jemalloc分配的,所以不会算在used_memory中。buffermemory:包含clientbuffer,copybacklogbuffer,AOFbufferclientbuffer:存放clientconnection的输入输出buffercopybacklogbuffer:用于部分copy功能AOFbuffer:用于AOFreplay写入时,保存最新的写入命令内存碎片:内存碎片是Redis在分配和回收物理内存的过程中产生的。3.Redis数据存储详解当我们执行一个redis命令时,比如:sethelloworld,redis的底层存储是干什么的?上面涉及到两个概念:jemalloc和RedisObject(1)jemallocmemoryallocator:可以是libc、jemalloc或tcmalloc,默认jemallocjemalloc内存划分:small,large,huge,每个划分成很多小的内存块单元(比如你需要存储一个大小为130字节的对象,jemalloc会把它放到一个160字节的内存单元。)(2)RedisObject(核心数据结构)Redis的五种类型都是通过RedisObject来存储的,Redis对象类型、内部编码、内存回收、共享对象等功能都需要RedisObject对象的支持。typedefstructredisObject{无符号类型:4;无符号编码:4;未签名的lru:REDIS_LRU_BITS;/*lru时间(相对于server.lruclock)*/intrefcount;void*ptr;}type:表示对象的数据类型,占4位。encoding:表示对象的内部编码,占4bits。对于redis的每一种数据类型,至少有两种内部编码。例如,字符串类型有:int、embstr、raw。lru:记录对象最后一次被命令程序访问的时间,不同版本占用的位数不同(如4.0版本占用24位,2.6版本占用22位)。refcount:1.概念:refcount记录对象被引用的次数,类型目前只有整型。2、作用:refcount的作用主要在于对象的引用计数和内存回收:①创建新对象时,将refcount初始化为1;②当有新程序使用该对象时,refcount加1;③当对象不再使用时,有新程序使用时,refcount减1;④当refcount变为0时,对象占用的内存将被释放。3、为什么只支持整数值的字符串对象?内存和CPU的平衡(时间):①对于整数值,判断运算复杂度为O(1);②对于普通字符串,判断复杂度为O(n);③对于hashes、lists、sets和Orderedcollection,判断的复杂度为O(n^2)。4、当前实现:Redis服务器在初始化时,会创建10000个字符串对象,其值为0到9999的整数值;可以通过调整参数REDIS_SHARED_INTEGERS(4.0中的OBJ_SHARED_INTEGERS)的值来更改数字10,000。(可以通过objectrefcount命令查看共享对象的引用数:)ptr:ptr指针指向具体的数据,比如前面的例子中,sethelloworld,ptr指向包含字符串world的SDS(3)SDS1,概念:Redis并没有直接使用C字符串(即以空字符'\0'结尾的字符数组)作为默认的字符串表示,而是使用SDS。SDS是SimpleDynamicString的缩写。2、结构体:3、相关计算:*:buf数组的长度=free+len+1(其中1表示字符串末尾的空字符)一个SDS结构体占用的空间=free占用的长度+占用的长度bylen+buf数组的长度=4+4+free+len+1=free+len+9。4、加“\0”的目的:为简单字符串调用c字符串的一些函数。4、redis的对象类型&内部编码(一)String1,字符串长度不超过512MB2,内部编码有3种:int、embstr、raw3,编码转换关系:int:integerembstr:<=39bytesofstringraw:>39-bytestring4、embstr和raw的区别:①embstr使用redisObject和sds结构存储②emstr创建只分配一次内存空间(redisObject和sds是一起分配的,因为是continuous)缺点:创建和删除都需要整个redisObject和sds重新分配空间,所以emstr实现为只读。③raw需要分配两次5.修改emstr时,不管是否达到39字节,都会先变成raw再修改。这也是为了避免创建整个redisObject和sds(2)List1.内部编码:ziplist和linkedlist:(每个节点指向redisObject)2.压缩列表:节省空间,连续的内存块3.编码转换:什么时候使用压缩列表?①列表元素<512②列表中的所有字符串对象都小于64字节(字符串长度)(3)hash:innerhash和outerhashinnerhash:ziplist,hashtableOuterhash:hashtable
