文档章节

Spring Cloud Alibaba gateway ribbon 自定义负载均衡规则

简单自由
 简单自由
发布于 2019/12/10 22:46
字数 2453
阅读 148
收藏 0

上一篇介绍了,ribbon的组件。本篇要自己写一个灰度方案。其实就是一个很简单的思维扩散。

需求

前端header请求携带version字段。路由服务根据version去需要对应版本的服务集合,进行或轮询或hash或权重的负载。请求路由到服务上,如果还要调用下游服务,也按照version规则去路由下游服务器。前端未携带版本按照后端服务最高version版本进行路由。

分析如果自己动手写一个灰度方案。需要考虑的因素有几点?

  • 服务对应的版本。key(版本号):value(对应版本号的服务集合)
  • 对应版本号的服务集合需要重新排序。
  • 重写负载均衡规则,就是ribbon的IRule方法。按照我们想要的负载规则去路由我们的请求

解决方案:

  • 利用注册中心的metadata属性元数据,让服务携带版本信息。
  • 拿到要请求的服务集合。spring cloud Alibaba nacos NamingService接口根据服务名称获取所有服务List集合,如果你使用的spring cloud 版本可以使用 ILoadBalancer 对象获取所有的服务集合
  • Instance服务里面携带了,服务注册到注册中心的自定义版本信息
  • 重写IRule负载规则。按照需求转发请求。

来写一下网关层的实现。 gateway负载规则有一个拦截器

创建负载规则的类信息GrayscaleProperties

public class GrayscaleProperties implements Serializable {
    private String version;
    private String serverName;
    private String serverGroup;
    private String active;
    private double weight = 1.0D;
}

因为gateway的特殊性LoadBalancerClientFilter过滤器主要解析lb:// 为前缀的路由规则,在通过LoadBalancerClient#choose(String) 方法获取到需要的服务实例,从而实现负载均衡。在这里我们要写自己的负载均衡就需要重新需要重写LoadBalancerClientFilter 过滤器 LoadBalancerClientFilter 介绍:次过滤器作用在url以lb开头的路由,然后利用loadBalancer来获取服务实例,构造目标requestUrl,设置到GATEWAY_REQUEST_URL_ATTR属性中,供NettyRoutingFilter使用。

GatewayLoadBalancerClientAutoConfiguration 在初始化会检测@ConditionalOnBean(LoadBalancerClient.class) 是否存在,如果存在就会加载LoadBalancerClientFilter负载过滤器

以下是源码

@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
		String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
		//判断url 前缀 如不是lb开头的就进行下一个过滤器
		if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
			return chain.filter(exchange);
		}
		//根据网关的原始网址。替换exchange url为 http://IP:PORT/path 路径的url
		//preserve the original url
		addOriginalRequestUrl(exchange, url);
	
		log.trace("LoadBalancerClientFilter url before: " + url);
		// 这里呢会进行调用真正的负载均衡
		final ServiceInstance instance = choose(exchange);

		if (instance == null) {
			String msg = "Unable to find instance for " + url.getHost();
			if(properties.isUse404()) {
				throw new FourOFourNotFoundException(msg);
			}
			throw new NotFoundException(msg);
		}

		URI uri = exchange.getRequest().getURI();

		// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
		// if the loadbalancer doesn't provide one.
		String overrideScheme = instance.isSecure() ? "https" : "http";
		if (schemePrefix != null) {
			overrideScheme = url.getScheme();
		}

		URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);

		log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
		return chain.filter(exchange);
	}
	。。。。
	// 因为注入了ribbon 会使用ribbon 进行负载均衡规则进行负载
    protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
        return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
    }

如果单单定制了 IRule 的实现类 Server choose(Object key) 方法里面的 key值就是一个默认值。就不知道转发到那个服务。所以要进行重写LoadBalancerClientFilter 这个类的 protected ServiceInstance choose(ServerWebExchange exchange) 进行key的赋值操作

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);
    }
}

自定义gateway灰度负载规则


@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 (StringUtils.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;
        }
    }
}

以上就是gateway的定制负载规则。

启动三个cloud-discovery-client服务

file

对应版本1、2、3

然后postman进行接口请求 http://localhost:9000/client/client/user/service/save header 里面添加 version 字段。分别请求对应的版本服务。

gateway 路由全部请求到了对应版本的路由服务上。

服务于服务间的版本请求。

其实和gateway 原理一样,只不过少了gateway 拦截器这一层。 创建自己的AbstractGrayscalLoadBalancerRule 继承AbstractLoadBalancerRule 抽象类,这个抽象类封装了一些我们需要用到的方法。

/**
 * @Author: xlr
 * @Date: Created in 1:03 PM 2019/11/24
 */
@Slf4j
@Data
public abstract class AbstractGrayscalLoadBalancerRule extends AbstractLoadBalancerRule {

    /**
     * asc 正序 反之desc 倒叙
     */
    protected boolean asc = true;

    /**
     * 筛选想要的值
     * @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(StringUtils.isBlank( version )){
            if(isAsc()){
                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;
    }
}

创建实现类GrayscaleLoadBalancerRule 继承自己定义的抽象类AbstractGrayscalLoadBalancerRule

/**
 * fegin 负载均衡。在获取到我们想设置的对象之后,我们还可以设置 服务、用户、角色等各个维度的黑白名单,限制、转发、等策略,具体的使用场景还得需要结合工作中的实际使用场景。
 * 这里只是提供一个简单的思路。希望看到这个注释的人。能够有举一反三的能力,定制自己的规则。
 * @Author: xlr
 * @Date: Created in 12:19 PM 2019/11/24
 */
@Slf4j
public class GrayscaleLoadBalancerRule extends AbstractGrayscalLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
        //留空
    }
    /**
     * gateway 特殊性。需要设置key值内容知道你要转发的服务名称。
     * @param key
     * @return
     */
    @Override
    public Server choose(Object key) {
        log.info("GrayscaleLoadBalancerRule 执行 choose方法 ,参数 key: {}",key);
        try {
            String clusterName = this.nacosDiscoveryProperties.getClusterName();
            DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer)this.getLoadBalancer();
            String name = loadBalancer.getName();
            NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance();
            List<Instance> instances = namingService.selectInstances(name, true);

            if (CollectionUtils.isEmpty(instances)) {
                log.warn("no instance in service {}", name);
                return null;
            } else {
                List<Instance> instancesToChoose = null;

                String version = (String) ThreadLocalUtils.getKey( GrayscaleConstant.GRAYSCALE_VERSION );

                List <Instance> instanceList = buildVersion( instances,version );

                if (StringUtils.isNotBlank(clusterName)) {
                    List<Instance> sameClusterInstances = (List)instanceList.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[]{name, clusterName, instanceList});
                    }
                }

                Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
                return new NacosServer(instance);
            }
        } catch (Exception var9) {
            log.warn("NacosRule error", var9);
            return null;
        }
    }
}

分别在client、server的启动类上,声明自定义的IRule

    @Bean
    IRule rule(){
        return new GrayscaleLoadBalancerRule();
    }

在启动三个server服务进行负载均衡。继续的测试效果。就不在贴图了。有兴趣的小伙伴们可以自己尝试写一下。

这里在多说一点,注意bean对象父子上下文。如果有没接触过这个的可以度娘一下这个知识点。

思考

企业定制路由规则,在根据gateway提供的谓词、断言、过滤器这几个要素组合,
定制企业自己想要的路由规则。到此时这样gateway才是企业真正想要的路由功能。

往期资料、参考资料

Sentinel 官方文档地址

摘自参考 spring cloud 官方文档

Spring Cloud alibaba 官网地址

示例代码地址

往期地址 spring cloud alibaba 地址

spring cloud alibaba 简介

Spring Cloud Alibaba (nacos 注册中心搭建)

Spring Cloud Alibaba 使用nacos 注册中心

Spring Cloud Alibaba nacos 配置中心使用

spring cloud 网关服务

Spring Cloud zuul网关服务 一

Spring Cloud 网关服务 zuul 二

Spring Cloud 网关服务 zuul 三 动态路由

Spring Cloud alibaba网关 sentinel zuul 四 限流熔断

Spring Cloud gateway 网关服务 一

Spring Cloud gateway 网关服务二 断言、过滤器

Spring Cloud gateway 三 自定义过滤器GatewayFilter

Spring Cloud gateway 网关四 动态路由

Spring Cloud gateway 五 Sentinel整合

Spring Cloud gateway 六 Sentinel nacos存储动态刷新

Spring Cloud gateway 七 Sentinel 注解方式使用

如何喜欢可以关注分享本公众号。 file

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。转载请附带公众号二维码

© 著作权归作者所有

简单自由
粉丝 0
博文 19
码字总数 31342
作品 0
朝阳
私信 提问
SpringCloud的负载均衡的初步

SpringCloud Ribbon是基于Netfix Ribbon实现的一套客户端 负载均衡的工具。 主要功能:提供客户端的软件负载均衡算法,将Netflix的中间服务连在一起。Ribbon客户端组件提供一系列完善的配置如...

濡沫
2018/08/13
59
0
Spring Cloud 服务消费(Ribbon)

之前介绍了使用 Eureka 作为服务发现组件,构建了 Eureka Server 作为服务注册中心,使用 Eureka Client 去注册服务 Spring Cloud 服务注册与发现、高可用(Eureka),那服务间又是怎样相互调...

TurboSanil
2019/05/05
23
0
跟我学Spring Cloud(Finchley版)-07-Ribbon入门

经过前文讲述,我们已经实现了服务发现。本节来解决 跟我学Spring Cloud(Finchley版)-02-构建分布式应用 提到的如下问题: 负载均衡如何考虑?难道得在电影微服务和用户微服务之间加个NGI...

周立_ITMuch
2019/01/06
136
0
微服务灰度中间件 - Spring Cloud Gray

Spring Cloud Gray 是一套开源的微服务灰度路由解决方案,它由 spring-cloud-gray-client,spring-cloud-gray-client-netflix 和 spring-cloud-tray-server,spring-cloud-gray-webui 组成。......

Saleson
2019/09/06
5.5K
11
Spring Cloud Gray,微服务灰度中间件

Spring Cloud Gray 是一套开源的微服务灰度路由解决方案,它由spring-cloud-gray-client,spring-cloud-gray-client-netflix 和 spring-cloud-tray-server,spring-cloud-gray-webui组成。 ......

Saleson
2019/09/06
3K
9

没有更多内容

加载失败,请刷新页面

加载更多

什么是专用字节,虚拟字节,工作集?

我试图使用perfmon Windows实用程序来调试进程中的内存泄漏。 这就是perfmon解释这些术语的方式: Working Set是此过程的工作集的当前大小(以字节为单位)。 工作集是过程中线程最近触及的一...

技术盛宴
29分钟前
52
0
创建重复N次的单个项目的列表

我想创建一系列长度不一的列表。 每个列表将包含相同的元件e ,重复n次(其中n列表=长度)。 如何创建列表,而不为每个列表使用列表[e for number in xrange(n)] ? #1楼 在Python中创建重复...

javail
45分钟前
64
0
为什么图片反复压缩后普遍会变绿,而不是其他颜色?

作者:Lion Yang 链接:https://www.zhihu.com/question/29355920/answer/119088684 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 业余版概要:安卓的...

shzwork
今天
47
0
每天AC系列(二):最接近的三数之和

1 题目 leetcode第16题,给定一个数组与一个目标数,找出数组中其中的三个数,这三个数的和要与目标数最接近。 2 暴力 按惯例先来一次O(n3)的暴力: int temp = nums[0]+nums[1]+nums[2];fo...

Blueeeeeee
今天
46
0
EFK教程(5) - ES集群开启用户认证

基于ES内置及自定义用户实现kibana和filebeat的认证 作者:“发颠的小狼”,欢迎转载 目录 ▪ 用途 ▪ 关闭服务 ▪ elasticsearch-修改elasticsearch.yml配置 ▪ elasticsearch-开启服务 ▪ ...

小慢哥
今天
58
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部