服务器内存被谁“偷”走了?
数据库服务器内存的高使用率一直困扰着明明used内存不高但是available可用的内存已经达到警戒线很大一部分又被buff/cache占用这其中究竟是什么关系1、free命令与/proc/meminfo的映射关系操作系统中常用TOP或者free命令查看当前系统的内存使用情况free命令的输出结果如下所示包含Mem和Swap两部分通常在数据库服务器中会关闭swap空间。total used free shared buff/cache available Mem: 16310248 3223164 8006500 12348 5080584 11680584 Swap: 0 0 0其中内存计算公式total used free buff/cache而available为估算值。total: 系统总物理内存free: 完全未被使用的内存used: 已被使用的内存。计算得出的值used total - free - buff/cacheshared: 被多个进程共享的内存。其数据主要来自/proc/meminfo中的Shmem字段buff/cache: 内核用于I/O优化的缓冲区Buffers和页缓存Page Cache的总和available: 预估的可供应用程序启动而无需进行交换的内存量/proc/meminfo文件内容示例如下数值单位为kBMemTotal: 16310248 kB MemFree: 8006500 kB MemAvailable: 11680584 kB Buffers: 347604 kB Cached: 3685460 kB SwapCached: 0 kB ... Shmem: 12348 kB SReclaimable: 210352 kB ...free命令Mem行各列与/proc/meminfo指标的精确映射关系如下所示free命令/proc/meminfo对应指标/计算公式解释totalMemTotal系统的总物理内存大小。usedMemTotal - MemFree - Buffers - Cached被应用程序和部分不可回收的内核结构占用的内存。free命令本身也可以通过内部计算 total - free - buff/cache 得出此值。freeMemFree完全未被使用的物理内存。sharedShmem进程间共享的内存总量。主要包括System V共享内存、POSIX共享内存以及通过tmpfs实现的共享匿名映射。buff/cacheBuffers Cached SReclaimable用于I/O优化的、可被内核回收的缓存总和availableMemAvailable估算的、可供新应用程序启动而不会导致系统交换swapping的内存量。衡量系统可用内存最重要的指标。1.1 buff/cache内存占用分析在free命令的输出中有时候看到buff/cache列的数值非常高这部分是哪一部分占用的先来看下buff/cache部分的构成buff/cache是Linux内核为了提升系统I/O性能而设计的一套核心缓存机制的总称它由两部分组成Buffer Cache缓冲区缓存和Page Cache页缓存Page Cache(页缓存)当应用程序读取文件时内核会首先检查所需数据是否已存在于Page Cache中。如果命中则直接从内存返回数据避免了磁盘I/O操作。如果未命中内核会从磁盘读取数据并将其一份存入Page Cache以备后续访问。同样当应用程序写入文件时数据也通常先被写入Page Cache然后由内核在稍后的某个时刻异步地写回磁盘 。Buffer Cache (缓冲区缓存)主要用于缓存块设备如磁盘的元数据如文件系统的目录、权限等以及在写操作时聚合小的写请求优化磁盘写入。buff/cache的占用者是操作系统内核本身其目的是服务于所有进程的文件I/O请求。如果buff/cache占用高并不一定代表内存不足。可能有以下原因I/O操作只要系统上有频繁的文件读写操作例如编译代码、运行数据库、启动应用程序、甚至简单的ls命令都会导致相关文件数据和元数据被加载到buff/cache中。内核的缓存策略内核会主动利用所有可用的空闲内存进行缓存。一个长时间运行且I/O密集的服务其buff/cache会随着时间的推移而增长直到接近系统总内存的某个稳定水平。内存的可回收性buff/cache中的大部分内存都被认为是可回收的Reclaimable。当应用程序需要更多内存时如果空闲内存不足内核会自动启动内存回收机制从buff/cache中“驱逐”掉最近最少使用的缓存页将这部分内存释放出来分配给应用程序 。通过以下命令可查看哪些文件被缓存lsof | grep -E REG.*(path_inode|/.*) | awk {print $9} | sort | uniq -c | sort -rn | head -20在数据库服务器中redo、binlog和relaylog文件的读写会占用较多的page cache。由于redo文件是复用的因此产生的page cache占用是有限的而binlog的写入是增量的随着时间推移会持续占用更多的page cache。1.2 Page Cache的清理机制Buff/cache支持手动清理的方式但是不建议在生产环境这样操作。通过向/proc/sys/vm/drop_caches文件写入特定数值来完成手动清理。echo 1 /proc/sys/vm/drop_caches清理Page Cache。 echo 2 /proc/sys/vm/drop_caches清理dentries和inodes属于Slab的一部分。 echo 3 /proc/sys/vm/drop_caches清理Page Cache、dentries和inodes。执行此操作后会立即看到free命令中buff/cache值大幅下降free值相应上升。这样操作会带来一定的性能抖动因为下一次对相同文件的访问得从磁盘中重新读取系统需要重新建立缓存。更多的是利用系统后台回收线程kswapd它会周期性地检查内存水位。当可用内存下降到低水位线以下时kswapd会被唤醒开始在后台异步地扫描和回收不活跃的内存页直到可用内存恢复到高水位线以上。在系统监控sar –B对应的指标pgscank和pgscand可以看到page cache回收的情况。操作系统中为了衡量内存的使用情况定义了三个内存阈值watermark也称为水位分别是 watermark[min/low/high]上图基本揭示了几个水位的含义当MemFree低于watermark[low]时kswapd进行内存回收直到空闲内存达到watermark[high]后停止回收。如果申请内存的速度太快导致空闲内存降至watermark[min]后内核就会进行direct reclaim直接回收用回收上来的空闲页满足内存申请这样会阻塞应用程序。而watermark[min]的大小等于内核参数min_free_kbytes的值其他几个水位的关系是watermark[low] watermark[min]*5/4 watermark[high] watermark[min]*3/2内核参数vm.min_free_kbytes为强制Linux内核保留的最小空闲内存在数据库服务器中一般为内存的5%左右。vm.watermark_scale_factor控制内存水位线min,low,high之间的间距值越大kswapd的活动范围越宽回收行为可能更平缓但也更频繁。1.3 Available可用内存的估算MemAvailable不是一个简单的统计值而是内核为估算在不触发磁盘I/O交换的情况下系统还能为进程提供多少内存而计算出的一个估算值。简化后的估算公式如下MemAvailable ≈ MemFree - watermark[low] (PageCache - min(PageCache/2, watermark[low]))watermark[low]由上述公式可计算得到PageCache主要是/proc/meminfo中的Active(file)与Inactive(file)之和是文件页缓存所以有时候free命令看到系统中used内存并不高但是available可用内存并不高实际上很大一部分是被buff/cache占用了。以8C32G的gaussdb数据库服务器为例#通过free命令查看如下 total-32019200、used-8868864、free-3593280、shared-8972224、buff/cache-19557056、available-9466240 #OS层内核参数 vm.min_free_kbytes1594560 watermark[low] 1594560*5/4 193200 #/proc/meminfo中page_cache10584832 #available可用内存估算如下 MemAvailable ≈ MemFree - watermark[low] (PageCache - min(PageCache/2, watermark[low])) 3593280 – 1993200 (10584832 - 1993200) 10191712估算的结果与free看到的结果基本一致。2、GoldenDB和GaussDB数据库shared内存差异观察到在不同数据库服务器MySQL系如GoldenDB和GaussDB中使用free命令查看内存使用时发现虽然都使用了数据库buffer缓存空间但是free命令显示的used和shared大小却并不一样。比如GoldenDB中服务器内存为64G数据库进程内存占用36gbufferpool设置为24G通过free看到used为42G、shared部分为3G而buff/cache部分为14G在GaussDB数据库中服务器内存为64g、数据库进程占用38Gshared_buffer设置为22Gfree看到used为22G、shared为24buff/cache部分为34G。两种数据库中为什么会有差异2.1 InnoDB缓冲池的内存机制GoldenDB存储引擎为InnoDB缓冲池的大小由innodb_buffer_pool_size参数控制。InnoDB在启动时会一次性分配缓冲池所需的内存。默认情况下InnoDB会通过标准的malloc()库函数来申请内存。malloc在申请大块内存时其底层通常会调用mmap系统调用并使用MAP_PRIVATE|MAP_ANONYMOUS标志。因此在默认配置下默认配置下的InnoDB缓冲池是一块巨大的私有匿名内存这部分内存将被内核归类为AnonPages。它属于MySQL进程的私有地址空间不属于Shmem也不会被计入free命令的shared列。同时为了避免数据库层和操作系统层的双重缓存InnoDB提供了innodb_flush_method参数。生产环境设置为O_DIRECTDirect I/OInnoDB在读写其数据文件.ibd文件时会通知内核绕过页缓存直接在InnoDB缓冲池和物理磁盘之间进行数据传输。O_DIRECT只对数据文件生效其他文件如二进制日志Binlog、Relaylog以及表结构定义文件等仍然使用标准的缓冲I/O因此它们的内容还是会被内核缓存。再看GoldenDB数据库进程服务器内存为64G、数据库进程占用36G其中bufferpool为24Gfree命令看到used-42G、shared-3G、buff/cache-14G。InnoDB缓冲池的24GB是通过malloc分配的私有匿名内存。它不属于buff/cache计入used内存部分。数据库进程总内存为36GB减去缓冲池的24GB剩下的12GB是MySQL进程的其他私有内存包括但不限于连接线程的私有内存如 sort_buffer_size,join_buffer_size、SQL缓存、日志缓冲区innodb_log_buffer_size、全局协调内存、代码段等。这12GB同样是私有内存属于正常的内存使用部分。shared部分为3G主要是内核部分的缓存占用且不可回收buff/cache为14G主要是内核对文件系统元数据的缓存、系统二进制文件和库文件缓存、innodb自身非O_DIRECT文件如日志文件的页缓存等。这部分内存是可回收的。对于InnoDB存储引擎当数据库内部占用的内存减少但是TOP命令查看到的内存实际占用的RES内存并没有随之减少这一现象和数据库内存的管理机制有关后续将专门分析。2.2 GaussDB缓冲池的内存机制GaussDB数据库中的共享缓存由参数shared_buffers控制与innodb引擎不同的是gaussdb继承了postgresql的特性其shared_buffers 是通过System V共享内存或POSIX共享内存来分配的。数据库启动时主进程会向操作系统申请一块巨大的共享内存段。之后所有为客户端连接服务的后端工作进程都会附加到这块共享内存上共同读写其中的数据。这样在内核看来这一块shared_buffer是一块典型的共享内存。再通过free命令查看时反映在shared列中计入/proc/meminfo的Shmem字段。再看GaussDB数据库进程服务器内存为64G、数据库进程占用28G其中shared_buffer为22Gfree命令看到used-22G、shared-24G、buff/cache-34G。shared达到24G是因为shared_buffer部分计入进来加上系统上其它共享内存占用buff/cache达到34G其中24G是Shmem的贡献其它10G为内核对数据库文件、系统文件、库文件等进行的纯粹的页缓存。used为22G由于部分shared_buffer内存作为buff/cache从总内存中减去了。因此shared_buffers部分不会贡献到usedused部分为数据库进程的私有内存如prepare缓存、plancache缓存等以及操作系统自身占用的内存空间。实际上在分析数据库进程内存消耗的时候是需要加上这部分shared内存。3、总结在日常运维监控中管理员关心的是数据库内存使用情况、是否存在泄露不回收的问题、服务器中available可用内存还有多少。当弄清楚上面available可用内存的计算方式以后能够判断出一个正常运行的数据库环境下available可用内存是否合理。当出现可用内存不足时是否需要扩内存还是要适当的调整bufferpool参数配置。比如一个MySQL数据库原来按照40%total内存分配bufferpool实际上在数据库运行一段时间后buff/cache会占用一部分、used空间也上去那么可用的内存空间就不够了此时bufferpool设置为30%更为合理些。而对于GaussDB数据库由于max_process_memory内存total*65%和shared_buffers在初始化实例的时候自动计算的这部分配置比例是偏高的实际运行的时候更容易出现可用内存低于阈值的情况。另外对服务器而言即使扩容内存这部分内存也会增加到buff/cache部分而不是在free部分对内核而言尽可能多的将可用的内存作为缓存使用以提升性能除非重启服务器再重新分配内存。不过内存扩容后可用的内存大小是会重新计算的。参考资料https://opensource.actionsky.com/20220920-mysql-2/