JRuby Getting started with JRuby

richfisher · 2016年02月05日 · 最后由 donglei 回复于 2016年10月11日 · 13753 次阅读
本帖已被管理员设置为精华贴

前言

最近尝试在 JRuby 里集成Java 工作流组件,遇到不少令人困惑的点,关于 JRuby 的资料不多,记录下一些心得。

安装 JRuby

环境和工具:Mac, rvm

rvm get head
rvm install jruby-9.0.4.0   
rvm use jruby-9.0.4.0

你也可以创建一个.ruby-version文件在项目目录,进入目录就会自动切换至 JRuby。

echo 'jruby-9.0.4.0' > .ruby-version

调用 Java

首先创建一个目录 getting-started-with-jruby, 下面的代码都可以在Github找到。

quick start

创建一个 Java 文件 Hello.java

public class Hello {
    public static void world(){
        System.out.println("Hello JRuby!");
    }
}

编译成 class 文件 javac Hello.java

在 JRuby 里require 'java' 后你可以访问所有在 classpath 里的 java classes。

创建一个 ruby 文件 calling-class-in-root.rb

require 'java'
Java::Hello.world()

在命令行运行 ruby calling-class-in-root.rb, 看到输出 Hello JRuby!

classpath

运行 jruby 所在的目录是 classpath, 所有运行目录里的.class文件可以在 JRuby 里访问。

classpath 可以通过设置 CLASSPATH 环境变量进行扩展

$CLASSPATH << "classes"
# or $CLASSPATH << "file:///#{File.expand_path('classes')}/"

创建一个 java 文件 java/src/main/java/SubHello.java, 并编译。

在项目目录创建一个 ruby 文件 calling-class-in-sub-folder.rb.

require 'java'
$CLASSPATH << "java/src/main/java"

Java::SubHello.world()

在命令行运行 ruby calling-class-in-sub-folder.rb, 看到输出 Hello jruby in sub folder!

import jar file

jar 文件须在 classpath 或者手动 require

require 'path/to/mycode.jar'

java目录创建pom.xml, 运行 mvn package 打包成 demo-1.0.jar

在项目目录创建一个 ruby 文件 calling-jar.rb,

require 'java'
require './java/target/demo-1.0.jar'
Java::SubHello.world()

在命令行运行 ruby calling-jar.rb, 看到输出 Hello jruby in sub folder!

jbundler

bundler一样管理 jar 依赖,首先安装

gem install jbundler

在项目目录创建Jarfile:

jar 'commons-io:commons-io', '2.4'

在命令行运行 jbundle install 安装声明的 jar 包。

在 JRuby 里require 'jbundler'后,你将能调用 Jarfile 里声明的包

在项目目录创建一个 ruby 文件 calling-jar-with-jbundler.rb

require 'java'
require 'jbundler'

file = java.io.File.new('./Jarfile')
lines = org.apache.commons.io.FileUtils.readLines(file, "UTF-8")
puts lines

你将看到输出 [jar 'commons-io:commons-io', '2.4']

你也可以使用驼峰风格调用 java

require 'java'
require 'jbundler'

file = Java::JavaIo::File.new('./Jarfile')
lines = Java::OrgApacheCommonsIo::FileUtils.readLines(file, "UTF-8")
puts lines

加速 JRuby 启动

JRuby 的 --dev 参数

使用 "--dev" 参数等价于同时设置以下几个 JRuby 参数:

  • client mode where applicable (generally older 32-bit JVMs). The client mode is designed to start up quickly and not optimize as much.
  • TieredCompilation and TieredStopAtLevel=1, equivalent to client mode on newer Hotspot-based JVMs
  • compile.mode=OFF to disable JRuby's JVM bytecode compiler
  • jruby.compile.invokedynamic=false to disable the slow-to-warmup invokedynamic features of JRuby

在开发过程,想要程序启动更快而不关心运行效率,--dev 参数应该不错。

在 rvm 使用 --dev 参数

RVM 提供了一个 hook 文件$rvm_path/hooks/after_use_jruby_opts,将它变成可执行之后,每次切换至 JRuby 会根据环境变量 PROJECT_JRUBY_OPTS 添加 JRuby 的启动参数。

chmod +x $rvm_path/hooks/after_use_jruby_opts
echo 'PROJECT_JRUBY_OPTS=(--dev)' > ~/.rvmrc

其他工具

  • [X] rails/spring 只支持 MRI Ruby, JRuby 不支持fork

  • [X] spork, 启动报错 TypeError: no implicit conversion of Fixnum into String

  • [✓] theine 与 Spork 类似 theine_server thenine some command

  • [X] drip 运行rails runner挂死

启动速度测试

分别用 MRI Ruby 和 JRuby 创建两个 Rails 项目。

rvm use 2.2.3
gem install rails
rails new ruby-on-rails

rvm use jruby-9.0.4.0
gem install rails
rails new jruby-on-rails

time rake test
time rails s
time rails runner "puts Rails.env"
ruby jruby jruby --dev theine
rake test 3.841s 13.547s 7.451s 4.882s
rails s 4.796s 20.914s 11.833s 5.084s
rails runner 2.128s 17.116s 9.718s 4.464s

总结:使用 --dev 参数可以减少大约 45% 的启动时间,使用 theine 还能进一步加速。

关于 JRuby 的运行效率

JRuby 的启动速度比较糟糕,运行效率又怎么样呢?

还是对两个空白的 Rails 项目进行简单的测试

Ruby on Rails JRuby on Rails
ab -n 1000 -c 1 22.103ms 16.275ms
ab -n 1000 -c 10 22.079ms 12.622ms
ab -n 1000 -c 50 22.051ms 12.236ms

虽然不代表真实的项目,从结果来看 JRuby 的运行效率是不错的。

参考资料

好文,

不过我没接触过 JRuby

没有 Java 历史包袱的项目不用入坑吧。JRuby 有什么优势吗?

#1 楼 @peter 我是做一些 Java 集成才用的 JRuby。 抛开历史包袱的话,JRuby 的 GC 更优;另外 MRI Ruby 因为 GIL 的原因,每个进程只能跑一个线程,而 JRuby 可以跑满所以核心。

JRuby 在 production 的性能不错,可以参考 @flyerhzm 的一个 slide https://speakerdeck.com/flyerhzm/jruby-at-openfeint

@richfisher 但是看你测试的运行时间,JRuby 速度比 Ruby 原生慢这么多啊。

搭车问一句,除了需要 java 集成的时候用之外,还有什么情境可以用。

#4 楼 @realwol 多线程啊,或者需要简化部署环境的时候。JRuby 项目用 warbler 可以打成一个 war 包发布,比 ruby 环境好搞多了。

#5 楼 @southwolf 哦,多谢解释。

#3 楼 @zfz 好问题,我尝试回答。我想测试的是初始化时间或者说启动速度,我测试的也是空白的项目。

code run fast 和 application starts up quickly 是两个概念。production 环境对应用的初始化速度并不敏感,重启应用也可以 Zero-downtime 无缝切换

新建两个 Rails 项目进行简单的测试。

Ruby on Rails JRuby on Rails
ab -n 1000 -c 1 22.103ms 16.275ms
ab -n 1000 -c 10 22.079ms 12.622ms
ab -n 1000 -c 50 22.051ms 12.236ms

虽然不代表真实的项目,从结果来看 JRuby 的运行效率是不错的。

#7 楼 @richfisher 谢谢回答,明白了。

JRuby 的问题是和其他 ruby lib 的 compatibility 太差了

JRuby 的启动速度太差了 比较 糟糕

我在想是不是 Gradle 这样的工具更能有效地管理依赖和启动呢?

JRuby 的巨大优势之一也是无数的 JVM 库,这些库被打包、测试和分发到 Maven/jcenter 上。重点如果是利用这些库的话,未必 jbundler 等是一个很好的办法

搭车问一下,ruby 访问 https,各种证书错误怎么办?已经按照网上说的添加了证书信任的

我记得当时我们有想法迁移到 jruby 上,就是因为听鬼佬说他们比较牛,多线程能跨核,天天说日本人水平不行…… 后来我写了一个测试脚本,跑的是以前我们的一个子工程,按照实际业务逻辑模拟了一些 API 调用,最后结论是 jruby+ thin 比 cruby+rainbows 慢上不少,不知道到底是我们的问题还是 jruby 本身就是慢。 后来我们就打消了迁移到 jruby 的想法了。

#9 楼 @allenwq 基本上现在主要的 gem 都兼容 jruby 了,没有的话可以点 这里看看有没有替代方案。不过 activerecord-jdbc-adapter 在 rails5 问题比较多,基本上没有人很积极地 contribute,如果要用 rails5 的话得登上一阵了。

#10 楼 @reboot 启动速度确实蛋疼,感觉用了 --dev也无法接受,可以试试楼主提到的 theine 以及 nailgun, drip。其实开发环境用 mri,staging 用 jruby 也挺好的。

#13 楼 @nagae_memooff 不知你用的 jruby 版本以及你跑的脚本是什么,反正 thin 和 jruby 配合的表现貌似并不算好,你可以试试最新的 jruby 9110 配合 torquebox 或者 trinidad。还是比较期待 truffle + graal 能跑 rails 那一天,不过估计到时 mri 3 都出来了。

16 楼 已删除
需要 登录 后方可回复, 如果你还没有账号请 注册新账号