由一个开发问题,引发对alibab-sentinel看源码(记录过程-上)

如果对解决思路不感兴趣,可以直接看记录过程-下。(本篇内含从sentinel-dashboard调用sentinel-core的解析)

前提

近日,某初级程序员在做网关限流的时候,用到了以前并不怎么用到过的sentinel。ok,代码简单使用:(为了切入重点,就不啰嗦那么多怎么引入包了,我用的是sentinel-core)

        List 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);

我的需求:流控规则放在内存中,需要给出接口动态更新。

问题出在哪了呢?问题出在了最后一步,需要动态更新。更新,也就直接指向了 loadRules加载规则这一步

FlowRuleManager.loadRules(flowRules);

对于没有经验的程序员(我)一看,哇?他怎么要load一个列表,那岂不是之前所有的规则要被覆盖,那会不会出现这样的问题

如果再次调用loadRules,会不会影响之前已经load的rules的窗口。 比如说第一次load了个名为“a”的100qps的规则,如果"a"资源在500ms内请求了100次。这时候我又load了一下名为“a”的规则,这时候这个“a"在前500ms还能再请求吗?

 虽然当时写了测试用例,不会有影响,但是没有证据证明,我还是很虚(show me)的。

看FlowRuleManager.loadRules(flowRules)

 我的第一步,看了一下这个方法

public static void loadRules(List 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 listener : listeners) {
            listener.configUpdate(newValue);
        }
        return true;
    }

 注意,这里有有一个isEqual的方法,如果你两次放入的是同一个list对象,则第二次返回false。主要看进行了listener的更新,跟着debug走到了FlowRuleManager::configUpdate

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

继续往下走,一直点这个build方法进入方法到FlowRuleUtil::buildFlowRuleMap(List list, Function groupFunction,Predicate filter, boolean shouldSort)

public static  Map> buildFlowRuleMap(List list, Function groupFunction,
                                                              Predicate filter, boolean shouldSort) {
        Map> newRuleMap = new ConcurrentHashMap<>();
        if (list == null || list.isEmpty()) {
            return newRuleMap;
        }
        Map> 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 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 comparator = new FlowRuleComparator();
        for (Entry> entries : tmpMap.entrySet()) {
            List rules = new ArrayList<>(entries.getValue());
            if (shouldSort) {
                // Sort the rules.
                Collections.sort(rules, comparator);
            }
            newRuleMap.put(entries.getKey(), rules);
        }

        return newRuleMap;
    }

直接看return,我一看,返回了一个新的ruleMap,相当于前边定义的rule是全新的,全被覆盖掉了,我心想,完蛋,这也不能证明上边提出的可能发生的问题不会发生啊,芭比Q。

看sentinel-dashboard怎么做

我寻思,这不该啊,一定有一个方法可以动态更新规则而不影响之前已经写好的规则(我以为FlowRuleManager.loadRules(flowRules)会影响)。我突然想到,sentinel不还有个服务端嘛,它不就是可以动态更新客户端流控规则?说干就干,找到下载sentinel-dashboard,运行查看,添加一个限流规则(这里要注意dashboard的加载属于懒加载,要请求一下接口才会显示出来我们的项目)

由一个开发问题,引发对alibab-sentinel看源码(记录过程-上)_第1张图片

 接着浏览器按F12查看请求的是哪个接口,如下/v1/flow/rule

 紧接着在sentinel-dashboard项目中找到接口,看他做了什么

public Result apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
        Result 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());
        }
    }

 上边一扫而过,直接看到publishRules。代码比较清晰,无脑往下,往return里边点就行了。最后走到了SentinelApiClient::executeCommand

    private CompletableFuture executeCommand(HttpUriRequest request) {
        CompletableFuture future = new CompletableFuture<>();
        httpClient.execute(request, new FutureCallback() {
            @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;
    }

主要是调用了一个httpclient的请求以更改客户端sentinel-core中的流控规则(我感觉我离找到‘无伤’动态修改规则越来越近了)。接下来找到服务端请求客户端的接口为/setRules

由一个开发问题,引发对alibab-sentinel看源码(记录过程-上)_第2张图片

 接下来找到sentinel-core中的ModifyRulesCommandHandler::handle(怀着激动的心情,我离真相越来越近了,到底是用什么方法调用的!)

由一个开发问题,引发对alibab-sentinel看源码(记录过程-上)_第3张图片

 然后我定睛一看!我丢,大意了(狙击手都打不住的那个男人)。竟然还是你

FlowRuleManager.loadRules(flowRules);

FlowRuleManager.loadRules(flowRules);

FlowRuleManager.loadRules(flowRules);

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

由一个开发问题,引发对alibab-sentinel看源码(记录过程-上)_第4张图片

 未完待续………………

你可能感兴趣的:(源码,sentinel,java)