如何获取redis内存使用情况 redis提供了info命令,可以获取redis内存使用情况。 其中关于redis内存的部分有七个参数,used_memory、used_memory_human、used_memory_rss、used_memory_peak、used_memory_peak_human、mem_fragmentation_ratio、used_memory_lua和mem_allocator。
各参数的含义如下:
used_memory : 由 Redis 分配器分配的内存总量,以字节(byte)为单位
used_memory_human : 以人类可读的格式返回 Redis 分配的内存总量
used_memory_rss : 从操作系统的角度,返回Redis已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps 等命令的输出一致。
used_memory_peak : Redis的内存消耗峰值(以字节为单位)
used_memory_peak_human : 以人类可读的格式返回 Redis 的内存消耗峰值
mem_fragmentation_ratio : used_memory_rss 和 used_memory 之间的比率
used_memory_lua : Lua 引擎所使用的内存大小(以字节为单位)
mem_allocator : 在编译时指定的,Redis所使用的内存分配器。可以是 libc 、 jemalloc 或者 tcmalloc 。
1 2 3 4 5 6 7 8 9 10 redis 127.0.0.1:6389> info ... used_memory:832323728 used_memory_human:793.77M used_memory_rss:874582016 used_memory_peak:1067840504 used_memory_peak_human:1018.37M mem_fragmentation_ratio:1.05 mem_allocator:jemalloc-2.2.5 ...
分析 从以上参数的官方介绍来看,统计redis内存使用量主要是used_memory used_memory_rss used_memory_lua,这三部分。其他参数(除mem_allocator)都是由它们产生的统计值。
其中
used_memory_peak统计的是used_memory的最大值。
mem_fragmentation_ratio统计的是used_memory_rss和used_memory的比值,其值与1的大小关系,代表redis的内存碎片率情况。正常情况下,从操作系统统计的内存使用量(used_memory_rss)应该稍大于redis内存分配器统计的内存使用(used_momory),即mem_fragmentation_ratio应稍大于1。若mem_fragmentation远大于1,代表操作系统实际分配内存要远大于redis自身统计值,代表redis内部可能有内存碎片,没有被redis统计到;若mem_fragmentation小于1,代表操作系统实际分配内存小于redis自身统计值,此时表示Redis的部分内存被操作系统换出交换空间了,此时redis会产生明显的延迟。
从源码跟踪这些参数 获取INFO返回信息的入口定义在redis.c的genRedisInfoString中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 //redis.c sds genRedisInfoString(char *section) { ... /* Memory */ info = sdscatprintf(info, "# Memory\r\n" "used_memory:%zu\r\n" "used_memory_human:%s\r\n" "used_memory_rss:%zu\r\n" "used_memory_peak:%zu\r\n" "used_memory_peak_human:%s\r\n" "used_memory_lua:%lld\r\n" "mem_fragmentation_ratio:%.2f\r\n" "mem_allocator:%s\r\n", zmalloc_used, hmem, server.resident_set_size, server.stat_peak_memory, peak_hmem, ((long long)lua_gc(server.lua,LUA_GCCOUNT,0))*1024LL, zmalloc_get_fragmentation_ratio(server.resident_set_size), ZMALLOC_LIB ); ... }
used_memory => zmalloc_used
used_memory_rss => server.resident_set_size
used_memory_peak => server.stat_pear_memory
used_memory_lua => lua_gc(server.lua,LUA_GCCOUNT,0))*1024LL
跟踪used_memory used_memory对应的变量zmalloc_used,是通过zmalloc_used_memory()返回的,该函数定义在zmalloc.c中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // zmalloc.c size_t zmalloc_used_memory(void) { size_t um; if (zmalloc_thread_safe) { #ifdef HAVE_ATOMIC um = __sync_add_and_fetch(&used_memory, 0); #else pthread_mutex_lock(&used_memory_mutex); um = used_memory; pthread_mutex_unlock(&used_memory_mutex); #endif } else { um = used_memory; } return um; }
从该函数可以看出,zmalloc_used的值取自used_memory变量。当然针对是否线程安全以及是否是有原子性操作,对used_memory的读取策略不同,但都是读取的used_memory的值。
used_memory的初始化以及修改操作都在zmalloc.c中完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 //zmalloc.c // 根据HAVE_ATOMIC来定义used_memory的加和减的方式 #ifdef HAVE_ATOMIC #define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n)) #define update_zmalloc_stat_sub(__n) __sync_sub_and_fetch(&used_memory, (__n)) #else #define update_zmalloc_stat_add(__n) do { \ pthread_mutex_lock(&used_memory_mutex); \ used_memory += (__n); \ pthread_mutex_unlock(&used_memory_mutex); \ } while(0) #define update_zmalloc_stat_sub(__n) do { \ pthread_mutex_lock(&used_memory_mutex); \ used_memory -= (__n); \ pthread_mutex_unlock(&used_memory_mutex); \ } while(0) #endif #define update_zmalloc_stat_alloc(__n) do { \ size_t _n = (__n); \ if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ if (zmalloc_thread_safe) { \ update_zmalloc_stat_add(_n); \ } else { \ used_memory += _n; \ } \ } while(0) #define update_zmalloc_stat_free(__n) do { \ size_t _n = (__n); \ if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ if (zmalloc_thread_safe) { \ update_zmalloc_stat_sub(_n); \ } else { \ used_memory -= _n; \ } \ } while(0) // used_memory 初始化 static size_t used_memory = 0; static int zmalloc_thread_safe = 0;
used_memory_rss对应的变量server.resident_set_size,该值来自于redis.c中的zmalloc_get_rss函数,
1 2 3 4 //redis.c /* Sample the RSS here since this is a relatively slow call. */ server.resident_set_size = zmalloc_get_rss();
这个函数定义在zmalloc.c中,会根据不同的操作系统,读取对应的操作系统分配的内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 // zmalloc.c // 如果是linux环境,则读取proc信息 #if defined(HAVE_PROC_STAT) #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> size_t zmalloc_get_rss(void) { int page = sysconf(_SC_PAGESIZE); size_t rss; char buf[4096]; char filename[256]; int fd, count; char *p, *x; snprintf(filename,256,"/proc/%d/stat",getpid()); if ((fd = open(filename,O_RDONLY)) == -1) return 0; if (read(fd,buf,4096) <= 0) { close(fd); return 0; } close(fd); p = buf; count = 23; /* RSS is the 24th field in /proc/<pid>/stat */ while(p && count--) { p = strchr(p,' '); if (p) p++; } if (!p) return 0; x = strchr(p,' '); if (!x) return 0; *x = '\0'; rss = strtoll(p,NULL,10); rss *= page; return rss; } // 如果是apple, 则读取task info #elif defined(HAVE_TASKINFO) #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/sysctl.h> #include <mach/task.h> #include <mach/mach_init.h> size_t zmalloc_get_rss(void) { task_t task = MACH_PORT_NULL; struct task_basic_info t_info; mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS) return 0; task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); return t_info.resident_size; } // 如果都不是,则读取used_memory作为used_memory_rss #else size_t zmalloc_get_rss(void) { /* If we can't get the RSS in an OS-specific way for this system just * return the memory usage we estimated in zmalloc().. * * Fragmentation will appear to be always 1 (no fragmentation) * of course... */ return zmalloc_used_memory(); } #endif
跟踪used_memory_lua used_memory_lua从lua_gc(server.lua,LUA_GCCOUNT,0))*1024LL获取,lua_gc定义在redis\deps\lua\src\lapi.c,通过该函数可以获取LUA引擎占用的内存。(具体lua内部是如何获取的就不清楚了。。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 // deps\lua\src\lapi.c /* ** Garbage-collection function */ LUA_API int lua_gc (lua_State *L, int what, int data) { int res = 0; global_State *g; lua_lock(L); g = G(L); switch (what) { case LUA_GCSTOP: { g->GCthreshold = MAX_LUMEM; break; } case LUA_GCRESTART: { g->GCthreshold = g->totalbytes; break; } case LUA_GCCOLLECT: { luaC_fullgc(L); break; } case LUA_GCCOUNT: { /* GC values are expressed in Kbytes: #bytes/2^10 */ res = cast_int(g->totalbytes >> 10); break; } case LUA_GCCOUNTB: { res = cast_int(g->totalbytes & 0x3ff); break; } case LUA_GCSTEP: { lu_mem a = (cast(lu_mem, data) << 10); if (a <= g->totalbytes) g->GCthreshold = g->totalbytes - a; else g->GCthreshold = 0; while (g->GCthreshold <= g->totalbytes) { luaC_step(L); if (g->gcstate == GCSpause) { /* end of cycle? */ res = 1; /* signal it */ break; } } break; } case LUA_GCSETPAUSE: { res = g->gcpause; g->gcpause = data; break; } case LUA_GCSETSTEPMUL: { res = g->gcstepmul; g->gcstepmul = data; break; } default: res = -1; /* invalid option */ } lua_unlock(L); return res; }
跟踪used_memory_peak used_memory_peak对应server.stat_peak_memory。该值每次读取时,都会通过zmalloc_used_memory()获取最新的used_memory的值,并记录used_memory的最大值。
1 2 3 4 5 //redis.c // used_memory是通过时间事件更新的,为防止出现峰值内存小于当前redis使用内存的情况,此时会重新读取一次最新的内存值,并更新峰值内存 size_t zmalloc_used = zmalloc_used_memory(); if (zmalloc_used > server.stat_peak_memory) server.stat_peak_memory = zmalloc_used;
内存碎片产生原因
操作系统使用不连续的内存碎片来提供给内存分配器。 当操作系统没有足够的内存时,无法提供整块的内存供redis内存分配器,内存分配器对内存碎片的利用率较低。
内存分配器为加快程序运行,额外使用了内存
内存分配器释放了内存块,但没有将内存返还给系统
分配内存块大小与内存分配器的类型(libc jemalloc tcmalloc)不同, 产生的内存碎片量也不同。
如果used_memory_peak和used_memory_rss大致相等,且远大于used_memory,则说明额外的内存碎片正在产生。