在调试Sentinel对接Scg网关的时候,出现了一个比较坑的现象,就是网关菜单,不显示请求链路和API管理的菜单项,这个问题主要是由于,Sentinel的dashboard前端sidebar.js的 代码逻辑中, /registry/machine查询返回的数据,返回的appType = 0
AppService.getApps().success(
function (data) {
if (data.code === 0) {
let path = $location.path().split('/');
let initHashApp = path[path.length - 1];
$scope.apps = data.data;
$scope.apps = $scope.apps.map(function (item) {
if (item.app === initHashApp) {
item.active = true;
}
let healthyCount = 0;
for (let i in item.machines) {
if (item.machines[i].healthy) {
healthyCount++;
}
}
item.healthyCount = healthyCount;
// 对应后端接口 /registry/machine查询数据,返回的appType = 0
item.isGateway = item.appType === 1 || item.appType === 11 || item.appType === 12;
if (item.shown) {
return item;
}
});
}
}
);
通过后端MachineRegistryController的receiveHeartBeat方法返回的数据 item的appType为0,这就使得菜单一直显示普通的服务类型的菜单,而不显示网关类型的菜单。
receiveHeartBeat方法实际是由连接dashboard的各个客户端发送心跳数据,传入的参数中含有appType, 而透过客户端的代码可以看到以,使用http方式传输为例,可以看一下客户端SimpleHttpHeartbeatSender的源码,这里的sendHeartbeat方法,就是对应dashboard的receiveHeartBeat来进行信息交互的,通过调试可以看出发送的心跳数据HeartbeatMessage中,appType就为0而不是为1,HeartbeatMessage类中app_type的值是来源于SentinelConfig.getAppType()
public HeartbeatMessage() {
message.put("hostname", HostNameUtil.getHostName());
message.put("ip", TransportConfig.getHeartbeatClientIp());
message.put("app", AppNameUtil.getAppName());
// Put application type (since 1.6.0).
message.put("app_type", String.valueOf(SentinelConfig.getAppType()));
message.put("port", String.valueOf(TransportConfig.getPort()));
}
而SentinelConfig中 appType的默认值APP_TYPE_COMMON = 0,由于SentinelSCGAutoConfiguration自动配置的时候,init初始化方法未能在发送心跳前将appType的值进行变更,所以导致都是按默认值0来传递数据的,也就是说,SimpleHttpHeartbeatSender中的HeartbeatMessage在初始化的时候SentinelSCGAutoConfiguration里的initAppType方法还未未执行,SentinelSCGAutoConfiguration是在SimpleHttpHeartbeatSender读取csp.sentinel.app.type之后执行的,所以SentinelSCGAutoConfiguration这里的自动配置csp.sentinel.app.type是无效的,这就使得dashboard一直按普通服务展示菜单。所以只能通过,SentinelConfigLoader 会从System.getProperty加载变量信息,并最终由SentinelConfig的loadProps方法加载到props的内存Map中,解决办法是可以在服务的启动命令中加上 -Dcsp.sentinel.app.type=1 或者 在服务的Application的main方法里加入System.setProperty("csp.sentinel.app.type", "1"); 这样就可以使得机器的appType变为1
@SpringBootApplication
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) {
System.setProperty("csp.sentinel.app.type", "1");
SpringApplication.run(Application.class, args);
}
}
这样调整之后只是改变了一半的问题,你会发现即便改变完成,dashboard刷新后,菜单仍然不会刷新变为网关类型,这是什么原因呢,调试代码发现,虽然客户端心跳发送的machineInfo已经变更appType,但是dashboard查询数据AppInfo里中的apptype依旧为0,
public class AppInfo {
private String app = "";
private Integer appType = 0;
private Set<MachineInfo> machines = ConcurrentHashMap.newKeySet(); 这里的appType已经变为1了
public AppInfo() {}
这又是什么原因呢?经过调试,最后发现,问题就出现在了,SimpleMachineDiscovery里的addMachine方法上了
@Component
public class SimpleMachineDiscovery implements MachineDiscovery {
private final ConcurrentMap<String, AppInfo> apps = new ConcurrentHashMap<>();
@Override
public long addMachine(MachineInfo machineInfo) {
AssertUtil.notNull(machineInfo, "machineInfo cannot be null");
AppInfo appInfo = apps.computeIfAbsent(machineInfo.getApp(), o -> new AppInfo(machineInfo.getApp(), machineInfo.getAppType()));
// 只有当apps里对象不存在的时候,才会放一个新的进去,才会发生变化
appInfo.addMachine(machineInfo);
return 1;
}
这是因为,apps里存储了所有服务的服务信息,开始提到的,item.isGateway = item.appType === 1 || item.appType === 11 || item.appType 这个item对象实际对应的是appInfo对象,而appInfo对象里的appType值,是在第一次apps里对象不存在的时候,才会放一个新的进去,才会发生变化。所以当你不重启dashboard的时候,apps里的服务列表中单个对象中的appType是不会变化的,即便是你网关服务已经变更appTpye=1并发送心跳过来。所以直到你重启dashboard ,apps重新装载数据的时候,apps里的服务列表中单个对象中的appType才会重新取到客户端修改后的值并存进去。
AppInfo appInfo = apps.computeIfAbsent(machineInfo.getApp(), o -> new AppInfo(machineInfo.getApp(), machineInfo.getAppType()));
machineInfo.getAppType()变为1了
apps的中AppInfo的appType才会被machineInfo.getAppType值覆盖掉
这个时候页面中的菜单才会变为