Netty中execution包功能详解

本文深入剖析了Netty中的execution包,重点讲解了ChannelEventRunnableFilter接口和ChannelUpstreamEventRunnable、ChannelDownstreamEventRunnable抽象类,以及ChainedExecutor和ExecutionHandler的实现。ExecutionHandler用于处理多线程操作,常与OrderedMemoryAwareThreadPoolExecutor结合使用,确保在高负载下避免OutOfMemoryError。此外,文中还介绍了MemoryAwareThreadPoolExecutor和OrderedMemoryAwareThreadPoolExecutor如何维护事件顺序,并给出了相关示例代码。
  1. 技术点描述

Netty中关于多线程处理的代码很多(netty框架的实现本身就是异步处理机制),此文档仅针对于execution包的功能做详细解说。

以下是整个包的目录结构:

        

包中的调用关系如下图所示:

  1. 实现方案

 

  1. 参考源码包

以下是对此包中的源码的分析(请注意后四个类为此包中最重要的类)

  1. ChannelEventRunnableFilter

此接口定义了一个抽象方法:

boolean filter(ChannelEventRunnable event);

如果传入的event是由Executor处理的,则返回true。

 

  1. ChannelUpstreamEventRunnableFilter

此类实现了ChannelEventRunnableFilter接口,判断传入的ChannelEventRunnable实例是不是属于ChannelDownstreamEventRunnable类

  1. ChannelDownstreamEventRunnableFilter

此类实现了ChannelEventRunnableFilter接口,判断传入的ChannelEventRunnable实例是不是属于ChannelDownstreamEventRunnable类

  1. ChannelEventRunnable

此抽象类实现了Runnable, EstimatableObjectWrapper两个接口

此类中有一个run方法:

public final void run() {

try {

PARENT.set(executor);

doRun();

} finally {

PARENT.remove();

}

 

}

 

protected abstract void doRun();

其中PARENT是一个ThreadLocal<Executor>类型的变量,仅供内部使用,告诉Executor当前worker获取到一个工作线程。

 

其中doRum();方法是一个抽象方法,需要根据不同的业务做不同的处理(常常是发布上行数据sendUpstream或者下行数据sendDownstream)。

实现了doRun();方法的类为ChannelUpstreamEventRunnable和ChannelDownstreamEventRunnable

  1. ChannelUpstreamEventRunnable

继承自ChannelEventRunnable抽象类,重写了doRun();方法

  1. ChannelDownstreamEventRunnable

继承自ChannelEventRunnable抽象类,重写了doRun();方法

  1. ChainedExecutor

这是一个特殊的Executor,这个Executor可以将多个Executors和ChannelEventRunnableFilter连接起来使用。

此类中的比较重要的方法:

public ChainedExecutor(ChannelEventRunnableFilter filter, Executor cur, Executor next) {

}

按照给出的ChannelEventRunnableFilter实例filter创建一个ChainedExecutor实例,如果filter是ChannelEventRunnableFilter的实例,则调用当前的Executor实例cur执行,否则,调用next实例执行。

 

  1. ExecutionHandler

此类的功能就是将一个ChannelEvent转发给Executor执行多线程操作。

当我们自定义的ChannelHandler执行阻塞操作时会花费较长时间,或者当我们的ChannelHandler访问诸如数据库等非CPU业务逻辑处理单元时,我们会用到这个类。执行上述操作时,如果在我们的pipeline中不采用ExecutorHandler,而进程比较多时,就可能会出现当前I/O线程无法执行I/O操作,从而出现一些想不到的异常情况。

在大多数情况下,ExecutorHandler会与OrderedMemoryAwareThreadPoolExecutor耦合在一起使用,这是因为这么做可以保证事件的执行顺序而且在负载较大情况下能够有效防止OutOfMemoryError

下面是调用的关键代码:

设置ExecutionHandler:

 

    static ExecutionHandler EXECUTIONHANDLER = new ExecutionHandler(

new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576));

启动服务时factory设置

TCPSERVER_BOOTSTRAP.setPipelineFactory(new TCPServerPipelineFactory(EXECUTIONHANDLER));

PipelineFactory的设置:

public class TCPServerPipelineFactory implements ChannelPipelineFactory {

    private final ExecutionHandler EXECUTIONHANDLER;

    public TCPServerPipelineFactory(ExecutionHandler executionHandler) {

        this.EXECUTIONHANDLER = executionHandler;

    }

 

    @Override

    public ChannelPipeline getPipeline() throws Exception {

        ChannelPipeline pipeline = Channels.pipeline();

//        pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));

//        pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));

        pipeline.addLast("decoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));

//     pipeline.addLast("encoder", new LengthFieldPrepender(4, false));

//        pipeline.addLast("GLOBAL_TRAFFIC_SHAPING", new ChannelTrafficShapingHandler(new HashedWheelTimer()));

        pipeline.addLast("handler", this.EXECUTIONHANDLER);

        pipeline.addLast("handler", new TCPServerHandler());

        return pipeline;

    }

当服务关闭时,需注意:

public static void tcpServerShutDown(){

        TCPSERVER_BOOTSTRAP.releaseExternalResources();

        EXECUTIONHANDLER.releaseExternalResources();

        LOGGER.info("TCP服务已关闭");

    }

针对其中源码分析发现,主要起作用的两个方法如下:

 

public void handleUpstream(

ChannelHandlerContext context, ChannelEvent e) throws Exception {

if (handleUpstream) {

executor.execute(new ChannelUpstreamEventRunnable(context, e, executor));

} else {

context.sendUpstream(e);

}

}

 

public void handleDownstream(

ChannelHandlerContext ctx, ChannelEvent e) throws Exception {

// check if the read was suspend

if (!handleReadSuspend(ctx, e)) {

if (handleDownstream) {

executor.execute(new ChannelDownstreamEventRunnable(ctx, e, executor));

} else {

ctx.sendDownstream(e);

}

}

}

 

其中在处理上下行数据的时候加入了Executor,采用多线程执行,具体的是采用的哪种Executor,常用哪些Executor,以及各种Executor的区别将在下面分别予以介绍。

  1. MemoryAwareThreadPoolExecutor

继承自ThreadPoolExecutor,具有如下特性:

当队列中有太多任务时,ThreadPoolExecutor会阻塞任务的提交,此时,每个channel和每个Executor的limition都会起作用。

当一个任务(如 Runnable)被提交以后,MemoryAwareThreadPoolExecutor会调用ObjectSizeEstimator.estimateSize(Object)来获取任务的预估大小(按字节计算),以此来计算这个尚未执行的任务的内存开销。

当此未执行的任务的总大小超出了channel或者executor的可执行大小的阀值,为了保证总处理的大小在阀值以下,队列中的任务未执行完以前,任何execute(Runnable)将会阻塞(挂起)。

替代任务大小估算策略:

虽然默认的实现类已经尽了最大的可能去估算位置类型对象的大小,然而,我们仍然希望避免错误的估算任务大小,这样,一个好的办法就是采用一个ObjectSizeEstimator的实现类来替代DefaultObjectSizeEstimator。

应用场景:

1.将MemoryAwareThreadPoolExecutor从ExecutionHandler独立出来

2.提交的任务的类型不是ChannelEventRunnable,或者ChannelEventRunnable中的MessageEvent消息类型不是ChannelBuffer

下面是具体的对ObjectSizeEstimator的实现类的改写(此段代码未经测试)

public class MyRunnable implements Runnable {

 

private final byte[] data;

 

public MyRunnable(byte[] data) {

this.data = data;

}

 

public void run() {

// Process 'data' ..

}

}

 

public class MyObjectSizeEstimator extends DefaultObjectSizeEstimator {

 

@Override

public int estimateSize(Object o) {

if (o instanceof MyRunnable) {

return ((MyRunnable) o).data.length + 8;

}

return super.estimateSize(o);

}

}

 

ThreadPoolExecutor pool = new MemoryAwareThreadPoolExecutor(

16, 65536, 1048576, 30, TimeUnit.SECONDS,

new MyObjectSizeEstimator(),

Executors.defaultThreadFactory());

 

pool.execute(new MyRunnable(data));

 

注意:这个Executor不会因为Channel相同而维持ChannelEvent的先后顺序(异步),对于此问题的解决办法,将在后文中给予说明。

下面针对此类中常用的方法进行源码分析:

 

参数最多的构造方法:

 

MemoryAwareThreadPoolExecutor(int corePoolSize, long maxChannelMemorySize, long maxTotalMemorySize, long keepAliveTime, TimeUnit unit, ObjectSizeEstimator objectSizeEstimator, ThreadFactory threadFactory){

...

}

Parameters:

corePoolSize 活动线程的最大值

maxChannelMemorySize 每个channel中队列中的events最大总大小。0表示无效

maxTotalMemorySize当前channel中队列中的events最大总大小。0表示无效

keepAliveTime 线程不活动后多长时间后自动关闭,默认为30

unit 保持活动的时间单元,默认为TimeUnit.SECONDS

threadFactory 当前线程池的线程工厂,默认为Executors.defaultThreadFactory()

objectSizeEstimator 当前线程池的预估对象大小,默认为new DefaultObjectSizeEstimator()

 

  1. OrderedMemoryAwareThreadPoolExecutor

此类继承了MemoryAwareThreadPoolExecutor,并对MemoryAwareThreadPoolExecutor中来自同一个Channel的ChannelEvent事件不能按顺序执行做了改进,使其能够按顺序执行。这个类中的几乎所有的功能都是继承自他的父类。

 

ChannelEvent执行顺序

 

当采用MemoryAwareThreadPoolExecutor时,事件的处理顺序可能是这样的:

 

--------------------------------> Timeline -------------------------------->

 

Thread X: --- Channel A (Event 2) --- Channel A (Event 1) --------------------------->

 

Thread Y: --- Channel A (Event 3) --- Channel B (Event 2) --- Channel B (Event 3) --->

 

Thread Z: --- Channel B (Event 1) --- Channel B (Event 4) --- Channel A (Event 4) --->

 

 

 

 

 

 

 

而采用OrderedMemoryAwareThreadPoolExecutor后的事件处理顺序会变成这样:

-------------------------------------> Timeline ------------------------------------>

 

Thread X: --- Channel A (Event A1) --. .-- Channel B (Event B2) --- Channel B (Event B3) --->

\ /

X

/ \

Thread Y: --- Channel B (Event B1) --' '-- Channel A (Event A2) --- Channel A (Event A3) --->

 

但是,这样也有一个坏处,当同一个Channel中的前一个ChannelEvent未执行完时,那么下一个ChannelEvent就不会执行。

还有一点需要注意:并不是说ChannelA这次调用的是ThreadX,下次再发ChannelEvent时就还是会调用ThreadX,这点无法保证。

 

换种方式来维持每个通道的事件执行顺序

 

OrderedMemoryAwareThreadPoolExecutor中,Executor会采用Channel来作为主键,用以维持每个通道(Channel)的执行顺序,然而,我们可以修改为其他的值作为顺序执行的主键。例如:远程Channel的IP地址。

具体实现方式(代码)如下:

public class RemoteAddressBasedOMATPE extends OrderedMemoryAwareThreadPoolExecutor {

 

... Constructors ...

 

@Override

protected ConcurrentMap<Object, Executor> newChildExecutorMap() {

// The default implementation returns a special ConcurrentMap that

// uses identity comparison only (see IdentityHashMap).

// Because SocketAddress does not work with identity comparison,

// we need to employ more generic implementation.

return new ConcurrentHashMap<Object, Executor>

}

 

protected Object getChildExecutorKey(ChannelEvent e) {

// Use the IP of the remote peer as a key.

return ((InetSocketAddress) e.getChannel().getRemoteAddress()).getAddress();

}

 

// Make public so that you can call from anywhere.

public boolean removeChildExecutor(Object key) {

super.removeChildExecutor(key);

}

}

注意:此处一定要当心子类中executor map的内存泄露问题,当整个主键的生命周期结束(如来自相同IP的所有连接都已经关闭了)后,一定要调用removeChileExecutor(Object)方法。请牢记:调用removeChileExecutor(Object)方法后,此主键可能还会出现(如移除掉那个IP后可能还会出现来自那个IP的新的连接),如果不确定此种情况是否会发生,可以定期从子类的executor map中修改旧有的未使用的或是失效的key。解决方案如下:

 

RemoteAddressBasedOMATPE executor = ...;

 

on every 3 seconds:

 

for (Iterator<Object> i = executor.getChildExecutorKeySet().iterator; i.hasNext();) {

InetAddress ip = (InetAddress) i.next();

if (there is no active connection from 'ip' now &&

there has been no incoming connection from 'ip' for last 10 minutes) {

i.remove();

}

}

 

如果预期的key的最大值比较小而且比较确定,这样可以使用"弱"map来替代自己管理key的生命周期(如 ConcurrentWeakHashMap或者 synchronized WeakHashMap)。

 

注:"弱"map 指仅当虚拟机内存不足时会对此map清空,正常情况下,不会主动做任何处理

 

此类能够按顺序执行来自同一个ChannelChannelEvent源码分析:

 

此类定义了一个ConcurrentMap<Object, Executor>类型的全局变量childExecutors,它的key存放的是channel

protected Object getChildExecutorKey(ChannelEvent e) {

return e.getChannel();

}

它的value存放的是Executor

执行execute操作时会先根据map查找,从而实现顺序执行。

 

  1. OrderedDownstreamThreadPoolExecutor

前面所研究的均是针对于上行数据(客户端向到服务器段发送数据,数据处理的执行顺序),此类主要是针对下行数据处理,使得下行数据能够按照上来的顺序下发下去。此类继承了OrderedMemoryAwareThreadPoolExecutor类,功能几乎全部与OrderedMemoryAwareThreadPoolExecutor类相同。

  1. Demo实现

 

  1. SocketServer

 

static ExecutionHandler EXECUTION_UP_HANDLER = new ExecutionHandler(

new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576),false,true);

    static ExecutionHandler EXECUTION_DOWN_HANDLER = new ExecutionHandler(

new OrderedDownstreamThreadPoolExecutor(16),true,false);

    /**TCP方式*/

    static ChannelFactory TCPCHANNEL_FACTORY = new NioServerSocketChannelFactory(

            Executors.newCachedThreadPool(),

            Executors.newCachedThreadPool());

    /**TCP方式*/

    static ServerBootstrap TCPSERVER_BOOTSTRAP = new ServerBootstrap(TCPCHANNEL_FACTORY);

 

public static void tcpServerStartUp() {

        TCPSERVER_BOOTSTRAP.setPipelineFactory(new TCPServerPipelineFactory(EXECUTION_UP_HANDLER,EXECUTION_DOWN_HANDLER));

        TCPSERVER_BOOTSTRAP.setOption("child.tcpNoDelay", true);

        TCPSERVER_BOOTSTRAP.setOption("child.keepAlive", true);

        TCPSERVER_BOOTSTRAP.setOption("reuseAddress", true);

        LOGGER.info("SERVER_NAME:"+Constants.SERVER_NAME);

        LOGGER.info("TCPSERVER_PORT:"+Constants.TCPSERVER_PORT);

        TCPSERVER_BOOTSTRAP.bind(new InetSocketAddress(Constants.SERVER_NAME,Constants.TCPSERVER_PORT));

        LOGGER.info("TCP服务已启动....");

    }

 

    public static void tcpServerShutDown(){

        TCPSERVER_BOOTSTRAP.releaseExternalResources();

        EXECUTION_UP_HANDLER.releaseExternalResources();

        EXECUTION_DOWN_HANDLER.releaseExternalResources();

        LOGGER.info("TCP服务已关闭");

    }

  1. TCPPipelineFactory

 

ChannelPipeline pipeline = Channels.pipeline(

                new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4),

                new LengthFieldPrepender(4, false),

                this.EXECUTION_UP_HANDLER,

                this.EXECUTION_DOWN_HANDLER,

                new TCPServerHandler());

        return pipeline;

  1. ClientPipelineFactory

 

ChannelPipeline pipeline = pipeline();

        pipeline.addLast("decoder", new LengthFieldBasedFrameDecoder(

                Integer.MAX_VALUE, 0, 4, 0, 4));

        pipeline.addLast("encoder", new LengthFieldPrepender(4, false));

        pipeline.addLast("handler", new ClientHandler());

 

        return pipeline;

<think>首先,用户的问题是:“我想查找使用Maven对Quarkus进行原生打生成jar的方法”。意思是他们想了解如何使用Maven为Quarkus应用程序进行原生打,生成可执行的JAR文件。 回顾系统级指令: - 所有行内数学表达式用 `$...$`。 - 独立公式用 `$$...$$` 并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,如 `[^1]`。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 用户提供了引用: - 引用[1]: 提到Quarkus的脚手架工程配置,Maven 3.8.7,Quarkus快速启动,打方式,括将程序打为二进制文件,并使用GraalVM。 - 引用[2]: 描述IDEA导入Maven项目,Quarkus的依赖和资源概念。 - 引用[3]: 提到Maven和Netty Spring项目打JAR时的错误解决办法。 - 引用[4]: 讨论Quarkus构建原生可执行文件,在Linux上需要安装的工具。 用户指定参考这些引用,但强调“并不是用户真正的需求”,所以我应该基于问题核心来回答:使用Maven对Quarkus进行原生打生成jar的方法。 关键点: - Quarkus原生打通常指的是使用GraalVM Native Image来创建原生可执行文件,而不是传统的JAR文件。原生打生成的是一个独立的二进制文件,而不是JAR。 - 用户说“生成jar”,但Quarkus的原生打生成的是二进制文件(如可执行文件),而传统的Quarkus打可以生成JAR文件(比如uber-jar或fast-jar)。 - 在引用[1]中提到“quarkus将程序打为二进制文件”,引用[4]也提到“Quarkus构建一个原生可执行文件”。 - 所以,我需要澄清:用户可能误解了“原生打”的含义。原生打生成二进制文件,不是JAR。但Quarkus也支持生成JAR文件。 可能的打方式: 1. **传统JAR打**:使用Maven生成普通的JAR文件(例如 uber-jar)。 2. **原生打**:使用GraalVM生成原生可执行二进制文件。 3. **Quarkus特定打**:Quarkus有“fast-jar”格式优化了启动时间。 用户说“原生打生成jar”,这可能是个矛盾。因为原生打生成二进制文件,而不是JAR。所以,在回答中,我应该: - 解释Quarkus的打选项。 - 专注于使用Maven生成原生二进制文件的方法。 - 如果用户需要JAR,则解释如何生成不同类型的JAR。 从引用[1]:“quarkus的三种打方式”——我需要回想Quarkus的打方式: - JVM模式:生成可运行的JAR。 - 原生模式:生成原生可执行文件。 - Docker容器。 引用[4]:“Quarkus构建一个原生可执行文件”——这涉及安装GraalVM和相关工具。 回答结构: 1. **澄清概念**:解释Quarkus的打方式,区分JAR和原生打。 2. **步骤指南**:提供使用Maven进行原生打的详细步骤。 3. **引用相关信息**:自然地整合引用。 4. **生成相关问题**:在末尾添加相关问题。 步骤使用Maven进行原生打: - 确保安装了GraalVM(引用[1]提到使用GraalVM)。 - 配置Maven项目(pom.xml)。 - 运行Maven命令:`mvn package -Pnative` 或类似。 - 环境设置:在Linux上需要安装开发工具(引用[4])。 详细步骤: 1. 安装GraalVM并设置JAVA_HOME。 2. 在项目目录中,运行Maven命令来构建原生镜像。 3. 生成的输出在target目录中,是可执行文件。 对于生成JAR: - 如果需要传统JAR,可以使用 `mvn package` 生成quarkus-app目录中的fast-jar或uber-jar。 在回答中: - 使用中文。 - 结构清晰:介绍、步骤、总结。 - 添加引用标识。 生成相关问题: - 类似于示例,列出2-3个相关问题。 最终回答结构: - 开头解释。 - 逐步说明方法。 - 代码块或命令示例。 - 注意事项。 - 相关问题。 </think>以下我将基于您的查询“使用Maven对Quarkus进行原生打生成jar的方法”进行解答。首先,我需要澄清一个关键概念:Quarkus的“原生打”通常指使用GraalVM生成**原生可执行二进制文件**(如Linux下的可执行文件),而不是传统的JAR文件。原生打优化了启动时间和资源占用,适合云原生场景。但如果您需要生成可运行的JAR文件(如Quarkus的fast-jar或uber-jar),Quarkus也支持传统JVM模式打[^1]。 您的引用内容提供了有用背景: - 引用[1]提到Quarkus脚手架工程配置、打方式(括将程序打为二进制文件),以及使用GraalVM。 - 引用[2]描述了IDEA集成Maven项目,Quarkus的依赖管理。 - 引用[3]涉及Maven打错误解决,但针对Netty Spring项目。 - 引用[4]详细说明了Quarkus原生打的环境要求(如安装GCC等工具)。 我将重点解释**使用Maven对Quarkus进行原生打生成原生可执行文件的方法**。如果您实际需要JAR文件,我会在步骤中补充传统JVM打方法。 ### 步骤详解:使用Maven对Quarkus进行原生打 原生打利用GraalVM将Java代码编译为本地机器码,生成一个独立的可执行文件。以下是完整步骤,适用于Linux/macOS/Windows(需额外工具)。整个过程基于Maven命令。 1. **前提条件安装** - **安装GraalVM**:从GraalVM官网下载GraalVM CE版本(推荐Java 11或更高),并设置`JAVA_HOME`环境变量。引用[1]提到使用GraalVM替换本地Java环境是基础[^1]。 - **安装Native Image工具**:运行GraalVM的安装命令: ```bash gu install native-image ``` - **系统依赖**:在Linux上,按引用[4]安装必要工具: - RPM-based系统(如CentOS): `sudo dnf install gcc glibc-devel zlib-devel libstdc++-static` - Debian-based系统(如Ubuntu): `sudo apt-get install build-essential libz-dev zlib1g-dev` Windows需额外安装Visual Studio或MinGW[^4]。 2. **配置Maven项目** - 确保您的Quarkus项目`pom.xml`中含Quarkus原生打插件。Quarkus脚手架通常已含此配置(引用[2]提到Quarkus的pom依赖quarkus-bom)[^2]。 ```xml <build> <plugins> <plugin> <groupId>io.quarkus</groupId> <artifactId>quarkus-maven-plugin</artifactId> <version>2.13.7</version> <!-- 使用您当前的Quarkus版本 --> <executions> <execution> <goals> <goal>build</goal> <goal>generate-code</goal> <goal>generate-code-tests</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ``` - 如果需要自定义打选项(如内存设置),在`pom.xml`中添加属性: ```xml <properties> <quarkus.native.additional-build-args>-J-Xmx4g</quarkus.native.additional-build-args> <!-- 设置GraalVM编译内存 --> </properties> ``` 3. **运行Maven命令进行原生打** 在项目根目录执行以下命令。这会触发GraalVM原生编译: ```bash mvn package -Pnative ``` - `-Pnative` 激活Quarkus的native profile,指示Maven生成原生可执行文件。 - 编译过程较慢(可能5-10分钟),因为它执行静态分析和优化。 4. **验证输出** - 打完成后,检查`target/`目录: - 原生可执行文件:`target/<project-name>-<version>-runner`(例如,`myapp-1.0.0-runner`)。 - 运行它:`./target/myapp-1.0.0-runner`。 - **重要**:这不是JAR文件,而是二进制可执行文件。它在启动时极快(毫秒级),适合容器化部署。 5. **故障排除** - **常见错误**:如果遇到依赖问题(如引用[3]提到的Netty错误),确保所有依赖兼容GraalVM。运行 `mvn quarkus:add-extension -Dextensions="quarkus-resteasy"` 添加缺失扩展。 - **内存不足**:增加GraalVM内存(如步骤2中的属性设置)。 - **环境问题**:Windows用户需配置MinGW或使用Docker(见下文)。 ### 附加:生成传统JAR文件的方法 如果您实际需要JAR文件(例如,用于传统JVM部署),Quarkus支持两种JAR格式: - **fast-jar**:默认格式,优化启动时间。运行 `mvn package`,输出在 `target/quarkus-app/` 目录。 - **uber-jar**:生成单个可运行JAR。在 `pom.xml` 添加属性: ```xml <properties> <quarkus.package.type>uber-jar</quarkus.package.type> </properties> ``` 然后运行 `mvn package`,输出为 `target/<project-name>-<version>-runner.jar`。 ### 注意事项 - **性能权衡**:原生打生成二进制文件启动快,但编译耗时长;JAR文件启动较慢,但移植性好(引用[1]比较了三种打方式的场景)[^1]。 - **Windows支持**:原生打在Windows可能需要Docker。替代方案: ```bash mvn package -Pnative -Dquarkus.native.container-build=true ``` 这会使用Docker容器执行GraalVM编译(需安装Docker)。 - **云原生优化**:原生打减少镜像大小,适合Kubernetes(引用[4]强调环境搭建的重要性)[^4]。 如果您提供更多项目细节(如OS或错误日志),我可以进一步针对性建议。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值