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