redis存在大量脏页问题的追查记录

case现场

线上发现一台机器内存负载很重,top后发现一个redis进程占了大量的内存,TOP内容如下:

27190   root    20   0  18.6g   18g  600 S  0.3     59.2    926:17.83   redis-server

发现redis占了18.6G的物理内存。由于redis只是用于cache一些程序数据,觉得很不可思议,执行redis的info命令,发现实际数据占用只有112M,如下:

# Memory
used_memory:118140384
used_memory_human:112.67M
used_memory_rss:19903766528
used_memory_peak:17871578336
used_memory_peak_human:16.64G
used_memory_lua:31744
mem_fragmentation_ratio:168.48
mem_allocator:libc

  

于是用了pmap -x 27190 查看redis进程的内存映像信息,结果如下:

27190:   ./redis-server ../redis.conf
Address             Kbytes      RSS     Dirty           Mode        Mapping
0000000000400000    548         184         0           r-x--       redis-server
0000000000689000    16          16          16          rw---       redis-server
000000000068d000    80          80          80          rw---       [ anon ]
0000000001eb6000    132         132         132         rw---       [ anon ]
0000000001ed7000    19436648    19435752    19435752    rw---       [ anon ]
00007f5862cb2000    4           0           0           -----       [ anon ]

发现存在大量的内存脏页。现在内存负载高的问题已经比较清晰了,是由于redis的脏页占用了大量的内存引起的。可是,redis为什么会存在那么多的脏页呢?

case 分析

看了下linux脏页的定义:

脏页是linux内核中的概念,因为硬盘的读写速度远赶不上内存的速度,系统就把读写比较频繁的数据事先放到内存中,以提高读写速度,这就叫高速缓存,linux是以页作为高速缓存的单位,当进程修改了高速缓存里的数据时,该页就被内核标记为脏页,内核将会在合适的时间把脏页的数据写到磁盘中去,以保持高速缓存中的数据和磁盘中的数据是一致的。

也就是说,脏页是因为内存中的很多数据没来得及更新到磁盘导致的。看了下linux系统的脏页flush机制: 
http://blog.chinaunix.net/uid-17196076-id-2817733.html 
发现跟内存flush的可以进行设置(/proc/sys/vm底下)

dirty_background_bytes/dirty_background_ratio:
    - 当系统的脏页到达一定值(bytes或者比例)后,启动后台进程把脏页刷到磁盘中,此时不影响内存的读写(当bytes设置存在时,ratio是自动计算的)

dirty_bytes/dirty_ratio:
    - 当系统的脏页到达一定值(bytes或者比例)后,启动进程把脏页刷到磁盘中,此时内存的写可能会被阻塞(当bytes设置存在时,ratio是自动计算的)

dirty_expire_centisecs:
    - 当内存和磁盘中的数据不一致存在多久以后(单位为百分之一秒),才会被定义为dirty,并在下一次的flush中被清理。不一致以磁盘中文件的inode时间戳为准

dirty_writeback_centisecs:
    - 系统每隔一段时间,会把dirty flush到磁盘中(单位为百分之一秒)

  

查看当前系统的flush配置,发现没问题,

dirty_background_ratio为10%dirty_ratio为20%dirty_writeback_centisecs为5sdirty_expire_centisecs为30s

可是为啥redis的脏页没有被flush到磁盘中呢?

一般脏页是要把内存中的数据flush到磁盘中,那么会不会是redis的持久化导致了脏页呢?查看下redis关于这些方面的配置:

rdb持久化已经被关闭
# save 900 1
# save 300 10
# save 60 10000

# append持久化也被关闭
appendonly no

# 最大内存设置、内存替换策略都是默认值
# maxmemory <bytes>
# maxmemory-policy volatile-lru

如上所示,发现redis自身已经完全关闭持久化,只是作为cache使用,而且对于最大内存使用默认值(代表没有限制),内存的淘汰机制是volatile-lru。翻看redis的文档,查看对应的淘汰机制:

volatile-lru:      从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰(默认值)
volatile-ttl:      从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:   从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:       从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:    从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction:      禁止驱逐数据

  

而在当前的使用环境中,程序对redis的使用是当做cache,并且会对数据设置expire超时时间,到期后等待redis进行删除的。那么脏页的原因,是不是因为过期数据清理机制的问题呢(比如清理不及时等)?因此,需要查看redis在对过期数据进行删除时采取的策略,参考信息如下: 
Redis中的内存释放与过期键删除 
redis 过期键的清除

redis过期键删除机制:

惰性删除:
    -  expire的键到期后,不会自动删除,不过在每次读取该键时进行检查,检查该键是否已经过期,如果过期,则进行删除动作。这样可以保证删除操作只会在非做不可的情况下进行    

定期删除:
    - 每隔一段时间执行一次cron删除操作,从每个db的expire-keys中随机找出一定数量的key(默认是20个),检查key是否超时。如果已经超时,则进行删除
    - 通过限制删除操作执行的时长和频率,并以此来减少删除操作对 CPU 时间的影响。

大于maxmemory的自动删除:
    - 每次client和server进行command交互时,server都会检查maxmemory的使用情况
    - 如果当前的内存已经超过了max-memory,那么则进行清理,直到内存占用在maxmemory线以下
    - 清理的策略基于淘汰机制(LRU,TTL,RANDOM等)

redis对于过期键删除使用的是 惰性删除 + 定期删除 + 大于maxmemory的自动清除 的策略。

case 定位

通过以上的分析,问题已经比较明确了,原因如下:

  1. 由于某种原因,redis使用的内存越来越大(可能是由于惰性删除,导致expire的数据越积越多,或者其它原因,具体原因取决于redis内部的实现)
  2. redis由于只当做cache,并没有实际读写文件,因此操作系统并不会帮它flush到磁盘中(因为没有地方可以flush)
  3. 由于redis没有设置maxmemory,因此默认的是机器的内存大小,只有当redis自身使用的内存达到机器内存大小时,redis才会自身进行清理(volatile-lru机制)
  4. 因此当前的redis的内存越来越大,而且脏页数据越来越多(大部分可能都是已经过期的数据)

case解决

为了解决这个问题,比较方便且合理的方法就是:

  • 惰性删除有时候并不是很靠谱,特别对于一些log类型的数据,在写入redis后就不管了,虽然设置了超时时间,但是没有效果
  • 定期删除对于expire-keys也不是很靠谱,存在随机性,也可能过期很久的数据没有删除
  • 相对而言,比较合理的方式是基于使用情况设置redis的maxmemory大小,用于让redis实现自身的数据清理机制,确保把mem限制在maxmemory设定范围内
时间: 2024-10-13 05:40:17

redis存在大量脏页问题的追查记录的相关文章

bitvec与脏页

摘自:http://sqlite.1065341.n5.nabble.com/Is-performance-of-v3-5-7-improved-with-new-bitvec-td36830.html 位图是一个非常有趣的话题,在SQLite中通过位图记录下,当前脏的页面,方便事务的回滚,当然了,我们已经记录下该结构对于journal的基本使用方式,大家可能不知道,如果事务发生了回滚,就会产生journal文件,那么情况来了,SQLite是如何记录,到底记录什么信息,保存在journal文件当

MySQL中InnoDB脏页刷新机制Checkpoint

我们知道InnoDB采用Write Ahead Log策略来防止宕机数据丢失,即事务提交时,先写重做日志,再修改内存数据页,这样就产生了脏页.既然有重做日志保证数据持久性,查询时也可以直接从缓冲池页中取数据,那为什么还要刷新脏页到磁盘呢?如果重做日志可以无限增大,同时缓冲池足够大,能够缓存所有数据,那么是不需要将缓冲池中的脏页刷新到磁盘.但是,通常会有以下几个问题: 服务器内存有限,缓冲池不够用,无法缓存全部数据 重做日志无限增大成本要求太高 宕机时如果重做全部日志恢复时间过长 事实上,当数据库

InnoDB脏页刷新机制Checkpoint

我们知道InnoDB采用Write Ahead Log策略来防止宕机数据丢失,即事务提交时,先写重做日志,再修改内存数据页,这样就产生了脏页.既然有重做日志保证数据持久性,查询时也可以直接从缓冲池页中取数据,那为什么还要刷新脏页到磁盘呢?如果重做日志可以无限增大,同时缓冲池足够大,能够缓存所有数据,那么是不需要将缓冲池中的脏页刷新到磁盘.但是,通常会有以下几个问题: 服务器内存有限,缓冲池不够用,无法缓存全部数据 重做日志无限增大成本要求太高 宕机时如果重做全部日志恢复时间过长 事实上,当数据库

linux中的脏页写回

为了减轻内存使用的压力,除了用户手动写回脏页以外,还有一些机制触发脏页写回. 比方说设置定时器,定期写回脏了很久的页. 具体介绍下面的写回机制,因为这种机制不像写回脏了很久的页的机制那样被动. wakeu_bdflush 复杂唤醒写回的核心函数. 能触发此函数条件,可能会是以下几点中的一点会多: 1.用户态进程调用sync强制写回 2.grow_buffers()分配一个新的缓冲区页失败时 .此时的页中缓冲区块大小与要求的不同,因此要释放掉. 3.页框回收算法调用free_more_memoy(

InnoDB Redo Flush及脏页刷新机制深入分析

概要: 我们知道InnoDB采用Write Ahead Log策略来防止宕机数据丢失,即事务提交时,先写重做日志,再修改内存数据页,这样就产生了脏页.既然有重做日志保证数据持久性,查询时也可以直接从缓冲池页中取数据,那为什么还要刷新脏页到磁盘呢?如果重做日志可以无限增大,同时缓冲池足够大,能够缓存所有数据,那么是不需要将缓冲池中的脏页刷新到磁盘.但是,通常会有以下几个问题: 服务器内存有限,缓冲池不够用,无法缓存全部数据 重做日志无限增大成本要求太高 宕机时如果重做全部日志恢复时间过长 事实上,

Mysql的刷脏页问题

平时的工作中,不知道你有没有遇到过这样的场景,一条 SQL 语句,正常执行的时候特别快,但是有时也不知道怎么回事,它就会变得特别慢,并且这样的场景很难复现,它不只随机,而且持续时间还很短. 当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”.内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”. 平时执行很快的更新操作,其实就是在写内存和日志,而 MySQL 偶尔“抖”一下的那个瞬间,可能就是在刷脏页(flush). 那么,什么情况会引发数据库的 flush

脏页flush和收缩表空间

mysql脏页 由于WAL机制,InnoDB在更新语句的时候,制作了写日志这一个磁盘操作,就是redo log,在内存写完redo log后,就返回给客户端, 即更新成功. 把内存里的数据写入磁盘的过程,术语就是flush,在flush之前,实际数据和数据库中的数据是不一致的,因为在redo log基础上更新了还未写入,数据库是老的,当内存数据页跟磁盘数据页内容不一致的时候,称这个内存页为脏页,内存写入后就一致了,称为干净页, 如果mysql偶尔运行速度很慢,很可能是在刷脏页.引发数据库flus

RDLC报表:每页显示N条记录

摘要: 提供一种方案,使分页浏览的报表每页显示固定条数记录,最后一页记录条数不足的,用空行补齐. 示例: 记录共7条,每页显示5条记录: 下载代码(vs2008,需要安装AdventureWorks数据库) 下载代码(vs2008,不需要数据库支持) 下载代码(仅rdlc报表定义文件) 原理: 由于表的分组包含“在起始处分页”和”在结束处分页”的功能,所以我们考虑先将数据分成若干个记录条数为N的组, 再启用“在结束处分页”的功能. 那么,如何分组呢?考虑记录的下标为 0,1,2,3... 的自然

jquery easyui datagrid 在翻页以后仍能记录被选中的行及刷新设置选中行数据

//easyUI的datagrid在复选框多选时,如何在翻页以后仍能记录被选中的行://注意datagrid中需要配置idField属性,一般为数据的主键 $.ajax({ type: 'GET', url:url, async:false, success: function (dt) { var grid = $('#list'); grid.datagrid('reload');//刷新表格数据 grid.datagrid({ onLoadSuccess:function(){ grid.