MySQL 操作系统内存分配机制结合 jemalloc

在MySQL运行环境中,碰到内存使用完之后不释放情况,概率还是比较高的。那有必要了解下,为什么会出现这样的情况。

在翻看很多材料和了解下源码实现。在早期开发InnoDB时,操作系统运行时提供的内存分配器,通常缺乏性能和可伸缩性。当时,还没有针对多核cpu进行优化的内存分配器库。因此,InnoDB在内存子系统中实现了自己的内存分配器。这个分配器由单个互斥锁保护,同时InnoDB还在系统分配器(malloc和free)周围实现了一个包装器接口,同样由单个互斥锁保护。随着多核系统的广泛应用,以及操作系统的成熟,操作系统提供的内存分配器也有了显著的改进。与过去相比,这些新的内存分配器性能更好,可扩展性更强。大多数工作负载,特别是那些经常分配和释放内存的工作负载,都可以从使用高度调优的内存分配器中获益,而不是使用内部的innodb的内存分配器,这可能成为瓶颈。

Linux操作系统内存分配方式

在Linux操作系统下进程通过C标准库中的内存分配函数 malloc 向系统申请内存(malloc() 并不是系统调用,也不是运算符,而是C库里的函数,用于动态分配内存。),但真正与内核交互之间,其实还隔了一层,即内存分配管理器(memory allocator)。

目前Linux系统常见的内存分配器包括:ptmalloc(Glibc)、tcmalloc(Google)、jemalloc(FreeBSD)。

分别介绍下大致的功能:

1.ptmalloc(glibc malloc)

GNU Libc 的内存分配器(allocator)—ptmalloc,起源于Doug Lea的malloc。由Wolfram Gloger改进得到可以支持多线程。
内存分配器中只有一个主分配区(main arena),每次分配内存都必须对主分配区加锁,分配完成后释放锁,在SMP多线程环境下,对主分配区的锁的争用很激烈,严重影响了malloc的分配效率。ptmalloc增加了动态分配区(dynamic arena),主分配区与动态分配区用环形链表进行管理。每一个分配区利用互斥锁(mutex)使线程对于该分配区的访问互斥。每个进程只有一个主分配区,但可能存在多个动态分配区,ptmalloc根据系统对分配区的争用情况动态增加动态分配区的数量,分配区的数量一旦增加,就不会再减少了。而动态分配区每次使用mmap()向操作系统“批发HEAP_MAX_SIZE大小的虚拟内存,如果内存耗尽,则会申请新的内存链到动态分配区heap data的“strcut malloc_state”。如果用户请求的大小超过HEAP_MAX_SIZE,动态分配区则会直接调用mmap()分配内存,并且当free的时候调用munmap(),该类型的内存块不会链接到任何heap data。

Chunk说明:
一个arena 中最顶部的 chunk 被称为「top chunk」。它不属于任何 bin 。当所有 bin 中都没有合适空闲内存时,就会使用 top chunk 来响应用户请求。当top chunk 的大小比用户请求的大小小的时候,top chunk 就通过 sbrk(main arena)或 mmap( thread arena)系统调用扩容。

优点:

  • ptmalloc是GNU C库(glibc)中的默认内存分配器,广泛用于Linux系统。
  • 基于Doug Lea的malloc实现,采用了多种技术,如自由链表、分离器和堆的延迟绑定等。
  • ptmalloc的特点是成熟、稳定,并且与GNU C库紧密集成。

缺点:

  • 如果后分配的内存先释放,无法及时归还系统。因为ptmalloc收缩内存是从top chunk开始,如果与top chunk相邻的 chunk不能释放, top chunk 以下的 chunk 都无法释放。
  • 内存不能在线程间移动,多线程使用内存不均衡将导致内存浪费。
  • 每个chunk至少8字节的开销很大。
  • 不定期分配长生命周期的内存容易造成内存碎片,不利于回收。
  • 加锁耗时,无论当前分区有无耗时,在内存分配和释放时,会首先加锁。
  • 从上述来看ptmalloc的主要问题其实是内存浪费、内存碎片、以及加锁导致的性能问题。

2.tcmalloc(Google malloc)

tcmalloc是Google开发的内存分配器,在Golang、Chrome中都有使用该分配器进行内存分配。有效的优化了ptmalloc中存在的问题。
tcmalloc是专门对多线并发的内存管理而设计的,tcmalloc主要是在线程级实现了缓存,使得用户在申请内存时大多情况下是无锁内存分配。整个 TCMalloc 实现了三级缓存,分别是ThreadCache(线程级缓存),Central Cache(中央缓存:CentralFreeeList),PageHeap(页缓存),最后两级需要加锁访问

  • tcmalloc是Google开发的内存分配器,主要用于Google的C++代码。
  • tcmalloc通过减少锁的竞争和减少内存碎片来提高性能。
  • 它使用线程本地缓存(Thread-Caching Malloc)的概念,将内存分配的任务分散到不同的线程中,以减少对共享数据结构的竞争。
  • tcmalloc还有其他一些优化策略,如小对象合并、高效的分配器缓存等。

tcmalloc也带来了一些问题,使用自旋锁虽然减少了加锁效率,但是如果使用大内存较多的情况下,内存在Central Cache或者Page Heap加锁分配。而tcmalloc对大小内存的分配过于保守,在一些内存需求较大的服务(如推荐系统),小内存上限过低,当请求量上来,锁冲突严重,CPU使用率将指数暴增。

3.jemalloc

jemalloc是facebook推出的,目前在firefox、facebook服务器、android 5.0 等服务中大量使用。 jemalloc最大的优势还是其强大的多核/多线程分配能力. 以现代计算机硬件架构来说, 最大的瓶颈已经不再是内存容量或cpu速度, 而是多核/多线程下的lock contention(锁竞争). 因为无论CPU核心数量如何多, 通常情况下内存只有一份. 可以说, 如果内存足够大, CPU的核心数量越多, 程序线程数越多, jemalloc的分配速度越快。

jemalloc解决方法是将一把global lock分散成很多与线程相关的lock。而针对多核心, 则要尽量把不同线程下分配的内存隔离开, 避免不同线程使用同一个cache-line的情况
jemalloc 按照内存分配请求的尺寸,分了 small object (例如 1 – 57344B)、 large object (例如 57345 – 4MB )、 huge object (例如 4MB以上)

  • jemalloc是一款通用的内存分配器,由FreeBSD社区开发,并逐渐被其他系统广泛采用。
  • jemalloc致力于提供高度可扩展性和低碎片化的内存分配。
  • 它使用了多个技术,如分离的内存区域、伙伴分配器、线程本地缓存等。
  • jemalloc还提供了高级特性,如背景线程执行释放、空间利用统计和分析等。
  • 多线程下加锁大大减少

4.小结:

性能方面:

  • ptmalloc在大多数情况下性能良好,但在多线程环境下可能存在一些竞争问题。
  • tcmalloc通过线程本地缓存和减少锁竞争,适用于高并发场景,尤其是多线程服务器应用。
  • jemalloc在可扩展性和碎片化方面表现出色,特别适用于大型内存分配和高负载场景。

场景方面:

  • ptmalloc适用于常规应用,与GNU C库集成紧密。
  • tcmalloc适用于高并发多线程环境,通过线程本地缓存减少竞争。
  • jemalloc适用于可扩展性和低碎片化要求高的场景,提供高级特性和统计信息。

总的来看,作为基础库的ptmalloc是最为稳定的内存管理器,无论在什么环境下都能适应,但是分配效率相对较低。而tcmalloc针对多核情况有所优化,性能有所提高,但是内存占用稍高,大内存分配容易出现CPU飙升。jemalloc的内存占用更高,但是在多核多线程下的表现也最为优异。
应该考虑好内存分配如何管理:

  • 多核多线程的情况下,内存管理需要考虑内存分配加锁、异步内存释放、多线程之间的内存共享、线程的生命周期
  • 内存当作磁盘使用的情况下,需要考虑内存分配和释放的效率,是使用内存管理库还是应该自己进行大对象大内存的管理。(在搜索以及推荐系统中尤为突出)。

MySQL使用的内存管理

  • 从下载官方安装软件时,可以了解到MySQL默认使用的是 glibc的ptmalloc作为内存分配器。

  • 同时在默认操作系统环境中,MySQL启动时,默认使用glibc库的malloc进行内存分配。

但是glibc库的malloc存在某些问题,例如内存分配效率较低,容易出现内存碎片等问题。当然默认的内存管理,结合操作系统在不停的开发完善,内存泄漏问题,也不是常见的。对于MySQL这类高并发、高数据量的数据库,如碰到内存不释放的情况,并避免内存泄漏等问题,开启jemalloc是一个不错的选择。

如何开启jemalloc内存管理

1.下载安装jemalloc内存分配器:

https://github.com/jemalloc/jemalloc/releases

#或
shell$> wget https://github.com/jemalloc/jemalloc/archive/refs/tags/5.3.0.tar.gz

#或
shell$> yum install epel-release -y
shell$> yum install jemalloc jemalloc-devel -y

#源码安装
shell$> ./configure
shell$> make && make install
shell$> echo "/usr/local/lib" > /etc/ld.so.conf.d/local.conf
shell$> ldconfig

2.安装好jemalloc之后,在MySQL配置文件中启用。修改配置文件:


#安装jemalloc库文件位置
shell$> ldconfig -p|grep malloc
    libjemalloc.so.2 (libc6,x86-64) => /lib64/libjemalloc.so.2
    libjemalloc.so (libc6,x86-64) => /lib64/libjemalloc.so
[mysqld]
malloc-lib=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1

这里的路径根据具体系统和jemalloc安装位置有所不同,需做相应调整,之后,重启MySQL服务即可生效。

开启jemalloc能够有效地提高MySQL的性能和稳定性,特别是在高并发的情况下。如果你对MySQL的性能有更高的要求,可以尝试使用更加高级的内存管理工具。

总结

当然官方都是安装操作系统做过很多适配测试,所以默认使用内存分配机制没问题。如碰到内存泄漏,无法修改源头语句,或则 MySQL软件升级。可以采取jemalloc方式(但未必100%有用)。


免责声明:

1、本站资源由自动抓取工具收集整理于网络。

2、本站不承担由于内容的合法性及真实性所引起的一切争议和法律责任。

3、电子书、小说等仅供网友预览使用,书籍版权归作者或出版社所有。

4、如作者、出版社认为资源涉及侵权,请联系本站,本站将在收到通知书后尽快删除您认为侵权的作品。

5、如果您喜欢本资源,请您支持作者,购买正版内容。

6、资源失效,请下方留言,欢迎分享资源链接

文章评论

0条评论