多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Hazelcast Linux CPU 亲和性辅助库(libaffinity_helper)编译与使用指南

Hazelcast Linux CPU 亲和性辅助库(libaffinity_helper)编译与使用指南 缓存KV存储消息队列流处理后端【免费下载链接】hazelcastHazelcast is a unified real-time data platform combining stream processing with a fast data store, allowing customers to act instantly on>项目地址https://gitcode.com/gh_mirrors/ha/hazelcast点击查看免费下载Hazelcast 为 Linux x86_64 平台提供了一套自研的 CPU 亲和性CPU AffinityJNI 共享库libaffinity_helper.so用于在 TPCThread-Per-Core引擎中把事件循环线程绑定到指定 CPU 核心从而减少缓存抖动、提升实时数据处理性能。本文以仓库中 affinity_helper.md 的编译步骤为核心结合 affinity_helper.c 源码与 ThreadAffinityHelper.java 集成实现完整讲解如何编译、打包、加载并使用这套亲和性辅助库读完即可独立完成构建并理解其底层工作原理。为什么需要自研亲和性辅助库在开启 TPCThread-Per-Core模式的 Hazelcast 中每个 Reactor 线程负责独立的事件循环最佳实践是让这些线程与物理 CPU 核心一一对应。业界常用的 OpenHFT Affinity 库虽然功能完整但要求用户自行加载共享库、并额外把 OpenHFT 的 jar 加入 classpath部署成本较高。从 ThreadAffinityHelper.java 的类注释可以看出Hazelcast 开发这套辅助库的目的正是简化 Linux x86_64 系统上的 CPU 亲和性配置它把一个小体积的共享库直接打进 Hazelcast 的 jar 包运行时自动解压加载用户无需手动干预。在非 Linux x86_64 平台或共享库加载失败时ThreadAffinityHelper.isAffinityAvailable() 会自动回退到 OpenHFT 实现。编译共享库核心步骤仓库中的 affinity_helper.md 给出了 Linux 下编译该共享库的标准步骤。编译前请先确认已安装 GCC 工具链已安装对应 JDK用于提供jni.hPATH_TO_JDK_INCLUDE_DIR你的 JDK 安装目录下 include 目录的完整路径通常形如/usr/lib/jvm/java-17-openjdk-amd64/include不同发行版路径不同以实际安装位置为准。按以下两步依次执行编译命令# 第一步编译目标文件-c 只编译不链接 gcc -c -I ${PATH_TO_JDK_INCLUDE_DIR} -fPIC -Os -o affinity_helper.o affinity_helper.c # 第二步链接生成共享库-shared 生成 .so gcc -shared -fPIC -Wl,-soname,libaffinity_helper.so -o affinity_helper.so affinity_helper.o -lc各编译选项的含义如下选项作用-c只编译为目标文件不执行链接便于分步构建-I ${PATH_TO_JDK_INCLUDE_DIR}指定头文件搜索路径让编译器找到jni.h及其平台子目录如linux/下的jni_md.h-fPIC生成位置无关代码Position Independent Code共享库必须开启-Os优化生成代码体积使打进 jar 的库尽可能小-o affinity_helper.o/-o affinity_helper.so指定输出文件名-Wl,-soname,libaffinity_helper.so设置共享库的 SONAME用于运行时动态链接标识-lc链接 C 标准库libc提供sched_*等系统调用封装所需符号编译成功后得到affinity_helper.so。需要注意的是-I只指定了 JDK include 目录由于源码中#include affinity_helper.h编译时需要把仓库中的 affinity_helper.h 放在同一目录下或通过额外-I指定其所在目录。关于头文件的说明affinity_helper.h 是标准 JNI 工具如javah生成的机器头文件声明了两个导出给 Java 侧调用的 native 方法Java_com_hazelcast_internal_util_ThreadAffinityHelper_getAffinity0无参数返回java/util/BitSet用于读取当前线程的 CPU 亲和性掩码Java_com_hazelcast_internal_util_ThreadAffinityHelper_setAffinity0接收一个java/util/BitSet参数用于设置当前线程的 CPU 亲和性掩码。方法名中的com_hazelcast_internal_util_ThreadAffinityHelper对应 Java 侧的类全限定名com.hazelcast.internal.util.ThreadAffinityHelper这是 JNI 符号导出规则包名中的点替换为下划线决定的。源码级原理解析读取亲和性getAffinity0affinity_helper.c 中的getAffinity0实现如下流程通过sysconf(_SC_NPROCESSORS_ONLN)获取系统在线 CPU 数量通过syscall(SYS_gettid)获取当前线程的 TID注意是线程 ID而非进程 ID——亲和性是线程粒度的属性在 Java 侧创建java.util.BitSet实例通过 JNIFindClass/GetMethodID/NewObject反射式构造调用sched_getaffinity(tid, sizeof(cpu_set_t), cpumask)读取当前线程的 CPU 掩码遍历每个 CPU 编号用CPU_ISSET(i, cpumask)判断该 CPU 是否在掩码中若是则调用 JavaBitSet#set(i)置位返回填充完成的BitSet对象给 Java 侧。代码中每一处 JNI 查找都做了空指针检查失败时通过throw_runtime_exception抛出带详细信息的java.lang.RuntimeException便于排查。设置亲和性setAffinity0affinity_helper.c 中的setAffinity0是反向流程获取系统在线 CPU 数量与当前线程 TID通过 JNI 调用 JavaBitSet#size()获取位集大小用BitSet#get(i)逐个读取每一位用CPU_ZERO(cpumask)清空本地掩码对置位的位调用CPU_SET(i, cpumask)调用sched_setaffinity(tid, sizeof(cpumask), cpumask)应用新的亲和性失败时抛出RuntimeException。整体实现非常轻量不依赖任何第三方 C 库只使用 glibc 的sched.h与 Linux 系统调用这正是它能以极小体积随 Hazelcast jar 分发的原因。Java 侧封装自动解压与加载Java 侧的 ThreadAffinityHelper.java 在静态初始化块中完成库的加载决策见 ThreadAffinityHelper.java#L123-L142检查系统属性hazelcast.affinity.lib.disabled是否为true若为true则完全跳过 Hazelcast 自有库一律使用 OpenHFT 实现否则在OS.isLinux()且 JVM 为 64 位!JVM.is32bit()的前提下尝试从 jar 资源lib/linux-x86_64/libaffinity_helper.so中读取共享库extractBundledLib() 把 jar 内的.so资源复制为临时文件文件名形如hazelcast-libaffinity-helper-*.so再通过System.load()加载任一环节失败都会记录fine级日志并回退到 OpenHFT保证亲和性功能不会成为阻塞性故障。对外暴露的 getAffinity() 与 setAffinity(BitSet) 会根据USE_HZ_LIB标志自动在自有库与 OpenHFT 之间切换异常时记录 warning 日志并返回空BitSet保持接口稳定。在 TPC 引擎中使用亲和性共享库的实际消费方是 TPC 引擎的 Reactor 线程。在 Reactor.java 的 StartEventloopTask#configureThreadAffinity 中事件循环线程启动时从ThreadAffinity对象获取当前线程被允许绑定的 CPU 集合allowedCpus调用ThreadAffinityHelper.setAffinity(allowedCpus)绑定再调用ThreadAffinityHelper.getAffinity()回读实际掩码并比对若不一致记录 warning 日志提示亲和性未生效一致则记录fine级日志确认绑定成功。亲和性规则本身通过系统属性配置。ReactorBuilder.java#L40 定义了属性名hazelcast.tpc.reactor.affinity其取值语法由 ThreadAffinity.java 中的AffinityParser解析支持 CPU 分组每个分组指定允许的 CPU 编号与重复线程数例如用形如0,1,2,3或分组语法2x0-3的表达方式为多个 Reactor 线程分配不同的 CPU 集合。若未设置该属性亲和性默认禁用ThreadAffinity.DISABLED见 ThreadAffinity.java#L38。典型使用场景与注意事项开发者自行编译替换默认情况下用户无需编译——Hazelcast jar 中已内置编译好的lib/linux-x86_64/libaffinity_helper.so。本文的编译步骤主要用于修改 C 源码后的调试、为其他 Linux 发行版重建、或研究 JNI 交互细节。若你重新编译了affinity_helper.so可参照 extractBundledLib() 读取的资源路径lib/linux-x86_64/libaffinity_helper.so将其替换进自己的打包流程。启用亲和性的前提仅 Linux 64 位 JVM 生效源码判断条件见 ThreadAffinityHelper.java#L132其他平台自动回退到 OpenHFT若 OpenHFT 也不可用如缺少 JNA则isAffinityAvailable()返回false使用亲和性配置时会抛出RuntimeException提示 Thread affinity support is not available。关闭自有库如遇与自研库相关的兼容性问题可通过 JVM 参数-Dhazelcast.affinity.lib.disabledtrue强制回退到 OpenHFT 实现。验证亲和性是否生效观察 Reactor 线程启动时的日志——绑定成功打印has affinity for CPUs:...fine 级失败打印affinity was not applied successfullywarning 级也可在运行期用taskset -pc tid核对实际掩码。小结本文完整继承了 affinity_helper.md 中两条核心编译命令并在此基础上补充了每个 gcc 选项的作用、JNI 头文件的符号约定、C 源码中sched_getaffinity/sched_setaffinity的实现细节以及 Java 侧自动解压加载与 TPC 引擎调用链路。掌握这些内容后你既能独立完成libaffinity_helper.so的构建也能在线上环境通过系统属性精确控制 Hazelcast 事件循环线程的 CPU 绑定为实时数据平台的性能调优打下基础。赞分享缓存KV存储消息队列流处理后端【免费下载链接】hazelcastHazelcast is a unified real-time data platform combining stream processing with a fast data store, allowing customers to act instantly on>项目地址https://gitcode.com/gh_mirrors/ha/hazelcast点击查看免费下载相关推荐taskset 命令完全指南Linux CPU 亲和性CPU Affinity设置与查询速查taskset 命令完全指南Linux CPU 亲和性CPU Affinity设置与查询速查 本篇技术指南以 Quick Reference 速查清单项目文档教程Rust 编译器测试指南minicore 测试辅助库与 core 存根的使用方法Rust 编译器测试指南minicore 测试辅助库与 core 存根的使用方法 导读 minicore 是 Rust 编译器rustc测试套件中一个特殊编程语言编译器语言运行时标准库上一篇GetQzonehistory 使用指南一键把 QQ 空间全部历史说说归档到本地下一篇microsoft/fast-colors lchToRGB() 函数解析从 CIELCH 到 RGBA 的色彩转换实战创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表