GreatSQL社区

搜索

DB架构师:曾凡坤

MYSQL内存.OOM.大页内存

DB架构师:曾凡坤 已有 101 次阅读2025-1-24 01:06 |个人分类:MYSQL|系统分类:运维实战| MySQL

在以前的公众号里写了好几篇关于MYSQL内存的探讨和研究挖掘的.现在在这个公号整理成一个完整篇.

对于从ORACLE过来的人来说对OOM不太熟悉,大页内存熟悉.ORACLE主要是SGA共享池碎片的问题. ORACLE内存比较稳定,关系简单.还有自动内存调节的功能.所以ORACLE只要配置好了,基本就稳定如老狗!

MYSQL呢? 其实也要配置好了,也稳如老狗.不过MYSQL比O还有更多隐藏的内存问题.

第一 OOM

OOM 是三个英文单词的组合,具体是什么,我这里不说.其实知道了没有什么价值,徒增给大脑带来麻烦.除了在PPT装个逼外.我们可以把OOM当做IT的专业英语单词.

OOM:中文解释是LINUX系统给进程分配物理内存的时候,发现不够了,SWAPFILE也不够.那就把使用最多内存的进程给KILL掉.

西方人,芬兰人这脑回路也够呛的.不过符合他们的文化.自己得不到就抢别人的!

当然系统也给出了选项,不过默认是KILL掉.

参数:panic_on_oom:用来控制当内存不足时该如何做。
/proc/sys/vm/panic_on_oom
值为0:内存不足时,启动OOM killer。
值为1:内存不足时,有可能会触发 kernel panic(系统重启),也有可能启动 OOM killer
值为2:内存不足时,表示强制触发 kernel panic,内核崩溃GG(系统重启)。

额! 怎么没有杀掉申请内存的进程呢?


既然这个参数没有,那我们启动打分机制,修改打分的对象,故意把PUA某个进程.

我们的LINUX系统给进程使用内存打个分,得分高的就KILL掉.

参数:oom_kill_allocating_task用来决定触发OOM时先杀掉哪种进程
/proc/sys/vm/oom_kill_allocating_task
值为0会 kill 掉得分最高的进程。
值为非0会kill 掉当前申请内存而触发OOM的进程


我说呢,怎么可能没有呢? 杀掉申请内存的进程. 所以我们这里选择非0.


要是是MYSQLD 自己申请内存呢? 岂不是自己杀自己啊?

oom_adj,oom_score_adj,oom_score用来控制进程打分
/proc/进程id/oom_score_adj
oom_score_adj-1000,也就是表示禁止 OOM killer 杀死该进程
另外两个参数不用关心


开启OOM日志,是的如果真的发生了,你想知道哪个进程被KILL ,使用的虚拟内存总量、物理内存、进程的页表信息

oom_dump_tasks以记录进程标识信息、该进程使用的虚拟内存总量、物理内存、进程的页表信息
/proc/sys/vm/oom_dump_tasks
值为0关闭打印上述日志。在大型系统中,可能存在上千进程,逐一打印使用内存信息可能会造成性能问题。
值为非0有三种情况会打印进程内存使用情况。1、由OOM 导致 kernel panic 时;2、没有找到符合条件的进程 kill 时;3、找到符合条件的进程并 kill 时。


好了 到此我们LINUX系统层面上针对数据库做了OOM相关参数的调整.


第二 大页内存

LINUX系统 把物理内存按4KB单位进行管理.  进程中的虚拟内存约有256TB那么大,这里说的地址寻址最大范围.其实2的64翻,不止是256TB.只不过LINUX64给普通进程最多256TB的空间.

虚拟内存的地址以前叫逻辑地址, 是进程操控的地址,程序打印各种变量的0X????的地址. 程序启动后申请各种内存来存放自己的代码,全局变量,函数参数,文件隐射,动态申请内存.

所以程序和进程实际上用不了那么多内存,尤其是256TB.也就是说程序运行过程中使用内存是动态的.

为此LINUX系统不会给进程分配物理内存,而是到了使用的时候才真正的分配.那怕你MALLOC后, 在系统来看只是申请了而已.

当进程申请内存的时候,只是在页表里开辟空间.

页表是什么?

页表是进程虚拟地址和系统里的物理地址1:1对应关系.

不过进程虚拟地址,它本身也要占用内存空间.64位地址需要8个字节.那么4G的虚拟内存空间全部地址需要占用多少物理内存呢?

4*1024*1024/4(4K页)*8(地址大小)=8M大小.看起来很少,要是加上系统的上千个进程呢? 也就是8GB.

这就有点大了.你说不可能,绝对不可能,我们的系统不可能有上千个进程.

不过你的系统有 256GB的内存吧? 像ORACLE和PG这种进程模式,每个链接都是个进程. 256GB内存的一半用于INNODB_BUF 也就是128GB,按照上面公司4GB需要8M物理内存. 128GB需要256MB内存.

还好对MYSQL来说无所谓,对ORACLE,对PG来说500个链接就死翘翘了.因为INNODB_BUF是共享的,都要占进程的虚拟内存,

256MB*500= 256GB的一半=128GB. 这就可怕吧.光存放地址都耗那么多内存,还要让人活不,还有没有法律啊?

当然LINUX也优化地址占用内存太大的问题. 通过5级页表来缩减.

第一级页表 4KB大小,可以存放64位占用8个字节的 512个地址.如果这512个地址是指向第2级的页表的话, 那么可以存放多少个地址呢?

512*4KB/8=26,2144个地址. 页表只要花费(512+1)*4KB=2052KB

不过26,2144只需要占用2048KB......

那就继续分成,分到5级.

算了我不算了.反正呢,就是呢? 通过页表节约了地址所占用的内存.

这里简单的实现了4级,也就是LINUX 32位的. CR3寄存器放的是第一的页表的首地址,然后虚拟地址最高9位,换算成数字,就是在页表的偏移量. 也就是页表数组的下标

PAGE_TABE_PGD[9], 该数组里面存放的是下一级的页表首地址也就是PUD,然后都是这样的,虚拟地址后面的都是偏移量.最后的是物理偏移量.

我在理解这段的时候,问过很多人,他们说虚拟地址是规划好的,又说是根据物理地址反向生成的. 有人把它类比于邮政编码! 都不对味!

实际上是根据进程逻辑地址线性增长的.也就是从0开始到128TB. 这个是死的.

按照我所思,一个进程的页表数组或者是动态数组.这个偏移量原先已经确定的.也就是说9位虚拟地址换算成数字,有可能一开始起跳是9, 然后又是1,3,5,8,再是2,4,6,0,7 这种乱序的. 哪怕是物理内存也是这样的,物理偏移量也是固定的.

也就是稀疏的动态数组.前面都是空地址:[ ],[ ],[ ],[ ],[ ],[ ],[ ],[ ],[9 ]


上图说明,我们的进程有六大区,基本上地址是递增的,只要栈区是向下递减的.

这六大区是紧凑型的呢? 也就代码段的结束地址跟数据段开始地址是否紧挨着的?



还是按照128TB空间,每个区单独享受一段地址空间.彼此之间隔着绿化带.

也许LINUX为了解决稀疏的问题,很可能每个区都是从0地址开始.



毕竟搞页表就是为了节约地址占用的空间,如果页表因为偏移量导致页表数组稀疏的话.就没啥价值啊. 辛苦老半天折腾内存,好小子你却在浪费内存.

原本32位系统有段+页的方式把逻辑地址,线性地址,虚拟地址. LINUX 64位只有分页的话,显然不合理! 上图使用VM_AREA_STRUCT结构体的VM_START和VM_END指针定位六大区的开始和结束地址.

解决稀疏页表和六大区动态分配地址.很大程度可能在VM_AREA_STRUCT这里解决.


不过这样还是嫌 页表 占用内存还是太多.要不继续搞分级下去,搞成18级页表?

这不行了,地址转换,查页表也会消耗很多CPU的.

我们是不是可以把4KB的物理页,改成2M的物理页呢? 哎 你的注意不错,就这么干.



透明大页.

所谓透明大页,其实就是动态大页, 就是动态分配大页给进程使用.所以对DB这个来说不太好,性能有影响,所以就关闭它吧


关闭大页内存动态分配 #LINUX  7[root@bdp04 ~]# cat /proc/version[root@bdp04 etc]# cd /etc/default/[root@bdp04 default]# cp grub grub_bak_180508[root@bdp04 default]# vi grubGRUB_CMDLINE_LINUX="rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet transparent_hugepage=never"生效配置[root@bdp04 default]# cp  /boot/grub2/grub.cfg  /boot/grub2/grub.cfg_bak180508[root@bdp04 default]#  grub2-mkconfig -o /boot/grub2/grub.cfg Linux7 第二种方式在/etc/rc.local中加入如下两行--先备份,修改后重启系统if test -f /sys/kernel/mm/transparent_hugepage/enabled; then echo never > /sys/kernel/mm/transparent_hugepage/enabledfiif test -f /sys/kernel/mm/transparent_hugepage/defrag; then echo never > /sys/kernel/mm/transparent_hugepage/defragfi  请相应地将RHEL内核的文件路径更改为/ sys / kernel / mm / redhat_transparent_hugepage / 验证查询是否关闭oracle@bdp04 ~]$ grep Huge /proc/meminfoAnonHugePages:         0 kB  与透明大页有关,透明大页关闭,则显示0[root@testhost ~]# cat /sys/kernel/mm/transparent_hugepage/enabledalways madvise [never]


MYSQL和大页

MYSQL支持大页的,从官方配置来看,不是很准确,从我生产实践中发现怪事.你明明分配了足够的静态大页,而告诉你MYSQL使用上了大页.

通过 CAT /PROC/MEMINFO 可看到使用了几个页! 然后系统报警内存已用上 90%,FREE非常少. 设置大页内存不会超过80%,留足了给系统.而且只是针对INNODB_BUFFER大小的.

后来发现MYSQL 还是用的传统4K页内存. 那么静态大页叠加4K页内存自然就超过了90%.

后来不知道怎么试来试去就解决掉了,好像MEMLOCK,静态分配INNODB_BUF.

注意:静态大页不要设置太多,避免系统自身内存不够,启动就进入黑屏.

MYSQL大佬测试 开启大页使用SYSBEANCH 好像不见性能有多大的提升.可CSDN有文章说能提升20%的性能.

虽然MYSQL 使用线程模型,单进程使用128GB内存BUF,也需要256MB的页表.还不包括链接线程内存和内存引擎内存.

虽然MYSQL不那么明显,不过使用大页理论上说可以降低页表和TLB压力


hugepage特点linux系统启动,hugepage就被分配并保留,不会pagein/pageout,除非人为干预,如改变hugepage的配置等;根据linux内核的版本和HW的架构,hugepage的大小从2M到256M不等。因为采用大page,所以也减少TLB和page table的管理压力为什么使用hugepage对于大内存(>8G),hugepage对于提高在linux上的oracle性能是非常有帮助的 1)Larger Page Size and Less of Pages:减少了HugeTLB 的工作量  2)No Page Table Lookups:因为hugepage是不swappable的,   所有就没有page table lookups。  3)No Swapping: 在Linux下,hugepage是不支持swapping  4)No 'kswapd' Operations:在linux下进程“kswapd”是管理swap的,如果是大内存,那pages的数量就非常大, 那“kswapd”就会被频繁的调用,从而会影响性能。

MYSQL 如何配置大页

在 MySQL 中启用大页(Huge Pages)可以提高性能,还可以减少内存的使用。大页能够减少操作系统的页表,由此可减轻 CPU 负担和内存空间,从而减少了内存管理开销。下面是在 Linux 系统中启用 MySQL 大页的步骤:

  1. 配置 Linux 系统大页:在物理或虚拟机上启用大页,请使用以下命令后设置了足够的大页:
echo 2048 > /proc/sys/vm/nr_hugepages

此处示例启用了 2048 个大页。请注意,此操作需要 root 用户权限。

1 计算需要多少 huge pages

启用 huge page之前 首先我们要计算分配多少huge page给mysql 使用。一般的建议是mysql使用的总内存大小加上10%。计算公式如下


S =  (query_cache_size + table_open_cache + innodb_buffer_pool_size + innodb_log_file_size + performance_schema.memory) + 10 %

2 设置mysql用户组使用huge pages


# id mysqluid=27(mysql) gid=27(mysql) groups=27(mysql)
3 为 mysql 用户提供memlock限制的“无限”值

编辑/etc/security/limits.conf 增加


#            ##Where:# can be:#        - a user name#        - @mysql 表示mysql用户组的不受限##设置mysql 使用 HugePages@mysql soft memlock unlimited@mysql hard memlock unlimited
  1. 通过运行下列命令可检查您的系统是否已经启用了大页:
grep Hugepagesize /proc/meminfo

输出信息应该如以下示例所示:

Hugepagesize:   2048 kB
  1. 停止 MySQL 服务。
sudo service mysql stop
  1. 按如下方式添加以下内容到 MySQL 配置文件 my.cnf:
[mysqld]large-pages = 1
  1. 启动 MySQL。
sudo service mysql start
  1. 检查 MySQL 是否已启用大页。

使用如下 SQL 语句可以检查 MySQL 是否启用了大页:

SHOW VARIABLES LIKE 'large_pages';

如果结果为 large_pages | ON 则表示启用了大页,否则说明大页未启用。下面日志可以查看


[Warning] InnoDB: Failed to allocate 140509184 bytes. errno 12[Warning] InnoDB: Using conventional memory pool

1.nr_hugepages的值*2M小于innodb使用的内存大小,需要调整nr_hugepages 的值。

2.针对mysql用户组的 memlock 是否设置。在启动mysql时,一定要先查看用ulimit -a 来查看max locked memory 设置是否合理。


需要注意的是,启用大页需要硬件和操作系统的支持,并且需要对系统进行适当的配置。在启用大页之前,请检查操作系统和硬件是否支持。在启用大页之后,您还需要进行一些测试和基准测试,以确保大页功能对 MySQL 的性能有积极的影响。

https://dev.mysql.com/doc/refman/5.7/en/large-page-support.html

https://www.cnblogs.com/gomysql/p/3627915.html



MYSQL 内存分配器

MYSQL自带的是GLIBC的内存分配器, 这个内存分配器不够快,有内存碎片的问题.

GLIBC的内存分配器和内存池主要是C/C++为了跨平台,而自己DIY个内存池. 图中KMAOLLC内存池是LINUX系统管理物理也搞了一套内存分配和缓存池.

这里我们要替换GLIBC内存池, 替换成JMALLOC,或者TMALLOC.一般MYSQL分支版本比如说PERCONA和GREATSQL 都自动替换了内存分配器为JMALLOC.

这就是我们经常从系统看到MYSQLD使用了大量物理内存,而你登陆MYSQL通过PS库或者SYS库查看内存,统计一下发现小于系统被占用的内存. 很大情况就是GLIBC内存分配器没有释放内存导致的.

检查jemalloc分配器
[root@localhost home]# lsof -n | grep jemalloc
mysqld     8114         root  mem       REG              253,2   2264568         71 /home/Percona8.0/lib/mysql/libjemalloc.so.1
mysqld     8114 8117    root  mem       REG              253,2   2264568         71 /home/Percona8.0/lib/mysql/libjemalloc.so.1
mysqld     8114 8118    root  mem       REG              253,2   2264568         71 /home/Percona8.0/lib/mysql/libjemalloc.so.1
mysqld     8114 8119    root  mem       REG              253,2   2264568         71 /home/Percona8.0/lib/mysql/libjemalloc.so.1
yum install -y lsof
jemalloc-3.6.0-1.el7.x86_64.rpm
https://repo.percona.com/yum/release/7/RPMS/x86_64/jemalloc-3.6.0-1.el7.x86_64.rpm 
rpm -ivh jemalloc-3.6.0-1.el7.x86_64.rpm
my.cnf中添加配置,并重启mysql(mysql 5.5+)
[mysqld_safe]
malloc-lib=/usr/local/lib/libjemalloc.so

<strong>下图说明INNODB层使用系统函数直接分配内存,绕过了GLIBC内存分配器<br><br></strong>

MYSQL 内存

MYSQL 主要分为

1 共享全局内存,

2 线程独享内存,

3 以及内存引擎占用内存

线程如下图,比较老而且有些错误,只是缺少了BINLOG_CACHE,它属于线程独享

下图BASE MEMORY属于共享内存,MEMORY PER CONNECTION是线程独享内存.

静态参数统计线程独享内存


SELECT ( @@read_buffer_size+ @@read_rnd_buffer_size+ @@sort_buffer_size+ @@join_buffer_size+ @@binlog_cache_size+ @@thread_stack+ @@tmp_table_size+ 2*@@net_buffer_length) / (1024 * 1024) AS MEMORY_PER_CON_MB;    


通过PS库查看全局内存

select event_name, current_alloc, high_allocfrom sys.memory_global_by_current_byteswhere current_count > 0;

全局内存如下

关于上图想下载高清的

通过网盘分享的文件:MYSQL内存_祖先教小凡仙.bmp链接: https://pan.baidu.com/s/130sIDTb-nnoBtm7MFMD7Bw?pwd=SHAR 提取码: SHAR

往期文章:

海鲨DB

PGSQL17客户端支持大量SHOW命令

国产数据库 SharkDB 17.1 源码安装




评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 立即注册

合作电话:010-64087828

社区邮箱:greatsql@greatdb.com

社区公众号
社区小助手
QQ群
GMT+8, 2025-2-14 05:56 , Processed in 0.014582 second(s), 10 queries , Redis On.
返回顶部