Quarkus 入門
GlassFish User Group Japan
May 16th, 2019
上妻 宜人 (あげつま のりと)
●
日本GlassFishユーザ会 理事
●
JBoss EAPのテクニカルサポートエンジニア
QuarkusSupersonic Subatomic Java
高速軽量なWebランタイム
Graal対応/100ミリ秒未満で起動
Quarkusは圧倒的に高速起動
WildFly16
JDK8u211
Thorntail-2.4
JDK8u211
Quarkus0.14.0
JDK8u211/java -jar
Quarkus0.14.0
GraalVM1.0.0RC16
native-image
9888ms
18224ms
2802ms
71ms
JAX-RS/CDI/JPAサンプルアプリケーションで測定
https://github.com/nagetsum/quarkus-glassfishjp
本日の内容
●
Quarkusが生まれた背景
●
デモ
●
Quarkusとは何か
●
Quarkusの注意点
●
まとめ
Quarkusが生まれた背景 1
k8sでもやっぱりJavaでコードを書きたい
●
コンテナ用途に小さくて軽いJavaが欲しい
●
APサーバではJVMプロセスは1サーバに数個〜10程度
●
k8sの1ノードでJavaプロセスを20以上起動したい
●
uber-jar登場により1war = 1プロセスが主流に
●
1プロセスに複数warをデプロイしなくなった
Quarkusが生まれた背景 1
k8sでもやっぱりJavaでコードを書きたい
●
もっと小さく
●
JDK8 HotSpotJVM メモリフットプリント 200MB 〜
●
WildFly16コンテナのイメージサイズ 371MB
●
Throntail uber-jar 125MB
●
もっと軽く
●
大きなアプリケーションではPod起動に20秒以上
●
起動速度はPodスケールのネックになる
(JAX-RS/CDI/JPA)
Quarkusが生まれた背景 2
GraalVMネイティブコンパイル登場
●
ネイティブバイナリは非常にコンパクト
●
Javaで書いて52MBの Mach-O or ELF シングルバイナリ
●
起動時メモリ使用量 (ps RSS) 30MB ※いずれも数値はQuarkusデモAPの場合
●
$GRAALVM_HOME/bin/native-image -jar <uber-jar>
ネイティブコンパイルを試みる
●
Undertow
●
WildFlyのWebサーバ実装
●
Java SEでも動く
import io.undertow.Undertow;
import io.undertow.util.Headers;
public class Main {
public static void main(final String[] args) {
Undertow server = Undertow.builder()
.addHttpListener(8080, "localhost")
.setHandler(exchange -> {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
exchange.getResponseSender().send("Hello World");
}).build();
server.start();
}
}
native-imageが通らない
●
ネイティブイメージ作成時の制約事項に引っかかる
●
java.nio.ByteBuffer#allocateDirectは使えない
●
Undertowの内部実装AjpServerRequestConduitクラスで利用
$ native-image -jar undertow-standalone.jar
Warning: Abort stand-alone image build. Unsupported features in 3 methods
Detailed message:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException:
Detected a direct/mapped ByteBuffer in the image heap. A direct ByteBuffer has a pointer
to unmanaged C memory, and C memory from the image generator is not available at image run time. A mapped
ByteBuffer references a file descriptor, which is no longer open and mapped at run time. The object was probably
created by a class initializer and is reachable from a static field. By default, all class initialization is done during
native image building.You can manually delay class initialization to image run time by using the option --delay-
class-initialization-to-runtime=<class-name>. Or you can write your own initialization methods and call them
explicitly from your main entry point.
Trace:
at parsing io.undertow.server.protocol.ajp.AjpServerRequestConduit.read
(AjpServerRequestConduit.java:195)
Call path from entry point to io.undertow.server.protocol.ajp.AjpServerRequestConduit.read(ByteBuffer):
at io.undertow.server.protocol.ajp.AjpServerRequestConduit.read(AjpServerRequestConduit.java:183)
at org.xnio.conduits.ConduitStreamSourceChannel.read(ConduitStreamSourceChannel.java:127)
Quarkusが生まれた背景 2
再掲: GraalVMネイティブコンパイル登場
●
ネイティブバイナリは非常にコンパクト
●
Javaで書いて 52MB の ELF or Mach-O シングルバイナリ
●
起動時メモリ使用量(ps RSS) 30MB (いずれも数値はQuarkusデモAPの場合)
●
シングルバイナリによりJVM不要の小さなイメージが作れる
●
native-imageコマンド
●
$GRAALVM_HOME/bin/native-image -jar <uber-jar>
●
Java EEでも簡単にnative-imageコマンドを通したい
本日の内容
●
Quarkusが生まれた背景
●
デモ
●
Quarkusとは何か
●
Quarkusの注意点
●
まとめ
Quarkus Demo
https://github.com/nagetsum/quarkus-glassfishjp
Quarkusデモのまとめ 1/2
●
Java EE APIでコードが書ける
●
mvn io.quarkus:quarkus-maven-plugin:0.14.0:create で
プロジェクト作成
●
mvn compile quarkus:dev でホットスワップ
●
HTTPリクエスト受信時にコード変更チェック
●
mvn package -Pnative でネイティブビルド
Quarkusデモのまとめ 2/2
●
JAX-RS
●
javax.ws.rs.core.Application継承クラスは省略可
●
JPA
●
Panacheによるボイラープレートコードの削減
●
テスト
●
@QuarkusTestとrest-assuredベースのテスト
●
@SubstrateTestによるネイティブバイナリへのテスト
本日の内容
●
Quarkusが生まれた背景
●
デモ
●
Quarkusとは何か
●
Quarkusの注意点
●
まとめ
Quarkusとは
●
ネイティブビルドが可能なWebフレームワーク
●
2019年3月にBeta版として公開 (現在もBeta)
●
Red HatがスポンサーのOSSプロジェクト
●
Apache Software License 2.0
Quarkusが実装したこと
●
既存ライブラリをネイティブビルド可能とした
●
RESTEasy / Hibernate / Smallrye(MicroProfile) 等
●
Extensionsと呼ばれる https://quarkus.io/extensions/
●
新たなCDI実装 “arc” の開発
●
Weldは実行時に内部処理で動的バイトコード生成を行う
●
SubstrateVM制約でクラス動的ロードができない為、
arcではビルド時にバイトコード生成
●
既存ライブラリやarcにおいて、元々デプロイ時に
実行していた処理をビルド時に実行して起動高速化
例: arcビルド時コード生成
@Injectをどうやって実現するか
@ApplicationScoped
public class SantaclausService {
@Inject
GiftDao giftDao;
}
public interface GiftDao {}
@ApplicationScoped
public class GiftDaoImpl implements GiftDao {}
例: arcビルド時コード生成
@Injectをどうやって実現するか
@ApplicationScoped
public class SantaclausService {
@Inject
GiftDao giftDao;
}
public interface GiftDao {}
@ApplicationScoped
public class GiftDaoImpl implements GiftDao {}
1. Bean定義アノテーションのスキャン
jar内の全クラスを探索し、Bean定義を示す
スコープアノテーションが付与されてるか探索。
アーカイブ内のBean一覧を作成。
1. Bean定義
アノテーションの
スキャン
例: arcビルド時コード生成
@Injectをどうやって実現するか
@ApplicationScoped
public class SantaclausService {
@Inject
GiftDao giftDao;
}
public interface GiftDao {}
@ApplicationScoped
public class GiftDaoImpl implements GiftDao {}
2. @Injectのスキャン
Beanクラスに@Injectがあるかチェックし、
依存性注入が必要な箇所を抽出。
2. @Inject
スキャン
1. Bean定義
アノテーションの
スキャン
例: arcビルド時コード生成
@Injectをどうやって実現するか
@ApplicationScoped
public class SantaclausService {
@Inject
GiftDao giftDao;
}
public interface GiftDao {}
@ApplicationScoped
public class GiftDaoImpl implements GiftDao {}
3. 依存性の解決 (実装クラスの探索)
インタフェースに対応する実装型を、
1.で抽出したBean定義一覧の中から特定
(この例の場合はGiftDaoImplクラス)
3. 依存性の
解決
2. @Inject
スキャン
1. Bean定義
アノテーションの
スキャン
例: arcビルド時コード生成
@Injectをどうやって実現するか
@ApplicationScoped
public class SantaclausService {
@Inject
GiftDao giftDao; //=GiftDao$Proxy$_$$_WeldClientProxy
}
public interface GiftDao {}
@ApplicationScoped
public class GiftDaoImpl implements GiftDao {}
4. クライアントプロキシのバイトコード生成
newしたGiftDaoImplインスタンスを注入して
参照関係を直接持たせると、スコープ完了時にGCできない。
プロキシクラスを動的バイトコード生成して注入。
3. 依存性の
解決
2. @Inject
スキャン
1. Bean定義
アノテーションの
スキャン
4. プロキシ
バイトコード
生成
例: arcビルド時コード生成
@Injectをどうやって実現するか
@ApplicationScoped
public class SantaclausService {
@Inject
GiftDao giftDao; //=GiftDao$Proxy$_$$_WeldClientProxy
}
public interface GiftDao {}
@ApplicationScoped
public class GiftDaoImpl implements GiftDao {}
5. Beanインスタンスの生成
各Beanの初回コール時にインスタンスを生成。
3. 依存性の
解決
2. @Inject
スキャン
1. Bean定義
アノテーションの
スキャン
5. Bean
生成
4. プロキシ
バイトコード
生成
例: arcビルド時コード生成
Weld(CDI参照実装)の場合
3. 依存性の
解決
2. @Inject
スキャン
1. Bean定義
アノテーションの
スキャン
5. Bean
生成
4. プロキシ
バイトコード
生成
デプロイ時に実行 初回コール時実行
例: arcビルド時コード生成
arc – QuarkusのCDI実装 の場合
3. 依存性の
解決
2. @Inject
スキャン
1. Bean定義
アノテーションの
スキャン
5. Bean
生成
4. プロキシ
バイトコード
生成
ビルド時に実行 (Quarkus Maven Plugin)
初回コール
時実行
Quarkusが速い理由
WildFly16
JDK8u211
Thorntail-2.4
JDK8u211
Quarkus0.14.0
JDK8u211/java -jar
Quarkus0.14.0
GraalVM1.0.0RC16
native-image
9888ms
18224ms
2802ms
71ms
●
Quarkusが速い理由はGraalVMの恩恵だけでない
●
Java EEランタイムのレイヤでもビルド時最適化を実装
●
だから java -jar でも速い
Quarkusの構成
RESTEasy
Maven/Gradle Plugin
Quarkus Core Deployment
(Build time processor framework)
Quarkus Core Runtime
(Launcher, Thread Pool, Logging etc..)
arc
(CDI implementation on Quarkus)
Quarkus Development Mode
(hot swap)
HotSpotJVM or GraalVM(SubstrateVM)
extension-resteasy
Hibernate
extension-hibernate-orm
H2/MariaDB/PostgreSQL
JDBC
extension-jdbc
Small-rye
(Microprofile impl)
extension-small-rye
… and mores
Quarkus extensions
Undertow
extension-undertow
既存のライブラリ
Quarkusで新たに
実装されたもの
VM
Quarkus Core
本日の内容
●
Quarkusが生まれた背景
●
デモ
●
Quarkusとは何か
●
Quarkusの注意点
●
まとめ
Quarkusで注意すること
●
ネイティブビルドの制約を意識してコードを書く
●
動的バイトコード生成しない (Objenesis, cglib etc..)
●
クラスパスから設定ファイルをロードしない
(ClassLoader#getResourceAsStreamを使わない)
●
ビルド時に存在しないクラスをリフレクションしない
●
staticイニシャライザはビルド時に動くことに注意
(最新のGraalVM19.0.0からランタイム時実行に変更になりました)
https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md
●
上記の制約は依存ライブラリにも適用される
●
引っかかる場合はネイティブ化は諦めて java -jar 利用
●
またはQuarkus Extensionを自作
Quarkusの気になるところ
●
非OSSに対するextention開発が難しい
●
エンプラ領域ではOracle JDBCは避けて通れない
●
extensionによりバージョンが固定される
●
依存先ライブラリではなくextensionを読み込む仕組み
●
例: pgjdbcバージョンアップにextensionが追従できるか
●
mvn package -Pnativeに時間が掛かる
●
GraalVMのネイティブコンパイル高速化に期待
●
ネイティブAPでもOOMエラーは起こる
●
DBからの大量レコードフェッチ、巨大なListやMap
●
Full GC連発の末にOOMエラー
●
メモリフットプリントは小さいが、アプリが大量に
要求するとメモリ使用量は増えることに注意
./quarkus-oom-0.1-runner -Xms256m -Xmx256m -XX:+PrintGC -XX:+PrintGCSummary
-XX:+PrintGCTimeStamps
...
[28878 msec: Incremental GC (CollectOnAllocation.Sometimes) 26650K->7973K, 0.0460786 secs]
[28985 msec: Incremental GC (CollectOnAllocation.Sometimes) 34312K->19966K, 0.0749783 secs]
...
[38259 msec: Full GC (CollectOnAllocation.Sometimes) 267658K->253378K, 1.8340780 secs]
[40128 msec: Full GC (CollectOnAllocation.Sometimes) 279897K->266638K, 1.8257359 secs]
2019-05-10 01:10:43,433 ERROR [io.und.request] (executor-thread-1) UT005023: Exception handling
request to /echo:
java.lang.OutOfMemoryError: Garbage-collected heap size exceeded.
バイナリ実行で
OutOfMemoryError
SubstrateVMのヒープ設定
●
最大ヒープサイズ
●
-Xmx または -XX:MaximumHeapSizePercent=xx
●
デフォルトは利用可能メモリの80% (物理 or cgroups)
●
コンテナのメモリを使い切るデフォルト値のため、
以前と比べてチューニングの機会は少ないのでは?
●
詳細はソースコード参照
●
https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.core.genscavenge/
src/com/oracle/svm/core/genscavenge/HeapPolicyOptions.java
本日の内容
●
Quarkusが生まれた背景
●
デモ
●
Quarkusとは何か
●
Quarkusの注意点
●
まとめ
Quarkusを学ぶには
●
公式サイト
●
https://quarkus.io
●
Quarkus Youtubeチャネル
●
https://www.youtube.com/channel/UCaW8QG_QoIk_FnjLgr5eOqg
●
SubstrateVM (Graal)
●
https://github.com/oracle/graal/tree/master/substratevm
まとめ
●
Quarkusは高速起動が特徴のフレームワーク
●
100ミリ秒未満で起動
●
起動停止が頻繁発生するコンテナ環境に効果的
●
速い理由
●
デプロイ時 or 実行中に行なっていた処理を
可能な限りビルド時に実行して高速化
●
GraalVMによるネイティブイメージ作成で高速化

Quarkus入門