(十二)實(shí)現(xiàn)灰度發(fā)布

在前面實(shí)現(xiàn)的功能中,動(dòng)態(tài)路由其實(shí)只是拓展了獲取路由數(shù)據(jù)的途徑,那么如何去控制路由規(guī)則的轉(zhuǎn)發(fā)呢?
zuul的ZoneAwareLoadBalancer的實(shí)現(xiàn)流程雖然看上去比較復(fù)雜,其實(shí)鏈路也是比較簡(jiǎn)單的。
下面簡(jiǎn)單梳理一下
1.先通過(guò)LoadBalancerStats的upServerListZoneMap找出對(duì)應(yīng)分區(qū)的所有服務(wù)列表
2.隨后通過(guò)LoadBalancerStats中的serverStatsCache,將服務(wù)列表的統(tǒng)計(jì)數(shù)據(jù)聚合
3.生成分區(qū)的統(tǒng)計(jì)數(shù)據(jù)快照Z(yǔ)oneSnapshot
4.通過(guò)規(guī)則挑選出合適的分區(qū)
5.通過(guò)分區(qū)的負(fù)載均衡器,結(jié)合rule挑選出合適的服務(wù)。
所以如果要控制路由的轉(zhuǎn)發(fā),其實(shí)通過(guò)Rule來(lái)控制即可。

實(shí)現(xiàn)灰度發(fā)布

那么下面簡(jiǎn)單實(shí)現(xiàn)一下灰度發(fā)布。
先說(shuō)一下實(shí)現(xiàn)的原理,我的方案比較簡(jiǎn)單,就是通過(guò)請(qǐng)求頭的一些信息(如應(yīng)用名稱(chēng),以及版本號(hào)),來(lái)確定最后轉(zhuǎn)發(fā)到哪個(gè)服務(wù)中。

1.自定義請(qǐng)求頭緩存過(guò)濾器

這個(gè)過(guò)濾器主要是將請(qǐng)求中的請(qǐng)求頭信息進(jìn)行緩存。

@Component
public class CacheFilter extends com.netflix.zuul.ZuulFilter{

    @Override
    public boolean shouldFilter() {
        //只攔截通過(guò)ribbon進(jìn)行路由的---此處與ribbon源碼一致
        RequestContext ctx = RequestContext.getCurrentContext();
        return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
                && ctx.sendZuulResponse());
    }

    @Override
    public Object run() throws ZuulException {
        將請(qǐng)求頭信息進(jìn)行緩存放入到請(qǐng)求上下文中。
        其中的app以及version都是自定義的請(qǐng)求頭。用于模擬版本號(hào)以及應(yīng)用名稱(chēng)
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String app = request.getHeader("app");
        String version = request.getHeader("version");
        ctx.put("app", app);
        ctx.put("version", version);
        return null;
    }

    @Override
    public String filterType() {
        return FilterConstants.ROUTE_TYPE;
    }
    
    @Override
    public int filterOrder() {
        return 0;
    }
}

上面的類(lèi),比較簡(jiǎn)單,那么要注意以下幾點(diǎn)。

    1. 該過(guò)濾器執(zhí)行順序需在RibbonRoutingFilter之前
      首先由于zuul處理請(qǐng)求的核心是以鏈?zhǔn)降倪^(guò)濾器去執(zhí)行的,所以需要控制好順序。先生成需要的緩存信息,那么在后續(xù)RibbonRoutingFilter執(zhí)行的時(shí)候,才可以通過(guò)LoadBalancer中的rule(rule需要通過(guò)緩存信息去用于判斷)去控制。
    1. 攔截什么類(lèi)型的請(qǐng)求
      路由規(guī)則中只有serverId的,這樣子底層是通過(guò)serverId去路由(服務(wù)id與真實(shí)ip地址的映射最終是通過(guò)注冊(cè)中心的客戶(hù)端拉取的注冊(cè)表中獲取)。

2.如何通過(guò)rule去控制路由

  • 2.1首先先定義一個(gè)Predicate
public class GrayPredicate extends AbstractServerPredicate{

    @Override
    public boolean apply(PredicateKey input) {
        DiscoveryEnabledServer server = (DiscoveryEnabledServer) input.getServer();
        Map<String, String> metaDataMap = server.getInstanceInfo().getMetadata();
        String app = RequestContext.getCurrentContext().get("app").toString();
        String version = RequestContext.getCurrentContext().get("version").toString();
        return metaDataMap.get("app").equals(app) && metaDataMap.get("version").equals(version);
    }
}

這個(gè)判斷的標(biāo)準(zhǔn),其實(shí)就是根據(jù)服務(wù)的元數(shù)據(jù),與請(qǐng)求頭對(duì)比,看看服務(wù)的元數(shù)據(jù)與請(qǐng)求頭中的應(yīng)用以及版本是否匹配,決定該服務(wù)是否符合標(biāo)準(zhǔn)。

  • 2.2 如何利用Predicate
    由于Rule本身支持多個(gè)Predicate的使用。另外要兼顧此前的一些判斷標(biāo)準(zhǔn)。
    所以此處新增一個(gè)Rule用于支持多種類(lèi)型Predicate。
public class GrayRule extends ZoneAvoidanceRule{
    
    private CompositePredicate compositePredicate;

    public GrayRule() {
        用于判斷該服務(wù)的分區(qū)是否是一個(gè)壞的分區(qū),需要避免使用該分區(qū)
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this, null);
        灰度,用于判斷該服務(wù)是否適用于該請(qǐng)求
        GrayPredicate grayPredicate = new GrayPredicate();
        用于判斷該服務(wù)的壓力是否過(guò)大
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this, null);
        compositePredicate = CompositePredicate.withPredicates(zonePredicate, availabilityPredicate, grayPredicate)
                .build();
    }

    覆寫(xiě)該方法,否則會(huì)返回父類(lèi)的Predicate
    @Override
    public AbstractServerPredicate getPredicate() {
        return compositePredicate;
    }
}
  • 2.3 如何讓負(fù)載均衡器使用該rule?
    簡(jiǎn)單進(jìn)行注入即可
@Configuration
public class GrayConfiguration {
    
    @Bean
    GrayRule grayRule() {
        GrayRule gr = new GrayRule();
        return gr;
    }
}

需要注意的點(diǎn),該Rule需要比起默認(rèn)的Rule(ZoneAvoidanceRule )先被掃描到。否則將不會(huì)被注入。
源碼如下

@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
        RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        if (this.propertiesFactory.isSet(IRule.class, name)) {
            return this.propertiesFactory.get(IRule.class, config, name);
        }
        ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
        rule.initWithNiwsConfig(config);
        return rule;
    }
}

3.簡(jiǎn)單測(cè)試

  • 3.1環(huán)境準(zhǔn)備
    準(zhǔn)備2個(gè)服務(wù)端對(duì)應(yīng)不同的版本,對(duì)應(yīng)的注冊(cè)信息是如下
    服務(wù)A-one:對(duì)應(yīng)版本beta-one
spring:
  application:
    name: client
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    registry-fetch-interval-seconds: 10
    heartbeat-executor-exponential-back-off-bound: 1
    cache-refresh-executor-exponential-back-off-bound: 1
    service-url:
      default-zone: http://localhost:8761/eureka/
  instance:
    lease-renewal-interval-in-seconds: 10
    metadata-map:
      app: clientA
      version:  beta-one
      zone: a

服務(wù)A-two:對(duì)應(yīng)版本beta-two

spring:
  application:
    name: client
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    registry-fetch-interval-seconds: 10
    heartbeat-executor-exponential-back-off-bound: 1
    cache-refresh-executor-exponential-back-off-bound: 1
    service-url:
      default-zone: http://localhost:8761/eureka/
  instance:
    lease-renewal-interval-in-seconds: 10
    metadata-map:
      app: clientA
      version:  beta-two
      zone: b

分別啟動(dòng)對(duì)應(yīng)的服務(wù)。


對(duì)于服務(wù)A-1的訪(fǎng)問(wèn)

對(duì)于服務(wù)A-2的訪(fǎng)問(wèn)

至此,簡(jiǎn)單的灰度發(fā)布就完成了,從上面的結(jié)果來(lái)看,可以通過(guò)請(qǐng)求頭從而控制最終的路由。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容