Redis占用了所有的内存并崩溃了。

一个运行在Ubuntu 14.04 VPS上的Redis服务器版本为2.8.4,配备了8GB的RAM和16GB的交换空间(使用SSD)。然而,htop显示Redis单独占用了22.4GB的内存! 由于内存不足,redis-server最终崩溃。Mem和Swp都达到了100%,然后redis-server被终止,其他服务也随之停止。 从dmesg中可以看到:
[165578.047682] Out of memory: Kill process 10155 (redis-server) score 834 or sacrifice child
[165578.047896] Killed process 10155 (redis-server) total-vm:31038376kB, anon-rss:5636092kB, file-rss:0kB
重新启动redis-server,无论是由于OOM崩溃还是service redis-server force-reload命令,都会导致内存使用量降至小于100MB。 问题:为什么redis-server会占用越来越多的内存直到崩溃?我们如何防止这种情况发生? 设置maxmemory是否有效,因为一旦redis达到maxmemory限制,它将开始删除数据?

enter image description here enter image description here

重启redis-server之后

enter image description here enter image description here

Redis版本:Redis服务器v=2.8.4 sha=00000000:0 malloc=jemalloc-3.4.1 bits=64 build=a44a05d76f06a5d9

更新

htop报告redis-server的内存使用量为4.4G RAM和22.6G Swap时,根据rdbtools的报告,redis中所有键占用的空间仅为60.59636307 MB。这也是redis-server在重新启动后占用的内存量。

redis-server占用大量内存时的INFO ALL

mem_fragmentation_ratio:0.19

127.0.0.1:6379> INFO all

# Server
redis_version:2.8.4
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:a44a05d76f06a5d9
redis_mode:standalone
os:Linux 3.13.0-24-generic x86_64
arch_bits:64
multiplexing_api:epoll
gcc_version:4.8.2
process_id:26858
run_id:4d4a507b325e567d5ada203a0c65891bcf4d02de
tcp_port:6379
uptime_in_seconds:100011
uptime_in_days:1
hz:10
lru_clock:165668
config_file:/etc/redis/redis.conf

# Clients
connected_clients:60
client_longest_output_list:768774
client_biggest_input_buf:0
blocked_clients:0

# Memory
used_memory:23973468008
used_memory_human:22.33G
used_memory_rss:4563857408
used_memory_peak:24083474760
used_memory_peak_human:22.43G
used_memory_lua:33792
mem_fragmentation_ratio:0.19
mem_allocator:jemalloc-3.4.1

# Persistence
loading:0
rdb_changes_since_last_save:127835154
rdb_bgsave_in_progress:0
rdb_last_save_time:1406716479
rdb_last_bgsave_status:err
rdb_last_bgsave_time_sec:1
rdb_current_bgsave_time_sec:-1
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok

# Stats
total_connections_received:110
total_commands_processed:386765263
instantaneous_ops_per_sec:3002
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:0
evicted_keys:0
keyspace_hits:1385878
keyspace_misses:23655
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:82

# Replication
role:master
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

# CPU
used_cpu_sys:10547.48
used_cpu_user:8240.36
used_cpu_sys_children:201.83
used_cpu_user_children:914.86

# Commandstats
cmdstat_del:calls=136,usec=1407,usec_per_call=10.35
cmdstat_exists:calls=161428,usec=1391252,usec_per_call=8.62
cmdstat_zadd:calls=64149642,usec=936323882,usec_per_call=14.60
cmdstat_zrem:calls=137,usec=2131,usec_per_call=15.55
cmdstat_zremrangebyscore:calls=2293,usec=111905082,usec_per_call=48802.91
cmdstat_zrange:calls=7925,usec=285907448,usec_per_call=36076.65
cmdstat_zrangebyscore:calls=921434,usec=292731002,usec_per_call=317.69
cmdstat_zcount:calls=8,usec=172,usec_per_call=21.50
cmdstat_zrevrange:calls=191184,usec=965447,usec_per_call=5.05
cmdstat_zcard:calls=5180,usec=13502,usec_per_call=2.61
cmdstat_zscore:calls=29856,usec=576044,usec_per_call=19.29
cmdstat_hset:calls=64145124,usec=199407095,usec_per_call=3.11
cmdstat_hget:calls=248487,usec=501220,usec_per_call=2.02
cmdstat_hincrby:calls=128339355,usec=2071112929,usec_per_call=16.14
cmdstat_hgetall:calls=193747,usec=1608260,usec_per_call=8.30
cmdstat_select:calls=1,usec=5,usec_per_call=5.00
cmdstat_rename:calls=134,usec=1090,usec_per_call=8.13
cmdstat_keys:calls=4503,usec=4997628,usec_per_call=1109.84
cmdstat_bgsave:calls=2,usec=20012,usec_per_call=10006.00
cmdstat_type:calls=603,usec=2736,usec_per_call=4.54
cmdstat_multi:calls=64181979,usec=383633610,usec_per_call=5.98
cmdstat_exec:calls=64181979,usec=4403181204,usec_per_call=68.60
cmdstat_info:calls=126,usec=28675,usec_per_call=227.58

# Keyspace
db0:keys=2109,expires=0,avg_ttl=0
2个回答

使用maxmemory来设置Redis数据库的增长限制。如果不这样做,Redis将会一直增长,直到内存耗尽,操作系统将会终止它(根据您目前的经验)。 maxmemory的使用应与maxmemory-policy相结合 - 根据您的用例需求,您可以选择不同的驱逐策略。例如,如果您使用allkeys-lru驱逐策略,当达到maxmemory时,Redis将开始驱逐(最近最少使用的)数据。或者,您可以使用volatile-lruvolatile-random策略指示Redis仅驱逐可过期的数据。最后,您可以将策略设置为noeviction,但这意味着一旦内存耗尽,Redis将拒绝进一步写入,并显示OOM消息。 编辑: 首先禁用交换空间 - Redis和交换空间不易混合使用,这可能会导致速度变慢。 也可以使用free -m命令来获取你的RAM状态的完整信息,而不是使用top命令(http://www.linuxatemyram.com/)。

谢谢,我对于为什么内存使用量不断增长感到困惑,但是执行bgsave并重新启动redis-server后,内存使用量会降至更合理的70 MB。这可能是内存泄漏吗? - Nyxynyx
可能但不太可能(否则其他人早就会报告了)...更有可能是一个分片问题。下次发生这种情况时,请发布您的Redis的INFO ALL输出。如果我猜对了,mem_fragmentation_ratio将会非常高。 - Itamar Haber
redis-server占用了所有的内存并且每天都会崩溃。现在它即将耗尽所有的内存,所以我已经捕获了INFO ALL的输出并添加到原始帖子中。mem_fragmentation_ratio:0.19 - Nyxynyx
如果Redis的数据集不超过250MB,并且maxmemory设置为1GB,这是否意味着当Redis的内存使用量达到1GB时,数据仍然会被逐出?由于Redis的mem_fragmentation_ratio0.19,这是否意味着存在过多的碎片化或者存储在交换空间中的数据过多,还是两者都有?有没有办法减少碎片化? - Nyxynyx
当redis-server由于OOM即将崩溃时,rdbtools显示redis中的键仅占用60MB。这看起来像是非常严重的碎片化问题?考虑到它占用了4.4GB的RAM和22.4GB的Swap。 - Nyxynyx
0.19的比率是良性的,所以看起来不像是核心问题。客户端连接如何?可能在进行大量读取操作吗?hgetall计数表明了这一点...询问的原因在这篇文章中有解释:http://redislabs.com/blog/top-redis-headaches-for-devops-client-buffers - Itamar Haber
每次有大约50个客户端连接(使用CLIENT LIST)到redis。我注意到在redis服务器重新启动后,客户端需要很短的时间来检索数据,但随着redis占用越来越多的内存,检索速度会逐渐变慢(最高可达40秒以上)。从CLIENT LIST中可以看出,许多客户端的age非常长,很多都已经达到了4个小时!而且它们的cmdexec - Nyxynyx
allkey-lru中有一个拼写错误,应该是'allkeys-lru'。 - Mohammad Sayeed
@MohdSayeed 谢谢 - 已修复。 - Itamar Haber

这几乎可以确定是内存碎片的问题,因为Redis在生产中非常出色并备受喜爱,你可能还没有发现内存泄漏的情况。 关于设置池大小的建议无法解决碎片问题。你需要专门降低Redis的大小——小于实际内存大小——因为Redis不能考虑到碎片问题。简单来说,你需要这样做,并且计划频繁重启服务器。 根据我的经验,在使用各种操作系统和内存数据库时,你需要实际内存的两倍,并且内存大小将在大约两周内稳定下来。 然而,这取决于你实际的分配模式和所使用的内存分配器。 目前,我在服务器上找到的最好的内存分配器是JEMalloc。我们现在在Aerospike中使用它来减少(几乎消除)长期内存碎片。JEMalloc具有一个特性,允许你创建一个内存“arena”(池),并在任何分配时选择哪个池,从而为你提供相同大小的分配,并管理类似的内存生命周期分配。在你讨论的这些情况下,它对我们来说带来了巨大的优势。 Zend PHP引擎在这方面非常复杂,因为引擎内部的所有分配都在每个事务内存或全局内存中进行。每个事务内存在事务结束时一次性释放,因此可以非常高效。 如果你使用Linux,内核内存分配器(Clib)经历了许多曲折,你所使用的版本将极大地决定碎片化的程度,实际应用模式也会起到影响。例如,当你稍微增加对象时,某些分配器会更好,而某些则更糟糕。遗憾的是,即使与其他Redis用户讨论,也需要谈论你使用的操作系统和操作系统版本。 你可以从持久化重新启动服务器并获得内存回收,这可能意味着内存泄漏,但更有可能指向碎片化问题。
  1. 禁止交换(对于Redis来说,OOM比交换更好)
  2. 减少Redis的内存大小
  3. 按计划重新启动

如何通过调整maxmemory来减小内存大小? - Nyxynyx