diff --git a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/pom.xml b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/pom.xml index ca0cbd4bc..7368ff741 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/pom.xml +++ b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/pom.xml @@ -64,6 +64,13 @@ + + + com.alibaba.csp + sentinel-datasource-extension + provided + + org.springframework.boot spring-boot-configuration-processor diff --git a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/CircuitBreakerRuleChangeListener.java b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/CircuitBreakerRuleChangeListener.java index 1fed9ab35..8f9b99c3f 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/CircuitBreakerRuleChangeListener.java +++ b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/CircuitBreakerRuleChangeListener.java @@ -2,20 +2,24 @@ package com.alibaba.cloud.circuitbreaker.sentinel.feign; import java.lang.reflect.Method; import java.util.*; +import java.util.stream.Collectors; import com.alibaba.cloud.circuitbreaker.sentinel.SentinelConfigBuilder; import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.cloud.client.circuitbreaker.AbstractCircuitBreakerFactory; import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; +import org.springframework.util.CollectionUtils; /** * Sentinel circuit breaker config change listener. @@ -23,11 +27,15 @@ import org.springframework.context.ApplicationListener; * @author freeman * @date 2022/1/10 12:23 */ -public class CircuitBreakerRuleChangeListener - implements ApplicationContextAware, ApplicationListener { +public class CircuitBreakerRuleChangeListener implements ApplicationContextAware, + ApplicationListener, SmartInitializingSingleton { private static final Logger LOGGER = LoggerFactory.getLogger(CircuitBreakerRuleChangeListener.class); private SentinelFeignClientProperties properties; + /** + * properties backup, prevent rules from being updated every time the container is refreshed + */ + private SentinelFeignClientProperties propertiesBackup; private AbstractCircuitBreakerFactory circuitBreakerFactory; private ApplicationContext applicationContext; @@ -35,12 +43,20 @@ public class CircuitBreakerRuleChangeListener public void onApplicationEvent(RefreshScopeRefreshedEvent event) { ensureReady(); + // No need to update the rules + if (Objects.equals(properties, propertiesBackup)) { + return; + } + clearRules(); // rebind + configureRulesInDataSource(); configureDefault(); configureCustom(); + updateBackup(); + LOGGER.info("sentinel circuit beaker rules refreshed."); } @@ -49,6 +65,12 @@ public class CircuitBreakerRuleChangeListener this.applicationContext = applicationContext; } + @Override + public void afterSingletonsInstantiated() { + this.propertiesBackup = applicationContext + .getBean(SentinelFeignClientProperties.class).copy(); + } + private void ensureReady() { // Do not inject these beans directly, // as it will cause the bean to be initialized prematurely, @@ -85,7 +107,45 @@ public class CircuitBreakerRuleChangeListener } private void clearSentinelDegradeManager() { - DegradeRuleManager.loadRules(Collections.emptyList()); + DegradeRuleManager.loadRules(new ArrayList<>()); + } + + private void updateBackup() { + this.propertiesBackup = this.properties.copy(); + } + + private void configureRulesInDataSource() { + // TODO allow feign client rules to be configured in the data source? + // How to distinguish feign client rules from ordinary rules ? + + // Temporarily does not support configuring feign client rules in the data source. + // But need to keep the normal degrade rules. + String[] dataSourceNames = applicationContext.getBeanNamesForType(AbstractDataSource.class); + + List rules = Arrays.stream(dataSourceNames) + .map(this::getDegradeRules) + .flatMap(Collection::stream) + .distinct() + .collect(Collectors.toList()); + + DegradeRuleManager.loadRules(rules); + } + + private List getDegradeRules(String dataSourceName) { + AbstractDataSource ds = applicationContext.getBean(dataSourceName, AbstractDataSource.class); + try { + @SuppressWarnings("unchecked") + List result = (List) ds.loadConfig(); + // be careful with generic wipes + if (!CollectionUtils.isEmpty(result) + && DegradeRule.class.isAssignableFrom(result.get(0).getClass())) { + return result; + } + } + catch (Exception ignored) { + // illegal config, ignore + } + return new ArrayList<>(); } // static method diff --git a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientProperties.java b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientProperties.java index 5977942ef..cb93eb7db 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientProperties.java +++ b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientProperties.java @@ -3,9 +3,12 @@ package com.alibaba.cloud.circuitbreaker.sentinel.feign; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -52,4 +55,33 @@ public class SentinelFeignClientProperties { this.rules = rules; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SentinelFeignClientProperties that = (SentinelFeignClientProperties) o; + return refreshRules == that.refreshRules + && Objects.equals(defaultRule, that.defaultRule) + && Objects.equals(rules, that.rules); + } + + @Override + public int hashCode() { + return Objects.hash(defaultRule, refreshRules, rules); + } + + public SentinelFeignClientProperties copy() { + try { + ObjectMapper objectMapper = new ObjectMapper(); + String json = objectMapper.writeValueAsString(this); + return objectMapper.readValue(json, this.getClass()); + } catch (JsonProcessingException ignored) { + } + return new SentinelFeignClientProperties(); + } + }