diff --git a/pom.xml b/pom.xml index 84224ba..4295f4c 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,7 @@ 1.17.5 1.6.3 2.12.6.1 + 1.7.1 @@ -159,6 +160,14 @@ ${sentinel.version} provided + + + io.github.resilience4j + resilience4j-spring-boot2 + ${resilience4j-spring.version} + provided + + diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java index 34c3824..bbf5d5f 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java @@ -4,14 +4,23 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import com.alibaba.csp.sentinel.SphU; +import com.github.lianjiatech.retrofit.spring.boot.degrade.DegradeRuleRegister; +import com.github.lianjiatech.retrofit.spring.boot.degrade.Resilience4jDegradeRuleRegister; +import com.github.lianjiatech.retrofit.spring.boot.degrade.SentinelDegradeRuleRegister; +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; @@ -29,7 +38,6 @@ import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitFactoryBean; import com.github.lianjiatech.retrofit.spring.boot.core.ServiceInstanceChooser; import com.github.lianjiatech.retrofit.spring.boot.degrade.BaseResourceNameParser; -import com.github.lianjiatech.retrofit.spring.boot.degrade.RetrofitDegradeRuleInitializer; import com.github.lianjiatech.retrofit.spring.boot.interceptor.GlobalAndNetworkInterceptorFinder; import com.github.lianjiatech.retrofit.spring.boot.interceptor.ServiceInstanceChooserInterceptor; import com.github.lianjiatech.retrofit.spring.boot.retry.BaseRetryInterceptor; @@ -71,7 +79,7 @@ public GlobalAndNetworkInterceptorFinder globalAndNetworkInterceptorFinder() { @Bean @ConditionalOnMissingBean - public RetrofitConfigBean retrofitConfigBean() throws IllegalAccessException, InstantiationException { + public RetrofitConfigBean retrofitConfigBean(ObjectProvider degradeRuleRegisterObjectProvider) throws IllegalAccessException, InstantiationException { RetrofitConfigBean retrofitConfigBean = new RetrofitConfigBean(retrofitProperties, globalAndNetworkInterceptorFinder()); // Initialize the connection pool @@ -121,9 +129,28 @@ public RetrofitConfigBean retrofitConfigBean() throws IllegalAccessException, In Class resourceNameParser = degrade.getResourceNameParser(); retrofitConfigBean.setResourceNameParser(resourceNameParser.newInstance()); + // degrade register + retrofitConfigBean.setDegradeRuleRegister(degradeRuleRegisterObjectProvider.getIfAvailable()); + return retrofitConfigBean; } + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(SphU.class) + @ConditionalOnProperty(name = "retrofit.degrade.degrade-type", havingValue = "sentinel") + public DegradeRuleRegister sentinelDegradeRuleRegister(){ + return new SentinelDegradeRuleRegister(); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(CircuitBreaker.class) + @ConditionalOnProperty(name = "retrofit.degrade.degrade-type", havingValue = "resilience4j") + public DegradeRuleRegister resilience4JDegradeRuleRegister(CircuitBreakerRegistry circuitBreakerRegistry){ + return new Resilience4jDegradeRuleRegister(circuitBreakerRegistry); + } + @Bean @ConditionalOnMissingBean @@ -145,11 +172,6 @@ public void setApplicationContext(ApplicationContext applicationContext) throws this.applicationContext = applicationContext; } - @Bean - public RetrofitDegradeRuleInitializer retrofitDegradeRuleInitializer() { - return new RetrofitDegradeRuleInitializer(retrofitProperties.getDegrade()); - } - @Configuration @Import({AutoConfiguredRetrofitScannerRegistrar.class}) @ConditionalOnMissingBean(RetrofitFactoryBean.class) diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitConfigBean.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitConfigBean.java index 4f55b4f..fac4639 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitConfigBean.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitConfigBean.java @@ -4,6 +4,7 @@ import java.util.Map; import com.github.lianjiatech.retrofit.spring.boot.degrade.BaseResourceNameParser; +import com.github.lianjiatech.retrofit.spring.boot.degrade.DegradeRuleRegister; import com.github.lianjiatech.retrofit.spring.boot.interceptor.GlobalAndNetworkInterceptorFinder; import com.github.lianjiatech.retrofit.spring.boot.interceptor.GlobalInterceptor; import com.github.lianjiatech.retrofit.spring.boot.interceptor.NetworkInterceptor; @@ -37,6 +38,8 @@ public class RetrofitConfigBean { private BaseResourceNameParser resourceNameParser; + private DegradeRuleRegister degradeRuleRegister; + public RetrofitProperties getRetrofitProperties() { return retrofitProperties; } @@ -105,4 +108,12 @@ public BaseResourceNameParser getResourceNameParser() { public void setResourceNameParser(BaseResourceNameParser resourceNameParser) { this.resourceNameParser = resourceNameParser; } + + public DegradeRuleRegister getDegradeRuleRegister() { + return degradeRuleRegister; + } + + public void setDegradeRuleRegister(DegradeRuleRegister degradeRuleRegister) { + this.degradeRuleRegister = degradeRuleRegister; + } } diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java index f97c8de..8dc853a 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java @@ -6,14 +6,11 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import com.github.lianjiatech.retrofit.spring.boot.degrade.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -36,14 +33,6 @@ import com.github.lianjiatech.retrofit.spring.boot.config.LogProperty; import com.github.lianjiatech.retrofit.spring.boot.config.RetrofitConfigBean; import com.github.lianjiatech.retrofit.spring.boot.config.RetrofitProperties; -import com.github.lianjiatech.retrofit.spring.boot.degrade.BaseResourceNameParser; -import com.github.lianjiatech.retrofit.spring.boot.degrade.Degrade; -import com.github.lianjiatech.retrofit.spring.boot.degrade.DegradeStrategy; -import com.github.lianjiatech.retrofit.spring.boot.degrade.DegradeType; -import com.github.lianjiatech.retrofit.spring.boot.degrade.FallbackFactory; -import com.github.lianjiatech.retrofit.spring.boot.degrade.RetrofitDegradeRule; -import com.github.lianjiatech.retrofit.spring.boot.degrade.RetrofitDegradeRuleInitializer; -import com.github.lianjiatech.retrofit.spring.boot.degrade.SentinelDegradeInterceptor; import com.github.lianjiatech.retrofit.spring.boot.interceptor.BaseLoggingInterceptor; import com.github.lianjiatech.retrofit.spring.boot.interceptor.BasePathMatchInterceptor; import com.github.lianjiatech.retrofit.spring.boot.interceptor.ErrorDecoderInterceptor; @@ -73,7 +62,7 @@ public class RetrofitFactoryBean implements FactoryBean, EnvironmentAware, private static final Map, CallAdapter.Factory> CALL_ADAPTER_FACTORIES_CACHE = new HashMap<>(4); - private Class retrofitInterface; + private final Class retrofitInterface; private Environment environment; @@ -83,7 +72,7 @@ public class RetrofitFactoryBean implements FactoryBean, EnvironmentAware, private ApplicationContext applicationContext; - private RetrofitClient retrofitClient; + private final RetrofitClient retrofitClient; private static final Map, Converter.Factory> CONVERTER_FACTORIES_CACHE = new HashMap<>(4); @@ -122,40 +111,52 @@ public T getObject() throws Exception { ); } + /** + * 加载熔断配置,熔断粒度可控制到方法级别 + */ private void loadDegradeRules() { - // 读取熔断配置 - Method[] methods = retrofitInterface.getMethods(); - for (Method method : methods) { - if (method.isDefault()) { - continue; - } - int modifiers = method.getModifiers(); - if (Modifier.isStatic(modifiers)) { - continue; - } - // 获取熔断配置 - Degrade degrade; - if (method.isAnnotationPresent(Degrade.class)) { - degrade = method.getAnnotation(Degrade.class); - } else { - degrade = retrofitInterface.getAnnotation(Degrade.class); - } - - if (degrade == null) { - continue; - } + DegradeProperty degradeProperty = retrofitProperties.getDegrade(); + DegradeRuleRegister degradeRuleRegister = retrofitConfigBean.getDegradeRuleRegister(); - DegradeStrategy degradeStrategy = degrade.degradeStrategy(); - BaseResourceNameParser resourceNameParser = retrofitConfigBean.getResourceNameParser(); - String resourceName = resourceNameParser.parseResourceName(method, environment); + if (!degradeProperty.isEnable()) { + return; + } + Assert.notNull(degradeRuleRegister, "[DegradeRuleRegister] not found bean instance"); + Method[] methods = retrofitInterface.getMethods(); + List retrofitDegradeRuleList = Arrays.stream(methods) + .map(this::convertSentinelRule) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + degradeRuleRegister.batchRegister(retrofitDegradeRuleList); + } - RetrofitDegradeRule degradeRule = new RetrofitDegradeRule(); - degradeRule.setCount(degrade.count()); - degradeRule.setDegradeStrategy(degradeStrategy); - degradeRule.setTimeWindow(degrade.timeWindow()); - degradeRule.setResourceName(resourceName); - RetrofitDegradeRuleInitializer.addRetrofitDegradeRule(degradeRule); + /** + * 提取熔断规则,优先级为方法>类>默认 + * @param method method + * @return RetrofitDegradeRule + */ + private RetrofitDegradeRule convertSentinelRule(Method method) { + if (method.isDefault()) { + return null; } + int modifiers = method.getModifiers(); + if (Modifier.isStatic(modifiers)) { + return null; + } + // 获取熔断配置 + Degrade degrade; + if (method.isAnnotationPresent(Degrade.class)) { + degrade = method.getAnnotation(Degrade.class); + } else { + degrade = retrofitInterface.getAnnotation(Degrade.class); + } + BaseResourceNameParser resourceNameParser = retrofitConfigBean.getResourceNameParser(); + String resourceName = resourceNameParser.parseResourceName(method, environment); + RetrofitDegradeRule degradeRule = new RetrofitDegradeRule(); + degradeRule.setCount(Optional.ofNullable(degrade).map(Degrade::count).orElse(null)); + degradeRule.setTimeWindow(Optional.ofNullable(degrade).map(Degrade::timeWindow).orElse(null)); + degradeRule.setResourceName(resourceName); + return degradeRule; } /** @@ -277,26 +278,14 @@ private synchronized OkHttpClient getOkHttpClient(Class retrofitClientInterfa } // add DegradeInterceptor + // TODO 这里稍微有点问题,开启熔断则所有实例都会加熔断拦截器,但是拦截器升不生效则取决于是否配置了@Degrade注解,可不可以把这里做成有一个全局默认配置,有@Degrade则走单独配置? DegradeProperty degradeProperty = retrofitProperties.getDegrade(); if (degradeProperty.isEnable()) { - DegradeType degradeType = degradeProperty.getDegradeType(); - switch (degradeType) { - case SENTINEL: { - try { - Class.forName("com.alibaba.csp.sentinel.SphU"); - SentinelDegradeInterceptor sentinelDegradeInterceptor = new SentinelDegradeInterceptor(); - sentinelDegradeInterceptor.setEnvironment(environment); - sentinelDegradeInterceptor.setResourceNameParser(retrofitConfigBean.getResourceNameParser()); - okHttpClientBuilder.addInterceptor(sentinelDegradeInterceptor); - } catch (ClassNotFoundException e) { - logger.warn("com.alibaba.csp.sentinel not found! No SentinelDegradeInterceptor is set."); - } - break; - } - default: { - throw new IllegalArgumentException("Not currently supported! degradeType=" + degradeType); - } - } + DegradeInterceptor degradeInterceptor = new DegradeInterceptor(); + degradeInterceptor.setEnvironment(environment); + degradeInterceptor.setResourceNameParser(retrofitConfigBean.getResourceNameParser()); + degradeInterceptor.setDegradeRuleRegister(retrofitConfigBean.getDegradeRuleRegister()); + okHttpClientBuilder.addInterceptor(degradeInterceptor); } // add ServiceInstanceChooserInterceptor diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/Degrade.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/Degrade.java index febad15..bef4491 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/Degrade.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/Degrade.java @@ -3,7 +3,8 @@ import java.lang.annotation.*; /** - * @author 陈添明 + * 应仅采用异常比例模式来控制熔断,超时导致的报错应在okhttp这一层做 + * @author 陈添明 yukdawn@gmail.com */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) @@ -11,17 +12,12 @@ public @interface Degrade { /** - * RT threshold or exception ratio threshold count. + * 异常比例 */ - double count(); + float count(); /** - * Degrade recover timeout (in seconds) when degradation occurs. + * 时间窗口size,单位:秒 */ int timeWindow() default 5; - - /** - * Degrade strategy (0: average RT, 1: exception ratio). - */ - DegradeStrategy degradeStrategy() default DegradeStrategy.AVERAGE_RT; } diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/BaseDegradeInterceptor.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeInterceptor.java similarity index 72% rename from src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/BaseDegradeInterceptor.java rename to src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeInterceptor.java index 547af27..1519bbb 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/BaseDegradeInterceptor.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeInterceptor.java @@ -12,12 +12,18 @@ /** * @author 陈添明 */ -public abstract class BaseDegradeInterceptor implements Interceptor { +public class DegradeInterceptor implements Interceptor { private Environment environment; private BaseResourceNameParser resourceNameParser; + protected DegradeRuleRegister degradeRuleRegister; + + public void setDegradeRuleRegister(DegradeRuleRegister degradeRuleRegister) { + this.degradeRuleRegister = degradeRuleRegister; + } + public void setEnvironment(Environment environment) { this.environment = environment; } @@ -47,5 +53,8 @@ public Response intercept(Chain chain) throws IOException { * @throws IOException IOException * */ - protected abstract Response degradeIntercept(String resourceName, Chain chain) throws RetrofitBlockException, IOException; + protected Response degradeIntercept(String resourceName, Chain chain) throws RetrofitBlockException, IOException { + Request request = chain.request(); + return this.degradeRuleRegister.exec(resourceName, () -> chain.proceed(request)); + } } diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeRuleRegister.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeRuleRegister.java new file mode 100644 index 0000000..742260c --- /dev/null +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeRuleRegister.java @@ -0,0 +1,32 @@ +package com.github.lianjiatech.retrofit.spring.boot.degrade; + +import java.io.IOException; +import java.util.List; + +import okhttp3.Response; + +/** + * @author yukdawn@gmail.com 2022/4/5 23:14 + */ +public interface DegradeRuleRegister { + + /** + * 批量注册规则 + * @param retrofitDegradeRuleList 规则描述对象集合 + */ + void batchRegister(List retrofitDegradeRuleList); + + /** + * 使用规则代理执行目标方法 + * @param resourceName 资源名称 + * @param func 目标方法 + * @return okhttp响应 + * @throws IOException IOException + */ + Response exec(String resourceName, DegradeProxyMethod func) throws IOException; + + @FunctionalInterface + interface DegradeProxyMethod{ + R get() throws IOException; + } +} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeStrategy.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeStrategy.java deleted file mode 100644 index d33ed4c..0000000 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeStrategy.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.lianjiatech.retrofit.spring.boot.degrade; - -/** - * @author 陈添明 - */ -public enum DegradeStrategy { - - /** - * average RT - */ - AVERAGE_RT, - - /** - * exception ratio - */ - EXCEPTION_RATIO, -} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeType.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeType.java index 0b25069..fb3900a 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeType.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/DegradeType.java @@ -7,4 +7,6 @@ public enum DegradeType { SENTINEL, + RESILIENCE4J; + } diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/Resilience4jDegradeRuleRegister.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/Resilience4jDegradeRuleRegister.java new file mode 100644 index 0000000..644517a --- /dev/null +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/Resilience4jDegradeRuleRegister.java @@ -0,0 +1,89 @@ +package com.github.lianjiatech.retrofit.spring.boot.degrade; + +import java.io.IOException; +import java.time.Duration; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import io.github.resilience4j.circuitbreaker.CallNotPermittedException; +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.core.StopWatch; +import okhttp3.Response; +import org.springframework.util.CollectionUtils; + +/** + * @author yukdawn@gmail.com 2022/4/5 23:15 + */ +public class Resilience4jDegradeRuleRegister implements DegradeRuleRegister{ + + private final CircuitBreakerRegistry circuitBreakerRegistry; + + public Resilience4jDegradeRuleRegister(CircuitBreakerRegistry circuitBreakerRegistry) { + this.circuitBreakerRegistry = circuitBreakerRegistry; + } + + @Override + public void batchRegister(List ruleList) { + if (CollectionUtils.isEmpty(ruleList)){ + return; + } + for (RetrofitDegradeRule rule : ruleList) { + circuitBreakerRegistry.circuitBreaker(rule.getResourceName(), this.convert(rule)); + } + + } + + @Override + public Response exec(String resourceName, DegradeProxyMethod func) throws IOException { + CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(resourceName); + System.out.println("当前断路器状态: "+ circuitBreaker.getState()); + + final StopWatch stopWatch = StopWatch.start(); + try { + circuitBreaker.acquirePermission(); + final Response response = func.get(); + circuitBreaker.onResult(stopWatch.stop().toNanos(), TimeUnit.NANOSECONDS, response); + return response; + }catch (CallNotPermittedException e){ + throw new RetrofitBlockException(e); + } catch (Exception exception) { + circuitBreaker.onError(stopWatch.stop().toNanos(), TimeUnit.NANOSECONDS, exception); + throw exception; + } + } + + private CircuitBreakerConfig convert(RetrofitDegradeRule rule){ + // add degrade rule + CircuitBreakerConfig.Builder circuitBreakerBuilder = CircuitBreakerConfig.from(newInstanceByDefault()); + if (Objects.nonNull(rule.getCount())){ + circuitBreakerBuilder.failureRateThreshold(rule.getCount()); + } + if (Objects.nonNull(rule.getTimeWindow())){ + circuitBreakerBuilder.slidingWindowSize(rule.getTimeWindow()); + } + return circuitBreakerBuilder.build(); + } + + public CircuitBreakerConfig newInstanceByDefault(){ + // 断路器配置 + return CircuitBreakerConfig.custom() + // 滑动窗口的类型为时间窗口 + .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED) + // 时间窗口的大小为60秒 + .slidingWindowSize(60) + // 在单位时间窗口内最少需要10次调用才能开始进行统计计算 + .minimumNumberOfCalls(10) + // 在单位时间窗口内调用失败率达到60%后会启动断路器 + .failureRateThreshold(60) + // 允许断路器自动由打开状态转换为半开状态 + .enableAutomaticTransitionFromOpenToHalfOpen() + // 在半开状态下允许进行正常调用的次数 + .permittedNumberOfCallsInHalfOpenState(5) + // 断路器打开状态转换为半开状态需要等待60秒 + .waitDurationInOpenState(Duration.ofSeconds(60)) + .build(); + } +} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/RetrofitDegradeRule.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/RetrofitDegradeRule.java index 757a4d4..c78ce23 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/RetrofitDegradeRule.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/RetrofitDegradeRule.java @@ -7,11 +7,9 @@ public class RetrofitDegradeRule { private String resourceName; - private double count; + private Float count; - private int timeWindow; - - private DegradeStrategy degradeStrategy; + private Integer timeWindow; public String getResourceName() { return resourceName; @@ -21,27 +19,20 @@ public void setResourceName(String resourceName) { this.resourceName = resourceName; } - public double getCount() { + public Float getCount() { return count; } - public void setCount(double count) { + public void setCount(Float count) { this.count = count; } - public int getTimeWindow() { + public Integer getTimeWindow() { return timeWindow; } - public void setTimeWindow(int timeWindow) { + public void setTimeWindow(Integer timeWindow) { this.timeWindow = timeWindow; } - public DegradeStrategy getDegradeStrategy() { - return degradeStrategy; - } - - public void setDegradeStrategy(DegradeStrategy degradeStrategy) { - this.degradeStrategy = degradeStrategy; - } } diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/RetrofitDegradeRuleInitializer.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/RetrofitDegradeRuleInitializer.java deleted file mode 100644 index 84a5798..0000000 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/RetrofitDegradeRuleInitializer.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.github.lianjiatech.retrofit.spring.boot.degrade; - -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; -import com.github.lianjiatech.retrofit.spring.boot.config.DegradeProperty; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.ApplicationListener; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * @author 陈添明 - */ -public class RetrofitDegradeRuleInitializer implements ApplicationListener { - - private final static Logger logger = LoggerFactory.getLogger(RetrofitDegradeRuleInitializer.class); - - private final DegradeProperty degradeProperty; - - - private static List LIST = new CopyOnWriteArrayList<>(); - - public RetrofitDegradeRuleInitializer(DegradeProperty degradeProperty) { - this.degradeProperty = degradeProperty; - } - - - public static void addRetrofitDegradeRule(RetrofitDegradeRule degradeRule) { - if (degradeRule == null) { - return; - } - LIST.add(degradeRule); - } - - - /** - * Handle an application event. - * - * @param event the event to respond to - */ - @Override - public void onApplicationEvent(ApplicationReadyEvent event) { - if (!degradeProperty.isEnable()) { - return; - } - - DegradeType degradeType = degradeProperty.getDegradeType(); - switch (degradeType) { - case SENTINEL: { - try { - Class.forName("com.alibaba.csp.sentinel.SphU"); - List rules = new ArrayList<>(); - - for (RetrofitDegradeRule degradeRule : LIST) { - DegradeStrategy degradeStrategy = degradeRule.getDegradeStrategy(); - int grade; - switch (degradeStrategy) { - case AVERAGE_RT: { - grade = 0; - break; - } - case EXCEPTION_RATIO: { - grade = 1; - break; - } - default: { - throw new IllegalArgumentException("Not currently supported! degradeStrategy=" + degradeStrategy); - } - } - String resourceName = degradeRule.getResourceName(); - // add degrade rule - DegradeRule rule = new DegradeRule() - .setGrade(grade) - // Max allowed response time - .setCount(degradeRule.getCount()) - // Retry timeout (in second) - .setTimeWindow(degradeRule.getTimeWindow()); - rule.setResource(resourceName); - rules.add(rule); - } - DegradeRuleManager.loadRules(rules); - - } catch (Exception e) { - logger.warn("com.alibaba.csp.sentinel not found! No SentinelDegradeInterceptor is set."); - } - break; - } - default: { - throw new IllegalArgumentException("Not currently supported! degradeType=" + degradeType); - } - - } - } -} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/SentinelDegradeInterceptor.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/SentinelDegradeInterceptor.java deleted file mode 100644 index 8fdf478..0000000 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/SentinelDegradeInterceptor.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.lianjiatech.retrofit.spring.boot.degrade; - -import com.alibaba.csp.sentinel.*; -import com.alibaba.csp.sentinel.slots.block.BlockException; -import okhttp3.Request; -import okhttp3.Response; - -import java.io.IOException; - -/** - * @author 陈添明 - */ -public class SentinelDegradeInterceptor extends BaseDegradeInterceptor { - - /** - * 熔断拦截处理 - * - * @param chain 请求执行链 - * @return 请求响应 - * @throws RetrofitBlockException 如果触发熔断,抛出RetrofitBlockException异常! - */ - @Override - protected Response degradeIntercept(String resourceName, Chain chain) throws RetrofitBlockException, IOException { - Request request = chain.request(); - Entry entry = null; - try { - entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); - return chain.proceed(request); - } catch (BlockException e) { - throw new RetrofitBlockException(e); - } finally { - if (entry != null) { - entry.exit(); - } - } - } -} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/SentinelDegradeRuleRegister.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/SentinelDegradeRuleRegister.java new file mode 100644 index 0000000..c1935cb --- /dev/null +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/degrade/SentinelDegradeRuleRegister.java @@ -0,0 +1,69 @@ +package com.github.lianjiatech.retrofit.spring.boot.degrade; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.ResourceTypeConstants; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import okhttp3.Response; +import org.springframework.util.CollectionUtils; + +/** + * @author yukdawn@gmail.com 2022/4/5 23:15 + */ +public class SentinelDegradeRuleRegister implements DegradeRuleRegister{ + + @Override + public void batchRegister(List retrofitDegradeRuleList) { + if (CollectionUtils.isEmpty(retrofitDegradeRuleList)){ + return; + } + DegradeRuleManager.loadRules(retrofitDegradeRuleList.stream().map(this::convert).collect(Collectors.toList())); + } + + @Override + public Response exec(String resourceName, DegradeProxyMethod func) throws IOException { + Entry entry = null; + try { + entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); + return func.get(); + } catch (BlockException e) { + throw new RetrofitBlockException(e); + } finally { + if (entry != null) { + entry.exit(); + } + } + } + + private DegradeRule convert(RetrofitDegradeRule retrofitDegradeRule){ + // add degrade rule + DegradeRule rule = defaultRuleNewInstance(); + if (Objects.nonNull(retrofitDegradeRule.getTimeWindow())){ + rule.setTimeWindow(retrofitDegradeRule.getTimeWindow()); + } + rule.setCount(rule.getTimeWindow() * + Optional.ofNullable(retrofitDegradeRule.getCount()).orElse(0.6F)); + rule.setResource(retrofitDegradeRule.getResourceName()); + return rule; + } + + public DegradeRule defaultRuleNewInstance(){ + return new DegradeRule() + // 使用异常数量策略,做个中转实现异常比例,异常比例策略在sentinel的实现为每秒检测,不是根据时间窗口来 + .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) + // 异常比例 + .setCount(60*0.6) + // 时间窗口 + .setTimeWindow(60); + } +} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/retry/DefaultRetryInterceptor.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/retry/DefaultRetryInterceptor.java index f99df58..5bc0a11 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/retry/DefaultRetryInterceptor.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/retry/DefaultRetryInterceptor.java @@ -42,6 +42,7 @@ protected Response retryIntercept(int maxRetries, int intervalMs, RetryRule[] re if (shouldThrowEx(retryRuleSet, e)) { throw new RuntimeException(e); } else { + // TODO 这里应该还把最后一次的原始错误也要抛出来 if (!retryStrategy.shouldRetry()) { // 最后一次还没成功,抛出异常 throw new RuntimeException("Retry Failed: Total " + maxRetries diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/RetrofitStarterTest.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/RetrofitStarterTest.java index 8af32c7..3b4465b 100644 --- a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/RetrofitStarterTest.java +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/RetrofitStarterTest.java @@ -8,7 +8,10 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; +import com.github.lianjiatech.retrofit.spring.boot.test.http.HttpApi4; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -56,6 +59,9 @@ public class RetrofitStarterTest { @Autowired private HttpApi3 httpApi3; + @Autowired + private HttpApi4 httpApi4; + private static final ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .setSerializationInclusion(JsonInclude.Include.NON_NULL); @@ -417,4 +423,32 @@ public void testBoolean() { System.out.println(apiBoolean); } + @Test + public void testDegrade() { + IntStream.range(0, 100).parallel().forEach((i) ->{ + try { + Person mockPerson = new Person().setId(1L) + .setName("test") + .setAge(10); + Result mockResult = new Result<>() + .setCode(0) + .setMsg("ok") + .setData(mockPerson); + MockResponse response = new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/text; charset=utf-8") + .addHeader("Cache-Control", "no-cache") + .setBody(objectMapper.writeValueAsString(mockResult)) + .setHeadersDelay(5, TimeUnit.SECONDS); + server.enqueue(response); + System.out.println(httpApi4.getPerson(2L).getCode()); + }catch (Exception e){ + System.out.println("抛出异常:" + e.getMessage()); + }finally { + System.out.println("当前请求轮次: "+ (i+1)); + } + }); + + } + } diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/HttpApi4.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/HttpApi4.java new file mode 100644 index 0000000..e6b12b8 --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/HttpApi4.java @@ -0,0 +1,52 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.http; + +import com.github.lianjiatech.retrofit.spring.boot.annotation.RetrofitClient; +import com.github.lianjiatech.retrofit.spring.boot.degrade.Degrade; +import com.github.lianjiatech.retrofit.spring.boot.degrade.FallbackFactory; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import retrofit2.http.GET; +import retrofit2.http.Query; + +/** + * @author 陈添明 + */ +@RetrofitClient(baseUrl = "${test.baseUrl}", fallbackFactory = HttpApi4.HttpDegradeFallbackFactory.class) +@Degrade(count = 0.5F) +public interface HttpApi4 { + + /** + * . + * + * @param id . + * @return . + */ + @GET("degrade/person") + Result getPerson(@Query("id") Long id); + + @Service + public class HttpDegradeFallbackFactory implements FallbackFactory { + Logger log = LoggerFactory.getLogger(HttpDegradeFallbackFactory.class); + /** + * Returns an instance of the fallback appropriate for the given cause + * + * @param cause fallback cause + * @return 实现了retrofit接口的实例。an instance that implements the retrofit interface. + */ + @Override + public HttpApi4 create(Throwable cause) { + log.error("触发熔断了! ", cause.getMessage(), cause); + return id -> { + Result fallback = new Result<>(); + fallback.setCode(100) + .setMsg("fallback") + .setData(new Person()); + return fallback; + }; + } + } + +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 70495cb..3115af8 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -54,7 +54,7 @@ retrofit: # 是否启用熔断降级 enable: true # 熔断降级实现方式 - degrade-type: sentinel + degrade-type: resilience4j # 熔断资源名称解析器 resource-name-parser: com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser # 全局连接超时时间