quarkus
introduce and make notes about the issues found during using quarkus
build加速
quarkus.native.builder-image=quay.io/quarkus/ubi9-quarkus-mandrel-builder-image:jdk-21
quarkus.native.container-build=true
quarkus.native.builder-image.pull=missing
在application.properties 中指定image并且不要每次去pullimage 加快编译。
docker build ubi8 vs ubi9
从 quarkus 3.19, 默认使用UBI9 作为native镜像, 对 vm里面的cpu有要求,可能会报错:(参考:url)
Fatal glibc error: CPU does not support x86-64-v2
所以, 使用ubi8:
# uib9
quarkus.native.builder-image=quay.io/quarkus/ubi9-quarkus-mandrel-builder-image:jdk-21
# ubi8
quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21
Dockerfile修改:
# ubi9
FROM registry.access.redhat.com/ubi9/ubi9-minimal:9.6
# ubi8
FROM registry.access.redhat.com/ubi8-minimal:8.10
native镜像在部分机器无法启动 报错
The current machine does not support all of the following CPU features that are required by the image: [CX8, CMOV, FXSR, MMX, SSE, SSE2, SSE3, SSSE3, SSE4_1, SSE4_2, POPCNT, LZCNT, AVX, AVX2, BMI1, BMI2, FMA].
Please rebuild the executable with an appropriate setting of the -march option.
解决办法:
quarkus.native.additional-build-args=-march=compatibility
docker 自定义镜像
可以在native基础上加上自己的软件 方便调查问题:
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.3
# 设置非交互模式并安装工具
# --releasever=9: 解决某些环境下无法识别版本的问题
# --nodocs: 不安装文档,显著减小体积
# clean all: 清理元数据缓存
RUN microdnf update -y --releasever=9 && \
microdnf install -y --releasever=9 --nodocs \
procps-ng \
net-tools \
wget \
vim-minimal && \
microdnf clean all -y --releasever=9 && \
rm -rf /var/cache/yum
grpc
常见配置:
quarkus.http.port=8077
quarkus.grpc.server.port=9097
quarkus.grpc.server.host=0.0.0.0
# Reflection (grpc.reflection.v1 / v1alpha) for grpcurl, Postman, etc. Dev mode enables it automatically;
# in prod it is off unless you set GRPC_SERVER_ENABLE_REFLECTION=true (reflection exposes service/schema info).
quarkus.grpc.server.enable-reflection-service=true
quarkus.grpc.server.use-separate-server=true
# Index external JARs so Jandex sees gRPC ImplBase BindableService; without this,
# prod/native finds zero bindable services and Quarkus skips starting the gRPC server
# (dev mode still wires server support, which hides the issue locally).
quarkus.index-dependency.business-protocol.group-id=cn.sichuancredit.datasource.business
quarkus.index-dependency.business-protocol.artifact-id=business-protocol
# Logging gRPC client (@GrpcClient("logging")) — set LOGGING_GRPC_HOST, LOGGING_GRPC_PORT
quarkus.grpc.clients.logging.host=${LOGGING_GRPC_HOST:192.168.102.224}
quarkus.grpc.clients.logging.port=${LOGGING_GRPC_PORT:6391}
quarkus.grpc.clients.logging.plain-text=true
注意:
(1)如果你实现了某个grpc服务,quarkus-index-dependency 这个需要设置,里面的内容就是你的服务所在的maven group 和 artifcat。 这个只在native模式有影响,不设置的话服务不会正常启动。
(2)打开:quarkus.grpc.server.enable-reflection-service=true 方便的你grpc 客户端可以通过reflection自动获取相关的定义。
移除grpc依赖避免log4j 引入
移除log4j的依赖避免native失败:
问题:
Caused by: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Discovered unresolved type during parsing: io.grpc.netty.shaded.io.netty.util.internal.logging.Log4J2Logger. This error is reported at image build time because class io.grpc.netty.shaded.io.netty.util.internal.logging.Log4J2LoggerFactory is registered for linking at image build time by command line and command line. Error encountered while parsing io.grpc.netty.shaded.io.netty.util.internal.logging.InternalLoggerFactory.newDefaultFactory(InternalLoggerFactory.java:42)
解决:
java {
xxxxxx
}
configurations.all {
exclude group: 'io.grpc', module: 'grpc-netty-shaded'
}
repositories {
yyyyyy
}
db
redis
自定义key:
实现一个这样的CacheKeyGenerator即可:
// 这个是忽略了参数中的第一个参数来组成cachekey:
@RegisterForReflection
public class CacheKeyGeneratorSkipFirstParam implements CacheKeyGenerator {
public CacheKeyGeneratorSkipFirstParam() {
}
@Override
public Object generate(Method method, Object... methodParams) {
StringBuilder sb = new StringBuilder();
sb.append(method.getName()).append('-');
for (int i = 1; i < methodParams.length; i++) {
sb.append(methodParams[i]).append('-');
}
return sb.toString();
}
}
注意必须有空的构造函数和注解:@RegisterForReflection
然后就可以:
/**
* Cache key matches legacy Spring {@code @Cacheable} (method + idCard + personName semantics via two keys only).
*/
@CacheResult(cacheName = "zzdtec", keyGenerator = CacheKeyGeneratorSkipFirstParam.class)
public FetchResult load(AccessLogContext accessLogContext, String idCard, String personName) {
return httpExecutor.fetch(accessLogContext, idCard, personName);
}
得到的缓存key就是:cache:zzdtec:load-341224xxxxx-涛yyyy-
配置
# 配置密码相关
quarkus.redis.hosts=${REDIS_HOSTS:redis://192.168.102.221:36379/13}
quarkus.redis.password=${REDIS_PASSWORD:xxxxx}
quarkus.cache.type=redis
# 配置TTL
quarkus.cache.redis.zzdtec.expire-after-write=${REDIS_CACHE_EXPIRE:30d}
# 还需要配置你的缓存的object 不然会失败。 同样的该类需要有相关注解:@RegisterForReflection
quarkus.cache.redis.zzdtec.value-type=cn.sichuancredit.zzdtec.server.api.FetchResult
HTTP CLIENT util
参考实现:
注意事项:【1】不要直接初始化对象 static
【2】application.properties中忽略相关,需要在运行时初始化,避免build时初始化
quarkus.native.additional-build-args=\
--initialize-at-run-time=com.xx.worker.util.HttpClientUtil,\
--initialize-at-run-time=com.xx.worker.util.HttpClientUtil$ClientHolder,\
--enable-http,\
--enable-https,\
package com.xx.worker.util;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;
/**
* HTTP client utility using JDK 21 standard HttpClient.
* Uses lazy initialization for GraalVM native image compatibility.
*/
public class HttpClientUtil {
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(30);
private static class ClientHolder {
static final HttpClient INSTANCE = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.build();
}
private static HttpClient getClient() {
return ClientHolder.INSTANCE;
}
/**
* Send GET request and return response body as string.
*
* @param url target URL
* @return response body
*/
public static String get(String url) {
return get(url, Map.of());
}
/**
* Send GET request with headers and return response body as string.
*
* @param url target URL
* @param headers request headers
* @return response body
*/
public static String get(String url, Map<String, String> headers) {
try {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(DEFAULT_TIMEOUT)
.GET();
headers.forEach(builder::header);
HttpRequest request = builder.build();
HttpResponse<String> response = getClient().send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new RuntimeException("HTTP request failed with status: " + response.statusCode());
}
return response.body();
} catch (Exception e) {
throw new RuntimeException("Failed to send GET request to " + url, e);
}
}
/**
* Send POST request with JSON body and return response body as string.
*
* @param url target URL
* @param json JSON request body
* @return response body
*/
public static String postJson(String url, String json) {
return postJson(url, json, Map.of());
}
/**
* Send POST request with JSON body and headers, return response body as string.
*
* @param url target URL
* @param json JSON request body
* @param headers additional headers
* @return response body
*/
public static String postJson(String url, String json, Map<String, String> headers) {
try {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(DEFAULT_TIMEOUT)
.header("Content-Type", "application/json; charset=utf-8")
.POST(HttpRequest.BodyPublishers.ofString(json));
headers.forEach(builder::header);
HttpRequest request = builder.build();
HttpResponse<String> response = getClient().send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new RuntimeException("HTTP request failed with status: " + response.statusCode());
}
return response.body();
} catch (Exception e) {
throw new RuntimeException("Failed to send POST request to " + url, e);
}
}
/**
* Send POST request with form data and return response body as string.
*
* @param url target URL
* @param formData form data
* @return response body
*/
public static String postForm(String url, Map<String, String> formData) {
try {
String body = formData.entrySet().stream()
.map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue()))
.reduce((a, b) -> a + "&" + b)
.orElse("");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(DEFAULT_TIMEOUT)
.header("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = getClient().send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new RuntimeException("HTTP request failed with status: " + response.statusCode());
}
return response.body();
} catch (Exception e) {
throw new RuntimeException("Failed to send POST form request to " + url, e);
}
}
/**
* Send PUT request with JSON body and return response body as string.
*
* @param url target URL
* @param json JSON request body
* @return response body
*/
public static String putJson(String url, String json) {
return putJson(url, json, Map.of());
}
/**
* Send PUT request with JSON body and headers, return response body as string.
*
* @param url target URL
* @param json JSON request body
* @param headers additional headers
* @return response body
*/
public static String putJson(String url, String json, Map<String, String> headers) {
try {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(DEFAULT_TIMEOUT)
.header("Content-Type", "application/json; charset=utf-8")
.PUT(HttpRequest.BodyPublishers.ofString(json));
headers.forEach(builder::header);
HttpRequest request = builder.build();
HttpResponse<String> response = getClient().send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new RuntimeException("HTTP request failed with status: " + response.statusCode());
}
return response.body();
} catch (Exception e) {
throw new RuntimeException("Failed to send PUT request to " + url, e);
}
}
/**
* Send DELETE request and return response body as string.
*
* @param url target URL
* @return response body
*/
public static String delete(String url) {
return delete(url, Map.of());
}
/**
* Send DELETE request with headers and return response body as string.
*
* @param url target URL
* @param headers request headers
* @return response body
*/
public static String delete(String url, Map<String, String> headers) {
try {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(DEFAULT_TIMEOUT)
.DELETE();
headers.forEach(builder::header);
HttpRequest request = builder.build();
HttpResponse<String> response = getClient().send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new RuntimeException("HTTP request failed with status: " + response.statusCode());
}
return response.body();
} catch (Exception e) {
throw new RuntimeException("Failed to send DELETE request to " + url, e);
}
}
private static String encode(String value) {
return java.net.URLEncoder.encode(value, java.nio.charset.StandardCharsets.UTF_8);
}
}