Redis中的used_memory与maxmemory解惑
条评论Redis中的used_memory与maxmemory解惑
概述
在Redis2.X系列和3.X系列执行info Memory,会打印出来一些Redis内存使用情况的信息,在2.X系列中显示如下:
127.0.0.1:6379> info Memory
used_memory:279440336
used_memory_human:266.50M
used_memory_rss:295079936
used_memory_peak:298650696
used_memory_peak_human:284.82M
used_memory_lua:36864
mem_fragmentation_ratio:1.06
mem_allocator:jemalloc-3.6.0
在3.X系列中显示如下:
used_memory:22259232
used_memory_human:21.23M
used_memory_rss:58331136
used_memory_rss_human:55.63M
used_memory_peak:98079600
used_memory_peak_human:93.54M
total_system_memory:7847321600
total_system_memory_human:7.31G
used_memory_lua:37888
used_memory_lua_human:37.00K
maxmemory:6000000000
maxmemory_human:5.59G
maxmemory_policy:noeviction
mem_fragmentation_ratio:2.62
mem_allocator:jemalloc-4.0.3
对比观察发现在3.X系列中,主要增加了system memory和maxmemory及maxmemory淘汰策略,在2.X系列中,获取这些值需要通过CONFIG GET maxmemory*来获取配置项。那么maxmemory和used_memory
是什么关系呢,used_memory为什么会超过maxmemory呢,为了解开这个疑惑,我们将从源码出发,详细的解读used_memory和maxmemory的关系。
Memory是如何统计的
在Redis的实现中并没有为每个Object设计大小计数器,而是充分利用了Redis单进程模型的特点,直接统计的进程占用的内存大小,相关代码如下:
size_t zmalloc_used_memory(void) {
size_t um;
if (zmalloc_thread_safe) {
#if defined(__ATOMIC_RELAXED) || defined(HAVE_ATOMIC)
um = update_zmalloc_stat_add(0);
#else
pthread_mutex_lock(&used_memory_mutex);
um = used_memory;
pthread_mutex_unlock(&used_memory_mutex);
#endif
}
else {
um = used_memory;
}
return um;
}
maxmemory是我们在配置文件中或者通过config命令进行配置的,最终都保存在redisServer的结构体中,代码如下:
/* Limits */
unsigned int maxclients; /* Max number of simultaneous clients */
unsigned long long maxmemory; /* Max number of memory bytes to use */
int maxmemory_policy; /* Policy for key eviction */
int maxmemory_samples; /* Pricision of random sampling */
什么时候触发maxmemory
Redis的作者在实现时,讲maxmemory判断策略放在了建立连接的函数中processCommand,这意味着每次新建立连接,Redis都会做maxmemory判断,具体实现是用freeMemoryIfNeeded函数实现,但是在
这个函数中,我们发现了一些好玩的事情,代码如下:
int freeMemoryIfNeeded(void) {
size_t mem_used, mem_tofree, mem_freed;
int slaves = listLength(server.slaves);
mstime_t latency, eviction_latency;
/* Remove the size of slaves output buffers and AOF buffer from the
* count of used memory. */
mem_used = zmalloc_used_memory();
if (slaves) {
listIter li;
listNode *ln;
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
client *slave = listNodeValue(ln);
unsigned long obuf_bytes = getClientOutputBufferMemoryUsage(slave);
if (obuf_bytes > mem_used)
mem_used = 0;
else
mem_used -= obuf_bytes;
}
}
if (server.aof_state != AOF_OFF) {
mem_used -= sdslen(server.aof_buf);
mem_used -= aofRewriteBufferSize();
}
这段代码暴露了作者的真实设计意图,maxmemory不是限制Redis最大可使用内存的,而是限制数据存储大小的,计算时减掉了一些buffer,但是在实现上存在一些比较严重的问题,下面我们详细的讨论这个问题。
使用者的疑惑
使用者的疑惑主要来自两个方面,在2.X系列版本中,很多使用者误以为maxmemory就是Redis可以使用的最大内存,maxmemory配置不合理导致一系列故障,如OOM,从库同步不成功等。
在3.X系列中,这个问题通过info命令彻底暴露给使用者了,为什么used_memory比maxmemory多,这两者的关系是什么,maxmemory到底是指什么,应该设置多大比较合理,作者并没有给出详细的解释也没有很好的文档说明。这就导致了使用者产生了众多疑惑。
在info命令的实现中,used_memory直接获取了Redis进程占用的内存,如下所示,就这段代码而言,used_memory大于maxmemory是必然的,并且出现了前后设计相违背的情况,比如info命令里面的lua memory也没有减去。
/* Memory */
if (allsections || defsections || !strcasecmp(section,"memory")) {
char hmem[64];
char peak_hmem[64];
char total_system_hmem[64];
char used_memory_lua_hmem[64];
char used_memory_rss_hmem[64];
char maxmemory_hmem[64];
size_t zmalloc_used = zmalloc_used_memory();
size_t total_system_mem = server.system_memory_size;
const char *evict_policy = evictPolicyToString();
long long memory_lua = (long long)lua_gc(server.lua,LUA_GCCOUNT,0)*1024;
/* Peak memory is updated from time to time by serverCron() so it
* may happen that the instantaneous value is slightly bigger than
* the peak value. This may confuse users, so we update the peak
* if found smaller than the current memory usage. */
if (zmalloc_used > server.stat_peak_memory)
server.stat_peak_memory = zmalloc_used;
bytesToHuman(hmem,zmalloc_used);
bytesToHuman(peak_hmem,server.stat_peak_memory);
bytesToHuman(total_system_hmem,total_system_mem);
bytesToHuman(used_memory_lua_hmem,memory_lua);
bytesToHuman(used_memory_rss_hmem,server.resident_set_size);
bytesToHuman(maxmemory_hmem,server.maxmemory);
还遗漏了什么
mem_used - obuf_bytes - aofRewriteBufferSize()是否就等于最大可以存储的数据大小呢,答案是否定的。就Redis中的buffer而言,除了主从同步的buffer、aofRewriteBuffer外还有其他的buffer,在Redis的配置中还有
client-output-buffer-limit这个参数,在Reids中所有的client请求redis数据的时候,redis要返回给client的数据都会先被存储在output-buffer中,等所有信息都被传送完毕之后,再清除output-buffer中的数据,这个参数可以可以解读为三段
normal,常规的client缓存返回结果的buffer
slave,slave节点同步数据的buffer
pubsub,pubsub时产生的buffer
其中每一段可以设置一个硬限,一个软限,一个超时时间,作者在上面的实现时,从mem_used减掉了slave节点的buffer大,但是并没有减掉normal和pubsub占用的buffer,这是作者遗漏的第一点。
在Redis2.8版本之前,没有增量复制功能,如果出现主从同步中断,则只能全量同步,在2.8版本之后,增加了部分复制的功能,为此引进了一个新的参数repl-backlog-size,repl-backlog-size是一个环形缓冲区,整个master进程中只会存在一个,所有的slave公用,主从同步时,不仅将
命令发送到slave,同时会计入repl-backlog-size,当某个slave断开重连时,使用psync将repl-backlog-size的内容发送给slave,实现增量复制,但是由于环形缓冲区是环形缓冲区,所以写满后会覆盖之前的部分,这个时候从节点断开时只能全量复制了。所以这个参数
在增量复制时是关键的参数,这部分在主节点上也会占用内存,这是作者遗漏的第二点。
第三点是RDB COW时占用的内存,代码如下,需要注意的是,Redis的bgsave和aofRewrite是通过后台线程来实现的,RDB过程中占用的内存页应该计算到Redis占用的内存中去。
if (retval == C_OK) {
size_t private_dirty = zmalloc_get_private_dirty();
if (private_dirty) {
serverLog(LL_NOTICE,
"RDB: %zu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
……
}
当然,上面这三点并不是遗漏的全部,还有诸如monitors,lua等,但是由于Redis是用减法的形式获得的mem_used,所以并不会特别精确。
如何改善
如果按照Redis作者的意图,那么maxmemory的判断至少应该减掉上面所提的三个点占用的内存,在info命令打印的内存信息中,增加data_used_memory,避免使用者的疑惑。
如果想用maxmemory控制Redis进程占用的最大内存大小,那么应该在freeMemoryIfNeeded函数中不进行减法,直接判断maxmemory,这样就可以限定最大使用内存上限。
总结
实际使用中需要理解已下几点:
maxmemory并不是Redis最大使用的内存上限
Redis最大使用的内存上限应该考虑数据存储+各种buffer+QPS+从节点数量等信息。
- 本文链接:http://www.darcy.org.cn/2016/08/14/Redis中的used-memory与maxmemory解惑/
- 版权声明:作者拥有版权,转发请注明出处来源