背景说明
在很多场景下,我们都有从java调用脚本的功能,常用的有groovy脚本,pyhon脚本和js等。在上篇中,列举了java调用pyhton脚本的几种方案,并最终选取了JEP的方式,但是随着业务的提升,JEP由于GIL锁的问题,每次只能执行一次脚本,并发高的情况下,由于资源竞争,JEP会变得非常慢,于是需要新的方案来解决这个问题。
GraalVM介绍
通过查阅资料,找到了一种可行的方案,首先我们了解下什么是GraalVM
GraalVM 是一款基于 Java 虚拟机 (JVM) 的新型全栈虚拟机,由 Oracle 公司开发和维护。GraalVM 除了支持 Java 语言之外,还支持多种编程语言,如 JavaScript、Python、Ruby、R 等。
GraalVM 的主要特点包括:
- 高性能:GraalVM 基于 JIT (Just-in-time) 编译技术,在运行时动态生成本地代码,可以显著提高程序的性能。
- 多语言支持:GraalVM 支持多种编程语言,并可以在不同的语言之间进行互操作。
- 低内存占用:GraalVM 使用了一种名为 GraalVM Native Image 的技术,可以将应用程序编译成本地可执行文件,从而减少内存占用和启动时间。
- 可扩展性:GraalVM 提供了多种插件和扩展点,可以方便地扩展虚拟机的功能和性能。
- GraalVM 还提供了多种工具和库,如 GraalVM Compiler、GraalVM Truffle、GraalVM Polyglot 等,可以帮助开发人员更好地使用和优化 GraalVM。
使用GraalVM实现python脚本
GraalPython 是基于 GraalVM 平台的 Python 解释器,由 Oracle 公司和社区开发维护。它支持 Python 3.7 语言规范,并且与 CPython 兼容,可以运行大部分的 Python 代码。
GraalPython 的主要特点包括:
- 高性能:GraalPython 使用了 GraalVM 平台的 JIT 编译器技术,可以将 Python 代码编译成本地代码,提高程序的执行效率。
- 多语言互操作:GraalPython 支持 GraalVM 平台的 Polyglot API,可以与其他编程语言进行互操作。
- 执行安全:GraalPython 支持沙箱机制,可以限制 Python 代码的访问权限,保证执行安全。
- 可扩展性:GraalPython 提供了多种扩展点和插件机制,可以方便地扩展其功能和性能。
GraalPython 与 CPython 之间的差异主要在于其内部实现机制。GraalPython 使用了 GraalVM 平台的 Truffle 框架,将 Python 代码转换为 AST (抽象语法树) 并进行优化,然后使用 JIT 编译器生成本地代码。相比之下,CPython 是使用 C 语言实现的,其执行速度较慢,但兼容性更好,可以运行大部分的 Python 代码。
需要使用GraalPython,我们首先要搭建GraalPython环境
安装 GraalVM:下载 GraalVM 安装包并解压缩,然后将其添加到环境变量中。可以参考官方文档:https://www.graalvm.org/docs/getting-started/linux/。
安装 GraalPython:打开终端,执行以下命令安装 GraalPython:
gu install python
该命令会自动安装最新版本的 GraalPython。
验证 GraalPython:执行以下命令验证 GraalPython 是否安装成功:
python --version
如果输出 Python 的版本信息,则说明 GraalPython 安装成功。
使用 GraalPython:在终端中执行以下命令启动 GraalPython 解释器:
graalpython
然后就可以在 GraalPython 环境中运行 Python 代码了。
安装好GraalPython后,我们需要构建venv环境, venv是 Python 3 自带的一个标准库,它可以帮助用户创建虚拟环境,使得不同的项目之间的依赖关系和库版本隔离开来,从而避免了不同项目之间的库冲突和版本不兼容等问题。
使用 venv 创建虚拟环境非常简单,可以按照以下步骤操作:
打开终端,进入项目的根目录,执行以下命令创建虚拟环境:
graalpy -m venv /home/appuser/venv
上述命令将在当前目录下创建名为 myenv 的虚拟环境。
激活虚拟环境:执行以下命令激活虚拟环境:
source /home/appuser/venv/bin/activate
执行上述命令后,终端的提示符会变为 (myenv),表示虚拟环境已经激活。
在虚拟环境中安装依赖:执行以下命令安装需要的依赖:
pip install package_name
在虚拟环境中安装的依赖包仅对当前项目有效,不会影响系统中的其他项目。
退出虚拟环境:执行以下命令退出虚拟环境:
deactivate
执行上述命令后,终端的提示符会恢复为原来的样子。
需要注意的是,每次进入项目的开发环境时,都需要激活虚拟环境,否则无法使用虚拟环境中的依赖和库。
环境准备好后,我们就可以使用java环境调用python脚本。
- 首先导入sdk包
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>22.3.1</version>
</dependency>
- 然后编写代码
private static final String PYTHON = "python";
private static final String PYTHON_PYTHON_PATH = "python.PythonPath";
private static final String PYTHON_EXECUTABLE = "python.Executable";
private static final String PYTHON_FORCE_IMPORT_SITE = "python.ForceImportSite";
private static Context createContext(String modulePath){
Engine engine = Engine.create();
Context context = Context.newBuilder(PYTHON).allowAllAccess(true).engine(engine)
.option(PYTHON_FORCE_IMPORT_SITE, "true")
.option(PYTHON_PYTHON_PATH, modulePath)
.option(PYTHON_EXECUTABLE, customizeConfig.getPythonExecutable())
.build();
return context;
}
public static void main(String[] args) {
Context context = createContext();
Value bindings = context.getBindings(PYTHON);
bindings.putMember("a", 1);
bindings.putMember("b", 2);
int i = context.eval(PYTHON, "a+b").asInt();
System.out.println(i);
}
讲解下关键参数:
- python.PythonPath 导入外部py文件目录的地址,比如a/a.py
- python.Executable隔离环境的地址,上文中的/home/appuser/venv
- python.ForceImportSite 强制开启site,这个功能主要是需要使用第三方库,如requests,则需要在这配置参数,开启sitespackge目录,当然,我们也可以通过脚本中运行”import site“开启。
至此我们通过graalpy实现了java中调用python脚本,在测试过程中,当我们不适用三方库的时候,性能比较客观,但是当我们使用ForceImportSite后性能有显著下降,当然整体比JEP好很多。
坑点
在预言过程中发现每次创建上下文Context非常耗时,一次执行就需要4s多,后来才支持因为cpu内核导致的,我使用的环境是mac m1,下载使用的事GraalVM(amd64),而m1以上的需要下载GraalVM(aarch64)版本,更改后瞬间将速度提升到毫秒级别。
当然GraalVM针对python的试验阶段,一旦能适用,几乎可以毫无成本的引入js脚本的支持,平台引入这个工具具有很高的扩展性
另外,目前对于第三方库python库的支持不是特别兼容,需要继续摸索
官方支持的兼容包如下:
Cython
Keras_preprocessing
Markdown
Pillow
PyYAML
Werkzeug
absl_py
astor
atomicwrites
attrs
cassowary
certifi
chardet
cppy
cycler
dateutil
gast
h5py
hypothesis
idna
joblib
kiwisolver
lightfm
matplotlib
mock
more_itertools
numpy
packaging
pandas
pkgconfig
pluggy
protobuf
py
pybind11
pyparsing
pytest
pytest_parallel
python_dateutil
pythran
pytz
requests
scikit_learn
scipy
setuptools
setuptools_scm
six
sortedcontainers
threadpoolctl
tox
urllib3
wcwidth
wheel
zipp
总结
是否使用GraalVM举要根据自己具体的使用场景去评估,如果只是简单的脚本调用就非常适合,性能客观,但是存在大量的第三方库和自定义库性能就会收到影响,同事需要考虑兼容问题。