alibaba/Sentinel

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

Open

#3012 opened on Jan 3, 2023

View on GitHub
 (6 comments) (0 reactions) (0 assignees)Java (23,109 stars) (8,150 forks)batch import
area/spihelp wanted

Description

Issue Description

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

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

  1. 复现demo
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<FlowRule> rules = new ArrayList<FlowRule>();
        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 相关配置

    <properties>
        <java.version>17</java.version>
        <sentinel.version>1.8.6</sentinel.version>
    </properties>
    <dependencies>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-core</artifactId>
            <version>${sentinel.version}</version>
        </dependency>
    
    
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <jvmArguments>
                        -agentlib:native-image-agent=config-merge-dir=src/main/resources/META-INF/native-image/
                    </jvmArguments>
                </configuration>
            </plugin>
        </plugins>
    </build>
  1. 执行mvn -Pnative spring-boot:run 来生成反射文件
  2. 执行 mvn -Pnative native:compile 生成native镜像
  3. 生成后执行对应的镜像可以看到以下错误
**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.<clinit>(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文件会添加以下配置

// 这里省略其他相关配置
{
  "name":"com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder",
  "methods":[{"name":"<init>","parameterTypes":[] }]
}

从而导致SPI被加载了2次

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

目前想到的解决方案是修改SpiLoader.java line:399 的判断逻辑 , 对于重复的加载的类将错误改为警告

                    if (classMap.containsKey(aliasName)) {
                        Class<? extends S> existClass = classMap.get(aliasName);
                        fail("Found repeat alias name for " + clazz.getName() + " and "
                                + existClass.getName() + ",SPI configuration file=" + fullFileName);
                    }

Contributor guide