1.引入jar包
<dependencies>
<!--nacos的服务发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--用redis实现限流-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--springboot端点-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
注意这里的springboot与springcloud的版本号:我遇到的坑是,我一开始使用的是springboot2.0.X的,springcloud用的F版本的,gateway好像不能很好的支持,
LoadBalancerClientFilter这个类没有choose这个方法,估计是我技术不行,没研究透,不过我把版本升上去,spingboot2.2.3.RELEASE springcloud Hoxton.RELEASE,就行了
2.编写程序
建个实体类:用于接收nacos的每个服务的元数据
@Data
@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
public class GrayscaleProperties {
private String version;
private String serverName;
private String serverGroup;
private String active;
private double weight = 1.0D;
}
@Slf4j
@Component
public class GatewayLoadBalancerClientFilter extends LoadBalancerClientFilter {
public GatewayLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
super(loadBalancer, properties);
}
@Override
protected ServiceInstance choose(ServerWebExchange exchange) {
if (this.loadBalancer instanceof RibbonLoadBalancerClient) {
RibbonLoadBalancerClient client = (RibbonLoadBalancerClient) this.loadBalancer;
HttpHeaders headers = exchange.getRequest().getHeaders();
String version = headers.getFirst(GrayscaleConstant.GRAYSCALE_VERSION);
String serviceId = ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost();
GrayscaleProperties build = GrayscaleProperties.builder().version( version ).serverName( serviceId ).build();
//这里使用服务ID 和 version 做为选择服务实例的key
//TODO 这里也可以根据实际业务情况做自己的对象封装
return client.choose(serviceId,build);
}
return super.choose(exchange);
}
}
package com.first.gateway.filter;
import cn.hutool.core.util.StrUtil;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.ribbon.ExtendBalancer;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.first.gateway.config.GrayscaleConstant;
import com.first.gateway.entity.GrayscaleProperties;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.*;
import java.util.stream.Collectors;
/**
* @Author lizn
* @Description:
* @Date $ $
*/
@Slf4j
public class GrayscaleLoadBalancerRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
/**
* gateway 特殊性。需要设置key值内容知道你要转发的服务名称 key已经在filter内设置了key值。
* @param key
* @return
*/
@Override
public Server choose(Object key) {
try {
GrayscaleProperties grayscale = (GrayscaleProperties) key;
String version = grayscale.getVersion();
String clusterName = this.nacosDiscoveryProperties.getClusterName();
NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance();
List<Instance> instances = namingService.selectInstances(grayscale.getServerName(), true);
if (CollectionUtils.isEmpty(instances)) {
log.warn("no instance in service {}", grayscale.getServerName());
return null;
} else {
List<Instance> instancesToChoose = buildVersion(instances,version);
//进行cluster-name分组筛选
// TODO 思考如果cluster-name 节点全部挂掉。是不是可以请求其他的分组的服务?可以根据情况在定制一份规则出来
if (StrUtil.isNotBlank(clusterName)) {
List<Instance> sameClusterInstances = (List)instancesToChoose.stream().filter((instancex) -> {
return Objects.equals(clusterName, instancex.getClusterName());
}).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToChoose = sameClusterInstances;
} else {
log.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", new Object[]{grayscale.getServerName(), clusterName, instances});
}
}
//按nacos权重获取。这个是NacosRule的代码copy 过来 没有自己实现权重随机。这个权重是nacos控制台服务的权重设置
// 如果业务上有自己特殊的业务。可以自己定制规则,黑白名单,用户是否是灰度用户,测试账号。等等一些自定义设置
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
return new NacosServer(instance);
}
} catch (Exception var9) {
log.warn("NacosRule error", var9);
return null;
}
}
/**
* 筛选想要的值
* @param instances
* @param version
* @return
*/
protected List <Instance> buildVersion(List<Instance> instances,String version){
//进行按版本分组排序
Map<String,List<Instance>> versionMap = getInstanceByScreen(instances);
if(versionMap.isEmpty()){
log.warn("no instance in service {}", version);
}
//如果version 未传值使用最低版本服务
if(StrUtil.isBlank( version )){
if(true){
version = getFirst( versionMap.keySet() );
}else {
version = getLast( versionMap.keySet() );
}
}
List <Instance> instanceList = versionMap.get( version );
return instanceList;
}
/**
* 根据version 组装一个map key value 对应 version List<Instance>
* @param instances
* @return
*/
protected Map<String,List<Instance>> getInstanceByScreen(List<Instance> instances){
Map<String,List<Instance>> versionMap = new HashMap<>( instances.size() );
instances.stream().forEach( instance -> {
String version = instance.getMetadata().get(GrayscaleConstant.GRAYSCALE_VERSION );
List <Instance> versions = versionMap.get( version );
if(versions == null){
versions = new ArrayList<>();
}
versions.add( instance );
versionMap.put( version,versions );
} );
return versionMap;
}
/**
* 获取第一个值
* @param keys
* @return
*/
protected String getFirst(Set<String> keys){
List <String> list = sortVersion( keys );
return list.get( 0 );
}
/**
* 获取最后一个值
* @param keys
* @return
*/
protected String getLast(Set <String> keys){
List <String> list = sortVersion( keys );
return list.get( list.size()-1 );
}
/**
* 根据版本排序
* @param keys
* @return
*/
protected List<String > sortVersion(Set <String> keys){
List<String > list = new ArrayList <>( keys );
Collections.sort(list);
return list;
}
}
@Configuration
public class GrayGatewayReactiveLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean({GatewayLoadBalancerClientFilter.class})
public GatewayLoadBalancerClientFilter gatewayLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
return new GatewayLoadBalancerClientFilter(loadBalancer, properties);
}
@Bean
@ConditionalOnMissingBean({GrayscaleLoadBalancerRule.class})
public GrayscaleLoadBalancerRule GrayscaleLoadBalancerRule() {
return new GrayscaleLoadBalancerRule();
}
}
前段要在head里放个versionzi端,naocs的服务元数据里也要放version字段。getway的灰度发布就结束了