spring cloud alibaba 应用无法注册到sentinel dashboard

一。技术背景

由于升级jdk17的需要 我们将项目中的 spring cloud spring cloud alibaba 以及springboot进行了升级 各版本如下
spring cloud 2021.0.5
spring cloud alibaba 2021.0.5.0
spring boot 2.6.13

二。问题表现

当启动项目服务后,服务无法注册到 sentinel-dashboard

三。错误排查

  • 首先检查sentinel-dashboard 启动状态 启动成功并且可以正常访问且不存在网络问题
  • 环境配置检查

<dependency>
  <groupId>org.springframework.cloudgroupId>
  <artifactId>spring-cloud-starter-gatewayartifactId>
dependency>

<dependency>
  <groupId>com.alibaba.cloudgroupId>
  <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>

<dependency>
  <groupId>com.alibaba.cloudgroupId>
  <artifactId>spring-cloud-alibaba-sentinel-gatewayartifactId>
dependency>

配置检查

#配置也正常
spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.transport.port=8719
  • 第三步源码追踪

接下来开始漫长源码分析步骤

然后点击 spring.cloud.sentinel.transport.dashboard 这条配置 跳转 com.alibaba.cloud.sentinel.SentinelProperties.Transport#setDashboard

然后点击 getDashboard() 方法查看在哪里调用 最后来到了 com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration#init

在如下代码中

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true)
@EnableConfigurationProperties(SentinelProperties.class)
public class SentinelAutoConfiguration {
	@PostConstruct
	private void init() {
        ///省略部分逻辑	
		if (StringUtils.isEmpty(System.getProperty(TransportConfig.SERVER_PORT))
				&& StringUtils.isNotBlank(properties.getTransport().getPort())) {
			System.setProperty(TransportConfig.SERVER_PORT,
					properties.getTransport().getPort());
		}
		if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER))
				&& StringUtils.isNotBlank(properties.getTransport().getDashboard())) {
			System.setProperty(TransportConfig.CONSOLE_SERVER,
					properties.getTransport().getDashboard());
		}
	}
}

断点时 发现配置成功被设置到 系统的属性配置中
接下来再来看 心跳发送具体类 HeartbeatSenderInitFunc
com.alibaba.csp.sentinel.transport.init.HeartbeatSenderInitFunc#init

@Override
public void init() {
   HeartbeatSender sender = HeartbeatSenderProvider.getHeartbeatSender();
   if (sender == null) {
       RecordLog.warn("[HeartbeatSenderInitFunc] WARN: No HeartbeatSender loaded");
       return;
   }

   initSchedulerIfNeeded();
   long interval = retrieveInterval(sender);
   setIntervalIfNotExists(interval);
   //定时调度发送心跳
   scheduleHeartbeatTask(sender, interval);
}

具体得调度逻辑 每5秒发送一次心跳

private void scheduleHeartbeatTask(/*@NonNull*/ 
						final HeartbeatSender sender, 
						/*@Valid*/ long interval) {
    pool.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            try {
                sender.sendHeartbeat();
            } catch (Throwable e) {
                RecordLog.warn("[HeartbeatSender] Send heartbeat error", e);
            }
        }
    }, 5000, interval, TimeUnit.MILLISECONDS);
    RecordLog.info("[HeartbeatSenderInit] HeartbeatSender started: "
        + sender.getClass().getCanonicalName());
}

我们再来看下具体的实现
sender.sendHeartbeat();
实现类只有一个 SimpleHttpHeartbeatSender

com.alibaba.csp.sentinel.transport.heartbeat.SimpleHttpHeartbeatSender#sendHeartbeat

@Override                                                                                                     
public boolean sendHeartbeat() throws Exception {                                                             
    if (TransportConfig.getRuntimePort() <= 0) {                                                              
        RecordLog.info("[SimpleHttpHeartbeatSender] Command server port not initialized, won't send heartbeat"
        return false;                                                                                         
    }                                                                                                         
    Endpoint addrInfo = getAvailableAddress();                                                                
    if (addrInfo == null) {                                                                                   
        return false;                                                                                         
    }                                                                                                         
                                                                                                              
    SimpleHttpRequest request = new SimpleHttpRequest(addrInfo, TransportConfig.getHeartbeatApiPath());       
    request.setParams(heartBeat.generateCurrentMessage());                                                    
    try {                                                                                                     
        SimpleHttpResponse response = httpClient.post(request);                                               
        if (response.getStatusCode() == OK_STATUS) {                                                          
            return true;                                                                                      
        } else if (clientErrorCode(response.getStatusCode()) || serverErrorCode(response.getStatusCode())) {  
            RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addrInfo              
                + ", http status code: " + response.getStatusCode());                                         
        }                                                                                                     
    } catch (Exception e) {                                                                                   
        RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addrInfo, e);             
    }                                                                                                         
    return false;                                                                                             
}                                                                                                             
                                                                                                              

以上就是发送心跳的逻辑
核心逻辑

  • 获取有效的链接
  • 创建连接发送心跳请求
  • 响应以及异常处理

但是在断点过程中 有效的链接列表居然是空的 这就是导致代码无法继续向下
spring cloud alibaba 应用无法注册到sentinel dashboard_第1张图片
然后我们继续围绕这个点进行排查

获取有效的地址列表方法如下

private Endpoint getAvailableAddress() {
    if (addressList == null || addressList.isEmpty()) {
      return null;
    }
    if (currentAddressIdx < 0) {
      currentAddressIdx = 0;
    }
    int index = currentAddressIdx % addressList.size();
    return addressList.get(index);
}

发现使用的成员变量 addressList 我们再来找下这个值的赋值操作

赋值操作只有一个地方 在SimpleHttpHeartbeatSender的构造方法中

public SimpleHttpHeartbeatSender() {
    // Retrieve the list of default addresses.
    List<Endpoint> newAddrs = TransportConfig.getConsoleServerList();
    if (newAddrs.isEmpty()) {
      RecordLog.warn("[SimpleHttpHeartbeatSender] Dashboard server address not configured or not available");
    } else {
      RecordLog.info("[SimpleHttpHeartbeatSender] Default console address list retrieved: {}", newAddrs);
    }
    this.addressList = newAddrs;
}

com.alibaba.csp.sentinel.transport.config.TransportConfig#getConsoleServerList

public static List<Endpoint> getConsoleServerList() {
    String config = SentinelConfig.getConfig(CONSOLE_SERVER);
    List<Endpoint> list = new ArrayList<Endpoint>();
    if (StringUtil.isBlank(config)) {
      return list;
    }

    //。。。。。省略部分
    return list;
}

com.alibaba.csp.sentinel.config.SentinelConfig#getConfig(java.lang.String)

public static String getConfig(String key) {
    AssertUtil.notNull(key, "key cannot be null");
    return props.get(key);
}

我们再来看下 props的初始化

在SentinelConfig的静态构造中

static {
    try {
        initialize();
        //加载配置
        loadProps();
        resolveAppName();
        resolveAppType();
        RecordLog.info("[SentinelConfig] Application type resolved: {}", appType);
    } catch (Throwable ex) {
        RecordLog.warn("[SentinelConfig] Failed to initialize", ex);
        ex.printStackTrace();
    }
}

com.alibaba.csp.sentinel.config.SentinelConfig#loadProps

private static void loadProps() {
    ///从配置加载中获取配置
    Properties properties = SentinelConfigLoader.getProperties();
    for (Object key : properties.keySet()) {
      setConfig((String) key, (String) properties.get(key));
    }
}

从SentinelConfigLoader 获取到配置

public static Properties getProperties() {
  	return properties;
}

而这个properties的初始化是在SentinelConfigLoader 静态构造方法中

static {
    try {
        load();
    } catch (Throwable t) {
        RecordLog.warn("[SentinelConfigLoader] Failed to initialize configuration items", t);
    }
}
private static void load() {
    // Order: system property -> system env -> default file (classpath:sentinel.properties) -> legacy path
    String fileName = System.getProperty(SENTINEL_CONFIG_PROPERTY_KEY);
    if (StringUtil.isBlank(fileName)) {
      fileName = System.getenv(SENTINEL_CONFIG_ENV_KEY);
      if (StringUtil.isBlank(fileName)) {
        fileName = DEFAULT_SENTINEL_CONFIG_FILE;
      }
    }

    Properties p = ConfigUtil.loadProperties(fileName);
    if (p != null && !p.isEmpty()) {
      RecordLog.info("[SentinelConfigLoader] Loading Sentinel config from {}", fileName);
      properties.putAll(p);
    }

    for (Map.Entry<Object, Object> entry : new CopyOnWriteArraySet<>(System.getProperties().entrySet())) {
      String configKey = entry.getKey().toString();
      String newConfigValue = entry.getValue().toString();
      String oldConfigValue = properties.getProperty(configKey);
      properties.put(configKey, newConfigValue);
      if (oldConfigValue != null) {
        RecordLog.info("[SentinelConfigLoader] JVM parameter overrides {}: {} -> {}",
                       configKey, oldConfigValue, newConfigValue);
      }
    }
}

看到这里就大概明白了,因为这是静态代码话 肯定优先初始化,而我们的地址在项目启动bean加载中才会设置到System的Properties里

所以获取的 地址一直是空的。 也无法注册到sentinel-dashboard上

四。解决办法

  • idea 启动类 增加参数 指定dashboard server地址 以及应用名称
-Dcsp.sentinel.dashboard.server=localhost:8080 -Dcsp.sentinel.app.name=gateway-service
  • 启动类设置系统变量
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayServiceApplication {

    public static void main(String[] args) {
        System.setProperty("csp.sentinel.dashboard.server","localhost:8080");
        System.setProperty("csp.sentinel.app.name","gateway-service");
        SpringApplication.run(GatewayServiceApplication.class, args);
    }

}

五。后续分析旧的版本的依赖对应的实现方式

旧的依赖版本为
springboot 2.3.12.RELEASE
spring cloud Hoxton.SR12
spring cloud alibaba 2.2.9.RELEASE

你可能感兴趣的:(springcloud,spring,cloud,sentinel,dashboard)