SpringCloud實現灰度發佈的方法步驟

1.什麼是灰度發佈?

灰度發佈又稱金絲雀發佈,是在系統升級的時候能夠平滑過渡的一種發佈方式。在其上可以進行A/B測試,即讓一部分用戶繼續用產品特性A,一部分用戶開始用產品特性B,如果用戶對B沒有什麼反對意見,那麼逐步擴大范圍,把所有用戶都遷移到B上面來。灰度發佈可以保證整體系統的穩定,在初始灰度的時候就可以發現、調整問題,以保證其影響度。

關於金絲雀發佈名稱的來歷:礦工下要礦井,要驗證是否有瓦斯,金絲雀對瓦斯很敏感,通過觀察金絲雀的反應判斷是否安全。

2.灰度發佈有什麼作用?

1.降低發佈帶來的影響,雖然功能都在測試環境測過,但畢竟沒有發佈到生產環境,如果先讓少部分用戶先使用新版本,提前發現bug,或者性能問題,提前做好修復,就可以降低新版本帶來的影響;

2.通過對新老版本的對比,觀察新版本帶來的效果。結合工作中使用到的灰度發佈實踐和對其他大廠的灰度發佈調研,總結瞭以下灰度發佈方案。

3.灰度發佈的實現方式:網關到服務,服務到服務

3.1網關到服務代碼實現

3.1.1整體流程

指定灰度規則->預制代碼規則->springcloud自定義metadata

3.1.2前置環境(需要自行搭建四個至少服務)

  • eureka:註冊中心
  • zuul:網關
  • service-v1:集群服務v1版本
  • service-v2:集群服務v2版本

3.1.3核心代碼

pom.xml

        <!-- 實現通過 metadata 進行灰度路由 -->
        <dependency>
            <groupId>io.jmnarloch</groupId>
            <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

灰度過濾器(核心代碼)

@Component
public class GrayFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }
 
    @Override
    public int filterOrder() {
        return 0;
    }
 
    @Override
    public boolean shouldFilter() {
        return true;//return false 關閉該過濾器
    }
 
    @Autowired
    private CommonGrayRuleDaoCustom commonGrayRuleDaoCustom;
 
    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
 
        String userId = request.getHeader("userId");
        // 根據用戶id查規則查庫,
        String rule = findRuleById(userId);
 
        // 金絲雀
        if ("v1".equals(rule)) {
            RibbonFilterContextHolder.getCurrentContext().add("version", "v1");
            // 普通用戶
        } else if ("v2".equals(rule)) {
            RibbonFilterContextHolder.getCurrentContext().add("version", "v2");
        }
 
        return null;
    }
 
    //查庫的偽代碼
    private String findRuleById(String userId) {
        Map<String, String> map = new HashMap();
        map.put("9527", "v1");
        map.put("9528", "v2");
        return map.get(userId);
    }
 
}

3.2網關到服務代碼實現

3.2.1整體流程

springcloud自定義metadata->獲取當前用戶的版本->遍歷服務獲取服務的的版本,返回合適的服務

3.2.2前置環境(需要自行搭建5個至少服務)

  • eureka:註冊中心
  • service-A:服務調用方
  • service-v1:集群服務v1版本
  • service-v2:集群服務v2版本

3.2.3核心代碼

threadlocal工具類 

public class RibbonParameters {
    private static final ThreadLocal local = new ThreadLocal();
 
    public static <T> T get() {
        return (T) local.get();
    }
 
    public static <T> void set(T t) {
        local.set(t);
    }
}

 切面獲取version的值 

@Aspect
@Component
public class RequestAspect {
 
    @Pointcut("execution(* com.mashibing.apipassenger.controller..*Controller*.*(..))")
    private void anyMehtod() {
 
    }
 
    @Before(value = "anyMehtod()")
    public void before(JoinPoint joinPoint) {
 
        HttpServletRequest request = ((ServletRequestAttributes)         RequestContextHolder.getRequestAttributes()).getRequest();
        String version = request.getHeader("version");
        //方式二:
        HashMap<Object, Object> map = new HashMap<>();
        map.put("version",version);
        RibbonParameters.set(map);
    }

rule規則

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import org.springframework.context.annotation.Configuration;
 
import java.util.List;
import java.util.Map;
 
@Configuration
public class GrayRule extends AbstractLoadBalancerRule {
 
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
 
    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }
 
    private Server choose(ILoadBalancer lb, Object key) {
        System.out.println("灰度,rule");
        Server server = null;
        while (server == null) {
            List<Server> reachableServers = lb.getReachableServers();
            //獲取當前線程的參數 用戶 version=v1
            Map<String, String> map = (Map<String, String>) RibbonParameters.get();
            String version = "";
            if (map != null && map.containsKey("version")) {
                version = map.get("version");
            }
            System.out.println("當前rule,version=" + version);
            //遍歷服務列表選取用戶服務
            for (int i = 0; i < reachableServers.size(); i++) {
                server = reachableServers.get(i);
                //用戶的version知道瞭,服務自定義的meta不知道
                Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
                String metaMap = metadata.get("version");
 
                //用戶的version知道瞭,服務meta也知道瞭
                if (version.trim().equals(metaMap)) {
                    return server;
                }
            }
 
        }
        return null;
    }
}

註意:提前踩坑,No qualifying bean of type ‘com.netflix.loadbalancer.IRule‘ available: expected single matching bean

當是覺得很奇怪,命名自己隻定義瞭grayRule負載均衡策略規則,metadataAwareRule這個我代碼中並沒有。經過排查自己使用在pom中引入瞭Ribbon的包,該包默認會帶負載均衡策略規則。導致有多個規則,從而報錯。

<dependency>
    <groupId>io.jmnarloch</groupId>
    <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
    <version>2.1.0</version>
</dependency>

刪除該包即可

刪除後重新運行

服務與服務的灰度發佈的另外一種方式:可以在requestAspect中獲取到version後,直接比對版本:RibbonFilterContextHolder.getCurrentContext().add("version", "v1"),這種凡是與網關與服務的灰度發佈相似。

自此灰度發佈完成。

到此這篇關於SpringCloud實現灰度發佈的方法步驟的文章就介紹到這瞭,更多相關SpringCloud 灰度發佈內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: