07 - 系统集成
第 7 章:系统集成
7.1 集成方式概览
| 集成方式 | 适用场景 | 侵入性 | 可控性 |
|---|---|---|---|
| 编译时链接 | 自有 C/C++ 项目 | 中 | 高 |
| LD_PRELOAD | 任意已有程序 | 无 | 中 |
| 编译软件时指定 | Redis、MySQL 等 | 低 | 中 |
| 语言运行时配置 | Go、PHP 等 | 低 | 低 |
7.2 C/C++ 项目集成
7.2.1 基本集成
// main.c
#include <stdio.h>
#include <stdlib.h>
// 方式 1:直接使用系统 malloc(配合 LD_PRELOAD)
// 方式 2:使用 jemalloc API
#ifdef USE_JEMALLOC
#include <jemalloc/jemalloc.h>
#define MALLOC(sz) je_malloc(sz)
#define FREE(p) je_free(p)
#define REALLOC(p, sz) je_realloc(p, sz)
#define CALLOC(n, sz) je_calloc(n, sz)
#else
#define MALLOC(sz) malloc(sz)
#define FREE(p) free(p)
#define REALLOC(p, sz) realloc(p, sz)
#define CALLOC(n, sz) calloc(n, sz)
#endif
int main() {
void *p = MALLOC(1024);
FREE(p);
return 0;
}
# 编译
gcc -O2 -DUSE_JEMALLOC -o myapp main.c -ljemalloc
7.2.2 CMake 集成
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyProject C)
find_package(PkgConfig REQUIRED)
pkg_check_modules(JEMALLOC REQUIRED IMPORTED_TARGET jemalloc)
add_executable(myapp main.c)
target_link_libraries(myapp PRIVATE PkgConfig::JEMALLOC)
target_compile_definitions(myapp PRIVATE USE_JEMALLOC)
7.2.3 Makefile 集成
CC = gcc
CFLAGS = -O2 -Wall -g
LDFLAGS = -ljemalloc -lpthread
# 如果 jemalloc 安装在非标准路径
JEMALLOC_PREFIX = /usr/local
CFLAGS += -I$(JEMALLOC_PREFIX)/include
LDFLAGS += -L$(JEMALLOC_PREFIX)/lib -Wl,-rpath,$(JEMALLOC_PREFIX)/lib
myapp: main.c
$(CC) $(CFLAGS) -DUSE_JEMALLOC -o $@ $< $(LDFLAGS)
clean:
rm -f myapp
7.2.4 自定义 Allocator 封装
// my_allocator.h - 生产级封装
#ifndef MY_ALLOCATOR_H
#define MY_ALLOCATOR_H
#include <stddef.h>
#ifdef USE_JEMALLOC
#include <jemalloc/jemalloc.h>
static inline void *my_malloc(size_t sz) { return je_malloc(sz); }
static inline void my_free(void *p) { je_free(p); }
static inline void *my_realloc(void *p, size_t sz) { return je_realloc(p, sz); }
static inline void *my_calloc(size_t n, size_t sz) { return je_calloc(n, sz); }
static inline size_t my_malloc_usable_size(void *p) { return je_malloc_usable_size(p); }
static inline void my_malloc_stats(void) {
je_malloc_stats_print(NULL, NULL, NULL);
}
static inline void my_get_stats(size_t *allocated, size_t *resident) {
uint64_t epoch = 1; size_t sz = sizeof(epoch);
je_mallctl("epoch", &epoch, &sz, &epoch, sz);
sz = sizeof(size_t);
if (allocated) je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
if (resident) je_mallctl("stats.resident", resident, &sz, NULL, 0);
}
#else
#include <stdlib.h>
#include <malloc.h>
static inline void *my_malloc(size_t sz) { return malloc(sz); }
static inline void my_free(void *p) { free(p); }
static inline void *my_realloc(void *p, size_t sz) { return realloc(p, sz); }
static inline void *my_calloc(size_t n, size_t sz) { return calloc(n, sz); }
static inline size_t my_malloc_usable_size(void *p) { return malloc_usable_size(p); }
static inline void my_malloc_stats(void) { /* no-op */ }
static inline void my_get_stats(size_t *a, size_t *r) { if (a) *a = 0; if (r) *r = 0; }
#endif
#endif
7.3 Redis 集成
Redis 从 2.4.0 开始默认使用 jemalloc(Linux 版本)。
7.3.1 编译时指定
# Redis 默认使用 jemalloc(如果有)
make MALLOC=jemalloc
# 明确使用 jemalloc(而非 libc 或 tcmalloc)
make MALLOC=jemalloc CFLAGS="-g -O2"
# 使用自定义 jemalloc 路径
make MALLOC=jemalloc \
JEMALLOC_PREFIX=/opt/jemalloc \
CFLAGS="-I/opt/jemalloc/include" \
LDFLAGS="-L/opt/jemalloc/lib -Wl,-rpath,/opt/jemalloc/lib"
7.3.2 运行时配置
# 启动 Redis 时配置 jemalloc
LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 \
MALLOC_CONF="narenas:8,dirty_decay_ms:3000,background_thread:true" \
redis-server /etc/redis/redis.conf
7.3.3 Redis 中的 jemalloc 命令
Redis 暴露了 jemalloc 的统计能力:
# Redis CLI 查看内存分配信息
redis-cli MEMORY STATS
redis-cli MEMORY DOCTOR
redis-cli MEMORY USAGE <key>
redis-cli INFO memory
# Redis 特有的 jemalloc 命令
redis-cli MEMORY MALLOC-STATS # 等同于 je_malloc_stats_print()
redis-cli MEMORY PURGE # 触发脏页回收
7.3.4 Redis 调优建议
| 参数 | 推荐值 | 说明 |
|---|---|---|
narenas | 4-8 | Redis 主要是单线程,不需要太多 Arena |
dirty_decay_ms | 1000-5000 | 及时归还内存 |
background_thread | true | 避免主线程阻塞 |
tcache_max | 32768 | 默认即可 |
7.4 MySQL / MariaDB 集成
7.4.1 编译时集成
# MariaDB 支持在编译时指定 jemalloc
cmake . \
-DCMAKE_BUILD_TYPE=Release \
-DWITH_JEMALLOC=ON \
-DJEMALLOC_PREFIX=/usr/local
make -j$(nproc)
sudo make install
7.4.2 运行时集成
# systemd 服务配置
# /etc/systemd/system/mysql.service.d/jemalloc.conf
[Service]
Environment="LD_PRELOAD=/usr/local/lib/libjemalloc.so.2"
Environment="MALLOC_CONF=narenas:8,dirty_decay_ms:5000,background_thread:true"
sudo systemctl daemon-reload
sudo systemctl restart mysql
7.4.3 MySQL 中的 jemalloc 统计
-- MariaDB 查看内存分配器信息(如果编译时启用了 jemalloc)
SHOW ENGINE INNODB STATUS\G
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';
7.5 PHP 集成
7.5.1 PHP-FPM 使用 jemalloc
# 方法 1:LD_PRELOAD
# /etc/systemd/system/php-fpm.service.d/jemalloc.conf
[Service]
Environment="LD_PRELOAD=/usr/local/lib/libjemalloc.so.2"
Environment="MALLOC_CONF=narenas:4,dirty_decay_ms:2000"
7.5.2 编译 PHP 时使用 jemalloc
./configure \
--prefix=/usr/local/php \
--enable-fpm \
LDFLAGS="-L/usr/local/lib -ljemalloc" \
CFLAGS="-I/usr/local/include"
make -j$(nproc) && sudo make install
7.6 Go 项目集成
Go 语言使用自己的内存分配器(基于 tcmalloc 思路),通常不直接使用 jemalloc。但在调用 C 库(CGO)时,C 代码的内存分配可以使用 jemalloc。
7.6.1 CGO 中使用 jemalloc
package main
/*
#cgo LDFLAGS: -ljemalloc
#include <jemalloc/jemalloc.h>
#include <stdlib.h>
void* alloc_with_jemalloc(size_t size) {
return je_malloc(size);
}
void dealloc_with_jemalloc(void* ptr) {
je_free(ptr);
}
*/
import "C"
import "unsafe"
func AllocWithJemalloc(size int) unsafe.Pointer {
return C.alloc_with_jemalloc(C.size_t(size))
}
func DeallocWithJemalloc(ptr unsafe.Pointer) {
C.dealloc_with_jemalloc(ptr)
}
func main() {
ptr := AllocWithJemalloc(1024)
defer DeallocWithJemalloc(ptr)
// 使用内存...
}
# 设置 CGO 编译路径
export CGO_CFLAGS="-I/usr/local/include"
export CGO_LDFLAGS="-L/usr/local/lib -ljemalloc"
go build -o myapp main.go
注意:Go 的 GC 管理的内存不经过 jemalloc,只有 C 代码分配的内存使用 jemalloc。
7.7 Python 中使用 jemalloc
7.7.1 LD_PRELOAD 方式
# 直接对 Python 进程使用 jemalloc
LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 python3 my_script.py
# 配合参数
LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 \
MALLOC_CONF="narenas:4,dirty_decay_ms:3000" \
python3 my_script.py
7.7.2 在代码中使用
# 方式 1:通过 ctypes 使用 jemalloc API
import ctypes
jemalloc = ctypes.CDLL("libjemalloc.so.2")
# 使用 jemalloc 的 malloc 和 free
ptr = jemalloc.je_malloc(1024)
jemalloc.je_free(ptr)
# 方式 2:全局 LD_PRELOAD 即可,无需代码改动
7.8 Rust 中使用 jemalloc
7.8.1 使用 jemallocator crate
# Cargo.toml
[dependencies]
jemallocator = "0.5"
// src/main.rs
#[global_allocator]
static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
fn main() {
// 所有 Rust 内存分配自动使用 jemalloc
let v = vec![0u8; 1024 * 1024];
println!("allocated {} bytes", v.len());
}
7.8.2 使用 tikv-jemallocator(维护更活跃)
# Cargo.toml
[dependencies]
tikv-jemallocator = "0.6"
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
7.9 Nginx 集成
# 编译 Nginx 时指定 jemalloc
./configure \
--prefix=/etc/nginx \
--with-ld-opt="-L/usr/local/lib -ljemalloc" \
--with-cc-opt="-I/usr/local/include"
make -j$(nproc) && sudo make install
或使用 LD_PRELOAD:
# /etc/systemd/system/nginx.service.d/jemalloc.conf
[Service]
Environment="LD_PRELOAD=/usr/local/lib/libjemalloc.so.2"
Environment="MALLOC_CONF=narenas:4,dirty_decay_ms:3000,background_thread:true"
7.10 集成注意事项
| 要点 | 说明 |
|---|---|
| malloc/free 必须匹配 | jemalloc 分配的内存必须用 jemalloc 的 free 释放 |
| LD_PRELOAD 全局生效 | 所有共享库的 malloc 都会被替换 |
| 自定义前缀共存 | 使用 --with-jemalloc-prefix 实现精确控制 |
| 调试时禁用 | ASan、Valgrind 等工具与 jemalloc 冲突 |
| 容器中使用 | 确保库文件在容器内可访问 |
7.11 本章小结
| 软件 | 集成方式 | 推荐配置 |
|---|---|---|
| Redis | 默认内置 | narenas:4-8,background_thread:true |
| MySQL/MariaDB | 编译选项或 LD_PRELOAD | narenas:8,dirty_decay_ms:5000 |
| PHP-FPM | LD_PRELOAD | narenas:4,dirty_decay_ms:2000 |
| Go (CGO) | CGO LDFLAGS | 按需配置 |
| Rust | jemallocator crate | 默认即可 |
| Nginx | ld-opt 或 LD_PRELOAD | narenas:4,background_thread:true |
扩展阅读
上一章:第 6 章:性能调优 下一章:第 8 章:基准测试