大飞哥的博客

借书满架,偃仰啸歌,冥然兀坐,万籁有声

0%

redis内存使用统计及碎片率分析

如何获取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

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,则说明额外的内存碎片正在产生。