1. 程式人生 > >Apollo學習(四):建立灰度配置並與zuul協作實現灰度釋出

Apollo學習(四):建立灰度配置並與zuul協作實現灰度釋出

說明

通過之前對Apollo的學習,對Apollo的使用已經有了大概的瞭解。本篇博文通過與Spring Cloud Zuul作為閘道器配合,Apollo配置灰度例項來學習灰度釋出。本文的核心是以github上的灰度釋出開源專案ribbon-discovery-filter-spring-cloud-starter作為基礎,通過對其中的過濾規則(Rule)的修改,來適合滿足需求實現灰度釋出。

正文

實現灰度釋出其中關鍵點在於服務例項的過濾,這裡我們希望通過zuul過濾器來判斷使用者是否設定為灰度使用者,灰度使用者會使用灰度例項,否則使用正常的例項。一個服務通常會部署多個例項,其中灰度例項的配置需要通過Apollo來完成,但是如何實現不同的使用者在不同的服務例項中選擇一個服務例項?這裡的不同指的是是否灰度。
使用zuul作為服務閘道器,zuul中又集成了ribbon,使用ribbon實現服務路由和負載均衡。在ribbon中使用rule來過濾服務例項,所以關鍵在於要實現適合的rule,而在rule中要使用服務例項的配置來判斷是否灰度,這裡的配置就是服務例項的metadata。
具體的實現原理在本篇博文不做太多介紹,簡單來說,就是通過Apollo來配置灰度例項,配置metadata資料,通過ribbon的服務過濾再通過負載均衡來達到灰度釋出。

1.建立ribbon-discovery-filter

基於開源專案ribbon-discovery-filter-spring-cloud-starter來建立自己的服務過濾rule,從github上將該專案clone到本地,由於該專案是gradle專案,這裡我們建立自己的maven專案。同時結合之前的博文《springboot學習(六): 建立自定義的springboot starter》來建立springboot的starter方便之後使用。
建立名為ribbondiscoveryfilter的maven專案,pom.xml檔案如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wds</groupId>
    <artifactId>ribbon-discovery-filter</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.17.BUILD-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.4.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
            <version>1.4.4.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

修改MetadataAwarePredicate的過濾邏輯,在本專案中使用了三個常量:GRAY_TAG表示該服務是否要灰度釋出,GRAY_INSTANCE_TAG表示該服務例項是否為灰度例項,GRAY_USER_TAG表示請求的使用者是否為灰度使用者

@Override
protected boolean apply(DiscoveryEnabledServer server) {
        final RibbonFilterContext context = RibbonFilterContextHolder.getCurrentContext();
        final String grayUserTag = context.get(Constants.GRAY_USER_TAG);
        //判斷是否設定了使用者的灰度標誌 如果沒有則表示正常例項
        if(grayUserTag == null || "".equals(grayUserTag)){
            return true;
        }
        System.out.println(grayUserTag);
        //判斷請求的服務是否要做灰度釋出
        String grayTag = "0";
        final Map<String,String> metadata = server.getInstanceInfo().getMetadata();
        if(metadata != null && metadata.containsKey(Constants.GRAY_TAG)){
            grayTag = metadata.get(Constants.GRAY_TAG);
            System.out.println(grayTag);

        }
        System.out.println(metadata.get(Constants.GRAY_INSTANCE_TAG));
        //若不做灰度
        if("0".equals(grayTag)){
            return true;
        }
        //若有灰度使用者標記且請求的服務有灰度例項  則過濾例項
        //根據不同的使用者過濾不同的服務例項
        if(grayUserTag.equals(metadata.get(Constants.GRAY_INSTANCE_TAG))){
            System.out.println(metadata.get(Constants.GRAY_INSTANCE_TAG));
            return true;
        }
        return false;
}

關於starter建立,請看之前的博文,一定要注意spring.factories檔案的配置。

2.建立zuul過濾器

通過建立zuul的pre型別過濾器,來對使用者進行判斷是否設定灰度使用者,若需要則以GRAY_USER_TAG為key,value為1表示灰度使用者,0或不設定表示正常使用者。
在本例中,通過判斷ip地址設定灰度使用者

public class GrayFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 100;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String ip = request.getRemoteAddr();
        System.out.println(ip);
        if("ip地址".equals(ip)){
            RibbonFilterContextHolder.getCurrentContext().add(Constants.GRAY_USER_TAG,"1");
        }
        return null;
    }
}

服務執行完畢後,清除該請求的RibbonFilterContext

public class GrayCleanFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(GrayCleanFilter.class);

    @Override
    public String filterType() {
        return "post";
    }

    @Override
    public int filterOrder() {
        return 1000;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RibbonFilterContextHolder.clearCurrentContext();
        logger.info("================clean the threadLocal!=================");
        return null;
    }
}

3.Apollo配置灰度例項

在前面說到服務例項的過濾需要服務的元資料配置引數。在apollo中為服務配置引數,元資料通過eureka.instance.metadata-map.KEY進行設定。之前提到在本例中使用了三個常量,GRAY_USER_INSTANCE是為使用者建立標記,其他兩個都是為服務例項建立元資料配置。GRAY_TAG表示該服務是否要做灰度釋出。0表示沒有灰度服務例項,1表示該服務存在灰度服務例項。GRAY_INSTANCE_TAY表示該服務例項是否為灰度例項,0表示不是,1表示為灰度服務例項。
在主版本配置引數
在這裡插入圖片描述

配置灰度引數
在這裡插入圖片描述

配置灰度規則
這裡配置灰度規則表示要將哪個服務例項作為灰度例項,以ip為單位進行設定
在這裡插入圖片描述

4.通過zuul訪問服務

在zuul中配置了服務請求路由,通過服務名進行請求,使用設定的ip訪問服務將得到是灰度版本的值。

最後

本篇博文只是簡單介紹了apollo與zuul實現灰度釋出的大概流程,在實際的開發中根據實際需要進行配置,建立過濾器,改寫Rule等。關於其服務例項的過濾原理,推薦大家可以看看ribbon的原始碼,瞭解其服務例項的獲取,過濾,負載均衡演算法,不同Rule的程式碼實現。

參考資料:
https://github.com/jmnarloch/ribbon-discovery-filter-spring-cloud-starter