提交 e6fd65c2 编写于 作者: J jarvis

feat: 提交loadbalancer添加版本控制

上级 4224ca83
......@@ -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";
}
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<ServiceInstance> instances);
}
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<ServiceInstance> 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;
}
}
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<ServiceInstance> 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;
}
}
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 {
}
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);
}
}
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;
}
}
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();
}
}
}
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<ServiceInstanceListSupplier> serviceInstanceListSuppliers;
private String serviceId;
private IRuleChooser ruleChooser;
public VersionLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSuppliers, String serviceId, IRuleChooser ruleChooser) {
this.serviceInstanceListSuppliers = serviceInstanceListSuppliers;
this.serviceId = serviceId;
this.ruleChooser = ruleChooser;
}
@Override
public Mono<Response<ServiceInstance>> 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<ServiceInstance> getInstanceResponse(List<ServiceInstance>instances){
String version = LbIsolationContextHolder.getVersion();
log.debug("选择的版本号为:{}", version);
List<ServiceInstance> 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();
}
}
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
......@@ -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
......@@ -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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册