From e6fd65c271bc89b6b38635585429590c7ea959a1 Mon Sep 17 00:00:00 2001 From: jarvis <649219050@qq.com> Date: Sun, 10 Apr 2022 15:37:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8F=90=E4=BA=A4loadbalancer=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=89=88=E6=9C=AC=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/constant/ConfigConstants.java | 10 ++ .../common/lb/chooser/IRuleChooser.java | 14 +++ .../common/lb/chooser/RandomRuleChooser.java | 27 ++++++ .../common/lb/chooser/RoundRuleChooser.java | 33 +++++++ .../lb/config/VerionAutoRegistryConfig.java | 18 ++++ .../lb/config/VersionLoadBalancerConfig.java | 70 ++++++++++++++ .../VersionRegisterBeanPostProcessor.java | 27 ++++++ .../common/lb/filter/LbIsolationFilter.java | 49 ++++++++++ .../lb/loadbalancer/VersionLoadBalancer.java | 95 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 3 +- .../src/main/resources/application.yml | 8 +- .../src/main/resources/application.yml | 7 +- 12 files changed, 358 insertions(+), 3 deletions(-) create mode 100644 zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/IRuleChooser.java create mode 100644 zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/RandomRuleChooser.java create mode 100644 zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/RoundRuleChooser.java create mode 100644 zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VerionAutoRegistryConfig.java create mode 100644 zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VersionLoadBalancerConfig.java create mode 100644 zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VersionRegisterBeanPostProcessor.java create mode 100644 zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/filter/LbIsolationFilter.java create mode 100644 zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/loadbalancer/VersionLoadBalancer.java diff --git a/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/ConfigConstants.java b/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/ConfigConstants.java index cd8375c..af2cadb 100644 --- a/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/ConfigConstants.java +++ b/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/ConfigConstants.java @@ -11,4 +11,14 @@ public interface ConfigConstants { * 是否开启自定义隔离规则 */ String CONFIG_RIBBON_ISOLATION_ENABLED = "zlt.ribbon.isolation.enabled"; + + String CONFIG_LOADBALANCE_ISOLATION = "zlt.loadbalance.isolation"; + + String CONFIG_LOADBALANCE_ISOLATION_ENABLE = CONFIG_LOADBALANCE_ISOLATION + ".enabled"; + + String CONFIG_LOADBALANCE_ISOLATION_CHOOSER = CONFIG_LOADBALANCE_ISOLATION + ".chooser"; + + String CONFIG_LOADBALANCE_VERSION = "zlt.loadbalance.version"; + + } diff --git a/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/IRuleChooser.java b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/IRuleChooser.java new file mode 100644 index 0000000..356d2c7 --- /dev/null +++ b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/IRuleChooser.java @@ -0,0 +1,14 @@ +package com.central.common.lb.chooser; + +import org.springframework.cloud.client.ServiceInstance; + +import java.util.List; + +/** + * service选择器类 + * + * @author jarvis create by 2022/3/13 + */ +public interface IRuleChooser { + ServiceInstance choose(List instances); +} diff --git a/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/RandomRuleChooser.java b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/RandomRuleChooser.java new file mode 100644 index 0000000..aec8257 --- /dev/null +++ b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/RandomRuleChooser.java @@ -0,0 +1,27 @@ +package com.central.common.lb.chooser; + +import com.alibaba.nacos.common.utils.CollectionUtils; +import lombok.extern.log4j.Log4j2; +import org.springframework.cloud.client.ServiceInstance; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 随机的选择器 + * + * @author jarvis create by 2022/3/13 + */ +@Log4j2 +public class RandomRuleChooser implements IRuleChooser { + @Override + public ServiceInstance choose(List instances) { + if(CollectionUtils.isNotEmpty(instances)){ + int randomValue = ThreadLocalRandom.current().nextInt(instances.size()); + ServiceInstance serviceInstance = instances.get(randomValue); + log.info("选择了ip为{}, 端口为:{}的服务", serviceInstance.getHost(), serviceInstance.getPort()); + return serviceInstance; + } + return null; + } +} diff --git a/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/RoundRuleChooser.java b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/RoundRuleChooser.java new file mode 100644 index 0000000..2817f0a --- /dev/null +++ b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/chooser/RoundRuleChooser.java @@ -0,0 +1,33 @@ +package com.central.common.lb.chooser; + +import com.alibaba.nacos.common.utils.CollectionUtils; +import lombok.extern.log4j.Log4j2; +import org.springframework.cloud.client.ServiceInstance; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 轮询选择器 + * + * @author jarvis create by 2022/3/13 + */ +@Log4j2 +public class RoundRuleChooser implements IRuleChooser{ + + private AtomicInteger position; + + public RoundRuleChooser() { + this.position = new AtomicInteger(1000); + } + + @Override + public ServiceInstance choose(List instances) { + if(CollectionUtils.isNotEmpty(instances)){ + ServiceInstance serviceInstance = instances.get(Math.abs(position.incrementAndGet() % instances.size())); + log.info("选择了ip为{}, 端口为:{}的服务", serviceInstance.getHost(), serviceInstance.getPort()); + return serviceInstance; + } + return null; + } +} diff --git a/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VerionAutoRegistryConfig.java b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VerionAutoRegistryConfig.java new file mode 100644 index 0000000..6277726 --- /dev/null +++ b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VerionAutoRegistryConfig.java @@ -0,0 +1,18 @@ +package com.central.common.lb.config; + +import com.central.common.constant.ConfigConstants; +import com.central.common.lb.filter.LbIsolationFilter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; +import org.springframework.context.annotation.Import; + +/** + * 示例 + * + * @author jarvis create by 2022/4/10 + */ +@LoadBalancerClients(defaultConfiguration = VersionLoadBalancerConfig.class) +@ConditionalOnProperty(prefix = ConfigConstants.CONFIG_LOADBALANCE_ISOLATION, name = "enabled", havingValue = "true", matchIfMissing = false) +@Import({VersionRegisterBeanPostProcessor.class, LbIsolationFilter.class}) +public class VerionAutoRegistryConfig { +} diff --git a/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VersionLoadBalancerConfig.java b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VersionLoadBalancerConfig.java new file mode 100644 index 0000000..b37b231 --- /dev/null +++ b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VersionLoadBalancerConfig.java @@ -0,0 +1,70 @@ +package com.central.common.lb.config; + +import com.central.common.constant.ConfigConstants; +import com.central.common.lb.chooser.IRuleChooser; +import com.central.common.lb.chooser.RoundRuleChooser; +import com.central.common.lb.loadbalancer.VersionLoadBalancer; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.util.ClassUtils; +import java.util.Objects; + + +/** + * 版本控制的路由选择类配置 + * + * @author jarvis create by 2022/3/9 + */ +@Log4j2 +public class VersionLoadBalancerConfig{ + + private IRuleChooser defaultRuleChooser = null; + + @Bean + @ConditionalOnMissingBean(IRuleChooser.class) + @ConditionalOnProperty(prefix = ConfigConstants.CONFIG_LOADBALANCE_ISOLATION, value = "chooser") + public IRuleChooser customRuleChooser(Environment environment, ApplicationContext context){ + + IRuleChooser chooser = new RoundRuleChooser(); + if (environment.containsProperty(ConfigConstants.CONFIG_LOADBALANCE_ISOLATION_CHOOSER)) { + String chooserRuleClassString = environment.getProperty(ConfigConstants.CONFIG_LOADBALANCE_ISOLATION_CHOOSER); + if(StringUtils.isNotBlank(chooserRuleClassString)){ + try { + Class ruleClass = ClassUtils.forName(chooserRuleClassString, context.getClassLoader()); + chooser = (IRuleChooser) ruleClass.newInstance(); + } catch (ClassNotFoundException e) { + log.error("没有找到定义的选择器,将使用内置的选择器", e); + } catch (InstantiationException e) { + log.error("没法创建定义的选择器,将使用内置的选择器", e); + } catch (IllegalAccessException e) { + log.error("没法创建定义的选择器,将使用内置的选择器", e); + } + } + } + return chooser; + } + + @Bean + @ConditionalOnMissingBean(value = IRuleChooser.class) + public IRuleChooser defaultRuleChooser(){ + return new RoundRuleChooser(); + } + + + @Bean + @ConditionalOnProperty(prefix = ConfigConstants.CONFIG_LOADBALANCE_ISOLATION, name = "enabled", havingValue = "true", matchIfMissing = false) + public ReactorServiceInstanceLoadBalancer versionServiceLoadBalancer(Environment environment + , LoadBalancerClientFactory loadBalancerClientFactory, IRuleChooser ruleChooser){ + String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); + return new VersionLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class) + , name, ruleChooser); + } +} diff --git a/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VersionRegisterBeanPostProcessor.java b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VersionRegisterBeanPostProcessor.java new file mode 100644 index 0000000..7f5a3d1 --- /dev/null +++ b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/config/VersionRegisterBeanPostProcessor.java @@ -0,0 +1,27 @@ +package com.central.common.lb.config; + +import com.alibaba.cloud.nacos.NacosDiscoveryProperties; +import com.alibaba.nacos.common.utils.StringUtils; +import com.central.common.constant.CommonConstant; +import com.central.common.constant.ConfigConstants; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * 将版本注册到注册中心的组件 + * + * @author jarvis create by 2022/3/20 + */ +public class VersionRegisterBeanPostProcessor implements BeanPostProcessor { + @Value("${"+ ConfigConstants.CONFIG_LOADBALANCE_VERSION+":}") + private String version; + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if(bean instanceof NacosDiscoveryProperties && StringUtils.isNotBlank(version)){ + NacosDiscoveryProperties nacosDiscoveryProperties = (NacosDiscoveryProperties) bean; + nacosDiscoveryProperties.getMetadata().putIfAbsent(CommonConstant.METADATA_VERSION, version); + } + return bean; + } +} diff --git a/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/filter/LbIsolationFilter.java b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/filter/LbIsolationFilter.java new file mode 100644 index 0000000..e18e7c0 --- /dev/null +++ b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/filter/LbIsolationFilter.java @@ -0,0 +1,49 @@ +package com.central.common.lb.filter; + +import cn.hutool.core.util.StrUtil; +import com.central.common.constant.CommonConstant; +import com.central.common.constant.ConfigConstants; +import com.central.common.context.LbIsolationContextHolder; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * web过滤器 + * 作用:将请求中的存放在参数和header的版本号获取出来并存入TreadLocal中 + * + * @author jarvis create by 2022/3/9 + */ +@ConditionalOnClass(Filter.class) +public class LbIsolationFilter extends OncePerRequestFilter { + @Value("${" + ConfigConstants.CONFIG_LOADBALANCE_ISOLATION_ENABLE + ":false}") + private boolean enableIsolation; + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + return !enableIsolation; + } + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + try { + String version = StringUtils.isNotBlank(request.getParameter(CommonConstant.Z_L_T_VERSION))? + request.getParameter(CommonConstant.Z_L_T_VERSION): + request.getHeader(CommonConstant.Z_L_T_VERSION); + if(StrUtil.isNotEmpty(version)){ + LbIsolationContextHolder.setVersion(version); + } + + filterChain.doFilter(request, response); + } finally { + LbIsolationContextHolder.clear(); + } + } +} diff --git a/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/loadbalancer/VersionLoadBalancer.java b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/loadbalancer/VersionLoadBalancer.java new file mode 100644 index 0000000..286b84f --- /dev/null +++ b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/java/com/central/common/lb/loadbalancer/VersionLoadBalancer.java @@ -0,0 +1,95 @@ +package com.central.common.lb.loadbalancer; + +import com.central.common.constant.CommonConstant; +import com.central.common.context.LbIsolationContextHolder; +import com.central.common.lb.chooser.IRuleChooser; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.DefaultResponse; +import org.springframework.cloud.client.loadbalancer.EmptyResponse; +import org.springframework.cloud.client.loadbalancer.Request; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; +import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.support.SimpleObjectProvider; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; + +/** + * 自定义版本路由选择 + * + * @author jarvis create by 2022/3/9 + */ +@Log4j2 +public class VersionLoadBalancer implements ReactorServiceInstanceLoadBalancer { + + private final static String KEY_DEFAULT = "default"; + + private ObjectProvider serviceInstanceListSuppliers; + + private String serviceId; + + private IRuleChooser ruleChooser; + + public VersionLoadBalancer(ObjectProvider serviceInstanceListSuppliers, String serviceId, IRuleChooser ruleChooser) { + this.serviceInstanceListSuppliers = serviceInstanceListSuppliers; + this.serviceId = serviceId; + this.ruleChooser = ruleChooser; + } + + @Override + public Mono> choose(Request request) { + return serviceInstanceListSuppliers.getIfAvailable().get(request).next().map(this::getInstanceResponse); + } + + /** + * 1. 先获取到拦截的版本,如果不为空的话就将service列表过滤,寻找metadata中哪个服务是配置的版本, + * 如果版本为空则不需要进行过滤直接提交给service选择器进行选择 + * 2. 如果没有找到版本对应的实例,则找所有版本为空或者版本号为default的实例 + * 3.将instance列表提交到选择器根据对应的策略返回一个instance + * @param instances + * @return + */ + private Response getInstanceResponse(Listinstances){ + String version = LbIsolationContextHolder.getVersion(); + log.debug("选择的版本号为:{}", version); + List filteredServiceIstanceList = instances; + if(StringUtils.isNotBlank(version)){ + if(CollectionUtils.isNotEmpty(instances)){ + filteredServiceIstanceList = instances.stream() + .filter(item->item.getMetadata().containsKey(CommonConstant.METADATA_VERSION)&& + version.equals(item.getMetadata().get(CommonConstant.METADATA_VERSION))) + .collect(Collectors.toList()); + } + } + // 如果没有找到对应的版本实例时,选择版本号为空的或这版本为default的实例 + if(CollectionUtils.isEmpty(filteredServiceIstanceList)){ + filteredServiceIstanceList = instances.stream() + .filter(item->!item.getMetadata().containsKey(CommonConstant.METADATA_VERSION) + || "default".equals(item.getMetadata().get(CommonConstant.METADATA_VERSION))) + .collect(Collectors.toList()); + } + // 经过两轮过滤后如果能找到的话就选择,不然返回空 + if(CollectionUtils.isNotEmpty(filteredServiceIstanceList)){ + ServiceInstance serviceInstance = this.ruleChooser.choose(filteredServiceIstanceList); + if(!Objects.isNull(serviceInstance)){ + log.debug("使用serviceId为:{}服务, 选择version为:{}, 地址:{}:{},", serviceId, version + , serviceInstance.getHost(), serviceInstance.getPort()); + return new DefaultResponse(serviceInstance); + } + } + // 返回空的返回体 + return new EmptyResponse(); + } +} diff --git a/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/resources/META-INF/spring.factories b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/resources/META-INF/spring.factories index bd79ca6..06406df 100644 --- a/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ b/zlt-commons/zlt-loadbalancer-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -1,2 +1,3 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.central.common.lb.RestTemplateAutoConfigure \ No newline at end of file +com.central.common.lb.RestTemplateAutoConfigure,\ +com.central.common.lb.config.VerionAutoRegistryConfig \ No newline at end of file diff --git a/zlt-demo/txlcn-demo/txlcn-demo-spring-service-a/src/main/resources/application.yml b/zlt-demo/txlcn-demo/txlcn-demo-spring-service-a/src/main/resources/application.yml index cd40204..7133edb 100644 --- a/zlt-demo/txlcn-demo/txlcn-demo-spring-service-a/src/main/resources/application.yml +++ b/zlt-demo/txlcn-demo/txlcn-demo-spring-service-a/src/main/resources/application.yml @@ -16,4 +16,10 @@ tx-lcn: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://${zlt.datasource.ip}:3306/tx_logger?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull username: ${zlt.datasource.username} - password: ${zlt.datasource.password} \ No newline at end of file + password: ${zlt.datasource.password} +zlt: + loadbalance: + version: test + isolation: + enabled: true + chooser: com.central.common.lb.chooser.RandomRuleChooser diff --git a/zlt-demo/txlcn-demo/txlcn-demo-spring-service-b/src/main/resources/application.yml b/zlt-demo/txlcn-demo/txlcn-demo-spring-service-b/src/main/resources/application.yml index cd40204..68d7e81 100644 --- a/zlt-demo/txlcn-demo/txlcn-demo-spring-service-b/src/main/resources/application.yml +++ b/zlt-demo/txlcn-demo/txlcn-demo-spring-service-b/src/main/resources/application.yml @@ -16,4 +16,9 @@ tx-lcn: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://${zlt.datasource.ip}:3306/tx_logger?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull username: ${zlt.datasource.username} - password: ${zlt.datasource.password} \ No newline at end of file + password: ${zlt.datasource.password} +zlt: + loadbalance: + version: test + isolation: + enabled: true \ No newline at end of file -- GitLab