All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tencent.polaris.plugins.circuitbreaker.composite.ResourceHealthChecker Maven / Gradle / Ivy

/*
 * Tencent is pleased to support the open source community by making Polaris available.
 *
 * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
 *
 * Licensed under the BSD 3-Clause License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://opensource.org/licenses/BSD-3-Clause
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.tencent.polaris.plugins.circuitbreaker.composite;

import static com.tencent.polaris.logging.LoggingConsts.LOGGING_HEALTHCHECK_EVENT;
import static com.tencent.polaris.plugins.circuitbreaker.composite.CircuitBreakerRuleContainer.compareService;
import static com.tencent.polaris.plugins.circuitbreaker.composite.CircuitBreakerRuleContainer.compareSingleValue;
import static com.tencent.polaris.plugins.circuitbreaker.composite.MatchUtils.matchMethod;
import static com.tencent.polaris.plugins.circuitbreaker.composite.MatchUtils.matchService;

import com.tencent.polaris.api.plugin.cache.FlowCache;
import com.tencent.polaris.api.plugin.circuitbreaker.ResourceStat;
import com.tencent.polaris.api.plugin.circuitbreaker.entity.InstanceResource;
import com.tencent.polaris.api.plugin.circuitbreaker.entity.Resource;
import com.tencent.polaris.api.plugin.detect.HealthChecker;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.DetectResult;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.utils.RuleUtils;
import com.tencent.polaris.client.pojo.Node;
import com.tencent.polaris.logging.LoggerFactory;
import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto.Level;
import com.tencent.polaris.specification.api.v1.fault.tolerance.FaultDetectorProto.FaultDetectRule;
import com.tencent.polaris.specification.api.v1.fault.tolerance.FaultDetectorProto.FaultDetectRule.DestinationService;
import com.tencent.polaris.specification.api.v1.fault.tolerance.FaultDetectorProto.FaultDetectRule.Protocol;
import com.tencent.polaris.specification.api.v1.fault.tolerance.FaultDetectorProto.FaultDetector;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.regex.Pattern;
import org.slf4j.Logger;

public class ResourceHealthChecker {

    private static final Logger HC_EVENT_LOG = LoggerFactory.getLogger(LOGGING_HEALTHCHECK_EVENT);

    private static final Logger LOG = LoggerFactory.getLogger(ResourceHealthChecker.class);

    private static final int DEFAULT_CHECK_INTERVAL = 10;

    private final Resource resource;

    private final FaultDetector faultDetector;

    private final ScheduledExecutorService checkScheduler;

    private final AtomicBoolean stopped = new AtomicBoolean(false);

    private final Map healthCheckers;

    private final PolarisCircuitBreaker polarisCircuitBreaker;

    private final Function regexToPattern;

    private final List> futures = new ArrayList<>();

    private final Map instances = new ConcurrentHashMap<>();

    public ResourceHealthChecker(Resource resource, FaultDetector faultDetector,
            PolarisCircuitBreaker polarisCircuitBreaker) {
        this.resource = resource;
        this.faultDetector = faultDetector;
        this.regexToPattern = regex -> {
            FlowCache flowCache = polarisCircuitBreaker.getExtensions().getFlowCache();
            return flowCache.loadOrStoreCompiledRegex(regex);
        };
        this.checkScheduler = polarisCircuitBreaker.getHealthCheckExecutors();
        this.healthCheckers = polarisCircuitBreaker.getHealthCheckers();
        this.polarisCircuitBreaker = polarisCircuitBreaker;
        if (resource instanceof InstanceResource) {
            addInstance((InstanceResource) resource, false);
        }
        start();
    }

    public void addInstance(InstanceResource instanceResource, boolean record) {
        ProtocolInstance protocolInstance = instances.get(instanceResource.getNode());
        if (null == protocolInstance) {
            instances.put(instanceResource.getNode(),
                    new ProtocolInstance(HealthCheckUtils.parseProtocol(instanceResource.getProtocol()),
                            instanceResource));
            return;
        }
        if (record) {
            protocolInstance.doReport();
        }
    }

    private static List sortFaultDetectRules(List rules) {
        List outRules = new ArrayList<>(rules);
        outRules.sort(new Comparator() {
            @Override
            public int compare(FaultDetectRule rule1, FaultDetectRule rule2) {
                // 1. compare destination service
                DestinationService targetService1 = rule1.getTargetService();
                String destNamespace1 = targetService1.getNamespace();
                String destService1 = targetService1.getService();
                String destMethod1 = targetService1.getMethod().getValue().getValue();

                DestinationService targetService2 = rule2.getTargetService();
                String destNamespace2 = targetService2.getNamespace();
                String destService2 = targetService2.getService();
                String destMethod2 = targetService2.getMethod().getValue().getValue();

                int svcResult = compareService(destNamespace1, destService1, destNamespace2, destService2);
                if (svcResult != 0) {
                    return svcResult;
                }
                return compareSingleValue(destMethod1, destMethod2);
            }
        });
        return outRules;
    }

    public static Map selectFaultDetectRules(Resource resource,
            FaultDetector faultDetector, Function regexToPattern) {
        List sortedRules = sortFaultDetectRules(faultDetector.getRulesList());
        Map out = new HashMap<>();
        for (FaultDetectRule sortedRule : sortedRules) {
            DestinationService targetService = sortedRule.getTargetService();
            if (!matchService(resource.getService(), targetService.getNamespace(), targetService.getService())) {
                continue;
            }
            if (resource.getLevel() == Level.METHOD) {
                if (!matchMethod(resource, targetService.getMethod(), regexToPattern)) {
                    continue;
                }
            } else {
                // only match empty method rules
                if (!RuleUtils.isMatchAllValue(targetService.getMethod())) {
                    continue;
                }
            }
            if (!out.containsKey(sortedRule.getProtocol().name())) {
                out.put(sortedRule.getProtocol().name(), sortedRule);
            }
        }
        return out;
    }

    private Instance createDefaultInstance(String host, int port) {
        DefaultInstance instance = new DefaultInstance();
        instance.setHost(host);
        instance.setPort(port);
        return instance;
    }

    private Runnable createCheckTask(Protocol protocol, FaultDetectRule faultDetectRule) {
        return () -> {
            if (stopped.get()) {
                return;
            }
            checkResource(protocol, faultDetectRule);
        };
    }

    private void checkResource(Protocol protocol, FaultDetectRule faultDetectRule) {
        int port = faultDetectRule.getPort();
        if (port > 0) {
            Set hosts = new HashSet<>();
            for (Map.Entry entry : instances.entrySet()) {
                Node instance = entry.getKey();
                if (!hosts.contains(instance.getHost())) {
                    hosts.add(instance.getHost());
                    boolean success = doCheck(createDefaultInstance(instance.getHost(), port), protocol, faultDetectRule);
                    entry.getValue().checkSuccess.set(success);
                }
            }
        } else {
            for (Map.Entry entry : instances.entrySet()) {
                Protocol currentProtocol = entry.getValue().getProtocol();
                if (currentProtocol == Protocol.UNKNOWN || protocol == currentProtocol) {
                    InstanceResource instance = entry.getValue().getInstanceResource();
                    boolean success = doCheck(
                            createDefaultInstance(instance.getHost(), instance.getPort()), protocol, faultDetectRule);
                    entry.getValue().checkSuccess.set(success);
                }
            }
        }
    }

    private void start() {
        Map protocol2Rules = selectFaultDetectRules(resource, faultDetector, regexToPattern);
        for (Map.Entry entry : protocol2Rules.entrySet()) {
            FaultDetectRule faultDetectRule = entry.getValue();
            Runnable checkTask = createCheckTask(Protocol.valueOf(entry.getKey()), entry.getValue());
            int interval = DEFAULT_CHECK_INTERVAL;
            if (faultDetectRule.getInterval() > 0) {
                interval = faultDetectRule.getInterval();
            }
            LOG.info("schedule task: resource {}, protocol {}, interval {}, rule {}", resource, entry.getKey(),
                    interval, faultDetectRule.getName());
            ScheduledFuture future = checkScheduler
                    .scheduleWithFixedDelay(checkTask, interval, interval, TimeUnit.SECONDS);
            futures.add(future);
        }
        if (resource.getLevel() != Level.INSTANCE) {
            long checkPeriod = polarisCircuitBreaker.getCheckPeriod();
            LOG.info("schedule expire task: resource {}, interval {}", resource, checkPeriod);
            ScheduledFuture future = checkScheduler.scheduleWithFixedDelay(new Runnable() {
                @Override
                public void run() {
                    cleanInstances();
                }
            }, checkPeriod, checkPeriod, TimeUnit.MILLISECONDS);
            futures.add(future);
        }
    }

    private boolean doCheck(Instance instance, Protocol protocol, FaultDetectRule faultDetectRule) {
        HealthChecker healthChecker = healthCheckers.get(protocol.name().toLowerCase());
        if (null == healthChecker) {
            HC_EVENT_LOG
                    .info("plugin not found, skip health check for instance {}:{}, resource {}, protocol {}",
                            instance.getHost(), instance.getPort(), resource, protocol);
            return false;
        }
        DetectResult detectResult = healthChecker.detectInstance(instance, faultDetectRule);
        ResourceStat resourceStat = new ResourceStat(resource, detectResult.getStatusCode(), detectResult.getDelay(),
                detectResult.getRetStatus());
        HC_EVENT_LOG
                .info("health check for instance {}:{}, resource {}, protocol {}, result: code {}, delay {}ms, status {}",
                        instance.getHost(), instance.getPort(), resource, protocol, detectResult.getStatusCode(),
                        detectResult.getDelay(), detectResult.getRetStatus());
        polarisCircuitBreaker.doReport(resourceStat, false);
        return resourceStat.getRetStatus() == RetStatus.RetSuccess;
    }

    public void cleanInstances() {
        long curTimeMilli = System.currentTimeMillis();
        long expireIntervalMilli = polarisCircuitBreaker.getHealthCheckInstanceExpireInterval();
        for (Map.Entry entry : instances.entrySet()) {
            ProtocolInstance protocolInstance = entry.getValue();
            long lastReportMilli = protocolInstance.getLastReportMilli();
            Node node = entry.getKey();
            if (!protocolInstance.isCheckSuccess() && curTimeMilli - lastReportMilli >= expireIntervalMilli) {
                instances.remove(node);
                HC_EVENT_LOG
                        .info("clean instance from health check tasks, resource {}, expired node {}, lastReportMilli {}",
                                resource, node, lastReportMilli);
            }
        }
    }

    public void stop() {
        LOG.info("health checker for resource {} has stopped", resource);
        stopped.set(true);
        for (ScheduledFuture future : futures) {
            future.cancel(true);
        }
    }

    public FaultDetector getFaultDetector() {
        return faultDetector;
    }

    private static class ProtocolInstance {

        final Protocol protocol;

        final InstanceResource instanceResource;

        final AtomicLong lastReportMilli = new AtomicLong(0);

        final AtomicBoolean checkSuccess = new AtomicBoolean(true);

        ProtocolInstance(
                Protocol protocol, InstanceResource instanceResource) {
            this.protocol = protocol;
            this.instanceResource = instanceResource;
            lastReportMilli.set(System.currentTimeMillis());
        }

        Protocol getProtocol() {
            return protocol;
        }

        InstanceResource getInstanceResource() {
            return instanceResource;
        }

        public long getLastReportMilli() {
            return lastReportMilli.get();
        }

        void doReport() {
            lastReportMilli.set(System.currentTimeMillis());
        }

        boolean isCheckSuccess() {return checkSuccess.get();}
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy