由于升级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
<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;
}
以上就是发送心跳的逻辑
核心逻辑
但是在断点过程中 有效的链接列表居然是空的 这就是导致代码无法继续向下
然后我们继续围绕这个点进行排查
获取有效的地址列表方法如下
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上
-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