Skip to content

Latest commit

 

History

History
359 lines (324 loc) · 12.3 KB

File metadata and controls

359 lines (324 loc) · 12.3 KB
title keywords description author date
RocketMQ Graalvm 适配
Spring Cloud Alibaba
Graalvm
RocketMQ
native
RocketMQ Graalvm 适配
铖朴
2024-01-27

GitHub issue 参见:alibaba/spring-cloud-alibaba#3101

经验教训

  • GraalVM Tracing Agent 收集到的信息可能不完整,所以依据这些信息编译出来的镜像运行时依然会报错,有时候需要手动补充 reflect-config.json中的内容。

适配过程

  1. fastjson 需要升级到 fastjson2 才支持 GraalVM,GraalVM 下不能用字节码做优化,走的是反射。
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.22</version>
</dependency>
  1. pom.xml 中需要增加 native-maven-plugin这个插件并添加相应的配置
<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <configuration>
        <classesDirectory>${project.build.outputDirectory}</classesDirectory>
        <metadataRepository>
            <enabled>true</enabled>
        </metadataRepository>
        <buildArgs>
            <arg>--initialize-at-build-time=org.apache.commons.logging.LogFactoryService</arg>
        </buildArgs>
        <quickBuild>true</quickBuild>
    </configuration>
</plugin>
  1. 由于 RocketMQ 大量使用 fastjson ,其核心的几个类对象大量会被反射调用,因此需要在 reflect-config.json 配置文件中,针对这几个核心类对象增加如下配置

以下配置是给 rocketmq-broadcast-producer-example这个用例中需要用到的。

{
  "name":"org.apache.rocketmq.remoting.protocol.RemotingCommand",
  "allDeclaredFields":true,
  "allDeclaredMethods":true,
  "allDeclaredConstructors":true
},
{
  "name":"org.apache.rocketmq.common.protocol.route.BrokerData",
  "allDeclaredFields":true,
  "allPublicMethods":true,
  "allPublicConstructors":true
},
{
  "name":"org.apache.rocketmq.common.protocol.route.TopicRouteData",
  "allDeclaredFields":true,
  "allPublicMethods":true,
  "allPublicConstructors":true
},
{
  "name":"org.apache.rocketmq.common.protocol.route.QueueData",
  "allDeclaredFields":true,
  "allPublicMethods":true,
  "allPublicConstructors":true
},

以下配置是给 rocketmq-broadcast-consuemr1-example这个用例中需要用到的。

{
  "name":"org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData",
  "allDeclaredFields":true,
  "allPublicMethods":true
},
{
  "name":"org.apache.rocketmq.common.protocol.heartbeat.ConsumerData",
  "allDeclaredFields":true,
  "allPublicMethods":true
},
{
  "name":"org.apache.rocketmq.common.protocol.heartbeat.ProducerData",
  "allDeclaredFields":true,
  "allPublicMethods":true
},
{
  "name":"org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData",
  "allDeclaredFields":true,
  "allPublicMethods":true
},

解释如下

  • allDeclaredFields 代码所声明的对象中的所有成员变量都可以被反射调用,包括 public 和 protected private 的成员变量
  • allPublicFields 代表所声明的对象中的所有 public 成员变量都可以被反射调用
  • allPublicMethods代码所声明的对象中所有的 public 方法都可以被反射调用
  • allPublicConstructors代码所声明的对象中所有的 public 的构造函数都可以被反射调用

以此类推。 当然,我们也可以声明某个具体的方法可以被反射调用,但是考虑到这几个核心对象的被反射的概率较大,而且方法和成员变量也不对,因此声明成所有都可以被反射。

graalvm-reachability-metadata

GraalVM 在编译的时候需要 reflect-json.config等一系列的 hint 文件来编译 native-image,但通过 Tracing Agent 收集的方式可能会有遗漏,最好的方式是让每个中间件自己提供一份文件出来,这份文件中的 hint 信息是经过充分测试,这样也是最可靠的。最好的方式是中间件提供的 jar 里面就自带了这份 hint 文件,但这样要求所有中间件要重新发布一个新版本,针对老的版本如果提供这样的信息呢?graalvm 提供了这样一个机制,也就是 graalvm-reachability-metadata 把这些信息放在一个外部的仓库中,在编译的时候会从这个仓库中拉取所需要的编译信息。

此处演示如果通过本地仓库的方式添加这个信息,可以再插件配置中,主要是在 12 行处,加入本地仓库的地址

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <configuration>
        <buildArgs>
            <arg>--initialize-at-build-time=org.apache.commons.logging.LogFactoryService</arg>
        </buildArgs>
        <quickBuild>true</quickBuild>
        <debug>true</debug>
        <metadataRepository>
            <enabled>true</enabled>
            <localPath>/Users/wangtao/.m2/repository/org/graalvm/buildtools/graalvm-reachability-metadata/0.9.19</localPath>
        </metadataRepository>
    </configuration>
</plugin>

在这个仓库下创建如下目录格式

$ tree org.apache.rocketmq
org.apache.rocketmq
└── rocketmq-client
    ├── 4.9.5-SNAPSHOT
    │   ├── index.json
    │   ├── jni-config.json
    │   ├── predefined-classes-config.json
    │   ├── proxy-config.json
    │   ├── reflect-config.json
    │   ├── resource-config.json
    │   └── serialization-config.json
    └── index.json

其中 第12行的 index.json 内容如下

[
  {
    "latest": true,
    "metadata-version": "4.9.5-SNAPSHOT",
    "module": "org.apache.rocketmq:rocketmq-client",
    "tested-versions": [
      "4.9.5-SNAPSHOT"
    ]
  }
]

org/apache/rocketmq/rocketmq-client/4.9.5-SNAPSHOT/index.json 内容如下:

$ cat org.apache.rocketmq/rocketmq-client/4.9.5-SNAPSHOT/index.json
[
  "reflect-config.json",
  "resource-config.json"
]

其中比较核心的是 reflect-config.json内容如下

[

{
  "name":"org.apache.rocketmq.client.consumer.store.OffsetSerializeWrapper",
  "allDeclaredFields":true,
  "allPublicFields":true,
  "queryAllPublicMethods":true,
  "methods":[{"name":"getOffsetTable","parameterTypes":[] }]
},
{
  "name":"org.apache.rocketmq.common.message.MessageQueue",
  "allDeclaredFields":true,
  "allPublicFields":true,
  "queryAllPublicMethods":true,
  "methods":[
    {"name":"getBrokerName","parameterTypes":[] },
    {"name":"getQueueId","parameterTypes":[] },
    {"name":"getTopic","parameterTypes":[] }
  ]
},
{
  "name":"org.apache.rocketmq.common.protocol.header.GetMaxOffsetRequestHeader",
  "allDeclaredFields":true
},
{
  "name":"org.apache.rocketmq.common.protocol.header.GetMaxOffsetResponseHeader",
  "allDeclaredFields":true,
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"org.apache.rocketmq.common.protocol.header.NotifyConsumerIdsChangedRequestHeader",
  "allDeclaredFields":true,
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader",
  "allDeclaredFields":true
},
{
  "name":"org.apache.rocketmq.common.protocol.header.PullMessageResponseHeader",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"org.apache.rocketmq.common.protocol.header.SendMessageRequestHeaderV2",
  "allDeclaredFields":true
},
{
  "name":"org.apache.rocketmq.common.protocol.header.SendMessageResponseHeader",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"org.apache.rocketmq.common.protocol.header.UnregisterClientRequestHeader",
  "allDeclaredFields":true
},
{
  "name":"org.apache.rocketmq.common.protocol.header.namesrv.GetRouteInfoRequestHeader",
  "allDeclaredFields":true
},
{
  "name":"org.apache.rocketmq.common.protocol.heartbeat.ConsumerData",
  "allDeclaredFields":true,
  "allPublicMethods":true
},
{
  "name":"org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData",
  "allDeclaredFields":true,
  "allPublicMethods":true
},
{
  "name":"org.apache.rocketmq.common.protocol.heartbeat.ProducerData",
  "allDeclaredFields":true,
  "allPublicMethods":true
},
{
  "name":"org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData",
  "allDeclaredFields":true,
  "allPublicMethods":true
},
{
  "name":"org.apache.rocketmq.common.protocol.route.BrokerData",
  "allDeclaredFields":true,
  "allPublicMethods":true,
  "allPublicConstructors":true
},
{
  "name":"org.apache.rocketmq.common.protocol.route.QueueData",
  "allDeclaredFields":true,
  "allPublicMethods":true,
  "allPublicConstructors":true
},
{
  "name":"org.apache.rocketmq.common.protocol.route.TopicRouteData",
  "allDeclaredFields":true,
  "allPublicMethods":true,
  "allPublicConstructors":true
},
{
  "name":"org.apache.rocketmq.remoting.netty.NettyDecoder"
},
{
  "name":"org.apache.rocketmq.remoting.netty.NettyEncoder"
},
{
  "name":"org.apache.rocketmq.remoting.netty.NettyRemotingClient$4"
},
{
  "name":"org.apache.rocketmq.remoting.netty.NettyRemotingClient$NettyClientHandler"
},
{
  "name":"org.apache.rocketmq.remoting.netty.NettyRemotingClient$NettyConnectManageHandler",
  "methods":[
    {"name":"close","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] },
    {"name":"connect","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.net.SocketAddress","java.net.SocketAddress","io.netty.channel.ChannelPromise"] },
    {"name":"disconnect","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] },
    {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] },
    {"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }
  ]
},
{
  "name":"org.apache.rocketmq.remoting.protocol.LanguageCode",
  "fields":[
    {"name":"CPP"},
    {"name":"DELPHI"},
    {"name":"DOTNET"},
    {"name":"ERLANG"},
    {"name":"GO"},
    {"name":"HTTP"},
    {"name":"JAVA"},
    {"name":"OMS"},
    {"name":"OTHER"},
    {"name":"PHP"},
    {"name":"PYTHON"},
    {"name":"RUBY"},
    {"name":"RUST"}
  ]
},
{
  "name":"org.apache.rocketmq.remoting.protocol.RemotingCommand",
  "allDeclaredFields":true,
  "allDeclaredMethods":true,
  "allDeclaredConstructors":true
},
{
  "name":"org.apache.rocketmq.remoting.protocol.RemotingSerializable",
  "allDeclaredFields":true,
  "queryAllPublicMethods":true
},
{
  "name":"org.apache.rocketmq.remoting.protocol.SerializeType",
  "fields":[
    {"name":"JSON"},
    {"name":"ROCKETMQ"}
  ]
}
]

这个内容是基于 Tracing Agent 收集的信息,加上手工补充的内容,形成的一个相对完整 hint 文件。GraalVM 在编译的时候会查找这个仓库,并使用这里面的信息编译 native image。在测试完成之后,可以提交到远程仓库中,https://github.com/oracle/graalvm-reachability-metadata

常见问题

如果运行时遇到以下异常:

Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Runtime reflection is not supported for public org.apache.rocketmq.common.protocol.route.TopicRouteData()
    at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89)
    at java.base@17.0.5/java.lang.reflect.Constructor.acquireConstructorAccessor(Constructor.java:68)
    at java.base@17.0.5/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:496)
    at java.base@17.0.5/java.lang.reflect.ReflectAccess.newInstance(ReflectAccess.java:128)
    at java.base@17.0.5/jdk.internal.reflect.ReflectionFactory.newInstance(ReflectionFactory.java:347)
    at java.base@17.0.5/java.lang.Class.newInstance(DynamicHub.java:645)
    at com.alibaba.fastjson2.reader.ConstructorSupplier.get(ConstructorSupplier.java:27)
    at com.alibaba.fastjson2.reader.ObjectReader4.readObject(ObjectReader4.java:309)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:496)
    ... 16 more

说明 fastjson 运行的时候进行了反射,调用了 public org.apache.rocketmq.common.protocol.route.TopicRouteData() 这个方法,但是 Tracing Agent 并没有收集到这个方法,导致编译 native image 的时候并没有把这个方法允许进行反射,所以就报错了,此时需要修改 reflect-config.json文件,讲该方法加入到运行反射的列表当中。