# 原理分析之预发布灰度发布

SOP网关采用`自定义负载均衡策略`来实现对预发布/灰度发布服务器实例的选择。

spring cloud gateway默认的负载均衡实现类在:`org.springframework.cloud.gateway.filter.LoadBalancerClientFilter.java`中

这个类主要做了几件事情：

1. 解析出请求路径中的scheme
2. 如果scheme不是以`lb`协议开头直接跳过
3. 如果scheme以`lb`协议开头，则说明需要进行负载均衡，选出一台微服务实例
4. 将`lb`协议解析成`http://ip:port`，继续向下请求

其中第4步是由`org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient`来完成的，
我们只要分别继承`LoadBalancerClientFilter`和`RibbonLoadBalancerClient`，然后重写其中的方法就能完成自定义负载均衡。

SOP中的重写类是：`SopLoadBalancerClientFilter`和`SopLoadBalancerClient`，核心代码委托给了`LoadBalanceServerChooser`处理，核心代码如下：

```java
    /**
     * 选择服务器
     *
     * @param serviceId             serviceId，仅gateway网关有作用
     * @param exchange              请求上下文
     * @param loadBalancer          loadBalancer
     * @param superChooser          父类默认的选择
     * @param serverChooserFunction 执行选择操作
     * @return 返回服务器实例，没有选到则返回null
     */
    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);
        }

        // 到这里说明不能访问预发/灰度服务器，则需要路由到非预发服务器
        // 注意：这里允许走灰度服务器，如果不允许走，注释notPreServers.addAll(grayServers);这行
        return serverChooserFunction.apply(notPreServers);
    }
```

其业务逻辑如下：

1. 选出`serviceId`对应的所有服务器实例
2. 将服务器实例进行分类，分别放进`预发布List`，`灰度List`，`非预发布List`中
3. 如果`预发布List`，`灰度List`都为空，表示没有开启任何预发/灰度服务，直接使用父类的负载均衡策略
4. 如果是从预发布域名访问过来，则认为是预发布请求，选出预发服务器
5. 如果是灰度请求，则认为是灰度用户，选出灰度服务器
6. 最后剩下的是正常用户，正常用户不能走预发环境

## 参考类

- com.gitee.sop.gatewaycommon.gateway.filter.SopLoadBalancerClientFilter
- com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient
- com.gitee.sop.gatewaycommon.gateway.loadbalancer.GatewayLoadBalanceServerChooser

