A development problem triggered to look at the source code of alibab-sentinel (recording process-on)

If you are not interested in the solution idea, you can directly see the recording process-down . (This article contains the analysis of calling sentinel-core from sentinel-dashboard)

premise

Recently, a junior programmer used sentinel, which was not used much before, when doing gateway current limiting. ok, the code is simple to use: (in order to get to the point, I won’t talk so much about how to import packages, I use sentinel-core)

        List<FlowRule> flowRules = new ArrayList<>();
        FlowRule flowRule = new FlowRule();
        // 设置受保护的资源
        flowRule.setResource(RESOURCE_NAME);
        // 设置流控规则 QPS
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置受保护的资源阈值
        // Set limit QPS to 1.
        flowRule.setCount(1);
        flowRules.add(flowRule);
        // 加载配置好的规则
        FlowRuleManager.loadRules(flowRules);

My requirement: flow control rules are stored in memory, and the interface needs to be updated dynamically.

What is the problem? The problem lies in the last step, which requires dynamic updates. Update, it also directly points to the step of loadRules loading rules

FlowRuleManager.loadRules(flowRules);

For an inexperienced programmer (me), take a look, wow? How does he want to load a list? Wouldn't all the previous rules be overwritten? Will there be such a problem ?

If loadRules is called again, will it affect the windows of the rules that have been loaded before? For example, if a 100qps rule named "a" is loaded for the first time, if the "a" resource is requested 100 times within 500ms. At this time, I loaded the rule named "a". At this time, can this "a" request again in the first 500ms?

 Although I wrote test cases at that time, it would not have any impact, but there is no evidence to prove that I am still very hypocritical (show me).

See FlowRuleManager.loadRules(flowRules)

 My first step, looked at this method

public static void loadRules(List<FlowRule> rules) {
        currentProperty.updateValue(rules);
    }

 DynamicSentinelProperty::updateValue

public boolean updateValue(T newValue) {
        if (isEqual(value, newValue)) {
            return false;
        }
        RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue);

        value = newValue;
        for (PropertyListener<T> listener : listeners) {
            listener.configUpdate(newValue);
        }
        return true;
    }

 Note that there is an isEqual method. If you put the same list object twice, it will return false for the second time . It mainly depends on the update of the listener, followed by debug to FlowRuleManager::configUpdate

public synchronized void configUpdate(List<FlowRule> value) {
            Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(value);
            if (rules != null) {
                flowRules = rules;
            }
            RecordLog.info("[FlowRuleManager] Flow rules received: {}", rules);
        }

Continue to go down, click this build method to enter the method to FlowRuleUtil::buildFlowRuleMap(List<FlowRule> list, Function<FlowRule, K> groupFunction, Predicate<FlowRule> filter, boolean shouldSort)

public static <K> Map<K, List<FlowRule>> buildFlowRuleMap(List<FlowRule> list, Function<FlowRule, K> groupFunction,
                                                              Predicate<FlowRule> filter, boolean shouldSort) {
        Map<K, List<FlowRule>> newRuleMap = new ConcurrentHashMap<>();
        if (list == null || list.isEmpty()) {
            return newRuleMap;
        }
        Map<K, Set<FlowRule>> tmpMap = new ConcurrentHashMap<>();

        for (FlowRule rule : list) {
            if (!isValidRule(rule)) {
                RecordLog.warn("[FlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule);
                continue;
            }
            if (filter != null && !filter.test(rule)) {
                continue;
            }
            if (StringUtil.isBlank(rule.getLimitApp())) {
                rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
            }
            TrafficShapingController rater = generateRater(rule);
            rule.setRater(rater);

            K key = groupFunction.apply(rule);
            if (key == null) {
                continue;
            }
            Set<FlowRule> flowRules = tmpMap.get(key);

            if (flowRules == null) {
                // Use hash set here to remove duplicate rules.
                flowRules = new HashSet<>();
                tmpMap.put(key, flowRules);
            }

            flowRules.add(rule);
        }
        Comparator<FlowRule> comparator = new FlowRuleComparator();
        for (Entry<K, Set<FlowRule>> entries : tmpMap.entrySet()) {
            List<FlowRule> rules = new ArrayList<>(entries.getValue());
            if (shouldSort) {
                // Sort the rules.
                Collections.sort(rules, comparator);
            }
            newRuleMap.put(entries.getKey(), rules);
        }

        return newRuleMap;
    }

Looking directly at the return, I saw that a new ruleMap was returned, which is equivalent to the fact that the rules defined earlier are brand new and completely overwritten. I thought to myself, it’s over . It happened, Barbie Q.

See how sentinel-dashboard does it

I thought, this shouldn't be, there must be a way to dynamically update the rules without affecting the previously written rules ( I thought FlowRuleManager.loadRules(flowRules) would affect ). It suddenly occurred to me that Sentinel also has a server. Doesn’t it just dynamically update the client’s flow control rules? Just do it, find and download sentinel-dashboard, run it to check, and add a current limiting rule (here, it should be noted that the loading of dashboard is lazy loading, and our project will be displayed only after requesting the interface)

 Then the browser presses F12 to check which interface is requested, as follows /v1/flow/rule

 Then find the interface in the sentinel-dashboard project and see what he did

public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
        Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
        if (checkResult != null) {
            return checkResult;
        }
        entity.setId(null);
        Date date = new Date();
        entity.setGmtCreate(date);
        entity.setGmtModified(date);
        entity.setLimitApp(entity.getLimitApp().trim());
        entity.setResource(entity.getResource().trim());
        try {
            entity = repository.save(entity);

            publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(5000, TimeUnit.MILLISECONDS);
            return Result.ofSuccess(entity);
        } catch (Throwable t) {
            Throwable e = t instanceof ExecutionException ? t.getCause() : t;
            logger.error("Failed to add new flow rule, app={}, ip={}", entity.getApp(), entity.getIp(), e);
            return Result.ofFail(-1, e.getMessage());
        }
    }

 Scan the top and see publishRules directly. The code is relatively clear, just go down without thinking, just click inside the return. Finally came to SentinelApiClient::executeCommand

    private CompletableFuture<String> executeCommand(HttpUriRequest request) {
        CompletableFuture<String> future = new CompletableFuture<>();
        httpClient.execute(request, new FutureCallback<HttpResponse>() {
            @Override
            public void completed(final HttpResponse response) {
                int statusCode = response.getStatusLine().getStatusCode();
                try {
                    String value = getBody(response);
                    if (isSuccess(statusCode)) {
                        future.complete(value);
                    } else {
                        if (isCommandNotFound(statusCode, value)) {
                            future.completeExceptionally(new CommandNotFoundException(request.getURI().getPath()));
                        } else {
                            future.completeExceptionally(new CommandFailedException(value));
                        }
                    }

                } catch (Exception ex) {
                    future.completeExceptionally(ex);
                    logger.error("HTTP request failed: {}", request.getURI().toString(), ex);
                }
            }

            @Override
            public void failed(final Exception ex) {
                future.completeExceptionally(ex);
                logger.error("HTTP request failed: {}", request.getURI().toString(), ex);
            }

            @Override
            public void cancelled() {
                future.complete(null);
            }
        });
        return future;
    }

It is mainly to call an httpclient request to change the flow control rules in the client sentinel-core (I feel that I am getting closer to finding a 'harmless' dynamic modification rule). Next, find the interface that the server requests the client to be /setRules

 Next, find the ModifyRulesCommandHandler::handle in sentinel-core ( with excitement, I am getting closer to the truth, what method is used to call it! )

 Then I took a closer look! I lost it and was careless (the man who couldn't even be hit by a sniper). it's still you

FlowRuleManager.loadRules(flowRules);

FlowRuleManager.loadRules(flowRules);

FlowRuleManager.loadRules(flowRules);

!!!!!!!!!!!!!!!!!!

 To be continued……………

Guess you like

Origin blog.csdn.net/wai_58934/article/details/127337899