uWSGI服务器中的JVM (更新至1.9)

介绍

自uWSGI 1.9起,你可以拥有一个嵌入在核心中的完整、线程安全的通用JVM。所有的插件可以通过 RPC subsystem 或者使用uWSGI uWSGI信号框架 来调用JVM函数 (用Java, JRuby, Jython, Clojure, 任何JVM可以运行的新式语言编写的)。JVM插件本身可以实现请求处理器来托管基于JVM的web应用。目前,支持 JWSGI接口Clojure/Ring JVM请求处理器 (Clojure)应用。长期目标是支持servlets,但它将需要大量的赞助和资金 (请随意到info@unbit.it询问更多有关项目的信息)。

构建JVM支持

首先,确保安装了一个完整的JDK发行版。uWSGI构建系统将试着检测常见的JDK设置 (Debian, Ubuntu, Centos, OSX...),但是,如果它没法找到一个JDK安装,那么它会需要一些来自用户的信息(见下)。要构建JVM插件,只需运行:

python uwsgiconfig.py --plugin plugins/jvm default

如果需要的话,修改’default’为你的构建配置文件。例如,如果你有一个Perl/PSGI单片构建,只需运行

python uwsgiconfig.py --plugin plugins/jvm psgi

或者对于一个完整的模块化构建

python uwsgiconfig.py --plugin plugins/jvm core

如果一切顺利,将会构建jvm_plugin。如果构建系统不能找到一个JDK案子,那么你将需要指定头文件目录(包含jni.h文件的目录)和lib目录(包含libjvm.so的目录)的地址。例如,如果jni.h在/opt/java/includes中,而libjvm.so在/opt/java/lib/jvm/i386中,那么这样运行构建系统:

UWSGICONFIG_JVM_INCPATH=/opt/java/includes UWSGICONFIG_JVM_LIBPATH=/opt/java/lib/jvm/i386 python uwsgiconfig --plugin plugins/jvm

成功构建之后,你会获得uwsgi.jar文件的路径。那个jarball包含了访问uWSGI API的类,而你应该将它拷贝到你的CLASSPATH中,或者至少手工从uWSGI的配置加载它。

通过RPC子系统公开函数

在这个例子中,我们将公开一个”hello” Java函数 (返回一个字符串),并且将从一个Python WSGI应用中调用它。这是我们的基本配置 (假设使用模块化构建)。

[uwsgi]
plugins = python,jvm
http = :9090
wsgi-file = myapp.py
jvm-classpath = /opt/uwsgi/lib/uwsgi.jar

jvm-classpath 是一个由JVM插件公开的选项,它允许你添加目录或者jar文件到你的classpath中。你可以指定任意多的 jvm-classpath 选项。这里,我们手工添加 uwsgi.jar ,因为我们并没有将它拷贝到我们的CLASSPATH中。这是我们的WSGI样例脚本。

import uwsgi

def application(environ, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    yield "<h1>"
    yield uwsgi.call('hello')
    yield "</h1>"

这里,我们使用 uwsgi.call() 来取代 uwsgi.rpc() ,作为一种快捷方式 (选项解析时会有小的性能提升)。现在,我们创建我们的Foobar.java类。它的 static void main() 函数将会在启动的时候由uWSGI运行。

public class Foobar {
   static void main() {

       // create an anonymous function
       uwsgi.RpcFunction rpc_func = new uwsgi.RpcFunction() {
           public String function(String... args) {
               return "Hello World";
           }
       };

       // register it in the uWSGI RPC subsystem
       uwsgi.register_rpc("hello", rpc_func);
   }
}

uwsgi.RpcFunction 接口允许你轻松编写uWSGI兼容的RPC函数。现在,编译Foobar.java文件:

javac Foobar.java

(最终,修正classpath,或者用-cp选项传递uwsgi.jar路径) 你现在有了一个Foobar.class,它可以由uWSGI加载。让我们完成配置……

[uwsgi]
plugins = python,jvm
http = :9090
wsgi-file = myapp.py
jvm-classpath = /opt/uwsgi/lib/uwsgi.jar
jvm-main-class = Foobar

最后一个选项 (jvm-main-class) 将会加载一个java类,并且执行它的 main() 方法。现在,我们可以访问localhost:9090,并且应该看到Hello World消息。

注册信号处理器

用与RPC子系统相同的方式,你可以注册信号处理器。你将能够在时间事件、文件修改、cron等上调用Java函数

我们的Sigbar.java:

public class Sigbar {
   static void main() {

       // create an anonymous function
       uwsgi.SignalHandler sh = new uwsgi.SignalHandler() {
           public void function(int signum) {
               System.out.println("Hi, i am the signal " + signum);
           }
       };

       // register it in the uWSGI signal subsystem
       uwsgi.register_signal(17, "", sh);
   }
}

uwsgi.SignalHandler 是用于信号处理器的接口。

每当触发了信号17,将会运行对应的JVM。记得编译这个文件,在uWSGI中加载它,并启用master进程 (没有它,信号子系统将不能用)。

fork()问题和多线程

JVM并不是 fork() 友好的。如果你在master中加载一个虚拟机,然后fork() (就像你通常在其他语言中做的那样) 子JVM将会失败 (这主要是因为JVM需要的线程并不会继承)。出于这样的原因,会为每个worker、mule和spooler生成一个JVM。幸运的是,与其他绝大部分平台不同,JVM带有真正强大的多线程支持。uWSGI支持它,因此,如果你想要运行请求处理器 (JWSGI, Clojure/Ring) 中的一个,那么只需记得使用 --threads 选项生成一批线程。

它是如何工作的呢?

uWSGI使用JNI接口嵌入JVM。不幸的是,我们不能依赖JVM的自动垃圾回收器,因此我们必须自动解引用所有分配的对象。这从性能和使用的角度来说并不是个问题,但是会让插件的开发与其他基于JVM的产品相比更难一点。幸运的是,当前的API简化了那个任务。

传递选项到JVM

你可以使用 --jvm-opt 选项传递指定的选项给JVM。

例如,要把堆使用限制为10兆字节:

[uwsgi]
...
jvm-opt = -Xmx10m

加载类 (不带main方法)

我们已经看到了如何加载类和在启动时运行它们的 main() 方法。通常来说,你会想要只在添加它们到JVM的时候加载类 (运行访问外部模块需要用到它们)。要加载一个类,你可以使用 --jvm-class

[uwsgi]
...
jvm-class = Foobar
jvm-class = org/unbit/Unbit

记住,类名必须使用’/’格式,而不是点!这个规则也应用到 --jvm-main-class

请求处理器

虽然Java(TM)的世界有它自己的J2EE环境来部署web应用,你可以响应使用一个不同的方法。uWSGI项目实现了J2EE没有的大量特性 (并且没有实现大量J2EE强大的特性),因此,你可能会发现它的方法更适合你的设置(或者口味,又或者技能)。

JVM插件公开了一个API,允许hook web请求。这个方法与uWSGI工作的“传统”方式有点托尼盖。JVM插件将自己注册为一个处理器,使用modifier1==8,但将会看看modifier2的值,以确定使用它的哪一个请求处理器来处理它。例如, Clojure/Ring JVM请求处理器 插件在JVM插件中注册,使用modifier2数为‘1’。因此,要传递请求给它,你需要类似于这样的:

[uwsgi]
http = :9090
http-modifier1 = 8
http-modifier2 = 1

或者使用nginx:

location / {
    include uwsgi_params;
    uwsgi_modifier1 8;
    uwsgi_modifier2 1;
    uwsgi_pass /tmp/uwsgi.socket;
}

目前,有2个JVM请求处理器可用:

前面已经说了,开发一个servlet请求处理器的想法是有的,但是它将需要赞助(也就是说,银子),因为这会需要相当大的努力。

注意事项

  • 要使用UNIX socket,无需特殊的jar文件 —— JVM插件可以访问所有的uWSGI特性。
  • 你可能会沉迷于log4j模块。这没啥不对,但是看看uWSGI的日志记录功能 (需要更少的资源,更少的配置,以及更少的进取心)
  • uWSGI API访问仍然未完成 (将会在1.9后更新)
  • JVM在受限地址空间的环境中无法愉快使用。如果你在你的实例中加载JVM,那么避免使用 --limit-as