feat: initial iShare project code
This commit is contained in:
35
pigx-common/pigx-common-gray/pom.xml
Normal file
35
pigx-common/pigx-common-gray/pom.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common</artifactId>
|
||||
<version>5.2.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pigx-common-gray</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<description>pigx 灰度路由</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-core</artifactId>
|
||||
</dependency>
|
||||
<!-- LB 扩展 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gray;
|
||||
|
||||
import com.pig4cloud.pigx.common.gray.feign.GrayFeignRequestInterceptor;
|
||||
import com.pig4cloud.pigx.common.gray.rule.GrayLoadBalancerClientConfiguration;
|
||||
import feign.RequestInterceptor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/1/12
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "gray.rule.enabled", matchIfMissing = true)
|
||||
@LoadBalancerClients(defaultConfiguration = GrayLoadBalancerClientConfiguration.class)
|
||||
public class GrayLoadBalancerAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor grayFeignRequestInterceptor() {
|
||||
return new GrayFeignRequestInterceptor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gray.feign;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.WebUtils;
|
||||
import com.pig4cloud.pigx.common.gray.support.NonWebVersionContextHolder;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/1/12
|
||||
* <p>
|
||||
* feign 请求VERSION 传递
|
||||
*/
|
||||
@Slf4j
|
||||
public class GrayFeignRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
/**
|
||||
* Called for every request. Add data using methods on the supplied
|
||||
* {@link RequestTemplate}.
|
||||
* @param template
|
||||
*/
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
String reqVersion = WebUtils.getRequest() != null ? WebUtils.getRequest().getHeader(CommonConstants.VERSION)
|
||||
: NonWebVersionContextHolder.getVersion();
|
||||
|
||||
if (StrUtil.isNotBlank(reqVersion)) {
|
||||
log.debug("feign gray add header version :{}", reqVersion);
|
||||
template.header(CommonConstants.VERSION, reqVersion);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.pig4cloud.pigx.common.gray.rule;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration;
|
||||
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/12/29
|
||||
*/
|
||||
public class GrayLoadBalancerClientConfiguration extends LoadBalancerClientConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
|
||||
return new GrayRoundRobinLoadBalancer(
|
||||
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.pig4cloud.pigx.common.gray.rule;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.cloud.nacos.NacosServiceInstance;
|
||||
import com.alibaba.nacos.client.naming.utils.Chooser;
|
||||
import com.alibaba.nacos.client.naming.utils.Pair;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.*;
|
||||
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/11/20
|
||||
*/
|
||||
@Slf4j
|
||||
public class GrayRoundRobinLoadBalancer extends RoundRobinLoadBalancer {
|
||||
|
||||
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
|
||||
|
||||
private String serviceId;
|
||||
|
||||
/**
|
||||
* @param serviceInstanceListSupplierProvider a provider of
|
||||
* {@link ServiceInstanceListSupplier} that will be used to get available instances
|
||||
* @param serviceId id of the service for which to choose an instance
|
||||
*/
|
||||
public GrayRoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
|
||||
String serviceId) {
|
||||
super(serviceInstanceListSupplierProvider, serviceId);
|
||||
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Response<ServiceInstance>> choose(Request request) {
|
||||
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
|
||||
.getIfAvailable(NoopServiceInstanceListSupplier::new);
|
||||
return supplier.get(request).next().map(serviceInstances -> getInstanceResponse(serviceInstances, request));
|
||||
|
||||
}
|
||||
|
||||
Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) {
|
||||
|
||||
// 注册中心无可用实例 抛出异常
|
||||
if (CollUtil.isEmpty(instances)) {
|
||||
log.warn("No instance available serviceId: {}", serviceId);
|
||||
return new EmptyResponse();
|
||||
}
|
||||
|
||||
if (request == null || request.getContext() == null) {
|
||||
return super.choose(request).block();
|
||||
}
|
||||
|
||||
DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();
|
||||
if (!(requestContext.getClientRequest() instanceof RequestData)) {
|
||||
return super.choose(request).block();
|
||||
}
|
||||
|
||||
RequestData clientRequest = (RequestData) requestContext.getClientRequest();
|
||||
HttpHeaders headers = clientRequest.getHeaders();
|
||||
|
||||
String reqVersion = headers.getFirst(CommonConstants.VERSION);
|
||||
if (StrUtil.isBlank(reqVersion)) {
|
||||
// 过滤出不含VERSION实例
|
||||
List<ServiceInstance> versionInstanceList = instances.stream()
|
||||
.filter(instance -> !instance.getMetadata().containsKey(CommonConstants.VERSION))
|
||||
.collect(Collectors.toList());
|
||||
if (CollUtil.isEmpty(versionInstanceList)) {
|
||||
// 根据权重获取实例
|
||||
return new DefaultResponse(randomByWeight(instances));
|
||||
}
|
||||
// 根据权重获取实例
|
||||
return new DefaultResponse(randomByWeight(versionInstanceList));
|
||||
}
|
||||
|
||||
// 遍历可以实例元数据,若匹配则返回此实例
|
||||
List<ServiceInstance> serviceInstanceList = instances.stream().filter(instance -> {
|
||||
NacosServiceInstance nacosInstance = (NacosServiceInstance) instance;
|
||||
Map<String, String> metadata = nacosInstance.getMetadata();
|
||||
String targetVersion = MapUtil.getStr(metadata, CommonConstants.VERSION);
|
||||
return reqVersion.equalsIgnoreCase(targetVersion);
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
// 存在 随机返回
|
||||
if (CollUtil.isNotEmpty(serviceInstanceList)) {
|
||||
ServiceInstance instance = randomByWeight(serviceInstanceList);
|
||||
|
||||
log.debug("gray instance available serviceId: {} , instanceId: {}", serviceId, instance.getInstanceId());
|
||||
return new DefaultResponse(instance);
|
||||
}
|
||||
else {
|
||||
// 不存在,降级策略,使用轮询策略
|
||||
return super.choose(request).block();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据nacos设置的权重返回实例
|
||||
* @param serviceInstances 服务实例集合
|
||||
* @return 服务实例:ServiceInstance
|
||||
*/
|
||||
protected ServiceInstance randomByWeight(final List<ServiceInstance> serviceInstances) {
|
||||
if (serviceInstances.size() == 1) {
|
||||
return serviceInstances.get(0);
|
||||
}
|
||||
else {
|
||||
List<Pair<ServiceInstance>> hostsWithWeight = new ArrayList<>();
|
||||
for (ServiceInstance serviceInstance : serviceInstances) {
|
||||
if ("true".equals(serviceInstance.getMetadata().getOrDefault("nacos.healthy", "true"))) {
|
||||
hostsWithWeight.add(new Pair<>(serviceInstance,
|
||||
Double.parseDouble(serviceInstance.getMetadata().getOrDefault("nacos.weight", "1"))));
|
||||
}
|
||||
}
|
||||
Chooser<String, ServiceInstance> vipChooser = new Chooser<>("www.taobao.com", hostsWithWeight);
|
||||
return vipChooser.randomWithWeight();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gray.support;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020-10-16
|
||||
* <p>
|
||||
* 灰度版本号传递工具 ,在非web 调用feign 传递之前手动setVersion
|
||||
*/
|
||||
@UtilityClass
|
||||
public class NonWebVersionContextHolder {
|
||||
|
||||
private final ThreadLocal<String> THREAD_LOCAL_VERSION = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* TTL 设置版本号<br/>
|
||||
* @param version 版本号
|
||||
*/
|
||||
public void setVersion(String version) {
|
||||
THREAD_LOCAL_VERSION.set(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取TTL中的版本号
|
||||
* @return 版本 || null
|
||||
*/
|
||||
public String getVersion() {
|
||||
return THREAD_LOCAL_VERSION.get();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
THREAD_LOCAL_VERSION.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "gray.rule.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"defaultValue": false,
|
||||
"sourceType": "com.pig4cloud.pigx.common.gray.GrayLoadBalancerAutoConfiguration",
|
||||
"description": "是否开启灰度路由功能"
|
||||
}
|
||||
],
|
||||
"hints": []
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.pig4cloud.pigx.common.gray.GrayLoadBalancerAutoConfiguration
|
||||
Reference in New Issue
Block a user