当前位置: 首页 > news >正文

Sentinel dashboard无法查询到应用的限流配置问题以及解决

一。问题引入

使用sentinle-dashboard控制台 项目整体升级后,发现控制台上无法看到流控规则了
在这里插入图片描述

之前的问题是无法注册上来 现在是注册上来了。结果看不到流控规则配置了。 关于注册不上来的问题,可以看另一篇文章
https://blog.csdn.net/a15835774652/article/details/132234943

项目的组件版本如下
spring cloud 2021.0.5
spring cloud alibaba 2021.0.5.0
spring boot 2.6.13

二。问题分析

  • 配置检查 无误
  • 控制台服务应用 检查 确认已经启动
  • 网络连接检查无误
  • 源码分析

首先我们打开检查- 网络 看下调用的接口

在这里插入图片描述
可以看到 接口地址为 http://localhost:8080//v1/flow/rules?app=local-gateway-service&ip=xxx&port=8720

然后打开源码 注意分支为release-1.8
业务代码 主要在 sentinel-dashboard module 中 控制类在 FlowControllerV1 中

com.alibaba.csp.sentinel.dashboard.controller.FlowControllerV1#apiQueryMachineRules

@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app,@RequestParam String ip,@RequestParam Integer port) {if (StringUtil.isEmpty(app)) {return Result.ofFail(-1, "app can't be null or empty");}if (StringUtil.isEmpty(ip)) {return Result.ofFail(-1, "ip can't be null or empty");}if (port == null) {return Result.ofFail(-1, "port can't be null");}if (!appManagement.isValidMachineOfApp(app, ip)) {return Result.ofFail(-1, "given ip does not belong to given app");}try {List<FlowRuleEntity> rules = sentinelApiClient.fetchFlowRuleOfMachine(app, ip, port);rules = repository.saveAll(rules);return Result.ofSuccess(rules);} catch (Throwable throwable) {logger.error("Error when querying flow rules", throwable);return Result.ofThrowable(-1, throwable);}
}

重点来看下 sentinelApiClient.fetchFlowRuleOfMachine的调用

com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient#fetchFlowRuleOfMachine

public List<FlowRuleEntity> fetchFlowRuleOfMachine(String app, String ip, int port) {List<FlowRule> rules = fetchRules(ip, port, FLOW_RULE_TYPE, FlowRule.class);if (rules != null) {return rules.stream().map(rule -> FlowRuleEntity.fromFlowRule(app, ip, port, rule)).collect(Collectors.toList());} else {return null;}
}

接着调用 fetchRules 方法

com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient#fetchRules

private <T extends Rule> List<T> fetchRules(String ip, int port, String type, Class<T> ruleType) {return fetchItems(ip, port, GET_RULES_PATH, type, ruleType);
}

com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient#fetchItems

private <T> List<T> fetchItems(String ip, int port, String api, String type, Class<T> ruleType) {try {AssertUtil.notEmpty(ip, "Bad machine IP");AssertUtil.isTrue(port > 0, "Bad machine port");Map<String, String> params = null;if (StringUtil.isNotEmpty(type)) {params = new HashMap<>(1);params.put("type", type);}return fetchItemsAsync(ip, port, api, type, ruleType).get();} catch (InterruptedException | ExecutionException e) {logger.error("Error when fetching items from api: {} -> {}", api, type, e);return null;} catch (Exception e) {logger.error("Error when fetching items: {} -> {}", api, type, e);return null;}
}
private <T> CompletableFuture<List<T>> fetchItemsAsync(String ip, int port, String api,String type, Class<T> ruleType) {AssertUtil.notEmpty(ip, "Bad machine IP");AssertUtil.isTrue(port > 0, "Bad machine port");Map<String, String> params = null;if (StringUtil.isNotEmpty(type)) {params = new HashMap<>(1);params.put("type", type);}return executeCommand(ip, port, api, params, false).thenApply(json -> JSON.parseArray(json, ruleType));
}

看到这里基本明白这个逻辑了 本质上就是发起http请求 同时指定api 以及type参数 这里的api名称是getRules type为 flow

请求的地址 为localhost: 8719

接下来我们来看到客户端对该接口的处理

Client命令的处理主要在CommandCenterInitFunc 中

public class CommandCenterInitFunc implements InitFunc {@Overridepublic void init() throws Exception {CommandCenter commandCenter = CommandCenterProvider.getCommandCenter();if (commandCenter == null) {RecordLog.warn("[CommandCenterInitFunc] Cannot resolve CommandCenter");return;}commandCenter.beforeStart();commandCenter.start();RecordLog.info("[CommandCenterInit] Starting command center: "+ commandCenter.getClass().getCanonicalName());}
}

重点关注start方法 来看实现类 com.alibaba.csp.sentinel.transport.command.SimpleHttpCommandCenter#start

@Override
public void start() throws Exception {//cpu核心数int nThreads = Runtime.getRuntime().availableProcessors();this.bizExecutor = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(10),            new NamedThreadFactory("sentinel-command-center-service-executor", true),new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {CommandCenterLog.info("EventTask rejected");throw new RejectedExecutionException();}});//初始化任务Runnable serverInitTask = new Runnable() {int port;{try {port = Integer.parseInt(TransportConfig.getPort());} catch (Exception e) {port = DEFAULT_PORT;}}@Overridepublic void run() {boolean success = false;//获取serverSocketServerSocket serverSocket = getServerSocketFromBasePort(port);if (serverSocket != null) {CommandCenterLog.info("[CommandCenter] Begin listening at port " + serverSocket.getLocalPort());socketReference = serverSocket;executor.submit(new ServerThread(serverSocket));success = true;port = serverSocket.getLocalPort();} else {CommandCenterLog.info("[CommandCenter] chooses port fail, http command center will not work");}if (!success) {port = PORT_UNINITIALIZED;}TransportConfig.setRuntimePort(port);executor.shutdown();}};new Thread(serverInitTask).start();
}

存在个server初始化任务

任务中主要有几个步骤

1。创建socket连接

2。向executor线程池中提交任务 ServerThread

3。更新port 以及成功标识 success

4。关闭 executor

核心来看ServerThread的run方法

com.alibaba.csp.sentinel.transport.command.SimpleHttpCommandCenter.ServerThread#run

@Override
public void run() {while (true) {Socket socket = null;try {socket = this.serverSocket.accept();setSocketSoTimeout(socket);HttpEventTask eventTask = new HttpEventTask(socket);bizExecutor.submit(eventTask);} catch (Exception e) {CommandCenterLog.info("Server error", e);if (socket != null) {try {socket.close();} catch (Exception e1) {CommandCenterLog.info("Error when closing an opened socket", e1);}}try {// In case of infinite log.Thread.sleep(10);} catch (InterruptedException e1) {// Indicates the task should stop.break;}}}}
}

run方法中的核心逻辑

1。接收socket连接

2。提交HttpEventTask 任务

再来看HttpEventTask 任务的逻辑

com.alibaba.csp.sentinel.transport.command.http.HttpEventTask#run

@Override
public void run() {if (socket == null) {return;}PrintWriter printWriter = null;InputStream inputStream = null;try {long start = System.currentTimeMillis();inputStream = new BufferedInputStream(socket.getInputStream());OutputStream outputStream = socket.getOutputStream();printWriter = new PrintWriter(new OutputStreamWriter(outputStream, Charset.forName(SentinelConfig.charset())));String firstLine = readLine(inputStream);CommandCenterLog.info("[SimpleHttpCommandCenter] Socket income: " + firstLine+ ", addr: " + socket.getInetAddress());CommandRequest request = processQueryString(firstLine);if (firstLine.length() > 4 && StringUtil.equalsIgnoreCase("POST", firstLine.substring(0, 4))) {// Deal with post methodprocessPostRequest(inputStream, request);}// Validate the target command.String commandName = HttpCommandUtils.getTarget(request);if (StringUtil.isBlank(commandName)) {writeResponse(printWriter, StatusCode.BAD_REQUEST, INVALID_COMMAND_MESSAGE);return;}// Find the matching command handler.CommandHandler<?> commandHandler = SimpleHttpCommandCenter.getHandler(commandName);if (commandHandler != null) {CommandResponse<?> response = commandHandler.handle(request);handleResponse(response, printWriter);} else {// No matching command handler.writeResponse(printWriter, StatusCode.BAD_REQUEST, "Unknown command `" + commandName + '`');}long cost = System.currentTimeMillis() - start;CommandCenterLog.info("[SimpleHttpCommandCenter] Deal a socket task: " + firstLine+ ", address: " + socket.getInetAddress() + ", time cost: " + cost + " ms");} catch (RequestException e) {writeResponse(printWriter, e.getStatusCode(), e.getMessage());} catch (Throwable e) {CommandCenterLog.warn("[SimpleHttpCommandCenter] CommandCenter error", e);try {if (printWriter != null) {String errorMessage = SERVER_ERROR_MESSAGE;e.printStackTrace();if (!writtenHead) {writeResponse(printWriter, StatusCode.INTERNAL_SERVER_ERROR, errorMessage);} else {printWriter.println(errorMessage);}printWriter.flush();}} catch (Exception e1) {CommandCenterLog.warn("Failed to write error response", e1);}} finally {closeResource(inputStream);closeResource(printWriter);closeResource(socket);}
}

核心处理逻辑

1。读取socket 输入流 解析转化为CommandRequest

2。校验命令commandName

3。获取与命令匹配的处理器 CommandHandler

4。处理器处理该命令并写入响应

我们再来看下具体的命令处理器的匹配逻辑

首先是一个map结构 key是命令名称 值是CommandHandler 具体的命令处理器

private static final Map<String, CommandHandler> handlerMap = new ConcurrentHashMap<String, CommandHandler>();

因为我们需要排查流控为啥没有显示到控制台,dashboard console上调用请求的是getRules 的api, 所以我们来看下 getRules 对应的处理器

@CommandMapping(name = "getRules", desc = "get all active rules by type, request param: type={ruleType}")
public class FetchActiveRuleCommandHandler implements CommandHandler<String> {@Overridepublic CommandResponse<String> handle(CommandRequest request) {String type = request.getParam("type");if ("flow".equalsIgnoreCase(type)) {return CommandResponse.ofSuccess(JSON.toJSONString(FlowRuleManager.getRules()));} else if ("degrade".equalsIgnoreCase(type)) {return CommandResponse.ofSuccess(JSON.toJSONString(DegradeRuleManager.getRules()));} else if ("authority".equalsIgnoreCase(type)) {return CommandResponse.ofSuccess(JSON.toJSONString(AuthorityRuleManager.getRules()));} else if ("system".equalsIgnoreCase(type)) {return CommandResponse.ofSuccess(JSON.toJSONString(SystemRuleManager.getRules()));} else {return CommandResponse.ofFailure(new IllegalArgumentException("invalid type"));}}
}

dashboard中传入的类型 为flow 所以执行的逻辑如下

CommandResponse.ofSuccess(JSON.toJSONString(FlowRuleManager.getRules()));

由于我们使用的网关限流 使用是用 GatewayRuleManager.loadRules(rules); 进行加载规则 而不是使用的

FlowRuleManager.loadRules(rules); 那使用FlowRuleManager.getRules() 获取到的规则就是空的

所以控制台无法显示我们的规则

备注:sentinel整合gateway 中也存在一个个 获取规则的命令处理器 com.alibaba.csp.sentinel.adapter.gateway.common.command.GetGatewayRuleCommandHandler 类代码如下

@CommandMapping(name = "gateway/getRules", desc = "Fetch all gateway rules")
public class GetGatewayRuleCommandHandler implements CommandHandler<String> {@Overridepublic CommandResponse<String> handle(CommandRequest request) {return CommandResponse.ofSuccess(JSON.toJSONString(GatewayRuleManager.getRules()));}
}

所以我们使用这个命令名称即可加载我们网关中的规则

改动方案

  • 网关加载流控规则使用FlowRuleManager.loadRules(rules);
  • dashboard请求接口 使用gateway/getRules 而不是 getRules

第一个方案无法满足我们的需求,所以我们需要基本源码来改造 sentinel-dashboard 修改请求的API

本来以为到这里结束了,结果后面前端的源码中发现了问题
源码路径
sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.js
其中有段获取APP列表的方法 ,方法中会进行判断该应用是否是网关

// appAppService.getApps().success(function (data) {if (data.code === 0) {console.log(`data: ${data}`);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;// 重点在这里  处理appType Handle appTypeitem.isGateway = item.appType === 1 || item.appType === 11 || item.appType === 12;if (item.shown) {return item;}});}});

而这个是否为网关字段在 左边的菜单又会用到
业务代码位置
sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html

  <li ui-sref-active="active" ng-if="entry.isGateway"><a ui-sref="dashboard.gatewayFlow({app: entry.app})"><i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则</a></li><li ui-sref-active="active" ng-if="!entry.isGateway"><a ui-sref="dashboard.flow({app: entry.app})"><i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则</a></li>

可以看到如果是网关的话 那么就会触发dashboard.gatewayFlow路由页面 最后会跳转到
app/views/gateway/flow.html 其中会触发 flow.js 加载规则列表 方法如下

 this.queryRules = function (app, ip, port) {var param = {app: app,ip: ip,port: port};return $http({url: '/gateway/flow/list.json',params: param,method: 'GET'});};

最终调用的接口在 GatewayFlowRuleController里
com.alibaba.csp.sentinel.dashboard.controller.gateway.GatewayFlowRuleController
接口详细代码如下

@GetMapping("/list.json")
@AuthAction(AuthService.PrivilegeType.READ_RULE)
public Result<List<GatewayFlowRuleEntity>> queryFlowRules(String app, String ip, Integer port) {if (StringUtil.isEmpty(app)) {return Result.ofFail(-1, "app can't be null or empty");}if (StringUtil.isEmpty(ip)) {return Result.ofFail(-1, "ip can't be null or empty");}if (port == null) {return Result.ofFail(-1, "port can't be null");}try {List<GatewayFlowRuleEntity> rules = sentinelApiClient.fetchGatewayFlowRules(app, ip, port).get();repository.saveAll(rules);return Result.ofSuccess(rules);} catch (Throwable throwable) {logger.error("query gateway flow rules error:", throwable);return Result.ofThrowable(-1, throwable);}
}public CompletableFuture<List<GatewayFlowRuleEntity>> fetchGatewayFlowRules(String app, String ip, int port) {if (StringUtil.isBlank(ip) || port <= 0) {return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));}try {return executeCommand(ip, port, FETCH_GATEWAY_FLOW_RULE_PATH, false).thenApply(r -> {List<GatewayFlowRule> gatewayFlowRules = JSON.parseArray(r, GatewayFlowRule.class);List<GatewayFlowRuleEntity> entities = gatewayFlowRules.stream().map(rule -> GatewayFlowRuleEntity.fromGatewayFlowRule(app, ip, port, rule)).collect(Collectors.toList());return entities;});} catch (Exception ex) {logger.warn("Error when fetching gateway flow rules", ex);return AsyncUtils.newFailedFuture(ex);}
}

最终调用发起调用的地址就是 gateway/getRules

到这里就明白两个东西

  • 如果应用是网关的话 调用的获取规则列表地址接口是正确的
  • 我们这应该使用gateway应该属于网关才对。那目前来看 这个appType 应该不正确,或者是没正确被赋值

那我们接着来看这个 appType 为什么没有有被正确赋值

首先这个 appType是从接口中获取的 接口类为 app/briefinfos.json

接口地址在sentinel-dashboard项目 com.alibaba.csp.sentinel.dashboard.controller.AppController#queryAppInfos

@GetMapping("/briefinfos.json")
public Result<List<AppInfo>> queryAppInfos(HttpServletRequest request) {List<AppInfo> list = new ArrayList<>(appManagement.getBriefApps());Collections.sort(list, Comparator.comparing(AppInfo::getApp));return Result.ofSuccess(list);
}

重点是从appManagement.getBriefApps() 方法获取的 我们在看来下

com.alibaba.csp.sentinel.dashboard.discovery.AppManagement#getBriefApps

@Override
public Set<AppInfo> getBriefApps() {return machineDiscovery.getBriefApps();
}

具体的实现类是在SimpleMachineDiscovery

com.alibaba.csp.sentinel.dashboard.discovery.SimpleMachineDiscovery#getBriefApps

@Override
public Set<AppInfo> getBriefApps() {return new HashSet<>(apps.values());
}

apps 是一个成员变量 那这个值又是从哪里赋值的呢 发现只有一个地方在进行赋值

就是添加的机器的方法

com.alibaba.csp.sentinel.dashboard.discovery.SimpleMachineDiscovery#addMachine

@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()));appInfo.addMachine(machineInfo);return 1;
}

在看下具体在哪里被调用

代码如下 com.alibaba.csp.sentinel.dashboard.controller.MachineRegistryController#receiveHeartBeat

@ResponseBody
@RequestMapping("/machine")
public Result<?> receiveHeartBeat(String app,@RequestParam(value = "app_type", required = false, defaultValue = "0")Integer appType, Long version, String v, String hostname, String ip,Integer port) {if (StringUtil.isBlank(app) || app.length() > 256) {return Result.ofFail(-1, "invalid appName");}if (StringUtil.isBlank(ip) || ip.length() > 128) {return Result.ofFail(-1, "invalid ip: " + ip);}if (!InetAddressUtils.isIPv4Address(ip) && !InetAddressUtils.isIPv6Address(ip)) {return Result.ofFail(-1, "invalid ip: " + ip);}if (port == null || port < -1) {return Result.ofFail(-1, "invalid port");}if (hostname != null && hostname.length() > 256) {return Result.ofFail(-1, "hostname too long");}if (port == -1) {logger.warn("Receive heartbeat from " + ip + " but port not set yet");return Result.ofFail(-1, "your port not set yet");}String sentinelVersion = StringUtil.isBlank(v) ? "unknown" : v;version = version == null ? System.currentTimeMillis() : version;try {MachineInfo machineInfo = new MachineInfo();machineInfo.setApp(app);machineInfo.setAppType(appType);machineInfo.setHostname(hostname);machineInfo.setIp(ip);machineInfo.setPort(port);machineInfo.setHeartbeatVersion(version);machineInfo.setLastHeartbeat(System.currentTimeMillis());machineInfo.setVersion(sentinelVersion);appManagement.addMachine(machineInfo);return Result.ofSuccessMsg("success");} catch (Exception e) {logger.error("Receive heartbeat error", e);return Result.ofFail(-1, e.getMessage());}
}

那这个接口又是什么时候被调用的呢

在客户端中 维护心跳的类是 HeartbeatSenderInitFunc

其中有个 定时发送心跳的方法

@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);
}private void scheduleHeartbeatTask(/*@NonNull*/ final HeartbeatSender sender, /*@Valid*/ long interval) {pool.scheduleAtFixedRate(new Runnable() {@Overridepublic 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();

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;
}

请求地址从配置csp.sentinel.heartbeat.api.path 没有配置的话 则使用 /registry/machine

那就这个流程就通了 断点来看下具体的参数

在这里插入图片描述

在这里插入图片描述
看看这个参数哪里配置

app_type为0的话 那这个就表示是非网关应用 那还是调用接口路径为getRules 加载规则 因为我们这个属于网关环境这个肯定获取不到

在HeartbeatMessage 构造中设置了 基础的配置 ip app_type app 等

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.getAppType()

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

public static int getAppType() {return appType;
}

发现appType 是个成员变量 默认值为0 另外 SentinelConfig 在初始化的时候 会对这个值 进行修改

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

static {try {initialize();loadProps();//解析appNameresolveAppName();//解析appTyperesolveAppType();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#resolveAppType

private static void resolveAppType() {try {//配置中获取  csp.sentinel.app.typeString type = getConfig(APP_TYPE_PROP_KEY);if (type == null) {appType = APP_TYPE_COMMON;return;}appType = Integer.parseInt(type);if (appType < 0) {appType = APP_TYPE_COMMON;}} catch (Exception ex) {appType = APP_TYPE_COMMON;}
}

首先从 csp.sentinel.app.type 配置中获取 如果为空 那就用默认值- 并返回

如果不为空 直接赋值 如果小于0 则赋值给0

异常情况 使用默认值

那我们再来看下 这个配置 csp.sentinel.app.type 是在哪里进行赋值的

发现只有一个地方 是在 网关的自动配置中

com.alibaba.cloud.sentinel.gateway.scg.SentinelSCGAutoConfiguration#initAppType

private void initAppType() {System.setProperty(SentinelConfig.APP_TYPE_PROP_KEY,ConfigConstants.APP_TYPE_SCG_GATEWAY);
}

那为什么设置到System中 会生效到SentinelConfig的属性中

那是因为 在 解析APPType之前还会调用 loadProps();

private static void loadProps() {Properties properties = SentinelConfigLoader.getProperties();for (Object key : properties.keySet()) {setConfig((String) key, (String) properties.get(key));}
}

方法中 SentinelConfigLoader.getProperties(); 触发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 pathString 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);}}
}

会加载到所有的System配置 会存入到SentinelConfigLoader的 properties成员变量中

分析到这里 感觉没啥问题

断点查看 发现了问题

在这里插入图片描述
在这里插入图片描述
可以看当网关还没设置app_type时 这个sentinelConfig已经被加载,对应的SentinelConfigLoader的properties 已经被加载 这时候System中属性还未设置。所以后面网关在设置app_type后,也无法生效了。因为配置已经初始化完成。

那这个解决的办法也就明朗了

  • 项目启动前手动设置 app_type 对应的配置项为 csp.sentinel.app.type

    • 可以在项目启动类设置

      public class GatewayServiceApplication {public static void main(String[] args) {System.setProperty("csp.sentinel.dashboard.server","localhost:8080");System.setProperty("csp.sentinel.app.name","local-gateway-service");System.setProperty("csp.sentinel.app.type","11");SpringApplication.run(GatewayServiceApplication.class, args);}}
      
    • jvm参数中添加 -Dcsp.sentinel.app.type=11 启动参数

同样这个问题本质还是sentinelConfig被提前初始化,导致后面设置的配置项都无法生效了

至于为什么升级了版本后 sentinelConfig会被提前初始化。一方面等待官方的回答,另一方面我们也自行寻找。
https://github.com/alibaba/Sentinel/issues/3201


good day

相关文章:

Sentinel dashboard无法查询到应用的限流配置问题以及解决

一。问题引入 使用sentinle-dashboard控制台 项目整体升级后&#xff0c;发现控制台上无法看到流控规则了 之前的问题是无法注册上来 现在是注册上来了。结果看不到流控规则配置了。 关于注册不上来的问题&#xff0c;可以看另一篇文章 https://blog.csdn.net/a15835774652/…...

【Spring Boot】社交网站中验证用户登录的checkUser方法

public boolean checkUser(User user) {User userInDb userRepository.findByUsername(user.getUsername());if (userInDb ! null && userInDb.getPassword().equals(user.getPassword())) {return true;} else {return false;}} } 这段代码是UserService类中的checkU…...

edge浏览器进行qq截图过保爆决过程

edge浏览器进行qq截图过保解决过程 参考&#xff1a;电脑截屏曝光特别高怎么解决&#xff1f; - 知乎 问题展示 饱和度过高&#xff0c;刺眼 1. 在chrome地址栏输入chrome://flags/ 2. 在页面的搜索栏搜索force color profile 3. 在选项中选择所对应的颜色管理。&#xff08…...

【Linux】Linux在防火墙firewall中开放或删除某端口

在生产中往往是不能关闭防火墙firewall的&#xff08;以下操作是在linux中执行的&#xff09; #补充一下查看防火墙的命令 #查看防火墙状态 systemctl status firewalld #关闭防火墙 systemctl stop firewalld #重启防火墙 systemctl restart firewalld #启动防火墙 systemctl …...

C++构造函数初始化列表

构造函数的一项重要功能是对成员变量进行初始化&#xff0c;为了达到这个目的&#xff0c;可以在构造函数的函数体中对成员变量一一赋值&#xff0c;还可以采用初始化列表。 C构造函数的初始化列表使得代码更加简洁&#xff0c;请看下面的例子&#xff1a; #include <iostre…...

c语言调用mciSendString播放音乐

如下所示&#xff0c;这是一个使用c语言调用系统方法mciSendString()&#xff0c;让系统播放音乐的示例&#xff1a; baihuaxiang 代码&#xff1a; #include <graphics.h> #include <Windows.h> #include <mmsystem.h>#pragma comment(lib,"WINMM.LIB…...

Qt:qRegisterMetaType为Qt信号和槽添加自定义参数类型

背景 qt信号和槽之间的参数传递默认只能传递qt内部已有的类型&#xff0c;例如QString等&#xff0c;若我们自定义类型作为参数时&#xff0c;虽然编译不会报错&#xff0c;但运行时会提示connect无效&#xff0c;无法识别自定义的类。 此时需要我们将自定义类进行注册&#…...

ffmpeg rtp发送video和audio并播放

发送h264 video ffmpeg -re -stream_loop -1 -i h264.mp4 -vcodec h264 -f rtp rtp://127.0.0.1:5006SDP: v0 o- 0 0 IN IP4 127.0.0.1 sNo Name cIN IP4 127.0.0.1 t0 0 atool:libavformat LIBAVFORMAT_VERSION mvideo 5006 RTP/AVP 96 artpmap:96 H264/90000 afmtp:96 packe…...

CSS打字回删效果动画源码

<!DOCTYPE html> <html lang="en"> <head><meta charset...

Vue全局后置守卫

全局后置守卫 一、在 router 目录下的 index.js 文件中配置全局后置守卫。 import Vue from vue import VueRouter from vue-router Vue.use(VueRouter)import Home from ../views/Home.vue import About from ../views/About.vue import Login from ../views/Login.vueconst…...

【Go语言】基于Socket编程的P2P通信程序示例

Go语言的Socket编程实现为开发者提供了一种高效且强大的方式来实现网络通信。通过Go语言的并发模型和内置的网络库&#xff0c;如net包&#xff0c;开发者可以轻松地创建基于套接字的通信应用。Go语言的goroutine和channel机制使并发处理变得简单&#xff0c;能够轻松处理多个连…...

16、Flink 的table api与sql之连接外部系统: 读写外部系统的连接器和格式以及Elasticsearch示例(2)

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…...

Java代码优化案例2:使用HashMap代替List进行数据查找

在开发过程中&#xff0c;我们经常需要在一个集合中查找某个元素。一种常见的做法是使用List来存储数据&#xff0c;然后通过循环遍历List来查找目标元素。然而&#xff0c;当数据量较大时&#xff0c;这种做法效率较低。我们可以通过使用HashMap来优这个过程。 1. 原始代码实…...

每天一道leetcode:542. 01 矩阵(图论中等广度优先遍历)

今日份题目&#xff1a; 给定一个由 0 和 1 组成的矩阵 mat &#xff0c;请输出一个大小相同的矩阵&#xff0c;其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。 两个相邻元素间的距离为 1 。 示例1 输入&#xff1a;mat [[0,0,0],[0,1,0],[0,0,0]] 输出&#xff…...

SQL SERVER 日期函数相关内容

最近跟日期相关的内容杠上了&#xff0c;为方便自己后期查阅&#xff0c;特地做笔记。 DECLARE chanenddate datetime----截止日期转成当天的年月日尾巴 DECLARE chanbengindate datetime----开始日期转成当天的年月日0000000 截取日期的 年月日&#xff0c;字符串类型 co…...

多维时序 | MATLAB实现SCNGO-BiGRU-Attention多变量时间序列预测

多维时序 | MATLAB实现SCNGO-BiGRU-Attention多变量时间序列预测 目录 多维时序 | MATLAB实现SCNGO-BiGRU-Attention多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | MATLAB实现SCNGO-BiGRU-Attention多变量时间序列预测。 模型描述…...

从零开始学习 Java:简单易懂的入门指南之JDK8时间相关类(十八)

JDK8时间相关类 JDK8时间相关类1.1 ZoneId 时区1.2 Instant 时间戳1.3 ZoneDateTime 带时区的时间1.4DateTimeFormatter 用于时间的格式化和解析1.5LocalDate 年、月、日1.6 LocalTime 时、分、秒1.7 LocalDateTime 年、月、日、时、分、秒1.8 Duration 时间间隔&#xff08;秒…...

Spring Boot实践八--用户管理系统(下)

step3&#xff1a;多线程task 首先&#xff0c;实现两个UserService和AsyncUserService两个服务接口&#xff1a; 接口&#xff1a; package com.example.demospringboot.service;public interface UserService {void checkUserStatus(); }package com.example.demospringbo…...

C语言入门 Day_10 判断的进阶

目录 前言 1.多重判断 2.代码块 3.条件运算符 3.易错点 4.思维导图 前言 if和else能够处理两种不同的情况&#xff0c;如果&#xff08;if&#xff09;满足条件&#xff0c;我们就执行这几行代码&#xff1b;否则&#xff08;else&#xff09;的话&#xff0c;我们就执行…...

机器学习基础13-基于集成算法优化模型(基于印第安糖尿病 Pima Indians数据集)

有时提升一个模型的准确度很困难。如果你曾纠结于类似的问题&#xff0c;那 我相信你会同意我的看法。你会尝试所有曾学习过的策略和算法&#xff0c;但模型正确率并没有改善。这时你会觉得无助和困顿&#xff0c;这也是 90%的数据科学家开始放弃的时候。不过&#xff0c;这才是…...

Rancher部署k8s集群

Rancher部署 Rancher是一个开源的企业级容器管理平台。通过Rancher&#xff0c;企业再也不必自己使用一系列的开源软件去从头搭建容器服务平台。Rancher提供了在生产环境中使用的管理Docker和Kubernetes的全栈化容器部署与管理平台。 首先所有节点部署docker 安装docker 安…...

前端油猴脚本开发小技巧笔记

调试模式下&#xff0c;单击选中某dom代码&#xff0c;控制台里可以用$0访问到该dom对象。 $0.__vue___ 可以访问到该dom对应的vue对象。 jquery 对象 a,a[0]是对应的原生dom对象&#xff0c;$(原生对象) 得到对应的 jquery 对象。 jquery 选择器&#xff0c;加空格是匹配下…...

软考高级系统架构设计师系列之:搭建论文写作的万能模版

软考高级系统架构设计师系列之:搭建论文写作的万能模版 一、选择合适的模版二、论文摘要模版1.论文摘要模版一2.论文摘要模版二3.论文摘要模版三4.论文摘要模版四三、项目背景四、正文写作五、论文结尾六、论文万能模版一、选择合适的模版 选择中、大型商业项目,一般金额在2…...

多线程常见面试题

常见的锁策略 这里讨论的锁策略,不仅仅局限于 Java 乐观锁 vs 悲观锁 锁冲突: 两个线程尝试获取一把锁&#xff0c;一个线程能获取成功,另一个线程阻塞等待。 乐观锁: 预该场景中,不太会出现锁冲突的情况。后续做的工作会更少。 悲观锁: 预测该场景,非常容易出现锁冲突。后…...

Java接收json参数

JSON 并不是唯一能够实现在互联网中传输数据的方式&#xff0c;除此之外还有一种 XML 格式。JSON 和 XML 能够执行许多相同的任务&#xff0c;那么我们为什么要使用 JSON&#xff0c;而不是 XML 呢&#xff1f; 之所以使用 JSON&#xff0c;最主要的原因是 JavaScript。众所周知…...

赤峰100吨每天医院污水处理设备产品特点

赤峰100吨每天医院污水处理设备产品特点 设备调试要求&#xff1a; 1、要清洗水池内所有的赃物、杂物。 2、对水泵及空压机等需要润滑部位进行加油滑。 3、通电源&#xff0c;启动水泵&#xff0c;检查转向是否与箭头所标方向一致。用水动控制启动空压机&#xff0c;检查空压机…...

nodejs+vue+elementui健身房教练预约管理系统nt5mp

运用新技术&#xff0c;构建了以vue.js为基础的私人健身和教练预约管理信息化管理体系。根据需求分析结果进行了系统的设计&#xff0c;并将其划分为管理员&#xff0c;教练和用户三种角色&#xff1a;主要功能包括首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;教…...

视频分割合并工具说明

使用说明书&#xff1a;视频分割合并工具 欢迎使用视频生成工具&#xff01;本工具旨在帮助您将视频文件按照指定的规则分割并合并&#xff0c;以生成您所需的视频。 本程序还自带提高分辨率1920:1080&#xff0c;以及增加10db声音的功能 软件下载地址 https://github.com/c…...

2023java面试深入探析Nginx的处理流程

推荐阅读 AI文本 OCR识别最佳实践 AI Gamma一键生成PPT工具直达链接 玩转cloud Studio 在线编码神器 玩转 GPU AI绘画、AI讲话、翻译,GPU点亮AI想象空间 资源分享 史上最全文档AI绘画stablediffusion资料分享 「java、python面试题」来自UC网盘app分享&#xff0c;打开手…...

Java的锁大全

Java的锁 各种锁的类型 乐观锁 VS 悲观锁 乐观锁与悲观锁是一种广义上的概念&#xff0c;体现了看待线程同步的不同角度。在Java和数据库中都有此概念对应的实际应用。 先说概念。对于同一个数据的并发操作&#xff0c;悲观锁认为自己在使用数据的时候一定有别的线程来修改数…...

Leetcode80. 删除有序数组中的重复项 II

给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 class Solu…...

电脑显示“Operating System not found”该怎么办?

“Operating System not found”是一种常见的电脑错误提示&#xff0c;这类错误会导致你无法成功启动Windows。那么电脑显示“Operating System not found”该怎么办呢&#xff1f; 方法1. 检查硬盘 首先&#xff0c;您可以测试硬盘是否存在问题。为此&#xff0c;您可以采取以…...

简析SCTP开发指南

目录 前言一、SCTP基本概念二、SCTP开发步骤1. **环境配置**&#xff1a;2. **建立Socket**&#xff1a;3. **绑定和监听**&#xff1a;4. **接收和发送数据**&#xff1a;5. **关闭连接**&#xff1a; 三、 C语言实现SCTP3.1SCTP客户端代码&#xff1a;3.2 SCTP服务器端代码&a…...

把Android手机变成电脑摄像头

一、使用 DroidCam 使用 DroidCam&#xff0c;你可以将手机作为电脑摄像头和麦克风。一则省钱&#xff0c;二则可以在紧急情况下使用&#xff0c;比如要在电脑端参加一个紧急会议&#xff0c;但电脑却没有摄像头和麦克风。 DroidCam 的安卓端分为免费的 DroidCam 版和收费的 …...

Linux线程篇(中)

有了之前对线程的初步了解我们学习了什么是线程&#xff0c;线程的原理及其控制。这篇文章将继续讲解关于线程的内容以及重要的知识点。 线程的优缺点&#xff1a; 线程的缺点 在这里我们来谈一谈线程健壮性&#xff1a; 首先我们先思考一个问题&#xff0c;如果一个线程出现…...

深度学习优化入门:Momentum、RMSProp 和 Adam

目录 深度学习优化入门&#xff1a;Momentum、RMSProp 和 Adam 病态曲率 1牛顿法 2 Momentum:动量 3Adam 深度学习优化入门&#xff1a;Momentum、RMSProp 和 Adam 本文&#xff0c;我们讨论一个困扰神经网络训练的问题&#xff0c;病态曲率。 虽然局部极小值和鞍点会阻碍…...

LeetCode 面试题 01.09. 字符串轮转

文章目录 一、题目二、C# 题解 一、题目 字符串轮转。给定两个字符串 s1 和 s2&#xff0c;请编写代码检查 s2 是否为 s1 旋转而成&#xff08;比如&#xff0c;waterbottle 是 erbottlewat 旋转后的字符串&#xff09;。 点击此处跳转题目。 示例1: 输入&#xff1a;s1 “wa…...

系统上线安全测评需要做哪些内容?

电力信息系统、航空航天、交通运输、银行金融、地图绘画、政府官网等系统再正式上线前需要做安全测试。避免造成数据泄露从而引起的各种严重问题。 那么系统上线前需要做哪些测试内容呢&#xff1f;下面由我给大家介绍 1、安全机制检测-应用安全 身份鉴别 登录控制模块 应提供…...

vue 中 axios 的安装及使用

vue 中 axios 的安装及使用 1. axios 安装2. axios使用 1. axios 安装 首先&#xff0c;打开当前的项目终端&#xff0c;输入 npm install axios --save-dev验证是否安装成功&#xff0c;检查项目根目录下的 package.json,其中的 devDependencies 里面会多出一个axios及其版本…...

数据结构——线性数据结构(数组,链表,栈,队列)

文章目录 1. 数组2. 链表2.1. 链表简介2.2. 链表分类2.2.1. 单链表2.2.2. 循环链表2.2.3. 双向链表2.2.4. 双向循环链表 2.3. 应用场景2.4. 数组 vs 链表 3. 栈3.1. 栈简介3.2. 栈的常见应用常见应用场景3.2.1. 实现浏览器的回退和前进功能3.2.2. 检查符号是否成对出现3.2.3. 反…...

多态(C++)

多态 一、初识多态概念“登场”1>. 多态的构成条件2>. 虚函数3>. 虚函数重写&#xff08;覆盖&#xff09;4>. 虚函数重写的两个例外1. 协变 一 基类和派生类虚函数返回值类型不同2. 析构函数重写&#xff08;基类和派生类析构函数名不同&#xff09; 小结 二、延伸…...

算法leetcode|73. 矩阵置零(rust重拳出击)

文章目录 73. 矩阵置零&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a;进阶&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 73. 矩阵置零&#xff1a; 给定一个 m x n 的矩…...

axios 二次封装

axios 二次封装 基本上每一个项目开发&#xff0c;都必须要二次封装 axios。主要是为了减少重复性工作&#xff0c;不可能每一次发起新请求时&#xff0c;都要重新配置请求域名、请求头 Content-Type、Token 等信息。所以需要把公用的部分都封装成一个函数&#xff0c;每次调用…...

Rust安全之数值

文章目录 数值溢出 数值溢出 编译通过,运行失败 cargo run 1 fn main() {let mut arg std::env::args().skip(1).map(|x| x.parse::<i32>().unwrap()).next().unwrap();let m_i i32::MAX - 1;let a m_i arg;println!("{:?}", a); }thread main panicked…...

4种方法实现html 页面内锚点定位及跳转

使用scrollIntoView进行锚点定位效果 不知道你有没有遇到这样的需求&#xff1a;锚点定位&#xff1f;进入页面某个元素需要出现在可视区&#xff1f;…这一类的需求归根结底就是处理元素与可视区域的关系。我接触了很多前端小伙伴&#xff0c;实现的方式有各种各样的&#xff…...

gitlab配置备忘

版本 gitlab 14.6.2 gitlab备份上传到阿里云oss ### Backup Settings ###! Docs: https://docs.gitlab.com/omnibus/settings/backups.html# gitlab_rails[manage_backup_path] true # gitlab_rails[backup_path] "/var/opt/gitlab/backups"###! Docs: https://…...

基于Centos搭建k8s仓库

系统环境&#xff1a; Red Hat Enterprise Linux 9.1 (Plow) Kernel: Linux 5.14.0-162.6.1.el9_1.x86_64 主机名地址master192.168.19.128node01192.168.19.129node02192.168.19.130 目录 1、关闭防火墙&#xff0c;关闭SElinxu &#xff0c;开启时间同步服务 2、关…...

浅谈泛在电力物联网发展形态与技术挑战

安科瑞 华楠 摘 要&#xff1a;泛在电力物联网是当前智能电网发展的一个方向。首先&#xff0c;总结了泛在电力物联网的主要作用和价值体现&#xff1b;其次&#xff0c;从智能电网各个环节概述了物联网技术在电力领域的已有研究和应用基础&#xff1b;进而&#xff0c;构思并…...

git reset --soft 用法

git reset --soft 是 Git 命令中的一个选项&#xff0c;它用于取消之前的提交&#xff0c;并将取消的更改保留在暂存区。这允许您重新组织提交历史或将更改合并到一个新的提交中&#xff0c;而不影响暂存区和工作目录中的更改。 这个命令的语法是&#xff1a; git reset --so…...

哪些测试仪器可以用于检测静电中和设备的性能

静电设备性能测试通常需要使用一些专门的仪器来进行。以下是一些常见的静电设备性能测试仪器&#xff1a; 1. 静电电压测试仪&#xff1a;用于测量物体表面的静电电压。它通常可以测量正负电压&#xff0c;并具有高精度和快速响应的特点。 2. 静电电荷仪&#xff1a;用于测量物…...