alibaba / alibaba/Sentinel

Sentinel GraalVM SPI 重复加载问题 | Duplicate SPI loading problem under GraalVM

Open
#3,012 6 comments 0 reactions 0 assignees View on GitHub
area/spi help wanted
Dominant language
Java
Stars
23.1k
Forks
8.1k
PR merge metrics
No merged PRs in 30d

Description

## Issue Description

Spring Boot使用 `sentinel-core`模块时,打包成native镜像 , spi被加载了2次

### How to reproduce it (as minimally and precisely as possible)

1. 复现demo
```java
package com.demo;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.ArrayList;
import java.util.List;

@SpringBootApplication
public class NativeDemoApp {

private static final String RES_KEY = "test";

public static void main(String[] args) {
SpringApplication.run(NativeDemoApp.class, args);
init();
System.out.println("========== 开始执行测试方法 ==========");
for (int i = 0; i < 3; i++) {
String result = test(i);
System.out.println(String.format("========== 第%s次执行结果: %s ==========", i + 1, result));
}
}

public static void init() {
System.out.println("========== 开始加载sentinel规则... ==========");
List rules = new ArrayList();
FlowRule rule1 = new FlowRule();
rule1.setResource(RES_KEY);
// Set max qps to 20
rule1.setCount(1);
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
System.out.println("========== sentinel规则加载完成! ==========");
}

public static String test(int i) {
Entry entry = null;
try {
entry = SphU.entry(RES_KEY);
} catch (BlockException ex) {
return "BlockException...";
} finally {
if (entry != null) {
entry.exit();
}
}
return "调用成功!";
}

}

```

Maven 相关配置
```xml

17
1.8.6




org.springframework.boot
spring-boot-starter-web



com.alibaba.csp
sentinel-core
${sentinel.version}








org.graalvm.buildtools
native-maven-plugin


org.springframework.boot
spring-boot-maven-plugin


-agentlib:native-image-agent=config-merge-dir=src/main/resources/META-INF/native-image/





```

2. 执行`mvn -Pnative spring-boot:run` 来生成反射文件
3. 执行 `mvn -Pnative native:compile` 生成native镜像
4. 生成后执行对应的镜像可以看到以下错误
```java
**2023-01-03T19:11:36.354+08:00 INFO 20736 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-01-03T19:11:36.354+08:00 INFO 20736 --- [ main] com.demo.NativeDemoApp : Started NativeDemoApp in 0.124 seconds (process running for 0.129)
========== 开始加载sentinel规则... ==========
INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: C:\Users\ruanx\logs\csp\
INFO: Sentinel log name use pid is: false
INFO: Sentinel log level is: INFO
========== sentinel规则加载完成! ==========
========== 开始执行测试方法 ==========
com.alibaba.csp.sentinel.spi.SpiLoaderException: [com.alibaba.csp.sentinel.init.InitFunc]Found repeat alias name for com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit and com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit,SPI configuration file=META-INF/services/com.alibaba.csp.sentinel.init.InitFunc
at com.alibaba.csp.sentinel.spi.SpiLoader.fail(SpiLoader.java:525)
at com.alibaba.csp.sentinel.spi.SpiLoader.load(SpiLoader.java:383)
at com.alibaba.csp.sentinel.spi.SpiLoader.loadInstanceListSorted(SpiLoader.java:169)
at com.alibaba.csp.sentinel.init.InitExecutor.doInit(InitExecutor.java:46)
at com.alibaba.csp.sentinel.Env.(Env.java:36)
at com.alibaba.csp.sentinel.SphU.entry(SphU.java:85)
at com.demo.NativeDemoApp.test(NativeDemoApp.java:63)
at com.demo.NativeDemoApp.main(NativeDemoApp.java:40)
Exception in thread "main" com.alibaba.csp.sentinel.spi.SpiLoaderException: [com.alibaba.csp.sentinel.slotchain.SlotChainBuilder]Found repeat alias name for com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder and com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder,SPI configuration file=META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder
at com.alibaba.csp.sentinel.spi.SpiLoader.fail(SpiLoader.java:525)
at com.alibaba.csp.sentinel.spi.SpiLoader.load(SpiLoader.java:383)
at com.alibaba.csp.sentinel.spi.SpiLoader.loadFirstInstanceOrDefault(SpiLoader.java:229)
at com.alibaba.csp.sentinel.slotchain.SlotChainProvider.newSlotChain(SlotChainProvider.java:44)
at com.alibaba.csp.sentinel.CtSph.lookProcessChain(CtSph.java:205)
at com.alibaba.csp.sentinel.CtSph.entryWithPriority(CtSph.java:136)
at com.alibaba.csp.sentinel.CtSph.entry(CtSph.java:176)
at com.alibaba.csp.sentinel.CtSph.entry(CtSph.java:315)
at com.alibaba.csp.sentinel.SphU.entry(SphU.java:85)
at com.demo.NativeDemoApp.test(NativeDemoApp.java:63)
at com.demo.NativeDemoApp.main(NativeDemoApp.java:40)**
```

预期正确的执行结果:
```
2023-01-03T19:17:15.920+08:00 INFO 21724 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-01-03T19:17:15.926+08:00 INFO 21724 --- [ main] com.demo.NativeDemoApp : Started NativeDemoApp in 1.057 seconds (process running for 1.52)
========== 开始加载sentinel规则... ==========
========== sentinel规则加载完成! ==========
========== 开始执行测试方法 ==========
========== 第1次执行结果: 调用成功! ==========
========== 第2次执行结果: BlockException... ==========
========== 第3次执行结果: BlockException... ==========
```
### Tell us your environment
Windows 11 , JDK17 , Spring Boot 3.0

### Anything else we need to know?

是由于`mvn -Pnative spring-boot:run` 这一步在`reflect-config.json`文件会添加以下配置
```json
// 这里省略其他相关配置
{
"name":"com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder",
"methods":[{"name":"","parameterTypes":[] }]
}
```
从而导致SPI被加载了2次

如果省略 `mvn -Pnative spring-boot:run` 这一步(`reflect-config.json`文件中不包含上面的反射配置) , 直接执行 `mvn -Pnative native:compile` 生成native镜像 , 则程序符合预期.

目前想到的解决方案是修改`SpiLoader.java line:399 ` 的判断逻辑 , 对于重复的加载的类将错误改为警告
```java
if (classMap.containsKey(aliasName)) {
Class existClass = classMap.get(aliasName);
fail("Found repeat alias name for " + clazz.getName() + " and "
+ existClass.getName() + ",SPI configuration file=" + fullFileName);
}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.