From 8bb09f1e25b30cb4a4ac30b963855953fbbd1965 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Mon, 1 Aug 2022 21:00:54 +0800 Subject: [PATCH] Support ribbon service-level rule customization --- .../PolarisLoadBalancerCompositeRule.java | 21 ++- ...alancerCompositeRuleBeanPostProcessor.java | 65 ++++++++ .../router/config/RibbonConfiguration.java | 18 +-- .../PolarisLoadBalancerCompositeRuleTest.java | 24 +-- ...cerCompositeRuleBeanPostProcessorTest.java | 147 ++++++++++++++++++ 5 files changed, 244 insertions(+), 31 deletions(-) create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/beanprocessor/PolarisLoadBalancerCompositeRuleBeanPostProcessor.java create mode 100644 spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/beanprocessor/PolarisLoadBalancerCompositeRuleBeanPostProcessorTest.java diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java index 752ccce00..4bab20194 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java @@ -21,6 +21,7 @@ package com.tencent.cloud.polaris.router; import java.util.ArrayList; import java.util.List; +import com.google.common.annotations.VisibleForTesting; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.AvailabilityFilteringRule; @@ -85,14 +86,21 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule { PolarisLoadBalancerProperties polarisLoadBalancerProperties, IClientConfig iClientConfig, List requestInterceptors, - List responseInterceptors) { + List responseInterceptors, + AbstractLoadBalancerRule delegate) { this.routerAPI = routerAPI; this.loadBalancerProperties = polarisLoadBalancerProperties; this.requestInterceptors = requestInterceptors; this.responseInterceptors = responseInterceptors; - delegateRule = getRule(); - delegateRule.initWithNiwsConfig(iClientConfig); + AbstractLoadBalancerRule loadBalancerRule = getRule(); + if (loadBalancerRule != null) { + delegateRule = loadBalancerRule; + delegateRule.initWithNiwsConfig(iClientConfig); + } + else { + delegateRule = delegate; + } } @Override @@ -176,7 +184,7 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule { public AbstractLoadBalancerRule getRule() { String loadBalanceStrategy = loadBalancerProperties.getStrategy(); if (StringUtils.isEmpty(loadBalanceStrategy)) { - return new ZoneAvoidanceRule(); + return null; } switch (loadBalanceStrategy) { case STRATEGY_RANDOM: @@ -198,4 +206,9 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule { return new ZoneAvoidanceRule(); } } + + @VisibleForTesting + public AbstractLoadBalancerRule getDelegateRule() { + return delegateRule; + } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/beanprocessor/PolarisLoadBalancerCompositeRuleBeanPostProcessor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/beanprocessor/PolarisLoadBalancerCompositeRuleBeanPostProcessor.java new file mode 100644 index 000000000..b6092be3c --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/beanprocessor/PolarisLoadBalancerCompositeRuleBeanPostProcessor.java @@ -0,0 +1,65 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent 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.cloud.polaris.router.beanprocessor; + +import java.util.List; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.AbstractLoadBalancerRule; +import com.tencent.cloud.common.util.BeanFactoryUtils; +import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties; +import com.tencent.cloud.polaris.router.PolarisLoadBalancerCompositeRule; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor; +import com.tencent.polaris.router.api.core.RouterAPI; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * Decorate IRule with PolarisLoadBalancerCompositeRule. + * + * @author derekyi 2022-08-01 + */ +public class PolarisLoadBalancerCompositeRuleBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { + + private BeanFactory beanFactory; + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof AbstractLoadBalancerRule) { + RouterAPI routerAPI = beanFactory.getBean(RouterAPI.class); + PolarisLoadBalancerProperties polarisLoadBalancerProperties = beanFactory.getBean(PolarisLoadBalancerProperties.class); + IClientConfig iClientConfig = beanFactory.getBean(IClientConfig.class); + List requestInterceptors = BeanFactoryUtils.getBeans(beanFactory, RouterRequestInterceptor.class); + List responseInterceptors = BeanFactoryUtils.getBeans(beanFactory, RouterResponseInterceptor.class); + return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties, iClientConfig, + requestInterceptors, responseInterceptors, ((AbstractLoadBalancerRule) bean)); + } + + return bean; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RibbonConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RibbonConfiguration.java index 79cc513ee..a22aea047 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RibbonConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RibbonConfiguration.java @@ -18,15 +18,7 @@ package com.tencent.cloud.polaris.router.config; -import java.util.List; - -import com.netflix.client.config.IClientConfig; -import com.netflix.loadbalancer.IRule; -import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties; -import com.tencent.cloud.polaris.router.PolarisLoadBalancerCompositeRule; -import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; -import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor; -import com.tencent.polaris.router.api.core.RouterAPI; +import com.tencent.cloud.polaris.router.beanprocessor.PolarisLoadBalancerCompositeRuleBeanPostProcessor; import org.springframework.context.annotation.Bean; @@ -38,11 +30,7 @@ import org.springframework.context.annotation.Bean; public class RibbonConfiguration { @Bean - public IRule polarisLoadBalancerCompositeRule(RouterAPI routerAPI, - PolarisLoadBalancerProperties polarisLoadBalancerProperties, - IClientConfig iClientConfig, List requestInterceptors, - List responseInterceptors) { - return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties, iClientConfig, - requestInterceptors, responseInterceptors); + public PolarisLoadBalancerCompositeRuleBeanPostProcessor polarisLoadBalancerCompositeRuleBeanPostProcessor() { + return new PolarisLoadBalancerCompositeRuleBeanPostProcessor(); } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRuleTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRuleTest.java index 19cde939e..adde6be6f 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRuleTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRuleTest.java @@ -113,7 +113,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testGetDefaultLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(""); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); AbstractLoadBalancerRule defaultRule = compositeRule.getRule(); @@ -124,7 +124,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testRandomLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RANDOM); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -135,7 +135,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testWeightLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_WEIGHT); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -146,7 +146,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testRetryLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RETRY); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -157,7 +157,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testWeightedResponseTimeLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RESPONSE_TIME_WEIGHTED); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -168,7 +168,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void tesBestAvailableLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_BEST_AVAILABLE); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -179,7 +179,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void tesRoundRobinLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_ROUND_ROBIN); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -190,7 +190,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testAvailabilityFilteringLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_AVAILABILITY_FILTERING); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -209,7 +209,7 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); @@ -244,7 +244,7 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); @@ -275,7 +275,7 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); @@ -306,7 +306,7 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, config, requestInterceptors, null); + polarisLoadBalancerProperties, config, requestInterceptors, null, null); ProcessRoutersResponse assembleResponse = assembleProcessRoutersResponse(); when(routerAPI.processRouters(any())).thenReturn(assembleResponse); diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/beanprocessor/PolarisLoadBalancerCompositeRuleBeanPostProcessorTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/beanprocessor/PolarisLoadBalancerCompositeRuleBeanPostProcessorTest.java new file mode 100644 index 000000000..3445185e4 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/beanprocessor/PolarisLoadBalancerCompositeRuleBeanPostProcessorTest.java @@ -0,0 +1,147 @@ +package com.tencent.cloud.polaris.router.beanprocessor; + +import com.netflix.loadbalancer.AbstractLoadBalancerRule; +import com.netflix.loadbalancer.BestAvailableRule; +import com.netflix.loadbalancer.IRule; +import com.netflix.loadbalancer.RandomRule; +import com.netflix.loadbalancer.RoundRobinRule; +import com.netflix.loadbalancer.ZoneAvoidanceRule; +import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties; +import com.tencent.cloud.polaris.router.PolarisLoadBalancerCompositeRule; +import com.tencent.cloud.polaris.router.config.RibbonConfiguration; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.router.api.core.RouterAPI; +import com.tencent.polaris.router.client.api.DefaultRouterAPI; +import org.junit.Assert; +import org.junit.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonClient; +import org.springframework.cloud.netflix.ribbon.RibbonClients; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +/** + * @author derekyi + * @date 2022/8/1 + */ +public class PolarisLoadBalancerCompositeRuleBeanPostProcessorTest { + + private static final String SERVICE_1 = "service1"; + + private static final String SERVICE_2 = "service2"; + + @Test + public void test1() { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(RibbonDefaultConfig.class, PolarisRibbonTest.class, RibbonAutoConfiguration.class)); + contextRunner.run(context -> { + SpringClientFactory springClientFactory = context.getBean(SpringClientFactory.class); + + IRule rule = springClientFactory.getInstance(SERVICE_1, IRule.class); + Assert.assertTrue(rule instanceof PolarisLoadBalancerCompositeRule); + AbstractLoadBalancerRule delegateRule = ((PolarisLoadBalancerCompositeRule) rule).getDelegateRule(); + //ZoneAvoidanceRule default + Assert.assertTrue(delegateRule instanceof ZoneAvoidanceRule); + }); + } + + @Test + public void test2() { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(RibbonDefaultConfig.class, PolarisRibbonTest.class, RibbonAutoConfiguration.class)) + .withPropertyValues("spring.cloud.polaris.loadbalancer.strategy = random"); + contextRunner.run(context -> { + SpringClientFactory springClientFactory = context.getBean(SpringClientFactory.class); + + IRule rule = springClientFactory.getInstance(SERVICE_1, IRule.class); + Assert.assertTrue(rule instanceof PolarisLoadBalancerCompositeRule); + AbstractLoadBalancerRule delegateRule = ((PolarisLoadBalancerCompositeRule) rule).getDelegateRule(); + //spring.cloud.polaris.loadbalancer.strategy = random + Assert.assertTrue(delegateRule instanceof RandomRule); + }); + } + + @Test + public void test3() { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(RibbonDefaultConfig.class, PolarisRibbonTest.class, RibbonAutoConfiguration.class)) + .withPropertyValues("service1.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule"); + contextRunner.run(context -> { + SpringClientFactory springClientFactory = context.getBean(SpringClientFactory.class); + + IRule rule1 = springClientFactory.getInstance(SERVICE_1, IRule.class); + Assert.assertTrue(rule1 instanceof PolarisLoadBalancerCompositeRule); + AbstractLoadBalancerRule delegateRule1 = ((PolarisLoadBalancerCompositeRule) rule1).getDelegateRule(); + //service1.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule + Assert.assertTrue(delegateRule1 instanceof RoundRobinRule); + + IRule rule2 = springClientFactory.getInstance(SERVICE_2, IRule.class); + Assert.assertTrue(rule2 instanceof PolarisLoadBalancerCompositeRule); + + AbstractLoadBalancerRule delegateRule2 = ((PolarisLoadBalancerCompositeRule) rule2).getDelegateRule(); + //ZoneAvoidanceRule default + Assert.assertTrue(delegateRule2 instanceof ZoneAvoidanceRule); + }); + } + + @Test + public void test4() { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(CustomRibbonConfig.class, PolarisRibbonTest.class, RibbonAutoConfiguration.class)); + contextRunner.run(context -> { + SpringClientFactory springClientFactory = context.getBean(SpringClientFactory.class); + + IRule rule1 = springClientFactory.getInstance(SERVICE_1, IRule.class); + Assert.assertTrue(rule1 instanceof PolarisLoadBalancerCompositeRule); + AbstractLoadBalancerRule delegateRule1 = ((PolarisLoadBalancerCompositeRule) rule1).getDelegateRule(); + //RibbonConfigForService1#loadBalancerRule returns BestAvailableRule + Assert.assertTrue(delegateRule1 instanceof BestAvailableRule); + + IRule rule2 = springClientFactory.getInstance(SERVICE_2, IRule.class); + Assert.assertTrue(rule2 instanceof PolarisLoadBalancerCompositeRule); + AbstractLoadBalancerRule delegateRule2 = ((PolarisLoadBalancerCompositeRule) rule2).getDelegateRule(); + //ZoneAvoidanceRule default + Assert.assertTrue(delegateRule2 instanceof ZoneAvoidanceRule); + }); + } + + @Configuration + @RibbonClients(defaultConfiguration = {RibbonConfiguration.class}) + static class RibbonDefaultConfig { + } + + @Configuration + @RibbonClients(value = {@RibbonClient(name = SERVICE_1, configuration = RibbonConfigForService1.class)}, defaultConfiguration = RibbonConfiguration.class) + static class CustomRibbonConfig { + } + + static class RibbonConfigForService1 { + + @Bean + public IRule loadBalancerRule() { + return new BestAvailableRule(); + } + } + + + @Configuration + @EnableConfigurationProperties(PolarisLoadBalancerProperties.class) + static class PolarisRibbonTest { + + @Bean + public SDKContext sdkContext() { + return SDKContext.initContext(); + } + + @Bean + public RouterAPI routerAPI(SDKContext sdkContext) { + return new DefaultRouterAPI(sdkContext); + } + } +}