目前公司有一个服务,有两个开发版本并行开发,需要同时测试联调;由于程序需要经过网关鉴权,不可采用直连形式区分服务;最后改造gateway服务,通过前端请求头添加version=dev调用服务,区分使用哪个实例提供服务;步骤如下:
剔除原有ribbon依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
增加两个配置类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* Description:
* 自定义灰度
* 通过给请求头添加Version 与 Service Instance 元数据属性进行对比
*/
public class VersionGrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
public static final Logger log = LoggerFactory.getLogger(VersionGrayLoadBalancer.class);
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final String serviceId;
private final AtomicInteger position;
public VersionGrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
this(serviceInstanceListSupplierProvider,serviceId,new Random().nextInt(1000));
}
public VersionGrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId, int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(serviceInstances,request));
}
private Response<ServiceInstance> processInstanceResponse(List<ServiceInstance> instances, Request request) {
if (instances.isEmpty()) {
log.warn("No servers available for service: " + this.serviceId);
return new EmptyResponse();
} else {
DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();
RequestData clientRequest = (RequestData) requestContext.getClientRequest();
HttpHeaders headers = clientRequest.getHeaders();
String reqVersion = headers.getFirst("version");
log.info("request header version : {}",reqVersion );
//判断元数据里是否有version
List<ServiceInstance> serviceInstances = instances.stream()
.filter(instance -> instance.getMetadata().get("version")!=null &&instance.getMetadata().get("version").equals(reqVersion))
.collect(Collectors.toList());
if(serviceInstances.size() > 0){
return processRibbonInstanceResponse(serviceInstances);
}else{
List<ServiceInstance> serviceInstances1 = instances.stream()
.filter(instance -> instance.getMetadata().get("version")==null)
.collect(Collectors.toList());
return processRibbonInstanceResponse(serviceInstances1);
}
}
}
/**
* 负载均衡器
* 参考 org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer#getInstanceResponse
* @author javadaily
*/
private Response<ServiceInstance> processRibbonInstanceResponse(List<ServiceInstance> instances) {
int pos = Math.abs(this.position.incrementAndGet());
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}
import org.springframework.cloud.client.ServiceInstance;
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;
/**
* Description:
* 自定义负载均衡器配置实现类
*/
public class VersionLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> versionGrayLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new VersionGrayLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
在gateway启动类上添加注解
@LoadBalancerClient(value = "服务名称", configuration = VersionLoadBalancerConfiguration.class)
这样gateway 网关整体改造完成;
现在需要在本地启动服务上添加元数据:spring.cloud. nacos.discovery.metadata.version= dev
这样打开nacos可以看到
此时使用postman模拟请求添加headers version=dev的,请求到配置元数据的服务上,不添加的请求的没有的服务上;
最终实现根据不同请求头来访问不同服务实例;