重构预发布/灰度发布环境选择

pull/1/MERGE
tanghc 5 years ago
parent 9bb6bcb9cb
commit 23bbf26db7
  1. 1
      changelog.md
  2. 2
      sop-common/sop-bridge-gateway/src/main/java/com/gitee/sop/bridge/SopGatewayAutoConfiguration.java
  3. 4
      sop-common/sop-bridge-gateway/src/main/resources/sop-bridge.properties
  4. 3
      sop-common/sop-bridge-zuul/src/main/java/com/gitee/sop/bridge/SopGatewayAutoConfiguration.java
  5. 14
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java
  6. 10
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiParamAware.java
  7. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java
  8. 30
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java
  9. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java
  10. 32
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/SopLoadBalancerClientFilter.java
  11. 29
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/GatewayLoadBalanceServerChooser.java
  12. 23
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/NacosServerIntrospector.java
  13. 107
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java
  14. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayForwardChooser.java
  15. 64
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/EnvironmentServerChooser.java
  16. 16
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/EurekaEnvironmentServerChooser.java
  17. 8
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/LoadBalanceConfig.java
  18. 127
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/LoadBalanceServerChooser.java
  19. 35
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java
  20. 19
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/AppIdGrayUserBuilder.java
  21. 24
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/GrayUserBuilder.java
  22. 19
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/IpGrayUserBuilder.java
  23. 25
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java
  24. 15
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvironmentKeys.java
  25. 10
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java
  26. 25
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java
  27. 6
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/EurekaRegistryListener.java
  28. 81
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/BaseServerChooser.java
  29. 55
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/EnvironmentServerChooser.java
  30. 24
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/ZuulLoadBalanceServerChooser.java
  31. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/ZuulForwardChooser.java
  32. 1
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingHandlerMapping.java

@ -3,6 +3,7 @@
## 3.0.0 ## 3.0.0
- 重构spring cloud gateway网关 - 重构spring cloud gateway网关
- 重构`预发布/灰度发布环境选择`
- zuul和gateway网关二合一 - zuul和gateway网关二合一
- 精简配置文件 - 精简配置文件
- 优化文档中心页面 - 优化文档中心页面

@ -1,12 +1,14 @@
package com.gitee.sop.bridge; package com.gitee.sop.bridge;
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration; import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
/** /**
* https://blog.csdn.net/seashouwang/article/details/80299571 * https://blog.csdn.net/seashouwang/article/details/80299571
* @author tanghc * @author tanghc
*/ */
@Configuration
@Import(AlipayGatewayConfiguration.class) @Import(AlipayGatewayConfiguration.class)
public class SopGatewayAutoConfiguration { public class SopGatewayAutoConfiguration {
} }

@ -4,7 +4,9 @@ spring.application.name=sop-gateway
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
# 网关入口 # 网关入口
gateway.index-path=/ sop.gateway-index-path=/
spring.main.allow-bean-definition-overriding=true
# nacos cloud配置 # nacos cloud配置
spring.cloud.nacos.discovery.server-addr=${nacos.url} spring.cloud.nacos.discovery.server-addr=${nacos.url}

@ -2,13 +2,16 @@ package com.gitee.sop.bridge;
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration; import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
/** /**
* https://blog.csdn.net/seashouwang/article/details/80299571 * https://blog.csdn.net/seashouwang/article/details/80299571
* @author tanghc * @author tanghc
*/ */
@Configuration
@EnableZuulProxy @EnableZuulProxy
@Import(AlipayZuulConfiguration.class) @Import(AlipayZuulConfiguration.class)
public class SopGatewayAutoConfiguration { public class SopGatewayAutoConfiguration {
} }

@ -3,6 +3,9 @@ package com.gitee.sop.gatewaycommon.bean;
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor; import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor;
import com.gitee.sop.gatewaycommon.limit.DefaultLimitManager; import com.gitee.sop.gatewaycommon.limit.DefaultLimitManager;
import com.gitee.sop.gatewaycommon.limit.LimitManager; import com.gitee.sop.gatewaycommon.limit.LimitManager;
import com.gitee.sop.gatewaycommon.loadbalancer.builder.AppIdGrayUserBuilder;
import com.gitee.sop.gatewaycommon.loadbalancer.builder.GrayUserBuilder;
import com.gitee.sop.gatewaycommon.loadbalancer.builder.IpGrayUserBuilder;
import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager; import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager;
import com.gitee.sop.gatewaycommon.manager.DefaultIPBlacklistManager; import com.gitee.sop.gatewaycommon.manager.DefaultIPBlacklistManager;
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager; import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
@ -41,6 +44,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -53,6 +57,9 @@ public class ApiConfig {
private static ApiConfig instance = new ApiConfig(); private static ApiConfig instance = new ApiConfig();
private ApiConfig() { private ApiConfig() {
grayUserBuilders = new ArrayList<>(4);
grayUserBuilders.add(new AppIdGrayUserBuilder());
grayUserBuilders.add(new IpGrayUserBuilder());
} }
/** /**
@ -193,6 +200,13 @@ public class ApiConfig {
private boolean useGateway; private boolean useGateway;
private List<GrayUserBuilder> grayUserBuilders;
public void addGrayUserBuilder(GrayUserBuilder grayUserBuilder) {
grayUserBuilders.add(grayUserBuilder);
grayUserBuilders.sort(Comparator.comparing(GrayUserBuilder::order));
}
public void addAppSecret(Map<String, String> appSecretPair) { public void addAppSecret(Map<String, String> appSecretPair) {
for (Map.Entry<String, String> entry : appSecretPair.entrySet()) { for (Map.Entry<String, String> entry : appSecretPair.entrySet()) {
this.isvManager.update(new IsvDefinition(entry.getKey(), entry.getValue())); this.isvManager.update(new IsvDefinition(entry.getKey(), entry.getValue()));

@ -0,0 +1,10 @@
package com.gitee.sop.gatewaycommon.bean;
import com.gitee.sop.gatewaycommon.param.ApiParam;
/**
* @author tanghc
*/
public interface ApiParamAware<T> {
ApiParam getApiParam(T t);
}

@ -54,4 +54,8 @@ public class SopConstants {
public static final String UNKNOWN_METHOD = "_sop_unknown_method_"; public static final String UNKNOWN_METHOD = "_sop_unknown_method_";
public static final String UNKNOWN_VERSION = "_sop_unknown_version_"; public static final String UNKNOWN_VERSION = "_sop_unknown_version_";
public static final String METADATA_ENV_KEY = "env";
public static final String METADATA_ENV_PRE_VALUE = "pre";
public static final String METADATA_ENV_GRAY_VALUE = "gray";
} }

@ -8,7 +8,9 @@ import com.gitee.sop.gatewaycommon.gateway.filter.GatewayModifyResponseGatewayFi
import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter; import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter; import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.ParameterFormatterFilter; import com.gitee.sop.gatewaycommon.gateway.filter.ParameterFormatterFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.SopLoadBalancerClientFilter;
import com.gitee.sop.gatewaycommon.gateway.handler.GatewayExceptionHandler; import com.gitee.sop.gatewaycommon.gateway.handler.GatewayExceptionHandler;
import com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient;
import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser; import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser;
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache; import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache;
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository; import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository;
@ -17,6 +19,10 @@ import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
@ -111,4 +117,28 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
GatewayForwardChooser gatewayForwardChooser() { GatewayForwardChooser gatewayForwardChooser() {
return new GatewayForwardChooser(); return new GatewayForwardChooser();
} }
/**
* 扩展默认的负载均衡选择默认使用的是RibbonLoadBalancerClient当配置了pre.domain时才生效
* @param clientFactory
* @return
*/
@Primary
@Bean
LoadBalancerClient sopLoadBalancerClient(SpringClientFactory clientFactory) {
return new SopLoadBalancerClient(clientFactory);
}
/**
* 扩展默认的负载均衡过滤器默认是LoadBalancerClientFilter当配置了pre.domain时才生效
* @param sopLoadBalancerClient SopLoadBalancerClient
* @param loadBalancerProperties loadBalancerProperties
* @return
*/
@Primary
@Bean
LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient sopLoadBalancerClient, LoadBalancerProperties loadBalancerProperties) {
return new SopLoadBalancerClientFilter(sopLoadBalancerClient, loadBalancerProperties);
}
} }

@ -43,7 +43,7 @@ public class IndexFilter implements WebFilter {
private static final String REST_PATH_PREFIX = "/rest"; private static final String REST_PATH_PREFIX = "/rest";
private static final String SOP_PATH_PREFIX = "/sop"; private static final String SOP_PATH_PREFIX = "/sop";
@Value("${gateway.index-path:/}") @Value("${sop.gateway-index-path:/}")
private String indexPath; private String indexPath;
@Autowired @Autowired

@ -0,0 +1,32 @@
package com.gitee.sop.gatewaycommon.gateway.filter;
import com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.web.server.ServerWebExchange;
import java.net.URI;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
/**
* 扩展负载均衡过滤器
* @author tanghc
*/
public class SopLoadBalancerClientFilter extends LoadBalancerClientFilter {
public SopLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
super(loadBalancer, properties);
}
@Override
protected ServiceInstance choose(ServerWebExchange exchange) {
if (loadBalancer instanceof SopLoadBalancerClient) {
SopLoadBalancerClient sopLoadBalancerClient = (SopLoadBalancerClient)loadBalancer;
return sopLoadBalancerClient.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost(), exchange);
} else {
return super.choose(exchange);
}
}
}

@ -0,0 +1,29 @@
package com.gitee.sop.gatewaycommon.gateway.loadbalancer;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.loadbalancer.LoadBalanceServerChooser;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.web.server.ServerWebExchange;
/**
* @author tanghc
*/
public class GatewayLoadBalanceServerChooser extends LoadBalanceServerChooser<ServerWebExchange, ServiceInstance> {
public GatewayLoadBalanceServerChooser(SpringClientFactory clientFactory) {
this.setClientFactory(clientFactory);
}
@Override
public String getHost(ServerWebExchange exchange) {
return exchange.getRequest().getURI().getHost();
}
@Override
public ApiParam getApiParam(ServerWebExchange exchange) {
return ServerWebExchangeUtil.getApiParam(exchange);
}
}

@ -0,0 +1,23 @@
package com.gitee.sop.gatewaycommon.gateway.loadbalancer;
import com.netflix.loadbalancer.Server;
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
import java.util.Map;
/**
* @author tanghc
*/
public class NacosServerIntrospector extends DefaultServerIntrospector {
@Override
public Map<String, String> getMetadata(Server server) {
if (server instanceof NacosServer) {
NacosServer discoveryServer = (NacosServer)server;
return discoveryServer.getInstance().getMetadata();
} else {
return super.getMetadata(server);
}
}
}

@ -0,0 +1,107 @@
package com.gitee.sop.gatewaycommon.gateway.loadbalancer;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.loadbalancer.ServerChooserContext;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.Server;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.RibbonUtils;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.web.server.ServerWebExchange;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* 重写负载均衡处理
* 默认使用的是RibbonLoadBalancerClient类详见org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#loadBalancerClient()
*
* @author tanghc
*/
public class SopLoadBalancerClient extends RibbonLoadBalancerClient implements ServerChooserContext<ServerWebExchange> {
private final SpringClientFactory clientFactory;
private GatewayLoadBalanceServerChooser loadBalanceServerChooser;
public SopLoadBalancerClient(SpringClientFactory clientFactory) {
super(clientFactory);
this.clientFactory = clientFactory;
this.loadBalanceServerChooser = new GatewayLoadBalanceServerChooser(clientFactory);
}
/**
* New: Select a server using a 'key'.
*/
@Override
public ServiceInstance choose(String serviceId, Object hint) {
return loadBalanceServerChooser.choose(
serviceId
, (ServerWebExchange) hint
, this.getLoadBalancer(serviceId)
, () -> super.choose(serviceId, hint)
, (servers) -> getRibbonServer(serviceId, servers)
);
}
@Override
public ApiParam getApiParam(ServerWebExchange exchange) {
return ServerWebExchangeUtil.getApiParam(exchange);
}
@Override
public String getHost(ServerWebExchange exchange) {
return exchange.getRequest().getURI().getHost();
}
private RibbonServer getRibbonServer(String serviceId, List<Server> servers) {
Server server = this.chooseRandomServer(servers);
if (server == null) {
return null;
}
return new RibbonServer(
serviceId
, server
, isSecure(server, serviceId)
, serverIntrospector(serviceId).getMetadata(server)
);
}
/**
* 随机选取一台实例
*
* @param servers 服务列表
* @return 返回实例没有返回null
*/
private Server chooseRandomServer(List<Server> servers) {
if (servers.isEmpty()) {
return null;
}
int serverCount = servers.size();
// 随机选取一台实例
int index = chooseRandomInt(serverCount);
return servers.get(index);
}
private int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
private ServerIntrospector serverIntrospector(String serviceId) {
ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
ServerIntrospector.class);
if (serverIntrospector == null) {
serverIntrospector = new DefaultServerIntrospector();
}
return serverIntrospector;
}
private boolean isSecure(Server server, String serviceId) {
IClientConfig config = this.clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
return RibbonUtils.isSecure(config, serverIntrospector, server);
}
}

@ -14,7 +14,7 @@ public class GatewayForwardChooser extends BaseForwardChooser<ServerWebExchange>
private static final String VALIDATE_ERROR_PATH = "/sop/validateError"; private static final String VALIDATE_ERROR_PATH = "/sop/validateError";
@Override @Override
protected ApiParam getApiParam(ServerWebExchange exchange) { public ApiParam getApiParam(ServerWebExchange exchange) {
return ServerWebExchangeUtil.getApiParam(exchange); return ServerWebExchangeUtil.getApiParam(exchange);
} }

@ -1,64 +0,0 @@
package com.gitee.sop.gatewaycommon.loadbalancer;
import com.gitee.sop.gatewaycommon.bean.SpringContext;
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.BaseServerChooser;
import com.netflix.loadbalancer.Server;
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
import org.springframework.core.env.Environment;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* 预发布灰度环境选择参考自https://segmentfault.com/a/1190000017412946
*
* @author tanghc
*/
public class EnvironmentServerChooser extends BaseServerChooser {
private static final String MEDATA_KEY_ENV = "env";
private static final String ENV_PRE_VALUE = "pre";
private static final String ENV_GRAY_VALUE = "gray";
/**
* 预发布机器域名
*/
private static final String PRE_DOMAIN = "localhost";
@Override
protected boolean isPreServer(Server server) {
String env = getEnvValue(server);
return ENV_PRE_VALUE.equals(env);
}
@Override
protected boolean isGrayServer(Server server) {
String env = getEnvValue(server);
return ENV_GRAY_VALUE.equals(env);
}
private String getEnvValue(Server server) {
// eureka存储的metadata
Map<String, String> metadata = getMetada(server);
return metadata.get(MEDATA_KEY_ENV);
}
protected Map<String, String> getMetada(Server server) {
return ((NacosServer) server).getMetadata();
}
/**
* 通过判断hostname来确定是否是预发布请求可修改此方法实现自己想要的
*
* @param request request
* @return 返回true可以进入到预发环境
*/
@Override
protected boolean canVisitPre(Server server, HttpServletRequest request) {
String serverName = request.getServerName();
String domain = SpringContext.getBean(Environment.class).getProperty("pre.domain", PRE_DOMAIN);
return domain.equals(serverName);
}
}

@ -1,16 +0,0 @@
package com.gitee.sop.gatewaycommon.loadbalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import java.util.Map;
/**
* @author tanghc
*/
public class EurekaEnvironmentServerChooser extends EnvironmentServerChooser {
@Override
protected Map<String, String> getMetada(Server server) {
return ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
}
}

@ -0,0 +1,8 @@
package com.gitee.sop.gatewaycommon.loadbalancer;
/**
* @author tanghc
*/
public class LoadBalanceConfig {
}

@ -0,0 +1,127 @@
package com.gitee.sop.gatewaycommon.loadbalancer;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.bean.SpringContext;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* 预发布灰度发布服务器选择
*
* @author tanghc
*/
public abstract class LoadBalanceServerChooser<T, R> implements ServerChooserContext<T> {
private SpringClientFactory clientFactory;
/**
* 选择服务器
*
* @param serviceId serviceId仅gateway网关有作用
* @param exchange 请求上下文
* @param loadBalancer loadBalancer
* @param superChooser 父类默认的选择
* @param serverChooserFunction 执行选择操作
* @return 返回服务器实例
*/
public R choose(
String serviceId
, T exchange
, ILoadBalancer loadBalancer
, Supplier<R> superChooser
, Function<List<Server>, R> serverChooserFunction) {
// 获取所有服务实例
List<Server> servers = loadBalancer.getReachableServers();
// 存放预发服务器
List<Server> preServers = new ArrayList<>(4);
// 存放灰度发布服务器
List<Server> grayServers = new ArrayList<>(4);
// 存放非预发服务器
List<Server> notPreServers = new ArrayList<>(4);
for (Server server : servers) {
// 获取实例metadata
Map<String, String> metadata = getMetadata(serviceId, server);
// 是否开启了预发模式
if (this.isPreServer(metadata)) {
preServers.add(server);
} else if (this.isGrayServer(metadata)) {
grayServers.add(server);
} else {
notPreServers.add(server);
}
}
notPreServers.addAll(grayServers);
// 如果没有开启预发布服务和灰度发布,直接用默认的方式
if (preServers.isEmpty() && grayServers.isEmpty()) {
return superChooser.get();
}
// 如果是从预发布域名访问过来,则认为是预发布请求
if (this.isRequestFromPreDomain(exchange)) {
return serverChooserFunction.apply(preServers);
}
// 如果是灰度请求
if (this.isRequestGrayServer(exchange)) {
return serverChooserFunction.apply(grayServers);
}
// 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器
return serverChooserFunction.apply(notPreServers);
}
protected Map<String, String> getMetadata(String serviceId, Server server) {
return serverIntrospector(serviceId).getMetadata(server);
}
protected SpringClientFactory getSpringClientFactory() {
if (clientFactory == null) {
clientFactory = SpringContext.getBean(SpringClientFactory.class);
}
return clientFactory;
}
public void setClientFactory(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
private ServerIntrospector serverIntrospector(String serviceId) {
ServerIntrospector serverIntrospector = getSpringClientFactory().getInstance(serviceId,
ServerIntrospector.class);
if (serverIntrospector == null) {
serverIntrospector = new DefaultServerIntrospector();
}
return serverIntrospector;
}
/**
* 是否是预发布服务器
*
* @param metadata metadata
* @return true
*/
private boolean isPreServer(Map<String, String> metadata) {
return Objects.equals(metadata.get(SopConstants.METADATA_ENV_KEY), SopConstants.METADATA_ENV_PRE_VALUE);
}
/**
* 是否是灰度发布服务器
*
* @param metadata metadata
* @return true
*/
private boolean isGrayServer(Map<String, String> metadata) {
return Objects.equals(metadata.get(SopConstants.METADATA_ENV_KEY), SopConstants.METADATA_ENV_GRAY_VALUE);
}
}

@ -0,0 +1,35 @@
package com.gitee.sop.gatewaycommon.loadbalancer;
import com.gitee.sop.gatewaycommon.bean.ApiParamAware;
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import org.apache.commons.lang.ArrayUtils;
import org.springframework.util.StringUtils;
/**
* @author tanghc
*/
public interface ServerChooserContext<T> extends ApiParamAware<T> {
/**
* 通过判断hostname来确定是否是预发布请求
*
* @param t t
* @return 返回true可以进入到预发环境
*/
default boolean isRequestFromPreDomain(T t) {
String domain = EnvironmentKeys.PRE_DOMAIN.getValue();
if (StringUtils.isEmpty(domain)) {
return false;
}
String[] domains = domain.split("\\,");
return ArrayUtils.contains(domains, getHost(t));
}
default boolean isRequestGrayServer(T t) {
ApiParam apiParam = getApiParam(t);
return apiParam.isGrayRequest();
}
String getHost(T t);
}

@ -0,0 +1,19 @@
package com.gitee.sop.gatewaycommon.loadbalancer.builder;
import com.gitee.sop.gatewaycommon.param.ApiParam;
/**
* @author tanghc
*/
public class AppIdGrayUserBuilder implements GrayUserBuilder {
@Override
public String buildGrayUserKey(ApiParam apiParam) {
return apiParam.fetchAppKey();
}
@Override
public int order() {
return 0;
}
}

@ -0,0 +1,24 @@
package com.gitee.sop.gatewaycommon.loadbalancer.builder;
import com.gitee.sop.gatewaycommon.param.ApiParam;
/**
* @author tanghc
*/
public interface GrayUserBuilder {
/**
* 获取灰度用户key
*
* @param apiParam apiParam
* @return 返回用户key
*/
String buildGrayUserKey(ApiParam apiParam);
/**
* 优先级数字小优先
*
* @return 返回数字
*/
int order();
}

@ -0,0 +1,19 @@
package com.gitee.sop.gatewaycommon.loadbalancer.builder;
import com.gitee.sop.gatewaycommon.param.ApiParam;
/**
* @author tanghc
*/
public class IpGrayUserBuilder implements GrayUserBuilder {
@Override
public String buildGrayUserKey(ApiParam apiParam) {
return apiParam.fetchIp();
}
@Override
public int order() {
return 1;
}
}

@ -4,15 +4,16 @@ import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.bean.BeanInitializer; import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
import com.gitee.sop.gatewaycommon.bean.SpringContext; import com.gitee.sop.gatewaycommon.bean.SpringContext;
import com.gitee.sop.gatewaycommon.gateway.loadbalancer.NacosServerIntrospector;
import com.gitee.sop.gatewaycommon.limit.LimitManager; import com.gitee.sop.gatewaycommon.limit.LimitManager;
import com.gitee.sop.gatewaycommon.loadbalancer.SopPropertiesFactory; import com.gitee.sop.gatewaycommon.loadbalancer.SopPropertiesFactory;
import com.gitee.sop.gatewaycommon.message.ErrorFactory; import com.gitee.sop.gatewaycommon.message.ErrorFactory;
import com.gitee.sop.gatewaycommon.param.ParameterFormatter; import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
import com.gitee.sop.gatewaycommon.route.ServiceRouteListener;
import com.gitee.sop.gatewaycommon.route.EurekaRegistryListener; import com.gitee.sop.gatewaycommon.route.EurekaRegistryListener;
import com.gitee.sop.gatewaycommon.route.NacosRegistryListener; import com.gitee.sop.gatewaycommon.route.NacosRegistryListener;
import com.gitee.sop.gatewaycommon.route.RegistryListener; import com.gitee.sop.gatewaycommon.route.RegistryListener;
import com.gitee.sop.gatewaycommon.route.ServiceListener; import com.gitee.sop.gatewaycommon.route.ServiceListener;
import com.gitee.sop.gatewaycommon.route.ServiceRouteListener;
import com.gitee.sop.gatewaycommon.secret.IsvManager; import com.gitee.sop.gatewaycommon.secret.IsvManager;
import com.gitee.sop.gatewaycommon.session.SessionManager; import com.gitee.sop.gatewaycommon.session.SessionManager;
import com.gitee.sop.gatewaycommon.validate.SignConfig; import com.gitee.sop.gatewaycommon.validate.SignConfig;
@ -23,6 +24,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent; import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.netflix.ribbon.PropertiesFactory; import org.springframework.cloud.netflix.ribbon.PropertiesFactory;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.eureka.EurekaServerIntrospector;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
@ -159,6 +162,26 @@ public class AbstractConfiguration implements ApplicationContextAware {
return createCorsFilter(); return createCorsFilter();
} }
/**
* 负责获取nacos实例的metadata
* @return
*/
@Bean
@ConditionalOnProperty("spring.cloud.nacos.discovery.server-addr")
ServerIntrospector nacosServerIntrospector() {
return new NacosServerIntrospector();
}
/**
* 负责获取eureka实例的metadata
* @return
*/
@Bean
@ConditionalOnProperty("eureka.client.serviceUrl.defaultZone")
ServerIntrospector eurekaServerIntrospector() {
return new EurekaServerIntrospector();
}
protected CorsFilter createCorsFilter() { protected CorsFilter createCorsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
this.registerCorsConfiguration(source); this.registerCorsConfiguration(source);

@ -1,6 +1,6 @@
package com.gitee.sop.gatewaycommon.manager; package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.loadbalancer.EnvironmentServerChooser; import com.gitee.sop.gatewaycommon.zuul.loadbalancer.EnvironmentServerChooser;
public enum EnvironmentKeys { public enum EnvironmentKeys {
SPRING_PROFILES_ACTIVE("spring.profiles.active", "default"), SPRING_PROFILES_ACTIVE("spring.profiles.active", "default"),
@ -9,7 +9,7 @@ public enum EnvironmentKeys {
*/ */
SPRING_APPLICATION_NAME("spring.application.name"), SPRING_APPLICATION_NAME("spring.application.name"),
/** /**
* 指定负载均衡规则类默认使用com.gitee.sop.gateway.loadbalancer.PreEnvironmentServerChooser * 指定负载均衡规则类
*/ */
ZUUL_CUSTOM_RULE_CLASSNAME("zuul.custom-rule-classname", EnvironmentServerChooser.class.getName()), ZUUL_CUSTOM_RULE_CLASSNAME("zuul.custom-rule-classname", EnvironmentServerChooser.class.getName()),
@ -35,8 +35,11 @@ public enum EnvironmentKeys {
/** /**
* 排除其它微服务正则形式多个用英文逗号隔开 * 排除其它微服务正则形式多个用英文逗号隔开
*/ */
SOP_SERVICE_EXCLUDE_REGEX("sop.service.exclude-regex") SOP_SERVICE_EXCLUDE_REGEX("sop.service.exclude-regex"),
; /**
* 预发布域名
*/
PRE_DOMAIN("pre.domain");
private String key; private String key;
private String defaultValue; private String defaultValue;
@ -57,4 +60,8 @@ public enum EnvironmentKeys {
public String getValue() { public String getValue() {
return EnvironmentContext.getValue(key, defaultValue); return EnvironmentContext.getValue(key, defaultValue);
} }
public String getValue(String defaultValue) {
return EnvironmentContext.getValue(key, defaultValue);
}
} }

@ -31,6 +31,8 @@ public class ApiParam extends JSONObject implements Param {
private String ip; private String ip;
private boolean isGrayRequest;
private transient UploadContext uploadContext; private transient UploadContext uploadContext;
public void fitNameVersion() { public void fitNameVersion() {
@ -248,4 +250,12 @@ public class ApiParam extends JSONObject implements Param {
public String fetchIp() { public String fetchIp() {
return ip; return ip;
} }
public boolean isGrayRequest() {
return isGrayRequest;
}
public void setGrayRequest(boolean grayRequest) {
isGrayRequest = grayRequest;
}
} }

@ -1,23 +1,28 @@
package com.gitee.sop.gatewaycommon.route; package com.gitee.sop.gatewaycommon.route;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.RouteDefinition; import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ApiParamAware;
import com.gitee.sop.gatewaycommon.bean.TargetRoute; import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.loadbalancer.builder.GrayUserBuilder;
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager; import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.ApiParam;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import java.util.Comparator;
import java.util.List;
/** /**
* @author tanghc * @author tanghc
*/ */
public abstract class BaseForwardChooser<T> implements ForwardChooser<T> { public abstract class BaseForwardChooser<T> implements ForwardChooser<T>, ApiParamAware<T> {
@Autowired @Autowired
private EnvGrayManager envGrayManager; private EnvGrayManager envGrayManager;
protected abstract ApiParam getApiParam(T t);
@Override @Override
public ForwardInfo getForwardInfo(T t) { public ForwardInfo getForwardInfo(T t) {
ApiParam apiParam = getApiParam(t); ApiParam apiParam = getApiParam(t);
@ -30,10 +35,11 @@ public abstract class BaseForwardChooser<T> implements ForwardChooser<T> {
// 如果服务在灰度阶段,返回一个灰度版本号 // 如果服务在灰度阶段,返回一个灰度版本号
String grayVersion = envGrayManager.getVersion(serviceId, nameVersion); String grayVersion = envGrayManager.getVersion(serviceId, nameVersion);
// 如果是灰度环境 // 如果是灰度环境
if (grayVersion != null && envGrayManager.containsKey(serviceId, apiParam.fetchAppKey())) { if (grayVersion != null && isGrayUser(serviceId, apiParam)) {
String newNameVersion = apiParam.fetchName() + grayVersion; String newNameVersion = apiParam.fetchName() + grayVersion;
TargetRoute targetRouteDest = RouteRepositoryContext.getRouteRepository().get(newNameVersion); TargetRoute targetRouteDest = RouteRepositoryContext.getRouteRepository().get(newNameVersion);
if (targetRouteDest != null) { if (targetRouteDest != null) {
apiParam.setGrayRequest(true);
if (BooleanUtils.toBoolean(routeDefinitionOrig.getCompatibleMode())) { if (BooleanUtils.toBoolean(routeDefinitionOrig.getCompatibleMode())) {
version = grayVersion; version = grayVersion;
} else { } else {
@ -46,4 +52,15 @@ public abstract class BaseForwardChooser<T> implements ForwardChooser<T> {
return new ForwardInfo(path, version); return new ForwardInfo(path, version);
} }
protected boolean isGrayUser(String serviceId, ApiParam apiParam) {
List<GrayUserBuilder> grayUserBuilders = ApiConfig.getInstance().getGrayUserBuilders();
for (GrayUserBuilder grayUserBuilder : grayUserBuilders) {
String userKey = grayUserBuilder.buildGrayUserKey(apiParam);
if (envGrayManager.containsKey(serviceId, userKey)) {
return true;
}
}
return false;
}
} }

@ -1,8 +1,6 @@
package com.gitee.sop.gatewaycommon.route; package com.gitee.sop.gatewaycommon.route;
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition; import com.gitee.sop.gatewaycommon.bean.InstanceDefinition;
import com.gitee.sop.gatewaycommon.loadbalancer.EurekaEnvironmentServerChooser;
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.shared.Application; import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications; import com.netflix.discovery.shared.Applications;
@ -25,10 +23,6 @@ import java.util.stream.Collectors;
*/ */
public class EurekaRegistryListener extends BaseRegistryListener { public class EurekaRegistryListener extends BaseRegistryListener {
static {
System.setProperty(EnvironmentKeys.ZUUL_CUSTOM_RULE_CLASSNAME.getKey(), EurekaEnvironmentServerChooser.class.getName());
}
private Set<ServiceHolder> cacheServices = new HashSet<>(); private Set<ServiceHolder> cacheServices = new HashSet<>();
@Override @Override

@ -1,81 +0,0 @@
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
import com.google.common.base.Optional;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;
/**
* 服务实例选择器
*
* @author tanghc
*/
@Slf4j
public abstract class BaseServerChooser extends ZoneAvoidanceRule {
/**
* 是否是预发布服务器
* @param server 服务器
* @return true
*/
protected abstract boolean isPreServer(Server server);
/**
* 是否是灰度发布服务器
* @param server 服务器
* @return true
*/
protected abstract boolean isGrayServer(Server server);
/**
* 能否访问预发布
* @param server 预发布服务器
* @param request request
* @return true
*/
protected abstract boolean canVisitPre(Server server, HttpServletRequest request);
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
// 获取服务实例列表
List<Server> allServers = lb.getAllServers();
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
List<Server> preServers = allServers.stream()
.filter(this::isPreServer)
.filter(server -> canVisitPre(server, request))
.collect(Collectors.toList());
if (!preServers.isEmpty()) {
return this.doChoose(preServers, key);
}
List<Server> grayServers = allServers.stream()
.filter(this::isGrayServer)
.collect(Collectors.toList());
if (!grayServers.isEmpty()) {
return doChoose(grayServers, key);
}
return super.choose(key);
}
protected Server doChoose(List<Server> servers, Object key) {
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(servers, key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}

@ -0,0 +1,55 @@
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
import com.gitee.sop.gatewaycommon.loadbalancer.ServerChooserContext;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
import com.google.common.base.Optional;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* 预发布灰度环境选择参考自https://segmentfault.com/a/1190000017412946
*
* @author tanghc
*/
@Slf4j
public class EnvironmentServerChooser extends ZoneAvoidanceRule implements ServerChooserContext<HttpServletRequest> {
private ZuulLoadBalanceServerChooser loadBalanceServerChooser = new ZuulLoadBalanceServerChooser();
@Override
public String getHost(HttpServletRequest request) {
return request.getServerName();
}
@Override
public ApiParam getApiParam(HttpServletRequest request) {
return ZuulContext.getApiParam();
}
@Override
public Server choose(Object key) {
return loadBalanceServerChooser.choose(
String.valueOf(key)
, RequestContext.getCurrentContext().getRequest()
, getLoadBalancer()
, () -> super.choose(key)
, (servers) -> this.doChoose(servers, key)
);
}
protected Server doChoose(List<Server> servers, Object key) {
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(servers, key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}

@ -0,0 +1,24 @@
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
import com.gitee.sop.gatewaycommon.loadbalancer.LoadBalanceServerChooser;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
import com.netflix.loadbalancer.Server;
import javax.servlet.http.HttpServletRequest;
/**
* @author tanghc
*/
public class ZuulLoadBalanceServerChooser extends LoadBalanceServerChooser<HttpServletRequest, Server> {
@Override
public String getHost(HttpServletRequest request) {
return request.getServerName();
}
@Override
public ApiParam getApiParam(HttpServletRequest request) {
return ZuulContext.getApiParam();
}
}

@ -11,7 +11,7 @@ import com.netflix.zuul.context.RequestContext;
public class ZuulForwardChooser extends BaseForwardChooser<RequestContext> { public class ZuulForwardChooser extends BaseForwardChooser<RequestContext> {
@Override @Override
protected ApiParam getApiParam(RequestContext requestContext) { public ApiParam getApiParam(RequestContext requestContext) {
return ZuulContext.getApiParam(); return ZuulContext.getApiParam();
} }

@ -19,7 +19,6 @@ import java.lang.reflect.Method;
*/ */
public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping implements PriorityOrdered { public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping implements PriorityOrdered {
private static StringValueResolver stringValueResolver = new ApiMappingStringValueResolver();
private static StringValueResolver stringValueResolverMVC = new ApiMappingStringValueResolverMVC(); private static StringValueResolver stringValueResolverMVC = new ApiMappingStringValueResolverMVC();
@Override @Override

Loading…
Cancel
Save